2

Can somebody explain me, how do I need to implement the first step from this blog? I can't find it in AWS documentation.

In other words, I need to translate a command:

curl --cert eeb81a0eb6-certificate.pem.crt --key eeb81a0eb6-private.pem.key -H "x-amzn-iot-thingname: myThingName" --cacert AmazonRootCA1.pem https://<prefix>.credentials.iot.us-west-2.amazonaws.com/role-aliases/MyAlias/credentials

to JAVA. How can I do it? I need AWS SDK for it (I prefer a solution without "custom client to make an HTTPS request")

UPDATE:

I tried to use a custom client to make an HTTPS request, but I stuck when strated to export my keys to Java KeyStore (BUT curl command works for me fine):

$ winpty openssl pkcs12 -export -in eeb81a0eb6-certificate.pem.crt -inkey eeb81a0eb6-private.pem.key -chain -CAfile AmazonRootCA1.pem -name mycompany.com -out my.p12

Error unable to get local issuer certificate getting chain.

ANOTHER UPDATE (WHAT I TRIED ALREADY)

  1. Convert myPrivateKey and deviceCertificate to JKS:

    winpty openssl pkcs12 -export -in eeb81a0eb6-certificate.pem.crt -inkey eeb81a0eb6-private.pem.key -name mycompany.com -out my.p12

    keytool -importkeystore -destkeystore mycompany.jks -srckeystore my.p12 -srcstoretype PKCS12

  2. Use this JKS from my code:

     System.setProperty("deployment.security.TLSv1.2", "true");
     System.setProperty("https.protocols", "TLSv1.2");
     System.setProperty("javax.net.debug", "ssl");
    
     HttpPost request = new HttpPost(clientEndpoint);
     request.setHeader("x-amzn-iot-thingname", "0ad16050-d974-4f78-88ea-c6ee2b0a551e");
    
     KeyStore keyStore;
     try (InputStream keyStoreStream = this.getClass().getResourceAsStream(KEYSTOREPATH)) {
         keyStore = KeyStore.getInstance("PKCS12");
         keyStore.load(keyStoreStream, KEYSTOREPASS.toCharArray());
     }
    
     SSLContext sslContext = SSLContexts.custom()
             .loadKeyMaterial(keyStore, KEYPASS.toCharArray()) // use null as second param if you don't have a separate key password
             .loadTrustMaterial(null, new TrustSelfSignedStrategy())
             .build();   
    
     SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext);
     Registry<ConnectionSocketFactory> registry = RegistryBuilder.<ConnectionSocketFactory>create()
                                                     .register("https", sslConnectionSocketFactory)
                                                     .register("http", new PlainConnectionSocketFactory())
                                                     .build();
    
     BasicHttpClientConnectionManager manager = new BasicHttpClientConnectionManager(registry);
    
     try (CloseableHttpClient httpClient = HttpClients
                                             .custom()
                                             .setSSLSocketFactory(sslConnectionSocketFactory)
                                             .setConnectionManager(manager)
                                             .build();
          CloseableHttpResponse response = httpClient.execute(request)) {
    
    
         System.out.println();
    
    
    
     } catch (IOException e) {
         System.err.println(e);
     }
    
  3. I get exception:

    javax.net.ssl.SSLHandshakeException: Received fatal alert: bad_certificate

Rougher
  • 834
  • 5
  • 19
  • 46
  • This `curl` command is a request using SSL/TLS. Search how to perform this using Java. Here you can find something related: https://stackoverflow.com/questions/875467/java-client-certificates-over-https-ssl – Dalton Cézane Oct 08 '20 at 12:28
  • I tried it, I got javax.net.ssl.SSLHandshakeException: Received fatal alert: bad_certificate. I prefer to use AWS SDK. It's very strange, if this API doesn't exist. – Rougher Oct 08 '20 at 16:11
  • Maybe this documentation can help you: https://docs.aws.amazon.com/sdk-for-java/v1/developer-guide/examples-iam-server-certificates.html – Dalton Cézane Oct 08 '20 at 17:49

1 Answers1

2

The AWS SDK provides several implementations of SdkHttpClient that you can use to interact with your Amazon Services, both synchronously or asynchronously.

For instance, you can use the ApacheHttpClient class.

All this HTTP clients are created and configured with Builders, ApacheHttpClient.Builder for ApacheHttpClient.

