从AWS凭证提供程序获取安全令牌

huangapple go评论78阅读模式
英文:

Get security token from AWS Credentials Provider

问题

以下是已翻译的内容:


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


英文:

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 &quot;x-amzn-iot-thingname: myThingName&quot; --cacert AmazonRootCA1.pem https://&lt;prefix&gt;.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(&quot;deployment.security.TLSv1.2&quot;, &quot;true&quot;);
     System.setProperty(&quot;https.protocols&quot;, &quot;TLSv1.2&quot;);
     System.setProperty(&quot;javax.net.debug&quot;, &quot;ssl&quot;);
    
     HttpPost request = new HttpPost(clientEndpoint);
     request.setHeader(&quot;x-amzn-iot-thingname&quot;, &quot;0ad16050-d974-4f78-88ea-c6ee2b0a551e&quot;);
    
     KeyStore keyStore;
     try (InputStream keyStoreStream = this.getClass().getResourceAsStream(KEYSTOREPATH)) {
         keyStore = KeyStore.getInstance(&quot;PKCS12&quot;);
         keyStore.load(keyStoreStream, KEYSTOREPASS.toCharArray());
     }
    
     SSLContext sslContext = SSLContexts.custom()
             .loadKeyMaterial(keyStore, KEYPASS.toCharArray()) // use null as second param if you don&#39;t have a separate key password
             .loadTrustMaterial(null, new TrustSelfSignedStrategy())
             .build();   
    
     SSLConnectionSocketFactory sslConnectionSocketFactory = new SSLConnectionSocketFactory(sslContext);
     Registry&lt;ConnectionSocketFactory&gt; registry = RegistryBuilder.&lt;ConnectionSocketFactory&gt;create()
     												.register(&quot;https&quot;, sslConnectionSocketFactory)
     												.register(&quot;http&quot;, 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

答案1

得分: 2

The AWS SDK提供了多种实现SdkHttpClient,您可以使用这些实现与Amazon服务进行同步或异步交互。

例如,您可以使用ApacheHttpClient类。

所有这些HTTP客户端都是使用Builder创建和配置的,对于ApacheHttpClient,使用ApacheHttpClient.Builder

ApacheHttpClient.Builder提供了一些方法,允许您为客户端端、远程对等方或双向认证配置安全的HTTP连接。

如果客户端必须进行身份验证,需要提供用于此目的的证书和私钥,对应于curl调用的--cert--key参数。

通常,此证书和私钥存储在受密码保护的KeyStore中,通常以PKCS#12格式(.p12.pfx文件)存储。

可以通过两种方式将此信息传递给ApacheHttpClient.Builder

首先,通过设置一系列System属性:

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(), &quot;pkcs12&quot;);
System.setProperty(SSL_KEY_STORE_PASSWORD.property(), &quot;password&quot;);

注意:static导入仅为标准JSSE属性javax.net.ssl.keyStorejavax.net.ssl.keyStorePasswordjavax.net.ssl.keyStoreType的常量。

其次,通过为ApacheHttpClient.BuildertlsKeyManagersProvider方法提供TlsKeyManagersProvider实现。例如:

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

实际上,在底层,上述基于System属性的配置由SystemPropertyTlsKeyManagersProvider使用,这是另一种TlsKeyManagersProvider实现。

如果需要对服务器进行身份验证,您还有两个选项。

首先,同样通过设置几个System属性:

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

正如您所见,为了简单起见,这次我们使用了不同类型的KeyStore,即jks。您可以使用类似以下的方式,从AWS服务器证书PEM文件(与curl命令中的--cacert关联的文件)构建这样的KeyStore

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

在双向认证的情况下,虽然可以重用同一个KeyStore,但最好的做法是维护两个,一个带有客户端私钥和证书,另一个带有您将信任的服务器证书(信任库)。

此外,您还可以通过定义需要使用的TrustManager来配置服务器端身份验证。

对于这个任务,ApacheHttpClient.Builder提供了tlsTrustManagersProvider方法。此方法需要TlsTrustManagersProvider接口的实现。

