从AWS凭据提供商获取安全令牌



有人能向我解释一下,我需要如何实现这个博客的第一步吗?我在AWS文档中找不到它。

换句话说,我需要翻译一个命令:

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

到JAVA。我该怎么做?我需要AWS SDK(我更喜欢没有"自定义客户端进行HTTPS请求"的解决方案)

更新:

我试图使用自定义客户端来发出HTTPS请求,但在将密钥导出到Java KeyStore时遇到了困难(但curl命令对我来说很好):

$ 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.

另一个更新(我已经尝试过了)

  1. 将myPrivateKey和设备证书转换为JKS:

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

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

  2. 使用我的代码中的这个JKS:

    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. 我得到例外:

    javax.net.ssl.ssl握手异常:收到致命警报:bad_certificate

AWS SDK提供了SdkHttpClient的几种实现,您可以使用这些实现与您的亚马逊服务进行同步或异步交互。

例如,您可以使用ApacheHttpClient类。

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

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.keyStorejavax.net.ssl.keyStorePasswordjavax.net.ssl.keyStoreType的常量。

其次,通过向ApacheHttpClient.BuildertlsKeyManagersProvider方法提供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");

正如您所看到的,为了简单起见,这次我们使用不同类型的KeyStorejks。您可以使用以下内容从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没有提供此接口的实现,您需要实现自己的接口(如果需要更多信息,请告诉我)。

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

如果你只需要像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");
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();

请在AWS Java SDK中查看此测试,它也会有所帮助。

最后,您还可以在项目中使用异步HTTP客户端。在这些客户端中配置安全HTTP通信的方式与上面段落中描述的方式非常相似。

您可以在AWS Java SDK v2 GitHub存储库中找到所有这些资源。

您可以在项目中导入整个SDK(我假设您使用的是Maven):

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

尽管,对于测试ApacheHTTP客户端,我认为以下依赖项将是唯一必要的:

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

尽管我试图将答案集中在AWS SDK提供的代码上,但我认为这是必要的,为了获得这些临时凭据,也可以使用任何允许安全连接到AWS的机制,如Apache HttpClient,如您的示例中的OkHttp等。

这些临时凭证可用于签署任何AWS请求,并根据假定的IAM角色在AWS服务上执行操作。例如,按照您指示的博客中的示例,您可以在DynamoDB表中插入一个项目:

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) {
//...
} 

关于您在上述评论中提出的如何续订获得的代币的问题,我必须认识到,我无法给您一个的答案

在我看来,我担心上述调用返回的临时凭据无法刷新,至少AWS SDK没有提供任何机制:正如您引用的博客和AWS官方文档中所示,该凭据提供商是为物联网设计的一个非常具体的用例。

AWS SDK提供了不同的支持令牌续订的AWSCredentialsProvider,如StsAssumeRoleCredentialsProviderStsGetSessionTokenCredentialsProvider等,但该用例没有特定的提供商。

如果有任何帮助,您可以查看基类StsCredentialsProvider的源代码,特别是其构造函数中与CachedSupplier的设置相关的代码以及相关内容。

最新更新