2

I'm trying to implement the solution presented in the following AWS article:


So I did next steps:

  1. Create local keystore:

    keystore winpty openssl pkcs12 -export -in eeb81a0eb6-certificate.pem.crt -inkey eeb81a0eb6-private.pem.key -name myname -out my.p12 -password pass:mypass

    keytool -importkeystore -destkeystore mykeystore.jks -srckeystore my.p12 -srcstoretype PKCS12 -deststorepass mypass -srcstorepass mypass

  2. Create local truststore:

    keytool -keystore my_ca.jks -alias myalias -import -file AmazonRootCA1.pem

  3. My code:

public class AWSSessionCredentialsProviderImpl implements AWSSessionCredentialsProvider  {
    
    private static final Logger LOGGER = LogManager.getLogger(AWSSessionCredentialsProviderImpl.class.getName());
    
    private final Gson gson = new Gson();
    
    private SdkHttpClient client;
    private HttpExecuteRequest request; 
    private String awsAccessKeyId;
    private String awsSecretAccessKeyId;
    private String awsSessionToken;
    
    public void init(String clientId) throws IOException, URISyntaxException {
        System.setProperty("javax.net.ssl.trustStore", Configuration.KEYSTOREPATH_CA.toAbsolutePath().toString());
        System.setProperty("javax.net.ssl.trustStoreType", "jks");
        
        try {
            System.setProperty("javax.net.ssl.trustStorePassword", new String(Files.readAllBytes(Configuration.KEYSTOREPATH_CA_PASS)));
        } catch (IOException e) {
            throw new IOException("Read password of trust store is failed", e);
        }
        
        
        System.setProperty("javax.net.ssl.keyStore", Configuration.KEYSTOREPATH.toAbsolutePath().toString());
        System.setProperty("javax.net.ssl.keyStoreType", "jks");
        
        try {
            System.setProperty("javax.net.ssl.keyStorePassword", new String(Files.readAllBytes(Configuration.KEYSTOREPATH_PASS)));
        } catch (IOException e) {
            throw new IOException("Read password of key store is failed", e);
        }

        client = ApacheHttpClient.builder().build();

        SdkHttpRequest httpRequest;
        try {
            httpRequest = SdkHttpFullRequest.builder()
                    .method(SdkHttpMethod.GET)
                    .uri(new URI(Configuration.CLIENT_ENDPOINT))
                    .putHeader("x-amzn-iot-thingname", clientId)
                    .build();
        } catch (URISyntaxException e) {
            throw new URISyntaxException(Configuration.CLIENT_ENDPOINT, "Building URI from client endpoint is failed");
        }

        request = HttpExecuteRequest.builder()
                .request(httpRequest)
                .build();
        try {
            setCredentials();
        } catch (IOException e) {
            throw new IOException("Set temporary credentials is failed", e);
        }
    }
    
    @Override
    public void refresh() {
        try {
            setCredentials();
        } catch (IOException e) {
            LOGGER.error("Refresh session credentials is failed", e);
        }
    }
    
    @Override
    public AWSSessionCredentials getCredentials() {
        return new BasicSessionCredentials(awsAccessKeyId, awsSecretAccessKeyId, awsSessionToken);
    }
    
    private void setCredentials() throws IOException {
        HttpExecuteResponse response = client.prepareRequest(request).call();
        String credStr = IoUtils.toUtf8String(response.responseBody().get());
        
        CredentialsJson credJson = gson.fromJson(credStr, CredentialsJson.class);
        awsAccessKeyId = credJson.credentials.accessKeyId;
        awsSecretAccessKeyId = credJson.credentials.secretAccessKey;
        awsSessionToken = credJson.credentials.sessionToken;
    }
}

  1. So, I get temporary credentials successfully, but when I use them:
AWSSessionCredentialsProviderImpl credentialsProvider = new AWSSessionCredentialsProviderImpl();
credentialsProvider.init("someid");

s3Client = AmazonS3ClientBuilder.standard()
                .withRegion(region)
                .withCredentials(credentialsProvider)
                .build();

s3Client.putObject(request); 

I get the following exception:

Caused by:
sun.security.provider.certpath.SunCertPathBuilderException: unable to find valid certification path to requested target

I don't understand why I get this exception if I can get temporary credentials successfully.

informatik01
  • 16,038
  • 10
  • 74
  • 104
Rougher
  • 834
  • 5
  • 19
  • 46

1 Answers1

4

The problem could be related with many things.

Most likely, your Java program will not be able to establish a trust relationship with the remote peer, probably because the AWS CA is not one of the preconfigured JVM trusted CAs.

