I am trying to connect to an internal ECS instance with the Amazon S3 SDK. The connection to ECS is secured with a certificate signed by an internal CA. I am trying to programmatically load a Java keystore file that has the CA inside of it, but the connection to ECS using the Amazon SDK keeps failing with certificate-related errors.
com.amazonaws.SdkClientException: Unable to execute HTTP request: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
Here is the small sample code that loads the keystore. :
val ks = KeyStore.getInstance(KeyStore.getDefaultType)
ks.load(this.getClass.getClassLoader.getResourceAsStream("cacerts.success"), "changeit".toCharArray)
val tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm)
tmf.init(ks)
val sslContext = SSLContext.getInstance("TLSv1.2")
sslContext.init(null, tmf.getTrustManagers, null)
HttpsURLConnection.setDefaultSSLSocketFactory(sslContext.getSocketFactory)
val response = Http("https://ecsNamespace.ecsHostname.internal.com:9021/bucketName/path/to/file").asString
So Http
has no problem with using the SSL socket factory from the custom SSL context object I created and set as the default.
However, the Amazon SDK does have issues. If I replace the Http
call to the public ECS resource with an instance of the S3 Client, then it fails with this error:
com.amazonaws.SdkClientException: Unable to execute HTTP request: sun.security.validator.ValidatorException: PKIX path building failed: sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target
This is the code:
val client2 = AmazonS3ClientBuilder.standard()
.withCredentials(new AWSStaticCredentialsProvider(new BasicAWSCredentials("keyId", "secret")))
.withEndpointConfiguration(new EndpointConfiguration("https://ecsNamespace.ecsHostname.internal.com:9021", "us-east-1")) // S3 SDK requires a region, but ECS will ignore it.
.withPathStyleAccessEnabled(true)
.build()
client2.getObject("bucketName", "path/to/file")
The Amazon SDK works with the internal CA if I set the system properties ahead of time:
export JAVA_OPTS="-Djavax.net.ssl.trustStore=/path/to/my/cacerts.success -Djavax.net.ssl.trustStorePassword=changeit"
So it would seem that the Amazon SDK (or whatever underlying HTTP library) is only getting the default SSL context when the application starts (instead of getting it every time it creates an SSL connection, so to react to run-time changes to the default SSL context).
Any ideas?