[AWS IoT] Implémentation de l'autorisation d'appels directs en Java [Java]

introduction

AWS IoT dispose d'un mécanisme permettant d'émettre des informations d'identification de sécurité temporaires à l'aide des certificats clients individuels de Thing pour accéder aux ressources AWS telles que S3. Il n'y avait pas d'exemple Java en tant qu'implémentation côté périphérique, alors je l'ai essayé. \ # En premier lieu, je ne pense pas qu'il existe de nombreux exemples d'implémentation d'applications qui s'exécutent sur des appareils IoT en Java ...

Autorisation d'appels directs Le mécanisme de base est résumé dans l'article suivant. [AWS IoT] Accéder à S3 / DynamoDB à l'aide d'un certificat client distinct pour Thing

Le code Java utilisé pour la vérification dans cet article se trouve ci-dessous. https://github.com/tmiki/aws-iot-authorizing-direct-calls-sample

Environnement vérifié

Plan de travail

--Créer divers fichiers KeyStore --Modifier le code source Java

Contenu de la vérification

Créer divers fichiers KeyStore

Créer un KeyStore pour le certificat de la station de l'autorité de certification racine

Tout d'abord, préparez le certificat de la station AWS Root CA, le certificat client et la clé privée en vous basant sur l'article suivant.

[AWS IoT] Accéder à S3 / DynamoDB à l'aide d'un certificat client distinct pour Thing

Téléchargez le certificat de la station racine CA à partir de l'URL ci-dessous et créez un KeyStore. https://www.amazontrust.com/repository/AmazonRootCA1.pem

Le nom du fichier KeyStore à créer est "AWSRootCaStore.bin".

$ keytool -import -trustcacerts -alias AmazonRootCA1 -file ./AmazonRootCA1.pem -keystore AWSRootCaStore.bin -storepass password_amazon_root_ca
Owner: CN=Amazon Root CA 1, O=Amazon, C=US
Issuer: CN=Amazon Root CA 1, O=Amazon, C=US
Serial number: 66c9fcf99bf8c0a39e2f0788a43e696365bca
Valid from: Tue May 26 09:00:00 JST 2015 until: Sun Jan 17 09:00:00 JST 2038
Certificate fingerprints:
      SHA1: 8D:A7:F9:65:EC:5E:FC:37:91:0F:1C:6E:59:FD:C1:CC:6A:6E:DE:16
      SHA256: 8E:CD:E6:88:4F:3D:87:B1:12:5B:A3:1A:C3:FC:B1:3D:70:16:DE:7F:57:CC:90:4F:E1:CB:97:C6:AE:98:19:6E
Signature algorithm name: SHA256withRSA
Subject Public Key Algorithm: 2048-bit RSA key
Version: 3

Extensions:

#1: ObjectId: 2.5.29.19 Criticality=true
BasicConstraints:[
CA:true
PathLen:2147483647
]

#2: ObjectId: 2.5.29.15 Criticality=true
KeyUsage [
DigitalSignature
Key_CertSign
Crl_Sign
]

#3: ObjectId: 2.5.29.14 Criticality=false
SubjectKeyIdentifier [
KeyIdentifier [
0000: 84 18 CC 85 34 EC BC 0C   94 94 2E 08 59 9C C7 B2  ....4.......Y...
0010: 10 4E 0A 08                                        .N..
]
]

Trust this certificate? [no]:  yes
Certificate was added to keystore

$ ls -l AWSRootCaStore.bin
-rw-r--r-- 1 mta 197121 1146 Aug 25 12:09 AWSRootCaStore.bin

Créer KeyStore pour le certificat client / la clé privée

Ensuite, créez un fichier KeyStore pour le certificat client. La commande keytool ne semble pas avoir de fonction pour importer directement le certificat / clé privée, donc créez-le une fois au format PKCS12 en utilisant OpenSSL. Il vous sera demandé le mot de passe pour exporter, alors entrez-le correctement.

