[AWS IoT] Implementieren der Autorisierung von Direktaufrufen in Java [Java]

Einführung

AWS IoT verfügt über einen Mechanismus zum Ausstellen temporärer Sicherheitsanmeldeinformationen unter Verwendung der einzelnen Clientzertifikate von Thing, um auf AWS-Ressourcen wie S3 zuzugreifen. Da es kein Java-Beispiel als geräteseitige Implementierung gab, habe ich es versucht. \ # Erstens glaube ich nicht, dass es viele Beispiele für die Implementierung von Apps gibt, die auf IoT-Geräten in Java ausgeführt werden ...

Autorisieren von Direktanrufen Der grundlegende Mechanismus wird im folgenden Artikel zusammengefasst. [AWS IoT] Zugriff auf S3 / DynamoDB mit einem separaten Client-Zertifikat für Thing

Der in diesem Artikel zur Überprüfung verwendete Java-Code befindet sich unten. https://github.com/tmiki/aws-iot-authorizing-direct-calls-sample

Verifizierte Umgebung

Arbeitsskizze

--Erstellen Sie verschiedene KeyStore-Dateien

Überprüfungsinhalt

Erstellen Sie verschiedene KeyStore-Dateien

Erstellen Sie den KeyStore für das Zertifikat der Stammzertifizierungsstelle

Bereiten Sie zunächst das AWS Root CA-Stationszertifikat, das Clientzertifikat und den privaten Schlüssel basierend auf dem folgenden Artikel vor.

[AWS IoT] Zugriff auf S3 / DynamoDB mit einem separaten Client-Zertifikat für Thing

Laden Sie das Zertifikat der Stammzertifizierungsstelle von der unten angegebenen URL herunter und erstellen Sie einen KeyStore. https://www.amazontrust.com/repository/AmazonRootCA1.pem

Der Name der zu erstellenden KeyStore-Datei lautet "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

KeyStore-Erstellung für Client-Zertifikat / privaten Schlüssel

Erstellen Sie als Nächstes eine KeyStore-Datei für das Client-Zertifikat. Der Befehl keytool scheint keine Funktion zum direkten Importieren des Zertifikats / privaten Schlüssels zu haben. Erstellen Sie ihn daher einmal im PKCS12-Format mit OpenSSL. Sie werden nach dem zu exportierenden Passwort gefragt. Geben Sie es daher entsprechend ein.

$ 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

Erstellen Sie eine KeyStore-Datei für das Client-Zertifikat / den privaten Schlüssel basierend auf der zuvor erstellten Datei. Obwohl es nicht vollständig überprüft wurde, scheint Java nicht nur das JKS-Format, sondern auch das PKCS12-Format als KeyStore verarbeiten zu können. Vielleicht ist diese Arbeit unnötig.

$ 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

Java-Beispielcodevorbereitung

Bitte klonen Sie zuerst das folgende Git-Repository entsprechend. https://github.com/tmiki/aws-iot-authorizing-direct-calls-sample

Speicherung von KeyStore-Dateien

Speichern Sie die folgende KeyStore-Datei im Verzeichnis direkt unter dem Beispielcode. Als KeyStore für Client-Zertifikat / privaten Schlüssel sind client_cert_key_store.bin und ClientCertKey.p12 praktisch gleichwertig. Java scheint in der Lage zu sein, beide Formate zu verarbeiten, daher habe ich es so implementiert, dass es beide verarbeiten kann. In diesem Artikel verwenden wir ClientCertKey.p12 zur Überprüfung.

Konstante ändern

Eine Konstante wird am Anfang der InvestigateAwsIotADC-Hauptklasse definiert. Ändern Sie sie daher entsprechend Ihrer Umgebung.

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****";

Lauf

Entschuldigung, bitte führen Sie "Investigate AwsIot ADC" von Eclipse aus. Ich wollte eine Jar-Datei mit Gradle erstellen, konnte aber die abhängigen Bibliotheken nicht einschließen ... Wir werden hier aktualisieren, sobald es bestätigt ist.

Der Beispielcode führt Folgendes aus:

--Erstellen Sie ein HTTP-Client-Objekt und

Wenn die Ausführung erfolgreich ist, wird Folgendes an die Konsole ausgegeben. WARNUNG kann ausgegeben werden, aber ignorieren Sie es hier.

Ausführungsergebnis


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.

Kommentar

HTTP-Client generieren

Erstellen Sie zunächst ein SSLContext-Objekt mit dem SSLContextBuilder von Apache HC, geben Sie jedoch zu diesem Zeitpunkt den KeyStore an. Geben Sie in der Methode loadTrustMaterial () den KeyStore an, in dem das RootCA-Stationszertifikat gespeichert ist. Geben Sie in der Methode loadKeyMaterial () den KeyStore an, der das Clientzertifikat und den privaten Schlüssel angibt. Zu diesem Zeitpunkt ist es erforderlich, das Kennwort für den gesamten KeyStore und das Kennwort für den privaten Schlüssel anzugeben. Da es jedoch bei Verwendung des PKCS12-Formats kein solches Konzept gibt, scheinen beide dasselbe Kennwort anzugeben. \ # Die Spezifikationen hier wurden noch nicht untersucht und sind unklar.

Erstellen Sie eine SSLConnectionSocketFactory, indem Sie das generierte SSLContext-Objekt angeben. Geben Sie beim Erstellen des CloseableHttpClient-Objekts dieses SSLConnectionSocketFactory-Objekt an und erstellen Sie es.

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();

	}

HTTP-Anfrage ausführen

Stellen Sie HTTP-Anforderungen auf die Standardmethode von Apache HC aus. Zu diesem Zeitpunkt müssen Sie den Thing-Namen im Header "x-amzn-iot-thingname" angeben.

Wenn die HTTP-Anforderung erfolgreich ist, werden temporäre Sicherheitsanmeldeinformationen im JSON-Format zurückgegeben.

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;
	}

Analysieren Sie den HTTP-Antworttext und generieren Sie das BasicSessionCredentials-Objekt

Analysieren Sie den zurückgegebenen HTTP-Antworttext als JSON und rufen Sie die Werte von AccessKeyId / SecretAccessKey / SessionToken ab. Dies wird in BasicSessionCredentials gepackt und zurückgegeben.

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);
	}

Holen Sie sich Dateien auf S3

Verwenden Sie BasicSessionCredentials, um die Datei in S3 abzurufen und ihren Inhalt an die Konsole auszugeben.

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;
	}

abschließend

Die Arbeit mit Client-Zertifikaten in Java war schwieriger als erwartet. Apache HC macht an einigen Stellen gute Arbeit, aber es ist immer noch ziemlich kompliziert.

Ich habe die Java KeyStore-Spezifikationen noch nicht vollständig verstanden, aber es scheint, dass sie nicht nur das JKS-Format, sondern auch das PKCS12-Format verarbeiten können. Hier möchte ich separat untersuchen.

Recommended Posts

[AWS IoT] Implementieren der Autorisierung von Direktaufrufen in Java [Java]
Versuchen Sie, Android Hilt in Java zu implementieren
Löschen von AWS S3-Objekten in Java
Umbenannte Ordner in AWS S3 (Java)
Versuchen Sie, AWS X-Ray in Java auszuführen
Programmierung mit dem direkten Summentyp in Java (Nachrichten)
Änderungen in Java 11
Janken in Java
Umfangsrate in Java
FizzBuzz in Java
Es ist spät! Versuchen Sie, Android Notification in Java (Anfänger) zu implementieren.