+ * It's disabled by default, and the client trusts the OS trust store even when these properties are set. When enabled,
+ * the properties are read once, when the client is built. If {@code javax.net.ssl.trustStore} is not set, the client
+ * trusts the OS trust store. The trust store also applies to the TLS connection to an HTTPS proxy. The trust store is not
+ * read when {@link SdkHttpConfigurationOption#TRUST_ALL_CERTIFICATES} is enabled.
+ *
+ *
+ * Unlike the Apache, Netty, and URL connection HTTP clients, which use the JDK's TLS implementation, building the client
+ * fails with an {@link IllegalStateException} if the trust store cannot be loaded (for example, the file does not exist,
+ * the password is wrong, or the store contains no certificates or more than 1024 certificates) or if
+ * {@code javax.net.ssl.trustStore} is {@code NONE}, instead of falling back to a default trust store. Certificates are
+ * validated by the native TLS implementation, so JDK validation settings such as
+ * {@code jdk.certpath.disabledAlgorithms} do not apply.
+ *
+ * @param useTrustStoreSystemProperties whether to use the trust store specified by the JSSE system properties;
+ * {@code null} reverts to the default ({@code false})
+ * @return The builder of the method chaining.
+ */
+ AwsCrtAsyncHttpClient.Builder useTrustStoreSystemProperties(Boolean useTrustStoreSystemProperties);
}
/**
diff --git a/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/AwsCrtHttpClient.java b/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/AwsCrtHttpClient.java
index 9fe64121b2ca..fad99ffc7f80 100644
--- a/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/AwsCrtHttpClient.java
+++ b/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/AwsCrtHttpClient.java
@@ -353,6 +353,31 @@ AwsCrtHttpClient.Builder tcpKeepAliveConfiguration(Consumer
+ * It's disabled by default, and the client trusts the OS trust store even when these properties are set. When enabled,
+ * the properties are read once, when the client is built. If {@code javax.net.ssl.trustStore} is not set, the client
+ * trusts the OS trust store. The trust store also applies to the TLS connection to an HTTPS proxy. The trust store is not
+ * read when {@link SdkHttpConfigurationOption#TRUST_ALL_CERTIFICATES} is enabled.
+ *
+ *
+ * Unlike the Apache, Netty, and URL connection HTTP clients, which use the JDK's TLS implementation, building the client
+ * fails with an {@link IllegalStateException} if the trust store cannot be loaded (for example, the file does not exist,
+ * the password is wrong, or the store contains no certificates or more than 1024 certificates) or if
+ * {@code javax.net.ssl.trustStore} is {@code NONE}, instead of falling back to a default trust store. Certificates are
+ * validated by the native TLS implementation, so JDK validation settings such as
+ * {@code jdk.certpath.disabledAlgorithms} do not apply.
+ *
+ * @param useTrustStoreSystemProperties whether to use the trust store specified by the JSSE system properties;
+ * {@code null} reverts to the default ({@code false})
+ * @return The builder of the method chaining.
+ */
+ AwsCrtHttpClient.Builder useTrustStoreSystemProperties(Boolean useTrustStoreSystemProperties);
}
/**
diff --git a/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/AwsCrtHttpClientBase.java b/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/AwsCrtHttpClientBase.java
index 703a196dd102..0574d52319f5 100644
--- a/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/AwsCrtHttpClientBase.java
+++ b/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/AwsCrtHttpClientBase.java
@@ -19,6 +19,8 @@
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.PROTOCOL;
import static software.amazon.awssdk.http.crt.internal.AwsCrtConfigurationUtils.buildSocketOptions;
import static software.amazon.awssdk.http.crt.internal.AwsCrtConfigurationUtils.buildTlsConnectionOptions;
+import static software.amazon.awssdk.http.crt.internal.AwsCrtConfigurationUtils.createTlsContext;
+import static software.amazon.awssdk.http.crt.internal.AwsCrtConfigurationUtils.overrideTrustStoreFromSystemProperties;
import static software.amazon.awssdk.http.crt.internal.AwsCrtConfigurationUtils.resolveCipherPreference;
import static software.amazon.awssdk.http.crt.internal.AwsCrtConfigurationUtils.resolveMinTlsVersion;
import static software.amazon.awssdk.http.crt.internal.AwsCrtConfigurationUtils.resolveMonitoringOptions;
@@ -112,18 +114,25 @@ abstract class AwsCrtHttpClientBase implements SdkAutoCloseable {
clientBootstrap = new ClientBootstrap(eventLoopGroup, null);
clientSocketOptions = buildSocketOptions(builder.getTcpKeepAliveConfiguration(),
config.get(SdkHttpConfigurationOption.CONNECTION_TIMEOUT));
+ boolean trustAllCertificates = config.get(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES);
clientTlsContextOptions =
TlsContextOptions.createDefaultClient()
.withCipherPreference(resolveCipherPreference(builder.getPostQuantumTlsEnabled()))
.withMinimumTlsVersion(resolveMinTlsVersion(builder.getMinTlsVersion()))
- .withVerifyPeer(!config.get(SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES));
+ .withVerifyPeer(!trustAllCertificates);
this.protocol = config.get(PROTOCOL);
if (protocol == Protocol.HTTP2) {
clientTlsContextOptions = clientTlsContextOptions.withAlpnList("h2");
}
+ boolean trustStoreFromSystemProperties =
+ Boolean.TRUE.equals(builder.getUseTrustStoreSystemProperties()) && !trustAllCertificates;
+ if (trustStoreFromSystemProperties) {
+ overrideTrustStoreFromSystemProperties(clientTlsContextOptions);
+ }
+
this.tlsContextOptions = registerOwnedResource(clientTlsContextOptions);
- clientTlsContext = new TlsContext(clientTlsContextOptions);
+ clientTlsContext = createTlsContext(clientTlsContextOptions, trustStoreFromSystemProperties);
// The bootstrap holds a native reference to its event-loop group, so the group is registered before (and thus
// closed after) the bootstrap to keep CRT teardown ordering correct. A null group leaves the shared static
diff --git a/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/internal/AwsCrtClientBuilderBase.java b/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/internal/AwsCrtClientBuilderBase.java
index eff0e8a6d874..30e2bdd446c1 100644
--- a/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/internal/AwsCrtClientBuilderBase.java
+++ b/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/internal/AwsCrtClientBuilderBase.java
@@ -36,6 +36,7 @@ public class AwsCrtClientBuilderBase {
private Boolean postQuantumTlsEnabled;
private TlsVersion minTlsVersion;
private Integer numEventLoopThreads;
+ private Boolean useTrustStoreSystemProperties;
protected AwsCrtClientBuilderBase() {
}
@@ -165,6 +166,15 @@ public TlsVersion getMinTlsVersion() {
return this.minTlsVersion;
}
+ public BuilderT useTrustStoreSystemProperties(Boolean useTrustStoreSystemProperties) {
+ this.useTrustStoreSystemProperties = useTrustStoreSystemProperties;
+ return thisBuilder();
+ }
+
+ public Boolean getUseTrustStoreSystemProperties() {
+ return this.useTrustStoreSystemProperties;
+ }
+
public BuilderT proxyConfiguration(Consumer proxyConfigurationBuilderConsumer) {
ProxyConfiguration.Builder builder = ProxyConfiguration.builder();
proxyConfigurationBuilderConsumer.accept(builder);
diff --git a/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/internal/AwsCrtConfigurationUtils.java b/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/internal/AwsCrtConfigurationUtils.java
index ce03372ae257..083bd0f5dda8 100644
--- a/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/internal/AwsCrtConfigurationUtils.java
+++ b/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/internal/AwsCrtConfigurationUtils.java
@@ -18,6 +18,7 @@
import java.time.Duration;
import software.amazon.awssdk.annotations.SdkInternalApi;
+import software.amazon.awssdk.crt.CrtRuntimeException;
import software.amazon.awssdk.crt.http.HttpMonitoringOptions;
import software.amazon.awssdk.crt.io.SocketOptions;
import software.amazon.awssdk.crt.io.TlsCipherPreference;
@@ -38,6 +39,8 @@ public final class AwsCrtConfigurationUtils {
// CRT rejects a throughput-failure interval below two seconds (HttpMonitoringOptions).
private static final int MIN_MONITORING_FAILURE_INTERVAL_SECONDS = 2;
+ private static final String TRUST_STORE_PROPERTY = "javax.net.ssl.trustStore";
+
private AwsCrtConfigurationUtils() {
}
@@ -142,4 +145,32 @@ public static TlsContextOptions.TlsVersions resolveMinTlsVersion(TlsVersion minT
}
}
+ /**
+ * Replaces the OS trust store in {@code tlsContextOptions} with the trust store specified by the JSSE trust store system
+ * properties. The CRT keeps the OS trust store when {@code javax.net.ssl.trustStore} is unset or empty.
+ */
+ public static void overrideTrustStoreFromSystemProperties(TlsContextOptions tlsContextOptions) {
+ try {
+ tlsContextOptions.overrideDefaultTrustStoreFromJavaSystemProperties();
+ } catch (CrtRuntimeException | IllegalArgumentException e) {
+ throw new IllegalStateException("Unable to load the trust store specified by the " + TRUST_STORE_PROPERTY
+ + " system property: " + e.getMessage(), e);
+ }
+ }
+
+ /**
+ * Creates the {@link TlsContext}. When {@code trustStoreFromSystemProperties} is true, a failure is reported as an
+ * {@link IllegalStateException}, since the native TLS layer only parses the trust store's certificates at this point.
+ */
+ public static TlsContext createTlsContext(TlsContextOptions tlsContextOptions, boolean trustStoreFromSystemProperties) {
+ try {
+ return new TlsContext(tlsContextOptions);
+ } catch (CrtRuntimeException e) {
+ if (trustStoreFromSystemProperties) {
+ throw new IllegalStateException("Unable to create the TLS context with useTrustStoreSystemProperties enabled: "
+ + e.getMessage(), e);
+ }
+ throw e;
+ }
+ }
}
diff --git a/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/AwsCrtAsyncHttpClientTrustStoreSystemPropertiesTest.java b/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/AwsCrtAsyncHttpClientTrustStoreSystemPropertiesTest.java
new file mode 100644
index 000000000000..bd29246441e5
--- /dev/null
+++ b/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/AwsCrtAsyncHttpClientTrustStoreSystemPropertiesTest.java
@@ -0,0 +1,62 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.http.crt;
+
+import static software.amazon.awssdk.http.SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES;
+import static software.amazon.awssdk.http.crt.CrtHttpClientTestUtils.createRequest;
+
+import java.net.URI;
+import java.util.concurrent.ExecutionException;
+import java.util.concurrent.TimeUnit;
+import software.amazon.awssdk.http.HttpTestUtils;
+import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
+import software.amazon.awssdk.utils.AttributeMap;
+
+class AwsCrtAsyncHttpClientTrustStoreSystemPropertiesTest
+ extends CrtTrustStoreSystemPropertiesTestSuite {
+
+ @Override
+ protected SdkAsyncHttpClient buildClient(Boolean useTrustStoreSystemProperties) {
+ return AwsCrtAsyncHttpClient.builder()
+ .useTrustStoreSystemProperties(useTrustStoreSystemProperties)
+ .connectionAcquisitionTimeout(CONNECTION_ACQUISITION_TIMEOUT)
+ .build();
+ }
+
+ @Override
+ protected SdkAsyncHttpClient buildClientTrustingAllCertificates(Boolean useTrustStoreSystemProperties) {
+ return AwsCrtAsyncHttpClient.builder()
+ .useTrustStoreSystemProperties(useTrustStoreSystemProperties)
+ .buildWithDefaults(AttributeMap.builder().put(TRUST_ALL_CERTIFICATES, true).build());
+ }
+
+ @Override
+ protected SdkAsyncHttpClient buildClientWithPrivateEventLoopGroup(Boolean useTrustStoreSystemProperties) {
+ return AwsCrtAsyncHttpClient.builder()
+ .numEventLoopThreads(2)
+ .useTrustStoreSystemProperties(useTrustStoreSystemProperties)
+ .build();
+ }
+
+ @Override
+ protected void executeGet(SdkAsyncHttpClient client, URI uri) throws Exception {
+ try {
+ HttpTestUtils.sendRequest(client, createRequest(uri)).get(30, TimeUnit.SECONDS);
+ } catch (ExecutionException e) {
+ throw (Exception) e.getCause();
+ }
+ }
+}
diff --git a/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/AwsCrtHttpClientTrustStoreSystemPropertiesTest.java b/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/AwsCrtHttpClientTrustStoreSystemPropertiesTest.java
new file mode 100644
index 000000000000..1846de6e4588
--- /dev/null
+++ b/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/AwsCrtHttpClientTrustStoreSystemPropertiesTest.java
@@ -0,0 +1,61 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.http.crt;
+
+import static software.amazon.awssdk.http.SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES;
+import static software.amazon.awssdk.http.crt.CrtHttpClientTestUtils.createRequest;
+import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely;
+
+import java.net.URI;
+import software.amazon.awssdk.http.HttpExecuteRequest;
+import software.amazon.awssdk.http.HttpExecuteResponse;
+import software.amazon.awssdk.http.SdkHttpClient;
+import software.amazon.awssdk.utils.AttributeMap;
+
+class AwsCrtHttpClientTrustStoreSystemPropertiesTest extends CrtTrustStoreSystemPropertiesTestSuite {
+
+ @Override
+ protected SdkHttpClient buildClient(Boolean useTrustStoreSystemProperties) {
+ return AwsCrtHttpClient.builder()
+ .useTrustStoreSystemProperties(useTrustStoreSystemProperties)
+ .connectionAcquisitionTimeout(CONNECTION_ACQUISITION_TIMEOUT)
+ .build();
+ }
+
+ @Override
+ protected SdkHttpClient buildClientTrustingAllCertificates(Boolean useTrustStoreSystemProperties) {
+ return AwsCrtHttpClient.builder()
+ .useTrustStoreSystemProperties(useTrustStoreSystemProperties)
+ .buildWithDefaults(AttributeMap.builder().put(TRUST_ALL_CERTIFICATES, true).build());
+ }
+
+ @Override
+ protected SdkHttpClient buildClientWithPrivateEventLoopGroup(Boolean useTrustStoreSystemProperties) {
+ return AwsCrtHttpClient.builder()
+ .numEventLoopThreads(2)
+ .useTrustStoreSystemProperties(useTrustStoreSystemProperties)
+ .build();
+ }
+
+ @Override
+ protected void executeGet(SdkHttpClient client, URI uri) throws Exception {
+ HttpExecuteResponse response = client.prepareRequest(HttpExecuteRequest.builder()
+ .request(createRequest(uri))
+ .build())
+ .call();
+ response.responseBody().ifPresent(body -> invokeSafely(body::close));
+ }
+}
diff --git a/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/CrtTrustStoreSystemPropertiesTestSuite.java b/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/CrtTrustStoreSystemPropertiesTestSuite.java
new file mode 100644
index 000000000000..bd52f2e4ccc5
--- /dev/null
+++ b/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/CrtTrustStoreSystemPropertiesTestSuite.java
@@ -0,0 +1,409 @@
+/*
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License").
+ * You may not use this file except in compliance with the License.
+ * A copy of the License is located at
+ *
+ * http://aws.amazon.com/apache2.0
+ *
+ * or in the "license" file accompanying this file. This file is distributed
+ * on an "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either
+ * express or implied. See the License for the specific language governing
+ * permissions and limitations under the License.
+ */
+
+package software.amazon.awssdk.http.crt;
+
+import static com.github.tomakehurst.wiremock.client.WireMock.aResponse;
+import static com.github.tomakehurst.wiremock.client.WireMock.get;
+import static com.github.tomakehurst.wiremock.client.WireMock.urlPathEqualTo;
+import static com.github.tomakehurst.wiremock.core.WireMockConfiguration.wireMockConfig;
+import static java.nio.charset.StandardCharsets.UTF_8;
+import static java.time.temporal.ChronoUnit.DAYS;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.assertj.core.api.Assertions.assertThatCode;
+import static org.assertj.core.api.Assertions.assertThatThrownBy;
+import static software.amazon.awssdk.http.crt.CrtHttpClientTestUtils.liveEventLoopGroups;
+import static software.amazon.awssdk.http.crt.CrtHttpClientTestUtils.newEventLoopGroups;
+
+import com.github.tomakehurst.wiremock.WireMockServer;
+import java.io.IOException;
+import java.io.OutputStream;
+import java.math.BigInteger;
+import java.net.URI;
+import java.nio.file.Files;
+import java.nio.file.Path;
+import java.security.GeneralSecurityException;
+import java.security.KeyPair;
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.cert.Certificate;
+import java.security.cert.X509Certificate;
+import java.time.Duration;
+import java.time.Instant;
+import java.util.Date;
+import java.util.HashMap;
+import java.util.Map;
+import java.util.Set;
+import java.util.stream.Stream;
+import javax.net.ssl.SSLHandshakeException;
+import org.assertj.core.api.ThrowableAssert.ThrowingCallable;
+import org.bouncycastle.asn1.x500.X500Name;
+import org.bouncycastle.asn1.x509.ExtendedKeyUsage;
+import org.bouncycastle.asn1.x509.Extension;
+import org.bouncycastle.asn1.x509.GeneralName;
+import org.bouncycastle.asn1.x509.GeneralNames;
+import org.bouncycastle.asn1.x509.KeyPurposeId;
+import org.bouncycastle.cert.X509v3CertificateBuilder;
+import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
+import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
+import org.bouncycastle.operator.ContentSigner;
+import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
+import org.junit.jupiter.api.AfterAll;
+import org.junit.jupiter.api.AfterEach;
+import org.junit.jupiter.api.BeforeAll;
+import org.junit.jupiter.api.BeforeEach;
+import org.junit.jupiter.api.Test;
+import org.junit.jupiter.params.ParameterizedTest;
+import org.junit.jupiter.params.provider.Arguments;
+import org.junit.jupiter.params.provider.MethodSource;
+import org.junit.jupiter.params.provider.NullSource;
+import org.junit.jupiter.params.provider.ValueSource;
+import software.amazon.awssdk.crt.CrtResource;
+import software.amazon.awssdk.crt.CrtRuntimeException;
+import software.amazon.awssdk.crt.http.HttpException;
+import software.amazon.awssdk.utils.SdkAutoCloseable;
+
+/**
+ * Verifies {@code useTrustStoreSystemProperties} against an HTTPS server whose self-signed certificate is not in the OS trust
+ * store, so a request succeeds only when the client trusts the trust store named by the JSSE system properties.
+ */
+abstract class CrtTrustStoreSystemPropertiesTestSuite {
+ private static final String TRUST_STORE = "javax.net.ssl.trustStore";
+ private static final String TRUST_STORE_PASSWORD = "javax.net.ssl.trustStorePassword";
+ private static final String TRUST_STORE_TYPE = "javax.net.ssl.trustStoreType";
+ private static final String TRUST_STORE_PROVIDER = "javax.net.ssl.trustStoreProvider";
+ private static final String[] TRUST_STORE_PROPERTIES = {TRUST_STORE, TRUST_STORE_PASSWORD, TRUST_STORE_TYPE,
+ TRUST_STORE_PROVIDER};
+ private static final String PASSWORD = "changeit";
+ // The CRT accepts at most 1024 certificates in a trust store, the s2n default chain limit.
+ private static final int MAX_TRUST_STORE_CERTIFICATES = 1024;
+ // Well above a loopback handshake (milliseconds) and well below s2n's 10s minimum blinding delay.
+ protected static final Duration CONNECTION_ACQUISITION_TIMEOUT = Duration.ofSeconds(2);
+ // AWS_ERROR_HTTP_CONNECTION_MANAGER_ACQUISITION_TIMEOUT
+ private static final int CONNECTION_ACQUISITION_TIMEOUT_ERROR = 2093;
+
+ private static Path tempDir;
+ private static WireMockServer server;
+ private static Path serverKeyStore;
+ private static Path serverCertTrustStore;
+ private static Path serverCertJksTrustStore;
+ private static Path unrelatedCertTrustStore;
+ private static Path emptyTrustStore;
+ private static Path corruptTrustStore;
+ private static Path oversizedTrustStore;
+
+ private final Map savedProperties = new HashMap<>();
+
+ protected abstract T buildClient(Boolean useTrustStoreSystemProperties);
+
+ protected abstract T buildClientTrustingAllCertificates(Boolean useTrustStoreSystemProperties);
+
+ protected abstract T buildClientWithPrivateEventLoopGroup(Boolean useTrustStoreSystemProperties);
+
+ protected abstract void executeGet(T client, URI uri) throws Exception;
+
+ @BeforeAll
+ static void setUpServer() throws Exception {
+ tempDir = Files.createTempDirectory("crt-truststore-test");
+ KeyPair serverKeyPair = generateKeyPair();
+ X509Certificate serverCert = selfSignedCertificate(serverKeyPair, "localhost", 1);
+ X509Certificate unrelatedCert = selfSignedCertificate(generateKeyPair(), "unrelated", 2);
+
+ KeyStore keyStore = newKeyStore("PKCS12");
+ keyStore.setKeyEntry("server", serverKeyPair.getPrivate(), PASSWORD.toCharArray(), new Certificate[] {serverCert});
+ serverKeyStore = store(keyStore, "server.p12");
+
+ serverCertTrustStore = store(trustStoreOf("PKCS12", serverCert), "server-cert.p12");
+ serverCertJksTrustStore = store(trustStoreOf("JKS", serverCert), "server-cert.jks");
+ unrelatedCertTrustStore = store(trustStoreOf("PKCS12", unrelatedCert), "unrelated-cert.p12");
+ emptyTrustStore = store(trustStoreOf("PKCS12"), "empty.p12");
+ corruptTrustStore = Files.write(tempDir.resolve("corrupt.p12"), "not a keystore".getBytes(UTF_8));
+
+ KeyPair bulkKeyPair = generateKeyPair();
+ X509Certificate[] bulkCerts = new X509Certificate[MAX_TRUST_STORE_CERTIFICATES + 1];
+ for (int i = 0; i < bulkCerts.length; i++) {
+ bulkCerts[i] = selfSignedCertificate(bulkKeyPair, "bulk" + i, 100 + i);
+ }
+ oversizedTrustStore = store(trustStoreOf("PKCS12", bulkCerts), "oversized.p12");
+
+ server = new WireMockServer(wireMockConfig().dynamicPort()
+ .dynamicHttpsPort()
+ .keystorePath(serverKeyStore.toString())
+ .keystorePassword(PASSWORD)
+ .keyManagerPassword(PASSWORD)
+ .keystoreType("PKCS12"));
+ server.start();
+ server.stubFor(get(urlPathEqualTo("/")).willReturn(aResponse().withStatus(200).withBody("ok")));
+ }
+
+ @AfterAll
+ static void tearDownServer() throws IOException {
+ server.stop();
+ try (Stream files = Files.list(tempDir)) {
+ for (Path file : (Iterable) files::iterator) {
+ Files.delete(file);
+ }
+ }
+ Files.delete(tempDir);
+ }
+
+ @BeforeEach
+ void clearTrustStoreProperties() {
+ for (String property : TRUST_STORE_PROPERTIES) {
+ savedProperties.put(property, System.getProperty(property));
+ System.clearProperty(property);
+ }
+ }
+
+ @AfterEach
+ void restoreTrustStoreProperties() {
+ savedProperties.forEach((property, value) -> {
+ if (value == null) {
+ System.clearProperty(property);
+ } else {
+ System.setProperty(property, value);
+ }
+ });
+ }
+
+ @Test
+ void useTrustStoreSystemProperties_trustStoreHasServerCertificate_requestSucceeds() throws Exception {
+ setTrustStoreProperties(serverCertTrustStore, PASSWORD);
+
+ try (T client = buildClient(true)) {
+ assertThatCode(() -> executeGet(client, serverUri())).doesNotThrowAnyException();
+ }
+ }
+
+ @Test
+ void useTrustStoreSystemProperties_trustStoreHasServerKeyEntry_requestSucceeds() throws Exception {
+ setTrustStoreProperties(serverKeyStore, PASSWORD);
+
+ try (T client = buildClient(true)) {
+ assertThatCode(() -> executeGet(client, serverUri())).doesNotThrowAnyException();
+ }
+ }
+
+ @Test
+ void useTrustStoreSystemProperties_trustStoreTypeSet_requestSucceeds() throws Exception {
+ setTrustStoreProperties(serverCertJksTrustStore, PASSWORD);
+ System.setProperty(TRUST_STORE_TYPE, "JKS");
+
+ try (T client = buildClient(true)) {
+ assertThatCode(() -> executeGet(client, serverUri())).doesNotThrowAnyException();
+ }
+ }
+
+ @Test
+ void useTrustStoreSystemProperties_trustStoreLacksServerCertificate_requestFailsHandshake() throws Exception {
+ setTrustStoreProperties(unrelatedCertTrustStore, PASSWORD);
+
+ try (T client = buildClient(true)) {
+ assertHandshakeRejected(() -> executeGet(client, serverUri()));
+ }
+ }
+
+ @ParameterizedTest
+ @NullSource
+ @ValueSource(booleans = false)
+ void useTrustStoreSystemProperties_notEnabled_ignoresTrustStoreProperties(Boolean useTrustStoreSystemProperties)
+ throws Exception {
+ setTrustStoreProperties(serverCertTrustStore, PASSWORD);
+
+ try (T client = buildClient(useTrustStoreSystemProperties)) {
+ assertHandshakeRejected(() -> executeGet(client, serverUri()));
+ }
+ }
+
+ // These cannot tell the OS trust store apart from another default such as the JDK cacerts, since neither contains the
+ // self-signed server certificate. They verify that build() succeeds and no store containing that certificate is loaded.
+ @Test
+ void useTrustStoreSystemProperties_trustStorePropertyUnset_buildSucceedsAndDoesNotTrustUnlistedCertificate()
+ throws Exception {
+ assertBuildSucceedsAndDoesNotTrustServerCertificate();
+ }
+
+ @Test
+ void useTrustStoreSystemProperties_trustStorePropertyEmpty_buildSucceedsAndDoesNotTrustUnlistedCertificate()
+ throws Exception {
+ System.setProperty(TRUST_STORE, "");
+
+ assertBuildSucceedsAndDoesNotTrustServerCertificate();
+ }
+
+ private void assertBuildSucceedsAndDoesNotTrustServerCertificate() throws Exception {
+ try (T client = buildClient(true)) {
+ assertHandshakeRejected(() -> executeGet(client, serverUri()));
+ }
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("trustStoreLoadFailures")
+ void useTrustStoreSystemProperties_trustStoreLoadFails_buildThrowsIllegalStateExceptionNamingTrustStore(
+ String description, String trustStore, String password, String type, String provider,
+ Class extends Throwable> expectedCause) {
+ setTrustStoreProperties(trustStore, password, type, provider);
+
+ assertThatThrownBy(() -> buildClient(true))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessageContaining(TRUST_STORE)
+ .hasMessageContaining(trustStore)
+ .hasCauseInstanceOf(expectedCause);
+ }
+
+ private static Stream trustStoreLoadFailures() {
+ return Stream.of(
+ Arguments.of("file does not exist", missingTrustStore(), PASSWORD, null, null, CrtRuntimeException.class),
+ Arguments.of("wrong password", serverCertTrustStore.toString(), "wrong", null, null, CrtRuntimeException.class),
+ Arguments.of("corrupt file", corruptTrustStore.toString(), PASSWORD, null, null, CrtRuntimeException.class),
+ Arguments.of("type unavailable", serverCertTrustStore.toString(), PASSWORD, "NO-SUCH-TYPE", null,
+ CrtRuntimeException.class),
+ Arguments.of("provider unavailable", serverCertTrustStore.toString(), PASSWORD, null, "NoSuchProvider",
+ CrtRuntimeException.class),
+ Arguments.of("NONE", "NONE", null, null, null, IllegalArgumentException.class));
+ }
+
+ @ParameterizedTest(name = "{0}")
+ @MethodSource("trustStoreRejections")
+ void useTrustStoreSystemProperties_trustStoreRejected_buildThrowsIllegalStateException(
+ String description, String trustStore, Class extends Throwable> expectedCause) {
+ setTrustStoreProperties(trustStore, PASSWORD, null, null);
+
+ assertThatThrownBy(() -> buildClient(true))
+ .isInstanceOf(IllegalStateException.class)
+ .hasMessageContaining(TRUST_STORE)
+ .hasCauseInstanceOf(expectedCause);
+ }
+
+ private static Stream trustStoreRejections() {
+ return Stream.of(
+ Arguments.of("whitespace-only path", " ", CrtRuntimeException.class),
+ Arguments.of("no certificates", emptyTrustStore.toString(), IllegalArgumentException.class),
+ Arguments.of("more certificates than the CRT accepts", oversizedTrustStore.toString(),
+ IllegalArgumentException.class));
+ }
+
+ @Test
+ void useTrustStoreSystemProperties_trustStoreLoadFails_doesNotLeakPrivateEventLoopGroup() {
+ // Creates the shared static default event-loop group up front so the before/after diff only sees private groups.
+ buildClient(null).close();
+ Set before = liveEventLoopGroups();
+ System.setProperty(TRUST_STORE, missingTrustStore());
+
+ assertThatThrownBy(() -> buildClientWithPrivateEventLoopGroup(true)).isInstanceOf(IllegalStateException.class);
+
+ assertThat(newEventLoopGroups(before)).isEmpty();
+ }
+
+ @Test
+ void useTrustStoreSystemProperties_trustAllCertificates_doesNotReadTrustStore() throws Exception {
+ System.setProperty(TRUST_STORE, missingTrustStore());
+
+ try (T client = buildClientTrustingAllCertificates(true)) {
+ assertThatCode(() -> executeGet(client, serverUri())).doesNotThrowAnyException();
+ }
+ }
+
+ @Test
+ void useTrustStoreSystemProperties_propertiesChangedAfterBuild_keepsTrustStoreReadAtBuild() throws Exception {
+ setTrustStoreProperties(serverCertTrustStore, PASSWORD);
+
+ try (T client = buildClient(true)) {
+ setTrustStoreProperties(unrelatedCertTrustStore, PASSWORD);
+
+ assertThatCode(() -> executeGet(client, serverUri())).doesNotThrowAnyException();
+ }
+ }
+
+ // s2n reports a rejected certificate only after its 10-30s blinding delay, so the acquisition timeout can fire first.
+ private static void assertHandshakeRejected(ThrowingCallable request) {
+ assertThatThrownBy(request).satisfiesAnyOf(
+ e -> assertThat(e).isInstanceOf(SSLHandshakeException.class),
+ e -> {
+ assertThat(e).isInstanceOf(IOException.class).hasCauseInstanceOf(HttpException.class);
+ assertThat(((HttpException) e.getCause()).getErrorCode()).isEqualTo(CONNECTION_ACQUISITION_TIMEOUT_ERROR);
+ });
+ }
+
+ private static URI serverUri() {
+ return URI.create("https://localhost:" + server.httpsPort());
+ }
+
+ private static void setTrustStoreProperties(Path trustStore, String password) {
+ System.setProperty(TRUST_STORE, trustStore.toString());
+ System.setProperty(TRUST_STORE_PASSWORD, password);
+ }
+
+ private static String missingTrustStore() {
+ return tempDir.resolve("missing.p12").toString();
+ }
+
+ private static void setTrustStoreProperties(String trustStore, String password, String type, String provider) {
+ System.setProperty(TRUST_STORE, trustStore);
+ setIfNotNull(TRUST_STORE_PASSWORD, password);
+ setIfNotNull(TRUST_STORE_TYPE, type);
+ setIfNotNull(TRUST_STORE_PROVIDER, provider);
+ }
+
+ private static void setIfNotNull(String property, String value) {
+ if (value != null) {
+ System.setProperty(property, value);
+ }
+ }
+
+ private static KeyPair generateKeyPair() throws GeneralSecurityException {
+ KeyPairGenerator generator = KeyPairGenerator.getInstance("RSA");
+ generator.initialize(2048);
+ return generator.generateKeyPair();
+ }
+
+ private static X509Certificate selfSignedCertificate(KeyPair keyPair, String commonName, long serialNumber)
+ throws Exception {
+ Instant now = Instant.now();
+ X500Name name = new X500Name("CN=" + commonName);
+ X509v3CertificateBuilder builder = new JcaX509v3CertificateBuilder(name, BigInteger.valueOf(serialNumber),
+ Date.from(now.minus(1, DAYS)),
+ Date.from(now.plus(1, DAYS)),
+ name, keyPair.getPublic());
+ builder.addExtension(Extension.subjectAlternativeName, false,
+ new GeneralNames(new GeneralName[] {new GeneralName(GeneralName.dNSName, "localhost"),
+ new GeneralName(GeneralName.iPAddress, "127.0.0.1")}));
+ builder.addExtension(Extension.extendedKeyUsage, false, new ExtendedKeyUsage(KeyPurposeId.id_kp_serverAuth));
+ ContentSigner signer = new JcaContentSignerBuilder("SHA256withRSA").build(keyPair.getPrivate());
+ return new JcaX509CertificateConverter().getCertificate(builder.build(signer));
+ }
+
+ private static KeyStore newKeyStore(String type) throws Exception {
+ KeyStore keyStore = KeyStore.getInstance(type);
+ keyStore.load(null, null);
+ return keyStore;
+ }
+
+ private static KeyStore trustStoreOf(String type, X509Certificate... certificates) throws Exception {
+ KeyStore keyStore = newKeyStore(type);
+ for (int i = 0; i < certificates.length; i++) {
+ keyStore.setCertificateEntry("cert" + i, certificates[i]);
+ }
+ return keyStore;
+ }
+
+ private static Path store(KeyStore keyStore, String fileName) throws Exception {
+ Path file = tempDir.resolve(fileName);
+ try (OutputStream out = Files.newOutputStream(file)) {
+ keyStore.store(out, PASSWORD.toCharArray());
+ }
+ return file;
+ }
+}
diff --git a/pom.xml b/pom.xml
index c5322230423b..667b62a3d6c3 100644
--- a/pom.xml
+++ b/pom.xml
@@ -131,7 +131,7 @@
3.1.5
1.17.1
1.37
- 0.48.5
+ 0.49.0
5.10.3
@@ -153,6 +153,9 @@
1.3.0
1.5.4
2.0.3
+ 1.80
+
+ 1.80.2
3.1.2
diff --git a/test/http-client-tests/pom.xml b/test/http-client-tests/pom.xml
index 0aec7a626fd1..901fca7ea582 100644
--- a/test/http-client-tests/pom.xml
+++ b/test/http-client-tests/pom.xml
@@ -126,7 +126,7 @@
org.bouncycastle
bcpkix-jdk18on
- 1.80
+ ${bouncycastle-bcpkix.version}
compile
diff --git a/test/protocol-tests/pom.xml b/test/protocol-tests/pom.xml
index 7fadd124d664..0885b529ca3f 100644
--- a/test/protocol-tests/pom.xml
+++ b/test/protocol-tests/pom.xml
@@ -293,7 +293,7 @@
org.bouncycastle
bcpkix-jdk18on
- 1.80
+ ${bouncycastle-bcpkix.version}
test
From 25efc8dda9d6ae01e66c31156b149b1cf931414c Mon Sep 17 00:00:00 2001
From: Zoe Wang <33073555+zoewangg@users.noreply.github.com>
Date: Fri, 16 Oct 2026 16:19:29 -0700
Subject: [PATCH 2/3] fix: propagate CRT TlsContext failures unwrapped
The CRT gives no signal when javax.net.ssl.trustStore is unset, so
wrapping TlsContext failures whenever the option was on made the
exception type depend on the option even when no Java trust store
was loaded. Let CrtRuntimeException propagate as it does with the
option off.
---
.../awssdk/http/crt/AwsCrtHttpClientBase.java | 7 ++-----
.../crt/internal/AwsCrtConfigurationUtils.java | 16 ----------------
2 files changed, 2 insertions(+), 21 deletions(-)
diff --git a/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/AwsCrtHttpClientBase.java b/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/AwsCrtHttpClientBase.java
index 0574d52319f5..31c4de353327 100644
--- a/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/AwsCrtHttpClientBase.java
+++ b/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/AwsCrtHttpClientBase.java
@@ -19,7 +19,6 @@
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.PROTOCOL;
import static software.amazon.awssdk.http.crt.internal.AwsCrtConfigurationUtils.buildSocketOptions;
import static software.amazon.awssdk.http.crt.internal.AwsCrtConfigurationUtils.buildTlsConnectionOptions;
-import static software.amazon.awssdk.http.crt.internal.AwsCrtConfigurationUtils.createTlsContext;
import static software.amazon.awssdk.http.crt.internal.AwsCrtConfigurationUtils.overrideTrustStoreFromSystemProperties;
import static software.amazon.awssdk.http.crt.internal.AwsCrtConfigurationUtils.resolveCipherPreference;
import static software.amazon.awssdk.http.crt.internal.AwsCrtConfigurationUtils.resolveMinTlsVersion;
@@ -125,14 +124,12 @@ abstract class AwsCrtHttpClientBase implements SdkAutoCloseable {
clientTlsContextOptions = clientTlsContextOptions.withAlpnList("h2");
}
- boolean trustStoreFromSystemProperties =
- Boolean.TRUE.equals(builder.getUseTrustStoreSystemProperties()) && !trustAllCertificates;
- if (trustStoreFromSystemProperties) {
+ if (Boolean.TRUE.equals(builder.getUseTrustStoreSystemProperties()) && !trustAllCertificates) {
overrideTrustStoreFromSystemProperties(clientTlsContextOptions);
}
this.tlsContextOptions = registerOwnedResource(clientTlsContextOptions);
- clientTlsContext = createTlsContext(clientTlsContextOptions, trustStoreFromSystemProperties);
+ clientTlsContext = new TlsContext(clientTlsContextOptions);
// The bootstrap holds a native reference to its event-loop group, so the group is registered before (and thus
// closed after) the bootstrap to keep CRT teardown ordering correct. A null group leaves the shared static
diff --git a/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/internal/AwsCrtConfigurationUtils.java b/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/internal/AwsCrtConfigurationUtils.java
index 083bd0f5dda8..a78f40862a72 100644
--- a/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/internal/AwsCrtConfigurationUtils.java
+++ b/http-clients/aws-crt-client/src/main/java/software/amazon/awssdk/http/crt/internal/AwsCrtConfigurationUtils.java
@@ -157,20 +157,4 @@ public static void overrideTrustStoreFromSystemProperties(TlsContextOptions tlsC
+ " system property: " + e.getMessage(), e);
}
}
-
- /**
- * Creates the {@link TlsContext}. When {@code trustStoreFromSystemProperties} is true, a failure is reported as an
- * {@link IllegalStateException}, since the native TLS layer only parses the trust store's certificates at this point.
- */
- public static TlsContext createTlsContext(TlsContextOptions tlsContextOptions, boolean trustStoreFromSystemProperties) {
- try {
- return new TlsContext(tlsContextOptions);
- } catch (CrtRuntimeException e) {
- if (trustStoreFromSystemProperties) {
- throw new IllegalStateException("Unable to create the TLS context with useTrustStoreSystemProperties enabled: "
- + e.getMessage(), e);
- }
- throw e;
- }
- }
}
From 34059d66cf6f97a49e06513ba710cbf3e6f29f4f Mon Sep 17 00:00:00 2001
From: Zoe Wang <33073555+zoewangg@users.noreply.github.com>
Date: Fri, 16 Oct 2026 16:25:32 -0700
Subject: [PATCH 3/3] test: assert response in CRT trust store tests
---
...pClientTrustStoreSystemPropertiesTest.java | 16 ++++++--
...pClientTrustStoreSystemPropertiesTest.java | 9 +++--
...rtTrustStoreSystemPropertiesTestSuite.java | 39 +++++++++++++++----
3 files changed, 50 insertions(+), 14 deletions(-)
diff --git a/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/AwsCrtAsyncHttpClientTrustStoreSystemPropertiesTest.java b/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/AwsCrtAsyncHttpClientTrustStoreSystemPropertiesTest.java
index bd29246441e5..ed46378814e3 100644
--- a/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/AwsCrtAsyncHttpClientTrustStoreSystemPropertiesTest.java
+++ b/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/AwsCrtAsyncHttpClientTrustStoreSystemPropertiesTest.java
@@ -19,9 +19,11 @@
import static software.amazon.awssdk.http.crt.CrtHttpClientTestUtils.createRequest;
import java.net.URI;
+import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
-import software.amazon.awssdk.http.HttpTestUtils;
+import software.amazon.awssdk.http.RecordingResponseHandler;
+import software.amazon.awssdk.http.async.AsyncExecuteRequest;
import software.amazon.awssdk.http.async.SdkAsyncHttpClient;
import software.amazon.awssdk.utils.AttributeMap;
@@ -52,11 +54,19 @@ protected SdkAsyncHttpClient buildClientWithPrivateEventLoopGroup(Boolean useTru
}
@Override
- protected void executeGet(SdkAsyncHttpClient client, URI uri) throws Exception {
+ protected GetResponse executeGet(SdkAsyncHttpClient client, URI uri) throws Exception {
+ RecordingResponseHandler recorder = new RecordingResponseHandler();
+ CompletableFuture execution = client.execute(AsyncExecuteRequest.builder()
+ .request(createRequest(uri))
+ .requestContentPublisher(new EmptyPublisher())
+ .responseHandler(recorder)
+ .build());
try {
- HttpTestUtils.sendRequest(client, createRequest(uri)).get(30, TimeUnit.SECONDS);
+ execution.get(30, TimeUnit.SECONDS);
+ recorder.completeFuture().get(30, TimeUnit.SECONDS);
} catch (ExecutionException e) {
throw (Exception) e.getCause();
}
+ return new GetResponse(recorder.responses().get(0).statusCode(), recorder.fullResponseAsString());
}
}
diff --git a/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/AwsCrtHttpClientTrustStoreSystemPropertiesTest.java b/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/AwsCrtHttpClientTrustStoreSystemPropertiesTest.java
index 1846de6e4588..bb8242ced3e1 100644
--- a/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/AwsCrtHttpClientTrustStoreSystemPropertiesTest.java
+++ b/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/AwsCrtHttpClientTrustStoreSystemPropertiesTest.java
@@ -17,13 +17,14 @@
import static software.amazon.awssdk.http.SdkHttpConfigurationOption.TRUST_ALL_CERTIFICATES;
import static software.amazon.awssdk.http.crt.CrtHttpClientTestUtils.createRequest;
-import static software.amazon.awssdk.utils.FunctionalUtils.invokeSafely;
import java.net.URI;
+import software.amazon.awssdk.http.AbortableInputStream;
import software.amazon.awssdk.http.HttpExecuteRequest;
import software.amazon.awssdk.http.HttpExecuteResponse;
import software.amazon.awssdk.http.SdkHttpClient;
import software.amazon.awssdk.utils.AttributeMap;
+import software.amazon.awssdk.utils.IoUtils;
class AwsCrtHttpClientTrustStoreSystemPropertiesTest extends CrtTrustStoreSystemPropertiesTestSuite {
@@ -51,11 +52,13 @@ protected SdkHttpClient buildClientWithPrivateEventLoopGroup(Boolean useTrustSto
}
@Override
- protected void executeGet(SdkHttpClient client, URI uri) throws Exception {
+ protected GetResponse executeGet(SdkHttpClient client, URI uri) throws Exception {
HttpExecuteResponse response = client.prepareRequest(HttpExecuteRequest.builder()
.request(createRequest(uri))
.build())
.call();
- response.responseBody().ifPresent(body -> invokeSafely(body::close));
+ try (AbortableInputStream body = response.responseBody().orElseThrow(IllegalStateException::new)) {
+ return new GetResponse(response.httpResponse().statusCode(), IoUtils.toUtf8String(body));
+ }
}
}
diff --git a/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/CrtTrustStoreSystemPropertiesTestSuite.java b/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/CrtTrustStoreSystemPropertiesTestSuite.java
index bd52f2e4ccc5..138a3af50f8b 100644
--- a/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/CrtTrustStoreSystemPropertiesTestSuite.java
+++ b/http-clients/aws-crt-client/src/test/java/software/amazon/awssdk/http/crt/CrtTrustStoreSystemPropertiesTestSuite.java
@@ -22,7 +22,6 @@
import static java.nio.charset.StandardCharsets.UTF_8;
import static java.time.temporal.ChronoUnit.DAYS;
import static org.assertj.core.api.Assertions.assertThat;
-import static org.assertj.core.api.Assertions.assertThatCode;
import static org.assertj.core.api.Assertions.assertThatThrownBy;
import static software.amazon.awssdk.http.crt.CrtHttpClientTestUtils.liveEventLoopGroups;
import static software.amazon.awssdk.http.crt.CrtHttpClientTestUtils.newEventLoopGroups;
@@ -87,6 +86,7 @@ abstract class CrtTrustStoreSystemPropertiesTestSuite executeGet(client, serverUri())).doesNotThrowAnyException();
+ assertStubResponse(executeGet(client, serverUri()));
}
}
@@ -192,7 +192,7 @@ void useTrustStoreSystemProperties_trustStoreHasServerKeyEntry_requestSucceeds()
setTrustStoreProperties(serverKeyStore, PASSWORD);
try (T client = buildClient(true)) {
- assertThatCode(() -> executeGet(client, serverUri())).doesNotThrowAnyException();
+ assertStubResponse(executeGet(client, serverUri()));
}
}
@@ -202,7 +202,7 @@ void useTrustStoreSystemProperties_trustStoreTypeSet_requestSucceeds() throws Ex
System.setProperty(TRUST_STORE_TYPE, "JKS");
try (T client = buildClient(true)) {
- assertThatCode(() -> executeGet(client, serverUri())).doesNotThrowAnyException();
+ assertStubResponse(executeGet(client, serverUri()));
}
}
@@ -312,7 +312,7 @@ void useTrustStoreSystemProperties_trustAllCertificates_doesNotReadTrustStore()
System.setProperty(TRUST_STORE, missingTrustStore());
try (T client = buildClientTrustingAllCertificates(true)) {
- assertThatCode(() -> executeGet(client, serverUri())).doesNotThrowAnyException();
+ assertStubResponse(executeGet(client, serverUri()));
}
}
@@ -323,10 +323,15 @@ void useTrustStoreSystemProperties_propertiesChangedAfterBuild_keepsTrustStoreRe
try (T client = buildClient(true)) {
setTrustStoreProperties(unrelatedCertTrustStore, PASSWORD);
- assertThatCode(() -> executeGet(client, serverUri())).doesNotThrowAnyException();
+ assertStubResponse(executeGet(client, serverUri()));
}
}
+ private static void assertStubResponse(GetResponse response) {
+ assertThat(response.statusCode()).isEqualTo(200);
+ assertThat(response.body()).isEqualTo(STUB_BODY);
+ }
+
// s2n reports a rejected certificate only after its 10-30s blinding delay, so the acquisition timeout can fire first.
private static void assertHandshakeRejected(ThrowingCallable request) {
assertThatThrownBy(request).satisfiesAnyOf(
@@ -406,4 +411,22 @@ private static Path store(KeyStore keyStore, String fileName) throws Exception {
}
return file;
}
+
+ protected static final class GetResponse {
+ private final int statusCode;
+ private final String body;
+
+ GetResponse(int statusCode, String body) {
+ this.statusCode = statusCode;
+ this.body = body;
+ }
+
+ int statusCode() {
+ return statusCode;
+ }
+
+ String body() {
+ return body;
+ }
+ }
}