$ openssl pkcs12 -export -in c231554173-certificate.pem.crt -inkey c231554173-private.pem.key -out ClientCertKey.p12
Enter Export Password:
Verifying - Enter Export Password:
     
$ ls -l ClientCertKey.p12
-rw-r--r-- 1 vagrant vagrant 2461 Aug 25 15:54 ClientCertKey.p12

Créez un fichier KeyStore pour le certificat client / la clé privée en fonction du fichier créé précédemment. Bien que cela n'ait pas été complètement vérifié, Java semble être capable de gérer non seulement le format JKS mais aussi le format PKCS12 en tant que KeyStore. Peut-être que ce travail est inutile.

$ keytool -importkeystore \
  -srckeystore ClientCertKey.p12 -srcstoretype pkcs12 \
  -destkeystore client_cert_key_store.bin -deststorepass "password_client_cert_key_store"
Importing keystore ClientCertKey.p12 to client_cert_key_store.bin...
Enter source keystore password:  previously_input_password
Entry for alias 1 successfully imported.
Import command completed:  1 entries successfully imported, 0 entries failed or cancelled

Préparation d'exemple de code Java

Tout d'abord, veuillez cloner le dépôt Git suivant de manière appropriée. https://github.com/tmiki/aws-iot-authorizing-direct-calls-sample

Stockage des fichiers KeyStore

Stockez le fichier KeyStore suivant dans le répertoire directement sous l'exemple de code. En tant que KeyStore pour le certificat client / clé privée, client_cert_key_store.bin et ClientCertKey.p12 sont pratiquement équivalents. Java semble être capable de gérer l'un ou l'autre format, je l'ai donc implémenté pour qu'il puisse gérer les deux. Dans cet article, nous utiliserons ClientCertKey.p12 pour la vérification.

Changer la constante

Une constante est définie au début de la classe principale InvestigateAwsIotADC, changez-la donc en fonction de votre environnement.

public class InvestigateAwsIotADC {
	// Constant values of the Amazon Root CA.
	static String ROOT_CA_FILE_PATH = "AWSRootCaStore.bin";
	static String ROOT_CA_FILE_PASSWORD = "password_amazon_root_ca";

	// Constant values of a client certificate.
	static String CLIENT_CERT_KEY_STORE_PKCS12_FILE_PATH = "ClientCertKey.p12";
	static String CLIENT_CERT_KEY_STORE_PKCS12_PASSWORD = "password_client_cert_key_store";

	// Replace the domain name with yours.
	static String AWS_IOT_ROLE_ALIAS_ENDPOINT = "https://****YourEndpointDomain****/role-aliases/****YourRoleAlias****/credentials";
	static String AWS_IOT_THING_NAME = "****YourThingName***";
	static String AWS_IOT_ADC_HEADER_NAME = "x-amzn-iot-thingname";


