I'm trying to implement the solution presented in the following AWS article:
So I did next steps:
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
Create local truststore:
keytool -keystore my_ca.jks -alias myalias -import -file AmazonRootCA1.pem
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;
}
}
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.
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.