英文:
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)
-
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
-
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); }
-
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 "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)
-
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
-
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); }
-
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(), "pkcs12");
System.setProperty(SSL_KEY_STORE_PASSWORD.property(), "password");
注意:static
导入仅为标准JSSE属性javax.net.ssl.keyStore
、javax.net.ssl.keyStorePassword
和javax.net.ssl.keyStoreType
的常量。
其次,通过为ApacheHttpClient.Builder
的tlsKeyManagersProvider
方法提供TlsKeyManagersProvider
实现。例如:
Path clientKeyStore = ...
TlsKeyManagersProvider keyManagersProvider = FileStoreTlsKeyManagersProvider.create(clientKeyStore, "pkcs12", "password");
实际上,在底层,上述基于System
属性的配置由SystemPropertyTlsKeyManagersProvider
使用,这是另一种TlsKeyManagersProvider
实现。
如果需要对服务器进行身份验证,您还有两个选项。
首先,同样通过设置几个System
属性:
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");
正如您所见,为了简单起见,这次我们使用了不同类型的KeyStore
,即jks
。您可以使用类似以下的方式,从AWS服务器证书PEM文件(与curl
命令中的--cacert
关联的文件)构建这样的KeyStore
:
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);
}
在双向认证的情况下,虽然可以重用同一个KeyStore
,但最好的做法是维护两个,一个带有客户端私钥和证书,另一个带有您将信任的服务器证书(信任库)。
此外,您还可以通过定义需要使用的TrustManager
来配置服务器端身份验证。
对于这个任务,ApacheHttpClient.Builder
提供了tlsTrustManagersProvider
方法。此方法需要TlsTrustManagersProvider
接口的实现。
此接口定义一个名为trustManagers
的方法,该方法返回用于在SSL通信中检查远程对等方的TrustManager
数组。
不幸的是,AWS SDK不提供此接口的实现,您需要自己实现(如果需要进一步的信息,请告诉我)。
一旦初始化和配置,您可以将此SdkHttpClient
或其SdkHttpClient.Builder
提供给自定义服务客户端,例如IotClient
,分别使用httpClient
或httpClientBuilder
方法。
如果您只需要像使用curl
命令一样测试TLS连接,可以尝试类似以下的代码:
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");
<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(), "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 TrustManager
s 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 TrustManager
s 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 AWSCredentialsProvider
s 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.
通过集体智慧和协作来改善编程学习和解决问题的方式。致力于成为全球开发者共同参与的知识库,让每个人都能够通过互相帮助和分享经验来进步。
评论