	// Replace the AWS resources configurations with yours.
	static Regions AWS_REGION = Regions.AP_NORTHEAST_1;
	static String AWS_S3_BUCKET_NAME = "****YourBucketName****";
	static String AWS_S3_FILE_NAME = "****YourFile****";

Exécuter

Excusez-moi, lancez "Investigate AwsIot ADC" depuis Eclipse. Je voulais créer un fichier Jar avec Gradle, mais je ne pouvais pas inclure les bibliothèques dépendantes ... Nous mettrons à jour ici dès qu'il sera confirmé.

L'exemple de code effectue les opérations suivantes:

--Créez un objet client HTTP et

Si l'exécution réussit, ce qui suit sera affiché sur la console. Un AVERTISSEMENT peut être émis, mais ignorez-le ici.

Résultat d'exécution


Started.
Http request: GET https://abcdefgh123456.credentials.iot.ap-northeast-1.amazonaws.com/role-aliases/InvestigateIotRoleAlias/credentials HTTP/1.1
Http Status Code: 200
Http Response Header: content-type: application/json
Http Response Header: content-length: 1189
Http Response Header: date: Sun, 25 Aug 2019 15:02:34 GMT
Http Response Header: x-amzn-RequestId: 0d33da6d-58d0-f450-aa66-f8278cf6834e
Http Response Body: {"credentials":{"accessKeyId":"ASIA................","secretAccessKey":"NPf6................................","expiration":"2019-08-25T16:02:34Z"}}
WARNING: Your profile name includes a 'profile ' prefix. This is considered part of the profile name in the Java SDK, so you will need to include this prefix in your profile name when you reference this profile from your Java code. [Mon Aug 26 00:02:35 JST 2019]
WARNING: Your profile name includes a 'profile ' prefix. This is considered part of the profile name in the Java SDK, so you will need to include this prefix in your profile name when you reference this profile from your Java code. [Mon Aug 26 00:02:35 JST 2019]
The content of the S3 file: retrieved


Finished.

Commentaire

Générer un client HTTP

Tout d'abord, créez un objet SSLContext à l'aide de SSLContextBuilder d'Apache HC, mais spécifiez le KeyStore à ce stade. Dans la méthode loadTrustMaterial (), spécifiez le KeyStore qui stocke le certificat de station RootCA. Dans la méthode loadKeyMaterial (), spécifiez le KeyStore qui spécifie le certificat client et la clé privée. À ce stade, il est nécessaire de spécifier le mot de passe pour l'ensemble du KeyStore et le mot de passe de la clé privée, mais comme il n'y a pas de tel concept lors de l'utilisation du format PKCS12, il semble que les deux spécifient le même mot de passe. \ # Les spécifications ici n'ont pas encore été étudiées et ne sont pas claires.

Créez un SSLConnectionSocketFactory en spécifiant l'objet SSLContext généré. Lors de la création de l'objet CloseableHttpClient, spécifiez cet objet SSLConnectionSocketFactory et générez-le.

createHttpClient()


	public static CloseableHttpClient createHttpClient() {
		// The "custom()" method returns "SSLContextBuilder".
		// Its parameters are a little complicated.
		//
		// https://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/org/apache/http/ssl/SSLContexts.html#custom()
		// https://hc.apache.org/httpcomponents-core-ga/httpcore/apidocs/org/apache/http/ssl/SSLContextBuilder.html
		SSLContext sslcontext = null;
		try {
			sslcontext = SSLContexts.custom()//
				.loadKeyMaterial(new File(CLIENT_CERT_KEY_STORE_PKCS12_FILE_PATH), CLIENT_CERT_KEY_STORE_PKCS12_PASSWORD.toCharArray(), CLIENT_CERT_KEY_STORE_PKCS12_PASSWORD.toCharArray())
				.loadTrustMaterial(new File(ROOT_CA_FILE_PATH), ROOT_CA_FILE_PASSWORD.toCharArray())
				.build();

		} catch (Exception e) {
			System.out.println("An Exception has been thrown while creating an SSLContext.");
			e.printStackTrace();
		}


		// https://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/conn/ssl/SSLConnectionSocketFactory.html
		SSLConnectionSocketFactory sslsf = new SSLConnectionSocketFactory(sslcontext);

		// https://hc.apache.org/httpcomponents-client-ga/httpclient/apidocs/org/apache/http/impl/client/HttpClients.html
		return HttpClients.custom().setSSLSocketFactory(sslsf).build();

	}

Exécution de la requête HTTP

Émettez des requêtes HTTP de la manière standard Apache HC. À ce stade, vous devez spécifier le nom de la chose dans l'en-tête "x-amzn-iot-thingname".

Si la requête HTTP aboutit, les informations d'identification de sécurité temporaires seront renvoyées au format JSON.

invokeHttpRequest()