ApacheHttpClient.Builder provides methods that allows you to configure secure HTTP connections for client side, remote peer, or mutual authentication.

If the client must be authenticated, it is necessary to provide the certificate and private key that must be used for that purpose, corresponding to the --cert and --key arguments of your curl invocation.

Typically, this certificate and private key are stored in one password protected KeyStore, usually in PKCS#12 format (a .p12 or .pfx file).

This information can be made accessible to ApacheHttpClient.Builder in two ways.

First, by setting a series of System properties:

import static software.amazon.awssdk.utils.JavaSystemSetting.SSL_KEY_STORE;
import static software.amazon.awssdk.utils.JavaSystemSetting.SSL_KEY_STORE_PASSWORD;
import static software.amazon.awssdk.utils.JavaSystemSetting.SSL_KEY_STORE_TYPE;

//...

Path clientKeyStore = Paths.get(...);

System.setProperty(SSL_KEY_STORE.property(), clientKeyStore.toAbsolutePath().toString());
System.setProperty(SSL_KEY_STORE_TYPE.property(), "pkcs12");
System.setProperty(SSL_KEY_STORE_PASSWORD.property(), "password");

NOTE: The static imports are only constants for the standard JSSE properties javax.net.ssl.keyStore, javax.net.ssl.keyStorePassword, and javax.net.ssl.keyStoreType.

Second, by providing a TlsKeyManagersProvider implementation to the tlsKeyManagersProvider method of ApacheHttpClient.Builder. For instance:

Path clientKeyStore = ...
TlsKeyManagersProvider keyManagersProvider = FileStoreTlsKeyManagersProvider.create(clientKeyStore, "pkcs12", "password");

In fact, under the hood, the above mentioned System properties based configuration is used by SystemPropertyTlsKeyManagersProvider, another TlsKeyManagersProvider implementation.

If you need to authenticate the server, you also have two options.

First, again, by setting several System properties:

Path serverKeyStore = Paths.get(...);
System.setProperty("javax.net.ssl.trustStore", serverKeyStore.toAbsolutePath().toString());
System.setProperty("javax.net.ssl.trustStorePassword", "password");
System.setProperty("javax.net.ssl.trustStoreType", "jks");

As you can see, for simplicity, this time we are using a different kind of KeyStore, jks. You can build such a KeyStore from your AWS server certificate PEM file (the one associated with the --cacert in your curl command) with something like this:

