4

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?

wheeler
  • 2,823
  • 3
  • 27
  • 43

1 Answers1

1

In order to work with custom SSL in runtime we need to load ApacheClient SSL using following code snippet

SSLContext sslcontext = SSLContexts.custom()
            .loadTrustMaterial(new File("/path/to/my/cacerts.success"),  "changeit".toCharArray(),
                    new TrustSelfSignedStrategy())
            .build();
// Allow TLSv1 protocol only add others if required
    SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(
            sslcontext,
            new String[] { "TLSv1" },
            null,
            SSLConnectionSocketFactory.getDefaultHostnameVerifier());
// Adding Custom SSL Support
    config.getApacheHttpClientConfig().setSslSocketFactory(sslsf);
    config.setSignerOverride("AWSS3V4SignerType");

Once certificate is loaded we can connect with following 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()

After this normal operations can be done such as

client2.getObject("bucketName", "path/to/file")