diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml
index 7d134f859..7a92abc28 100644
--- a/.github/workflows/ci.yml
+++ b/.github/workflows/ci.yml
@@ -7,7 +7,7 @@ on:
- '!main'
env:
- BUILDER_VERSION: v0.9.11
+ BUILDER_VERSION: v0.9.14
BUILDER_SOURCE: releases
BUILDER_HOST: https://d19elf31gohf1l.cloudfront.net
PACKAGE_NAME: aws-iot-device-sdk-java-v2
diff --git a/pom.xml b/pom.xml
index faa659c87..bef673035 100644
--- a/pom.xml
+++ b/pom.xml
@@ -14,6 +14,7 @@
samples/PubSubStress
samples/RawPubSub
samples/Pkcs11PubSub
+ samples/WindowsCertPubSub
samples/Shadow
samples/Identity
diff --git a/samples/Pkcs11PubSub/src/main/java/pkcs11pubsub/Pkcs11PubSub.java b/samples/Pkcs11PubSub/src/main/java/pkcs11pubsub/Pkcs11PubSub.java
index ddde08cd3..8a9e62515 100644
--- a/samples/Pkcs11PubSub/src/main/java/pkcs11pubsub/Pkcs11PubSub.java
+++ b/samples/Pkcs11PubSub/src/main/java/pkcs11pubsub/Pkcs11PubSub.java
@@ -131,16 +131,18 @@ public void onConnectionResumed(boolean sessionPresent) {
pkcs11Options.withPrivateKeyObjectLabel(pkcs11KeyLabel);
}
- try (
- AwsIotMqttConnectionBuilder builder = AwsIotMqttConnectionBuilder
- .newMtlsPkcs11Builder(pkcs11Options)) {
+ try (AwsIotMqttConnectionBuilder builder = AwsIotMqttConnectionBuilder
+ .newMtlsPkcs11Builder(pkcs11Options)) {
if (rootCaPath != null) {
builder.withCertificateAuthorityFromPath(null, rootCaPath);
}
- builder.withConnectionEventCallbacks(callbacks).withClientId(clientId)
- .withEndpoint(endpoint).withPort((short) port).withCleanSession(true)
+ builder.withConnectionEventCallbacks(callbacks)
+ .withClientId(clientId)
+ .withEndpoint(endpoint)
+ .withPort((short) port)
+ .withCleanSession(true)
.withProtocolOperationTimeoutMs(60000);
try (MqttClientConnection connection = builder.build()) {
diff --git a/samples/README.md b/samples/README.md
index e185d3d5b..ee14b38ea 100644
--- a/samples/README.md
+++ b/samples/README.md
@@ -2,6 +2,7 @@
* [BasicPubSub](#basicpubsub)
* [Pkcs11PubSub](#pkcs11pubsub)
+* [WindowsCertPubSub](#windowscertpubsub)
* [Shadow](#shadow)
* [Jobs](#jobs)
* [fleet provisioning](#fleet-provisioning)
@@ -91,6 +92,70 @@ To run this sample using [SoftHSM2](https://www.opendnssec.org/softhsm/) as the
mvn compile exec:java -pl samples/Pkcs11PubSub -Dexec.mainClass=pkcs11pubsub.Pkcs11PubSub -Dexec.args='--endpoint --cert --rootca --pkcs11Lib --pin --tokenLabel --keyLabel '
```
+## WindowsCertPubSub
+
+WARNING: Windows only
+
+This sample shows connecting to IoT Core using mutual TLS,
+but your certificate and private key are in a
+[Windows certificate store](https://docs.microsoft.com/en-us/windows-hardware/drivers/install/certificate-stores),
+rather than simply being files on disk.
+
+To run this sample you need the path to your certificate in the store,
+which will look something like:
+"CurrentUser\MY\A11F8A9B5DF5B98BA3508FBCA575D09570E0D2C6"
+(where "CurrentUser\MY" is the store and "A11F8A9B5DF5B98BA3508FBCA575D09570E0D2C6" is the certificate's thumbprint)
+
+If your certificate and private key are in a
+[TPM](https://docs.microsoft.com/en-us/windows/security/information-protection/tpm/trusted-platform-module-overview),
+you would use them by passing their certificate store path.
+
+source: `samples/WindowsCertPubSub`
+
+To run this sample with a basic certificate from AWS IoT Core:
+
+1) Create an IoT Thing with a certificate and key if you haven't already.
+
+2) Combine the certificate and private key into a single .pfx file.
+
+ You will be prompted for a password while creating this file. Remember it for the next step.
+
+ If you have OpenSSL installed:
+ ```powershell
+ openssl pkcs12 -in certificate.pem.crt -inkey private.pem.key -out certificate.pfx
+ ```
+
+ Otherwise use [CertUtil](https://docs.microsoft.com/en-us/windows-server/administration/windows-commands/certutil).
+ ```powershell
+ certutil -mergePFX certificate.pem.crt,private.pem.key certificate.pfx
+ ```
+
+3) Add the .pfx file to a Windows certificate store using PowerShell's
+ [Import-PfxCertificate](https://docs.microsoft.com/en-us/powershell/module/pki/import-pfxcertificate)
+
+ In this example we're adding it to "CurrentUser\My"
+
+ ```powershell
+ $mypwd = Get-Credential -UserName 'Enter password below' -Message 'Enter password below'
+ Import-PfxCertificate -FilePath certificate.pfx -CertStoreLocation Cert:\CurrentUser\My -Password $mypwd.Password
+ ```
+
+ Note the certificate thumbprint that is printed out:
+ ```
+ Thumbprint Subject
+ ---------- -------
+ A11F8A9B5DF5B98BA3508FBCA575D09570E0D2C6 CN=AWS IoT Certificate
+ ```
+
+ So this certificate's path would be: "CurrentUser\MY\A11F8A9B5DF5B98BA3508FBCA575D09570E0D2C6"
+
+4) Now you can run the sample:
+
+ ```sh
+ mvn compile exec:java -pl samples/WindowsCertPubSub "-Dexec.mainClass=windowscertpubsub.WindowsCertPubSub" "-Dexec.args=--endpoint xxxx-ats.iot.xxxx.amazonaws.com --cert CurrentUser\MY\A11F8A9B5DF5B98BA3508FBCA575D09570E0D2C6 --rootca AmazonRootCA1.pem"
+ ```
+
+
## Shadow
This sample uses the AWS IoT
diff --git a/samples/WindowsCertPubSub/pom.xml b/samples/WindowsCertPubSub/pom.xml
new file mode 100644
index 000000000..6c8cd82c7
--- /dev/null
+++ b/samples/WindowsCertPubSub/pom.xml
@@ -0,0 +1,54 @@
+
+ 4.0.0
+ software.amazon.awssdk.iotdevicesdk
+ WindowsCertPubSub
+ jar
+ 1.0-SNAPSHOT
+ ${project.groupId}:${project.artifactId}
+ Sample for connecting to AWS IoT Core with certificate in a Windows certificate store
+ https://github.com/awslabs/aws-iot-device-sdk-java-v2
+
+ 1.8
+ 1.8
+ UTF-8
+
+
+
+ software.amazon.awssdk.iotdevicesdk
+ aws-iot-device-sdk
+ 1.0.0-SNAPSHOT
+
+
+
+
+
+ org.codehaus.mojo
+ exec-maven-plugin
+ 1.4.0
+
+ main
+
+
+
+ org.codehaus.mojo
+ build-helper-maven-plugin
+ 3.2.0
+
+
+ add-source
+ generate-sources
+
+ add-source
+
+
+
+ ../Utils/CommandLineUtils
+
+
+
+
+
+
+
+
diff --git a/samples/WindowsCertPubSub/src/main/java/windowscertpubsub/WindowsCertPubSub.java b/samples/WindowsCertPubSub/src/main/java/windowscertpubsub/WindowsCertPubSub.java
new file mode 100644
index 000000000..544fb76f3
--- /dev/null
+++ b/samples/WindowsCertPubSub/src/main/java/windowscertpubsub/WindowsCertPubSub.java
@@ -0,0 +1,152 @@
+/**
+ * Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
+ * SPDX-License-Identifier: Apache-2.0.
+ */
+
+package windowscertpubsub;
+
+import software.amazon.awssdk.crt.*;
+import software.amazon.awssdk.crt.io.*;
+import software.amazon.awssdk.crt.mqtt.*;
+import software.amazon.awssdk.iot.AwsIotMqttConnectionBuilder;
+
+import java.nio.charset.StandardCharsets;
+import java.util.UUID;
+import java.util.concurrent.CompletableFuture;
+import java.util.concurrent.CountDownLatch;
+import java.util.concurrent.ExecutionException;
+
+import utils.commandlineutils.CommandLineUtils;
+
+public class WindowsCertPubSub {
+
+ // When run normally, we want to exit nicely even if something goes wrong
+ // When run from CI, we want to let an exception escape which in turn causes the
+ // exec:java task to return a non-zero exit code
+ static String ciPropValue = System.getProperty("aws.crt.ci");
+ static boolean isCI = ciPropValue != null && Boolean.valueOf(ciPropValue);
+
+ static String clientId = "test-" + UUID.randomUUID().toString();
+ static String rootCaPath;
+ static String windowsCertStorePath;
+ static String endpoint;
+ static String topic = "test/topic";
+ static String message = "Hello World!";
+ static int messagesToPublish = 10;
+ static int port = 8883;
+
+ static CommandLineUtils cmdUtils;
+
+ /*
+ * When called during a CI run, throw an exception that will escape and fail the
+ * exec:java task When called otherwise, print what went wrong (if anything) and
+ * just continue (return from main)
+ */
+ static void onApplicationFailure(Throwable cause) {
+ if (isCI) {
+ throw new RuntimeException("execution failure", cause);
+ } else if (cause != null) {
+ System.out.println("Exception encountered: " + cause.toString());
+ }
+ }
+
+ public static void main(String[] args) {
+
+ cmdUtils = new CommandLineUtils();
+ cmdUtils.registerProgramName("WindowsCertPubSub");
+ cmdUtils.addCommonMQTTCommands();
+ cmdUtils.removeCommand("cert");
+ cmdUtils.removeCommand("key");
+ cmdUtils.registerCommand("cert", "", "Path to certificate in Windows cert store. " +
+ "e.g. \"CurrentUser\\MY\\6ac133ac58f0a88b83e9c794eba156a98da39b4c\"");
+ cmdUtils.registerCommand("client_id", "", "Client id to use (optional, default='test-*').");
+ cmdUtils.registerCommand("port", "", "Port to connect to on the endpoint (optional, default='8883').");
+ cmdUtils.registerCommand("topic", "", "Topic to subscribe/publish to (optional, default='test/topic').");
+ cmdUtils.registerCommand("message", "", "Message to publish (optional, default='Hello World').");
+ cmdUtils.registerCommand("count", "", "Number of messages to publish (optional, default='10').");
+ cmdUtils.registerCommand("help", "", "Prints this message");
+ cmdUtils.sendArguments(args);
+
+ if (cmdUtils.hasCommand("help")) {
+ cmdUtils.printHelp();
+ System.exit(1);
+ }
+
+ endpoint = cmdUtils.getCommandRequired("endpoint", "");
+ windowsCertStorePath = cmdUtils.getCommandRequired("cert", "");
+ rootCaPath = cmdUtils.getCommandOrDefault("root_ca", rootCaPath);
+ clientId = cmdUtils.getCommandOrDefault("client_id", clientId);
+ port = Integer.parseInt(cmdUtils.getCommandOrDefault("port", String.valueOf(port)));
+ topic = cmdUtils.getCommandOrDefault("topic", topic);
+ message = cmdUtils.getCommandOrDefault("message", message);
+ messagesToPublish = Integer.parseInt(cmdUtils.getCommandOrDefault("count", String.valueOf(messagesToPublish)));
+
+ MqttClientConnectionEvents callbacks = new MqttClientConnectionEvents() {
+ @Override
+ public void onConnectionInterrupted(int errorCode) {
+ if (errorCode != 0) {
+ System.out.println("Connection interrupted: " + errorCode + ": " + CRT.awsErrorString(errorCode));
+ }
+ }
+
+ @Override
+ public void onConnectionResumed(boolean sessionPresent) {
+ System.out.println("Connection resumed: " + (sessionPresent ? "existing session" : "clean session"));
+ }
+ };
+
+ try (AwsIotMqttConnectionBuilder builder =
+ AwsIotMqttConnectionBuilder.newMtlsWindowsCertStorePathBuilder(windowsCertStorePath)) {
+
+ if (rootCaPath != null) {
+ builder.withCertificateAuthorityFromPath(null, rootCaPath);
+ }
+
+ builder.withConnectionEventCallbacks(callbacks)
+ .withClientId(clientId)
+ .withEndpoint(endpoint)
+ .withPort((short) port)
+ .withCleanSession(true)
+ .withProtocolOperationTimeoutMs(60000);
+
+ try (MqttClientConnection connection = builder.build()) {
+
+ CompletableFuture connected = connection.connect();
+ try {
+ boolean sessionPresent = connected.get();
+ System.out.println("Connected to " + (!sessionPresent ? "new" : "existing") + " session!");
+ } catch (Exception ex) {
+ throw new RuntimeException("Exception occurred during connect", ex);
+ }
+
+ CountDownLatch countDownLatch = new CountDownLatch(messagesToPublish);
+
+ CompletableFuture subscribed = connection.subscribe(topic, QualityOfService.AT_LEAST_ONCE, (message) -> {
+ String payload = new String(message.getPayload(), StandardCharsets.UTF_8);
+ System.out.println("MESSAGE: " + payload);
+ countDownLatch.countDown();
+ });
+
+ subscribed.get();
+
+ int count = 0;
+ while (count++ < messagesToPublish) {
+ CompletableFuture published = connection.publish(new MqttMessage(topic, message.getBytes(), QualityOfService.AT_LEAST_ONCE, false));
+ published.get();
+ Thread.sleep(1000);
+ }
+
+ countDownLatch.await();
+
+ CompletableFuture disconnected = connection.disconnect();
+ disconnected.get();
+ }
+ } catch (CrtRuntimeException | InterruptedException | ExecutionException ex) {
+ onApplicationFailure(ex);
+ }
+
+ CrtResource.waitForNoResources();
+
+ System.out.println("Complete!");
+ }
+}
diff --git a/sdk/pom.xml b/sdk/pom.xml
index 20a712a6c..d7c3ea031 100644
--- a/sdk/pom.xml
+++ b/sdk/pom.xml
@@ -42,7 +42,7 @@
software.amazon.awssdk.crt
aws-crt
- 0.15.23
+ 0.16.0
org.slf4j
@@ -169,7 +169,7 @@
software.amazon.awssdk.eventstream*
- pubsub:greengrass:identity:jobs:pubsubstress:rawpubsub:pkcs11pubsub:shadow
+ pubsub:greengrass:identity:jobs:pubsubstress:rawpubsub:pkcs11pubsub:windowscertpubsub:shadow
AWS IoT Device SDK Java V2 API Reference
Copyright © 2021. All rights reserved.
diff --git a/sdk/src/main/java/software/amazon/awssdk/iot/AwsIotMqttConnectionBuilder.java b/sdk/src/main/java/software/amazon/awssdk/iot/AwsIotMqttConnectionBuilder.java
index 4dd63a2f9..ee2a659af 100644
--- a/sdk/src/main/java/software/amazon/awssdk/iot/AwsIotMqttConnectionBuilder.java
+++ b/sdk/src/main/java/software/amazon/awssdk/iot/AwsIotMqttConnectionBuilder.java
@@ -129,6 +129,8 @@ public static AwsIotMqttConnectionBuilder newMtlsBuilder(byte[] certificate, byt
/**
* Create a new builder with mTLS, using a PKCS#11 library for private key operations.
*
+ * NOTE: Unix only
+ *
* @param pkcs11Options PKCS#11 options
* @return {@link AwsIotMqttConnectionBuilder}
*/
@@ -138,6 +140,24 @@ public static AwsIotMqttConnectionBuilder newMtlsPkcs11Builder(TlsContextPkcs11O
}
}
+ /**
+ * Create a new builder with mTLS, using a certificate in a Windows certificate store.
+ *
+ * NOTE: Windows only
+ *
+ * @param certificatePath Path to certificate in a Windows certificate store.
+ * The path must use backslashes and end with the
+ * certificate's thumbprint. Example:
+ * {@code CurrentUser\MY\A11F8A9B5DF5B98BA3508FBCA575D09570E0D2C6}
+ * @return {@link AwsIotMqttConnectionBuilder}
+ */
+ public static AwsIotMqttConnectionBuilder newMtlsWindowsCertStorePathBuilder(String certificatePath) {
+ try (TlsContextOptions tlsContextOptions = TlsContextOptions
+ .createWithMtlsWindowsCertStorePath(certificatePath)) {
+ return new AwsIotMqttConnectionBuilder(tlsContextOptions);
+ }
+ }
+
/**
* Create a new builder with no default Tls options
*