此接口定义一个名为trustManagers的方法,该方法返回用于在SSL通信中检查远程对等方的TrustManager数组。

不幸的是,AWS SDK不提供此接口的实现,您需要自己实现(如果需要进一步的信息,请告诉我)。

一旦初始化和配置,您可以将此SdkHttpClient或其SdkHttpClient.Builder提供给自定义服务客户端,例如IotClient,分别使用httpClienthttpClientBuilder方法。

如果您只需要像使用curl命令一样测试TLS连接,可以尝试类似以下的代码:

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

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



<details>
<summary>英文:</summary>

The AWS SDK provides several implementations of [```SdkHttpClient```](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/SdkHttpClient.html) that you can use to interact with your Amazon Services, both synchronously or asynchronously.

For instance, you can use the [```ApacheHttpClient```](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/apache/ApacheHttpClient.html) class.

All this HTTP clients are created and configured with [```Builder```](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/SdkHttpClient.Builder.html)s, [```ApacheHttpClient.Builder```](https://sdk.amazonaws.com/java/api/latest/software/amazon/awssdk/http/apache/ApacheHttpClient.Builder.html) 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:

```java
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(), &quot;pkcs12&quot;);
System.setProperty(SSL_KEY_STORE_PASSWORD.property(), &quot;password&quot;);

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, &quot;pkcs12&quot;, &quot;password&quot;);

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(&quot;javax.net.ssl.trustStore&quot;, serverKeyStore.toAbsolutePath().toString());
System.setProperty(&quot;javax.net.ssl.trustStorePassword&quot;, &quot;password&quot;);
System.setProperty(&quot;javax.net.ssl.trustStoreType&quot;, &quot;jks&quot;);

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(&quot;X.509&quot;);
  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(&quot;javax.net.ssl.keyStore&quot;, clientKeyStore.toAbsolutePath().toString());
System.setProperty(&quot;javax.net.ssl.keyStoreType&quot;, &quot;pkcs12&quot;);
System.setProperty(&quot;javax.net.ssl.keyStorePassword&quot;, &quot;password&quot;);

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

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

SdkHttpRequest httpRequest = SdkHttpFullRequest.builder()
        .method(SdkHttpMethod.GET)
        .uri(new URI(&quot;https://&lt;prefix&gt;.credentials.iot.us-west-2.amazonaws.com/role-aliases/MyAlias/credentials&quot;))
        .putHeader(&quot;x-amzn-iot-thingname&quot;, &quot;myThingName&quot;)
        .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):

&lt;dependency&gt;
  &lt;groupId&gt;software.amazon.awssdk&lt;/groupId&gt;
  &lt;artifactId&gt;aws-sdk-java&lt;/artifactId&gt;
  &lt;version&gt;2.15.7&lt;/version&gt;
&lt;/dependency&gt;

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

&lt;dependency&gt;
  &lt;groupId&gt;software.amazon.awssdk&lt;/groupId&gt;
  &lt;artifactId&gt;apache-client&lt;/artifactId&gt;
  &lt;version&gt;2.15.7&lt;/version&gt;
&lt;/dependency&gt;

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(
  &quot;the_returned_access_key_id&quot;,
  &quot;the_returned_secret_key_id&quot;,
  &quot;the_returned_session_token&quot;
);

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

 HashMap&lt;String,AttributeValue&gt; itemValues = new HashMap&lt;String,AttributeValue&gt;();

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

PutItemRequest request = PutItemRequest.builder()
  .tableName(&quot;MyHomeThermostat&quot;)
  .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.

huangapple
  • 本文由 发表于 2020年10月8日 15:13:36
  • 转载请务必保留本文链接:https://go.coder-hub.com/64257498.html
匿名

发表评论

匿名网友

:?: :razz: :sad: :evil: :!: :smile: :oops: :grin: :eek: :shock: :???: :cool: :lol: :mad: :twisted: :roll: :wink: :idea: :arrow: :neutral: :cry: :mrgreen:

确定