	public static String invokeHttpRequest(CloseableHttpClient httpclient) {

		// Add a header specify an AWS IoT thing name described in the AWS IOT document below.
		// https://docs.aws.amazon.com/iot/latest/developerguide/authorizing-direct-aws.html

		HttpUriRequest request = RequestBuilder.get() //
				.setUri(AWS_IOT_ROLE_ALIAS_ENDPOINT)
				.setHeader(AWS_IOT_ADC_HEADER_NAME, AWS_IOT_THING_NAME)
				.build();


		System.out.println("Http request: "+ request.getRequestLine());

		try {
			CloseableHttpResponse response = httpclient.execute(request);

			// Prints an HTTP Status Code, HTTP Headers and an HTTP Response Body.
			System.out.println("Http Status Code: " + response.getStatusLine().getStatusCode());
			List<Header> headers = Arrays.asList(response.getAllHeaders());
			headers.forEach((header) -> {System.out.println("Http Response Header: " + header);});
			String response_body = IOUtils.toString(response.getEntity().getContent(), "UTF-8");
			System.out.println("Http Response Body: " + response_body);

			response.close();

			return (response_body);

		} catch (IOException e) {
			System.out.println("An Exception has been thrown while invoking an HTTP request.");
			e.printStackTrace();
		}

		return null;
	}

Analyser le corps de la réponse HTTP et générer l'objet BasicSessionCredentials

Analysez le corps de la réponse HTTP renvoyé en tant que JSON et récupérez les valeurs de AccessKeyId / SecretAccessKey / SessionToken respectivement. Ceci est emballé dans BasicSessionCredentials et renvoyé.

BasicSessionCredentials (AWS SDK for Java - 1.11.618) https://docs.aws.amazon.com/AWSJavaSDK/latest/javadoc/com/amazonaws/auth/BasicSessionCredentials.html

parseCredentialsFromHttpResponse()


	public static BasicSessionCredentials parseCredentialsFromHttpResponse(String http_response) {
		JSONObject response_json = new JSONObject(http_response);

		String access_key_id = response_json.getJSONObject("credentials").getString("accessKeyId");
		String secret_access_key = response_json.getJSONObject("credentials").getString("secretAccessKey");
		String session_token = response_json.getJSONObject("credentials").getString("sessionToken");

		return new BasicSessionCredentials(access_key_id, secret_access_key, session_token);
	}

Obtenir des fichiers sur S3

Utilisez BasicSessionCredentials pour obtenir le fichier sur S3 et afficher son contenu sur la console.

getS3Object()


	public static String getS3Object(BasicSessionCredentials temporary_credentials) {
		// See also the following document.
		// https://docs.aws.amazon.com/AmazonS3/latest/dev/AuthUsingTempSessionTokenJava.html
		AmazonS3 s3Client = AmazonS3ClientBuilder.standard() //
				.withCredentials(new AWSStaticCredentialsProvider(temporary_credentials))
				.withRegion(AWS_REGION)
				.build();

		S3Object s3object = s3Client.getObject(AWS_S3_BUCKET_NAME, AWS_S3_FILE_NAME);


		try {
			String s3object_content = IOUtils.toString(s3object.getObjectContent(), "UTF-8");
			return s3object_content;
		} catch (IOException e) {
			System.out.println("An Exception has been thrown while retreiving an S3 object.");
			e.printStackTrace();
		}

		return null;
	}

en conclusion

Travailler avec des certificats clients en Java a été plus difficile que prévu. Apache HC fait du bon travail à certains endroits, mais c'est quand même assez compliqué.

Je n'ai pas entièrement saisi les spécifications Java KeyStore, mais il semble qu'il puisse gérer non seulement le format JKS mais également le format PKCS12. C'est là que je voudrais enquêter séparément.

Recommended Posts

[AWS IoT] Implémentation de l'autorisation d'appels directs en Java [Java]
Essayez d'implémenter Android Hilt en Java
Suppression d'objets AWS S3 dans Java
Dossiers renommés dans AWS S3 (Java)
Essayez d'exécuter AWS X-Ray en Java
Programmation utilisant le type de somme directe en Java (news)
Changements dans Java 11
Janken à Java
Taux circonférentiel à Java
FizzBuzz en Java
Il est tard! Essayez d'implémenter la notification Android en Java (débutant)