Skip to content

Commit 2f6d9d0

Browse files
authored
Add a config option to allow users to enable post quantum TLS support in CRT HTTP client (#3727)
* Add a config option to allow users to enable post quantum TLS support * Fix typo * Rename it to postQuantumTlsEnabled
1 parent 89625c6 commit 2f6d9d0

File tree

4 files changed

+205
-62
lines changed

4 files changed

+205
-62
lines changed
Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,6 @@
1+
{
2+
"type": "feature",
3+
"category": "AWS CRT HTTP Client",
4+
"contributor": "",
5+
"description": "Allow users to enable Post Quantum TLS via `AwsCrtAsyncHttpClient.builder().postQuantumTlsEnabled(true).build()`."
6+
}

http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/AwsCrtAsyncHttpClient.java

Lines changed: 32 additions & 62 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,10 @@
1717

1818
import static software.amazon.awssdk.http.HttpMetric.HTTP_CLIENT_NAME;
1919
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.PROTOCOL;
20+
import static software.amazon.awssdk.http.crt.internal.AwsCrtConfigurationUtils.buildProxyOptions;
21+
import static software.amazon.awssdk.http.crt.internal.AwsCrtConfigurationUtils.buildSocketOptions;
22+
import static software.amazon.awssdk.http.crt.internal.AwsCrtConfigurationUtils.resolveCipherPreference;
23+
import static software.amazon.awssdk.http.crt.internal.AwsCrtConfigurationUtils.resolveHttpMonitoringOptions;
2024
import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely;
2125
import static software.amazon.awssdk.utils.Validate.paramNotNull;
2226

@@ -36,7 +40,6 @@
3640
import software.amazon.awssdk.crt.http.HttpProxyOptions;
3741
import software.amazon.awssdk.crt.io.ClientBootstrap;
3842
import software.amazon.awssdk.crt.io.SocketOptions;
39-
import software.amazon.awssdk.crt.io.TlsCipherPreference;
4043
import software.amazon.awssdk.crt.io.TlsContext;
4144
import software.amazon.awssdk.crt.io.TlsContextOptions;
4245
import software.amazon.awssdk.http.Protocol;
@@ -97,10 +100,11 @@ private AwsCrtAsyncHttpClient(DefaultBuilder builder, AttributeMap config) {
97100
}
98101

99102
try (ClientBootstrap clientBootstrap = new ClientBootstrap(null, null);
100-
SocketOptions clientSocketOptions = buildSocketOptions(builder, config);
103+
SocketOptions clientSocketOptions = buildSocketOptions(builder.tcpKeepAliveConfiguration,
104+
config.get(SdkHttpConfigurationOption.CONNECTION_TIMEOUT));
101105
TlsContextOptions clientTlsContextOptions =
102106
TlsContextOptions.createDefaultClient()
103-
.withCipherPreference(TlsCipherPreference.TLS_CIPHER_SYSTEM_DEFAULT)
107+
.withCipherPreference(resolveCipherPreference(builder.postQuantumTlsEnabled))
104108
.withVerifyPeer(!config.get(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES));
105109
TlsContext clientTlsContext = new TlsContext(clientTlsContextOptions)) {
106110

@@ -109,69 +113,12 @@ private AwsCrtAsyncHttpClient(DefaultBuilder builder, AttributeMap config) {
109113
this.tlsContext = registerOwnedResource(clientTlsContext);
110114
this.readBufferSize = builder.readBufferSize == null ? DEFAULT_STREAM_WINDOW_SIZE : builder.readBufferSize;
111115
this.maxConnectionsPerEndpoint = config.get(SdkHttpConfigurationOption.MAX_CONNECTIONS);
112-
this.monitoringOptions = revolveHttpMonitoringOptions(builder.connectionHealthConfiguration);
116+
this.monitoringOptions = resolveHttpMonitoringOptions(builder.connectionHealthConfiguration);
113117
this.maxConnectionIdleInMilliseconds = config.get(SdkHttpConfigurationOption.CONNECTION_MAX_IDLE_TIMEOUT).toMillis();
114-
this.proxyOptions = buildProxyOptions(builder.proxyConfiguration);
118+
this.proxyOptions = buildProxyOptions(builder.proxyConfiguration, tlsContext);
115119
}
116120
}
117121

118-
private HttpMonitoringOptions revolveHttpMonitoringOptions(ConnectionHealthConfiguration config) {
119-
if (config == null) {
120-
return null;
121-
}
122-
123-
HttpMonitoringOptions httpMonitoringOptions = new HttpMonitoringOptions();
124-
httpMonitoringOptions.setMinThroughputBytesPerSecond(config.minimumThroughputInBps());
125-
int seconds = (int) config.minimumThroughputTimeout().getSeconds();
126-
httpMonitoringOptions.setAllowableThroughputFailureIntervalSeconds(seconds);
127-
return httpMonitoringOptions;
128-
}
129-
130-
private HttpProxyOptions buildProxyOptions(ProxyConfiguration proxyConfiguration) {
131-
if (proxyConfiguration == null) {
132-
return null;
133-
}
134-
135-
HttpProxyOptions clientProxyOptions = new HttpProxyOptions();
136-
137-
clientProxyOptions.setHost(proxyConfiguration.host());
138-
clientProxyOptions.setPort(proxyConfiguration.port());
139-
140-
if ("https".equalsIgnoreCase(proxyConfiguration.scheme())) {
141-
clientProxyOptions.setTlsContext(tlsContext);
142-
}
143-
144-
if (proxyConfiguration.username() != null && proxyConfiguration.password() != null) {
145-
clientProxyOptions.setAuthorizationUsername(proxyConfiguration.username());
146-
clientProxyOptions.setAuthorizationPassword(proxyConfiguration.password());
147-
clientProxyOptions.setAuthorizationType(HttpProxyOptions.HttpProxyAuthorizationType.Basic);
148-
} else {
149-
clientProxyOptions.setAuthorizationType(HttpProxyOptions.HttpProxyAuthorizationType.None);
150-
}
151-
152-
return clientProxyOptions;
153-
}
154-
155-
private SocketOptions buildSocketOptions(DefaultBuilder builder, AttributeMap config) {
156-
SocketOptions clientSocketOptions = new SocketOptions();
157-
158-
Duration connectionTimeout = config.get(SdkHttpConfigurationOption.CONNECTION_TIMEOUT);
159-
if (connectionTimeout != null) {
160-
clientSocketOptions.connectTimeoutMs = NumericUtils.saturatedCast(connectionTimeout.toMillis());
161-
}
162-
163-
TcpKeepAliveConfiguration tcpKeepAliveConfiguration = builder.tcpKeepAliveConfiguration;
164-
if (tcpKeepAliveConfiguration != null) {
165-
clientSocketOptions.keepAliveIntervalSecs =
166-
NumericUtils.saturatedCast(tcpKeepAliveConfiguration.keepAliveInterval().getSeconds());
167-
clientSocketOptions.keepAliveTimeoutSecs =
168-
NumericUtils.saturatedCast(tcpKeepAliveConfiguration.keepAliveTimeout().getSeconds());
169-
170-
}
171-
172-
return clientSocketOptions;
173-
}
174-
175122
/**
176123
* Marks a Native CrtResource as owned by the current Java Object.
177124
*
@@ -418,6 +365,22 @@ Builder connectionHealthConfiguration(Consumer<ConnectionHealthConfiguration.Bui
418365
*/
419366
Builder tcpKeepAliveConfiguration(Consumer<TcpKeepAliveConfiguration.Builder>
420367
tcpKeepAliveConfigurationBuilder);
368+
369+
/**
370+
* Configure whether to enable a hybrid post-quantum key exchange option for the Transport Layer Security (TLS) network
371+
* encryption protocol when communicating with services that support Post Quantum TLS. If Post Quantum cipher suites are
372+
* not supported on the platform, the SDK will use the default TLS cipher suites.
373+
*
374+
* <p>
375+
* See <a href="https://docs.aws.amazon.com/kms/latest/developerguide/pqtls.html">Using hybrid post-quantum TLS with AWS KMS</a>
376+
*
377+
* <p>
378+
* It's disabled by default.
379+
*
380+
* @param postQuantumTlsEnabled whether to prefer Post Quantum TLS
381+
* @return The builder of the method chaining.
382+
*/
383+
Builder postQuantumTlsEnabled(Boolean postQuantumTlsEnabled);
421384
}
422385