Path pemPath = ...;
try(final InputStream is = Files.newInputStream(pemPath) {
  CertificateFactory certificateFactory = CertificateFactory.getInstance("X.509");
  X509Certificate cert = (X509Certificate) certificateFactory.generateCertificate(is);
  String alias = cert.getSubjectX500Principal().getName();
  KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
  keyStore.load(null);
  keyStore.setCertificateEntry(alias, cert);
}

In the case of mutual authentication, although you can reuse the same KeyStore, it is a best practice maintain two, one with the client private key and certificate, and other with the server certificates you will trust (the trust store).

Alternatively, you can also configure server side authentication by defining the TrustManagers that need to be used.

For this task, the ApacheHttpClient.Builder provides the method tlsTrustManagersProvider. This method requires an implementation of the TlsTrustManagersProvider interface.

This interface define a single method, trustManagers, that returns the array of TrustManagers that must be used to check the remote peer in the SSL communication.

Unfortunately, the AWS SDK does not provide an implementation of this interface, you need to implement your own (let me know if you need further info).

Once initialized and configured, you can provide this SdkHttpClient or its SdkHttpClient.Builder, to a custom service client, like IotClient, using the httpClient or the httpClientBuilder methods, respectively.

If you just need to test TLS connectivity like with your curl command, you can try something like this:

Path clientKeyStore = Paths.get(...);
System.setProperty("javax.net.ssl.keyStore", clientKeyStore.toAbsolutePath().toString());
System.setProperty("javax.net.ssl.keyStoreType", "pkcs12");
System.setProperty("javax.net.ssl.keyStorePassword", "password");

Path serverKeyStore = Paths.get(...);
System.setProperty("javax.net.ssl.trustStore", serverKeyStore.toAbsolutePath().toString());
System.setProperty("javax.net.ssl.trustStorePassword", "password");
System.setProperty("javax.net.ssl.trustStoreType", "jks");

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

SdkHttpRequest httpRequest = SdkHttpFullRequest.builder()
        .method(SdkHttpMethod.GET)
        .uri(new URI("https://<prefix>.credentials.iot.us-west-2.amazonaws.com/role-aliases/MyAlias/credentials"))
        .putHeader("x-amzn-iot-thingname", "myThingName")
        .build();

HttpExecuteRequest request = HttpExecuteRequest.builder()
        .request(httpRequest)
        .build();

HttpExecuteResponse response = client.prepareRequest(request).call();

Please, review this test in the AWS Java SDK, it can be also helpful.

Finally, there is also async HTTP clients that you can use in your project. The way in which secure HTTP communication is configured in these clients is very similar to the one described in the above paragraphs.

You can find all these resources in the AWS Java SDK v2 GitHub repository.

You can import in your project the whole SDK (I assume you are using Maven):

<dependency>
  <groupId>software.amazon.awssdk</groupId>
  <artifactId>aws-sdk-java</artifactId>
  <version>2.15.7</version>
</dependency>

Although, for testing Apache HTTP client, I think that the following dependency will be the only necessary:

<dependency>
  <groupId>software.amazon.awssdk</groupId>
  <artifactId>apache-client</artifactId>
  <version>2.15.7</version>
</dependency>

Although I have tried to focus the answer on code provided by the AWS SDK, as I understood it was necessary, to obtain these temporary credentials it is also possible to use any mechanism that allows a secure connection to AWS, such as Apache HttpClient, like in your example, OkHttp, etcetera.

These temporary credentials can be used to sign any AWS Request and perform operations - according to the assumed IAM role - on AWS services. For instance, following the example in the blog that you indicated, you can insert an item in a DynamoDB table:

AwsSessionCredentials credentials = AwsSessionCredentials.create(
  "the_returned_access_key_id",
  "the_returned_secret_key_id",
  "the_returned_session_token"
);

DynamoDbClient ddb = DynamoDbClient.builder()
  .region(Region.US_EAST_1)
  .credentialsProvider(StaticCredentialsProvider.create(credentials))
  .build();

 HashMap<String,AttributeValue> itemValues = new HashMap<String,AttributeValue>();

itemValues.put("serial_number", AttributeValue.builder().s("123456789").build());
itemValues.put("timestamp", AttributeValue.builder().s("2017-11-20T06:00:00.000Z").build());
itemValues.put("current_temp", AttributeValue.builder().n("65").build());
itemValues.put("target_temp", AttributeValue.builder().n("70").build());
itemValues.put("humidity", AttributeValue.builder().n("45").build());

PutItemRequest request = PutItemRequest.builder()
  .tableName("MyHomeThermostat")
  .item(itemValues)
  .build();

try {
  ddb.putItem(request);
} catch (ResourceNotFoundException e) {
  //...
} catch (DynamoDbException e) {
  //...
} 

In relation to your question in the comments above how to renew the obtained token, I must recognize that I am unable to give you an answer.

In my opinion, I am afraid that the temporary credentials returned by the above mentioned call cannot be refreshed, at least the AWS SDK does not provide any mechanism for that: this credential provider is a very specific use case designed for IoT as indicated in the blog you cited and in the official AWS documentation.

The AWS SDK provides different AWSCredentialsProviders that supports token renewal, like StsAssumeRoleCredentialsProvider or StsGetSessionTokenCredentialsProvider, among others, but there is no specific provider for this use case.

If it is of any help, you can review the source code of the base class StsCredentialsProvider, specially the code in its constructor related with the setup of CachedSupplier and related stuff.

jccampanero
  • 50,989
  • 3
  • 20
  • 49
  • Ok. I understood why my code didn't work. Thanks to your code I understood, that I need 1. two different stores (key store and trust store). 2. change POST to GET. – Rougher Oct 11 '20 at 08:58
  • Can you only explain, what shall I do with security token? If I understand right from your solution, I need to change session security token by myself whet it is expired. I thought that there is some more convenient AWS API for it. – Rougher Oct 11 '20 at 09:01
  • Sorry for the late reply. Yes, you are right, although you can reuse the same keystore, it is good practice to keep two, one with the private key and the client certificate and one with the server certificates you want to trust (the truststore). I have updated my answer to make it clearer. – jccampanero Oct 11 '20 at 17:56
  • If you can see my another question (continue to this question) it will be great. https://stackoverflow.com/questions/64347224/unable-to-find-valid-certification-path-to-requested-target-with-aws-security-to – Rougher Oct 14 '20 at 05:38