I think the best approach you can take to solve the problem is to pass the SdkHttpClient that you already have to the S3 client as well.

Please, be aware that in your sample code you are using AmazonS3ClientBuilder, a AWS Java SDK version 1 class, meanwhile the rest of the code is using AWS SDK v2.

Maybe you can update your code to the latest version of the S3Client and try something like this:

System.setProperty("javax.net.ssl.trustStore", Configuration.KEYSTOREPATH_CA.toAbsolutePath().toString());
System.setProperty("javax.net.ssl.trustStoreType", "jks");

try {
    System.setProperty("javax.net.ssl.trustStorePassword", new String(Files.readAllBytes(Configuration.KEYSTOREPATH_CA_PASS)));
} catch (IOException e) {
    throw new IOException("Read password of trust store is failed", e);
}


System.setProperty("javax.net.ssl.keyStore", Configuration.KEYSTOREPATH.toAbsolutePath().toString());
System.setProperty("javax.net.ssl.keyStoreType", "jks");

try {
    System.setProperty("javax.net.ssl.keyStorePassword", new String(Files.readAllBytes(Configuration.KEYSTOREPATH_PASS)));
} catch (IOException e) {
    throw new IOException("Read password of key store is failed", e);
}

SdkHttpClient client = ApacheHttpClient.builder().build();

// The idea is reuse the configured HTTP client, modify it as per your needs
AWSSessionCredentialsProviderImpl credentialsProvider = new AWSSessionCredentialsProviderImpl(client);
credentialsProvider.init("someid");

S3Client s3 = S3Client.builder()
  .httpClient(client)
  .region(region)
  .credentialsProvider(credentialsProvider)
  .build();

Please, be sure that your trust store contains the actual SSL certificate. You have the root CA certificate of AWS, but maybe not the corresponding to the actual service.

If necessary, you can obtain the service SSL certificate with something like this:

openssl s_client -connect s3.us-west-1.amazonaws.com:443

Please, change the command according to your region. You need to extract the PEM content from the response.

As indicated in the comments to the answer, another alternative could be unset the System properties established when you obtain your credentials before the invocation of the S3Client:

System.clearProperty("javax.net.ssl.trustStore");
System.clearProperty("javax.net.ssl.trustStorePassword");
System.clearProperty("javax.net.ssl.trustStoreType");

It will provide the AWS SDK with a fresh environment for invoking S3.

jccampanero
  • 50,989
  • 3
  • 20
  • 49
  • I checked. The same exception happens. I get temporary credentials successfully, but put object to S3 is failed. – Rougher Oct 14 '20 at 09:04
  • I see Rougher. Please, just for digging in the problem, can you unset the ```System``` properties you established, ```System.clearProperty("javax.net.ssl.trustStore"); System.clearProperty("javax.net.ssl.trustStorePassword"); System.clearProperty("javax.net.ssl.trustStoreType");``` before ```S3Client``` invocation and create the ```S3Client``` without passing the HTTP client in the ```httpClient``` method? Sorry, if I did not explain myself very well. – jccampanero Oct 14 '20 at 10:11
  • It works! I don't understand. So for getting security token we need this trustStore and for other aws command we want to use for default one (jdk1.8.0_111\jre\lib\security\cacerts)? It's very strange – Rougher Oct 14 '20 at 10:17
  • I tried to add AmazonRootCA1.pem to jdk1.8.0_111\jre\lib\security\cacerts, but it didn't help. – Rougher Oct 14 '20 at 10:44
  • Sorry for the late reply. Yes, I think the problem consists in that your trust store does not contain the actual SSL certificate. You have the root CA certificate of AWS, but not the corresponding to the actual service. Maybe you can try to download that specific certificate and include in your trust store. You can obtain the certificate with something like this: ```openssl s_client -connect s3.us-west-1.amazonaws.com:443```. Change the command according to your region. You need to extract the pem content from the response. Please, can you try? – jccampanero Oct 14 '20 at 11:14
  • Great Rougher! And, a pleasure! – jccampanero Oct 14 '20 at 12:16
  • I think I'll take the solution with System.clearProperty, because s3 certificate is valid for year only. I install my software on client's device, so I can't access to this device to change a certificate after year. – Rougher Oct 14 '20 at 12:27
  • It is fine. Please, be aware that you can also setup the TrustManagers as I told you in your [previous question](https://stackoverflow.com/questions/64257498/get-security-token-from-aws-credentials-provider/64295637#64295637) providing a properly configured ```TlsTrustManagersProvider```. This would allow you to get the credentials properly without the need to set and then clean the system properties afterwards. – jccampanero Oct 14 '20 at 12:36