423386
/**
@@ -430,6 +393,7 @@ private static final class DefaultBuilder implements Builder {
430393
private ProxyConfiguration proxyConfiguration;
431394
private ConnectionHealthConfiguration connectionHealthConfiguration;
432395
private TcpKeepAliveConfiguration tcpKeepAliveConfiguration;
396+
private Boolean postQuantumTlsEnabled;
433397

434398
private DefaultBuilder() {
435399
}
@@ -509,6 +473,12 @@ public Builder tcpKeepAliveConfiguration(Consumer<TcpKeepAliveConfiguration.Buil
509473
return tcpKeepAliveConfiguration(builder.build());
510474
}
511475

476+
@Override
477+
public Builder postQuantumTlsEnabled(Boolean postQuantumTlsEnabled) {
478+
this.postQuantumTlsEnabled = postQuantumTlsEnabled;
479+
return this;
480+
}
481+
512482
@Override
513483
public Builder proxyConfiguration(Consumer<ProxyConfiguration.Builder> proxyConfigurationBuilderConsumer) {
514484
ProxyConfiguration.Builder builder = ProxyConfiguration.builder();
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,113 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.http.crt.internal;
17+
18+
19+
import java.time.Duration;
20+
import software.amazon.awssdk.annotations.SdkInternalApi;
21+
import software.amazon.awssdk.crt.http.HttpMonitoringOptions;
22+
import software.amazon.awssdk.crt.http.HttpProxyOptions;
23+
import software.amazon.awssdk.crt.io.SocketOptions;
24+
import software.amazon.awssdk.crt.io.TlsCipherPreference;
25+
import software.amazon.awssdk.crt.io.TlsContext;
26+
import software.amazon.awssdk.http.crt.AwsCrtAsyncHttpClient;
27+
import software.amazon.awssdk.http.crt.ConnectionHealthConfiguration;
28+
import software.amazon.awssdk.http.crt.ProxyConfiguration;
29+
import software.amazon.awssdk.http.crt.TcpKeepAliveConfiguration;
30+
import software.amazon.awssdk.utils.Logger;
31+
import software.amazon.awssdk.utils.NumericUtils;
32+
33+
@SdkInternalApi
34+
public final class AwsCrtConfigurationUtils {
35+
private static final Logger log = Logger.loggerFor(AwsCrtAsyncHttpClient.class);
36+
37+
private AwsCrtConfigurationUtils() {
38+
}
39+
40+
public static SocketOptions buildSocketOptions(TcpKeepAliveConfiguration tcpKeepAliveConfiguration,
41+
Duration connectionTimeout) {
42+
SocketOptions clientSocketOptions = new SocketOptions();
43+
44+
if (connectionTimeout != null) {
45+
clientSocketOptions.connectTimeoutMs = NumericUtils.saturatedCast(connectionTimeout.toMillis());
46+
}
47+
48+
if (tcpKeepAliveConfiguration != null) {
49+
clientSocketOptions.keepAliveIntervalSecs =
50+
NumericUtils.saturatedCast(tcpKeepAliveConfiguration.keepAliveInterval().getSeconds());
51+
clientSocketOptions.keepAliveTimeoutSecs =
52+
NumericUtils.saturatedCast(tcpKeepAliveConfiguration.keepAliveTimeout().getSeconds());
53+
54+
}
55+
56+
return clientSocketOptions;
57+
}
58+
59+
public static HttpProxyOptions buildProxyOptions(ProxyConfiguration proxyConfiguration, TlsContext tlsContext) {
60+
if (proxyConfiguration == null) {
61+
return null;
62+
}
63+
64+
HttpProxyOptions clientProxyOptions = new HttpProxyOptions();
65+
66+
clientProxyOptions.setHost(proxyConfiguration.host());
67+
clientProxyOptions.setPort(proxyConfiguration.port());
68+
69+
if ("https".equalsIgnoreCase(proxyConfiguration.scheme())) {
70+
clientProxyOptions.setTlsContext(tlsContext);
71+
}
72+
73+
if (proxyConfiguration.username() != null && proxyConfiguration.password() != null) {
74+
clientProxyOptions.setAuthorizationUsername(proxyConfiguration.username());
75+
clientProxyOptions.setAuthorizationPassword(proxyConfiguration.password());
76+
clientProxyOptions.setAuthorizationType(HttpProxyOptions.HttpProxyAuthorizationType.Basic);
77+
} else {
78+
clientProxyOptions.setAuthorizationType(HttpProxyOptions.HttpProxyAuthorizationType.None);
79+
}
80+
81+
return clientProxyOptions;
82+
}
83+
84+
public static HttpMonitoringOptions resolveHttpMonitoringOptions(ConnectionHealthConfiguration config) {
85+
if (config == null) {
86+
return null;
87+
}
88+
89+
HttpMonitoringOptions httpMonitoringOptions = new HttpMonitoringOptions();
90+
httpMonitoringOptions.setMinThroughputBytesPerSecond(config.minimumThroughputInBps());
91+
int seconds = (int) config.minimumThroughputTimeout().getSeconds();
92+
httpMonitoringOptions.setAllowableThroughputFailureIntervalSeconds(seconds);
93+
return httpMonitoringOptions;
94+
}
95+
96+
public static TlsCipherPreference resolveCipherPreference(Boolean postQuantumTlsEnabled) {
97+
TlsCipherPreference defaultTls = TlsCipherPreference.TLS_CIPHER_SYSTEM_DEFAULT;
98+
if (postQuantumTlsEnabled == null || !postQuantumTlsEnabled) {
99+
return defaultTls;
100+
}
101+
102+
// TODO: change this to the new PQ TLS Policy that stays up to date when it's ready
103+
TlsCipherPreference pqTls = TlsCipherPreference.TLS_CIPHER_PREF_PQ_TLSv1_0_2021_05;
104+
if (!pqTls.isSupported()) {
105+
log.warn(() -> "Hybrid post-quantum cipher suites are not supported on this platform. The SDK will use the system "
106+
+ "default cipher suites instead");
107+
return defaultTls;
108+
}
109+
110+
return pqTls;
111+
}
112+
113+
}
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,54 @@
1+
/*
2+
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
3+
*
4+
* Licensed under the Apache License, Version 2.0 (the "License").
5+
* You may not use this file except in compliance with the License.
6+
* A copy of the License is located at
7+
*
8+
* http://aws.amazon.com/apache2.0
9+
*
10+
* or in the "license" file accompanying this file. This file is distributed
11+
* on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
12+
* express or implied. See the License for the specific language governing
13+
* permissions and limitations under the License.
14+
*/
15+
16+
package software.amazon.awssdk.http.crt.internal;
17+
18+
import static org.assertj.core.api.AssertionsForClassTypes.assertThat;
19+
import static software.amazon.awssdk.crt.io.TlsCipherPreference.TLS_CIPHER_PREF_PQ_TLSv1_0_2021_05;
20+
import static software.amazon.awssdk.crt.io.TlsCipherPreference.TLS_CIPHER_SYSTEM_DEFAULT;
21+
22+
import java.util.stream.Stream;
23+
import org.junit.jupiter.api.Assumptions;
24+
import org.junit.jupiter.api.Test;
25+
import org.junit.jupiter.params.ParameterizedTest;
26+
import org.junit.jupiter.params.provider.Arguments;
27+
import org.junit.jupiter.params.provider.MethodSource;
28+
import software.amazon.awssdk.crt.io.TlsCipherPreference;
29+
30+
class AwsCrtConfigurationUtilsTest {
31+
32+
@ParameterizedTest
33+
@MethodSource("cipherPreferences")
34+
void resolveCipherPreference_pqNotSupported_shouldFallbackToSystemDefault(Boolean preferPqTls,
35+
TlsCipherPreference tlsCipherPreference) {
36+
Assumptions.assumeFalse(TLS_CIPHER_PREF_PQ_TLSv1_0_2021_05.isSupported());
37+
assertThat(AwsCrtConfigurationUtils.resolveCipherPreference(preferPqTls)).isEqualTo(tlsCipherPreference);
38+
}
39+
40+
@Test
41+
void resolveCipherPreference_pqSupported_shouldHonor() {
42+
Assumptions.assumeTrue(TLS_CIPHER_PREF_PQ_TLSv1_0_2021_05.isSupported());
43+
assertThat(AwsCrtConfigurationUtils.resolveCipherPreference(true)).isEqualTo(TLS_CIPHER_PREF_PQ_TLSv1_0_2021_05);
44+
}
45+
46+
private static Stream<Arguments> cipherPreferences() {
47+
return Stream.of(
48+
Arguments.of(null, TLS_CIPHER_SYSTEM_DEFAULT),
49+
Arguments.of(false, TLS_CIPHER_SYSTEM_DEFAULT),
50+
Arguments.of(true, TLS_CIPHER_SYSTEM_DEFAULT)
51+
);
52+
}
53+
54+
}

0 commit comments

Comments
 (0)