Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Pkcs12 connect sample #401

Merged
merged 5 commits into from
Apr 13, 2023
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
7 changes: 7 additions & 0 deletions .github/workflows/ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -191,6 +191,13 @@ jobs:
- name: run PubSub sample
run: |
python3 ./utils/run_sample_ci.py --file ./.github/workflows/ci_run_pubsub_cfg.json
- name: run PKCS12 Connect sample
run: |
cert=$(aws secretsmanager get-secret-value --region us-east-1 --secret-id "ci/PubSub/cert" --query "SecretString" | cut -f2 -d":" | cut -f2 -d\") && echo -e "$cert" > /tmp/certificate.pem
key=$(aws secretsmanager get-secret-value --region us-east-1 --secret-id "ci/PubSub/key" --query "SecretString" | cut -f2 -d":" | cut -f2 -d\") && echo -e "$key" > /tmp/privatekey.pem
pkcs12_password=$(aws secretsmanager get-secret-value --region us-east-1 --secret-id "ci/PubSub/key_pkcs12_password" --query "SecretString" | cut -f2 -d":" | cut -f2 -d\")
openssl pkcs12 -export -in /tmp/certificate.pem -inkey /tmp/privatekey.pem -out ./pkcs12-key.p12 -name PubSub_Thing_Alias -password pass:$pkcs12_password
python3 ./utils/run_sample_ci.py --file ./.github/workflows/ci_run_pkcs12_connect_cfg.json
- name: configure AWS credentials (MQTT5)
uses: aws-actions/configure-aws-credentials@v1
with:
Expand Down
20 changes: 20 additions & 0 deletions .github/workflows/ci_run_pkcs12_connect_cfg.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,20 @@
{
"language": "Java",
"sample_file": "samples/Pkcs12Connect",
"sample_region": "us-east-1",
"sample_main_class": "pkcs12connect.Pkcs12Connect",
"arguments": [
{
"name": "--endpoint",
"secret": "ci/endpoint"
},
{
"name": "--pkcs12_file",
"data": "./pkcs12-key.p12"
},
{
"name": "--pkcs12_password",
"secret": "ci/PubSub/key_pkcs12_password"
}
]
}
12 changes: 12 additions & 0 deletions documents/MQTT5_Userguide.md
Original file line number Diff line number Diff line change
Expand Up @@ -214,6 +214,18 @@ AwsIotMqtt5ClientBuilder builder = AwsIotMqtt5ClientBuilder.newDirectMqttBuilder

**Note**: Currently, TLS integration with PKCS#11 is only available on Unix devices.

### **Direct MQTT with PKCS12 Method**

A MQTT5 direct connection can be made using a PKCS12 file rather than using a PEM encoded private key. To create a MQTT5 builder configured for this connection, see the following code:

~~~ java

String clientEndpoint = "<prefix>-ats.iot.<region>.amazonaws.com";
AwsIotMqtt5ClientBuilder builder = AwsIotMqtt5ClientBuilder.newDirectMqttBuilderWithMtlsFromPkcs11(clientEndpoint, "<PKCS12 file path>", "<PKCS12 password>");
~~~

**Note**: Currently, TLS integration with PKCS12 is only available on MacOS devices.

### **Direct MQTT with Custom Key Operation Method**

A MQTT5 direct connection can be made with a set of custom private key operations during the mutual TLS handshake. This is necessary if you require an external library to handle private key operations such as signing and decrypting. To create a MQTT5 builder configured for this connection, see the following code:
Expand Down
1 change: 1 addition & 0 deletions pom.xml
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@
<module>samples/WebsocketConnect</module>
<module>samples/X509CredentialsProviderConnect</module>
<module>samples/Pkcs11Connect</module>
<module>samples/Pkcs12Connect</module>
<module>samples/CustomAuthorizerConnect</module>
<module>samples/JavaKeystoreConnect</module>
<module>samples/CognitoConnect</module>
Expand Down
64 changes: 64 additions & 0 deletions samples/Pkcs12Connect/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,64 @@
# Websocket Connect

[**Return to main sample list**](../README.md)

This sample is similar to the [Basic Connect](../BasicConnect/README.md) sample, in that it connects via Mutual TLS (mTLS) using a certificate and key file. However, unlike the Basic Connect where the certificate and private key file are stored on disk, this sample uses a PKCS#12 file instead.

**WARNING: MacOS only**. Currently, TLS integration with PKCS12 is only available on MacOS devices.

Your IoT Core Thing's [Policy](https://docs.aws.amazon.com/iot/latest/developerguide/iot-policies.html) must provide privileges for this sample to connect. Below is a sample policy that can be used on your IoT Core Thing that will allow this sample to run as intended.

<details>
<summary>(see sample policy)</summary>
<pre>
{
"Version": "2012-10-17",
"Statement": [
{
"Effect": "Allow",
"Action": [
"iot:Connect"
],
"Resource": [
"arn:aws:iot:<b>region</b>:<b>account</b>:client/test-*"
]
}
]
}
</pre>

Replace with the following with the data from your AWS account:
* `<region>`: The AWS IoT Core region where you created your AWS IoT Core thing you wish to use with this sample. For example `us-east-1`.
* `<account>`: Your AWS IoT Core account ID. This is the set of numbers in the top right next to your AWS account name when using the AWS IoT Core website.

Note that in a real application, you may want to avoid the use of wildcards in your ClientID or use them selectively. Please follow best practices when working with AWS on production applications using the SDK. Also, for the purposes of this sample, please make sure your policy allows a client ID of `test-*` to connect or use `--client_id <client ID here>` to send the client ID your policy supports.

</details>

## How to run

To run the PKCS12 connect use the following command:

```sh
mvn compile exec:java -pl samples/Pkcs12Connect -Dexec.mainClass=pkcs12connect.Pkcs12Connect -Dexec.args="--endpoint <endpoint> --pkcs12_file <path to PKCS12 file> --pkcs12_file <password for PKCS12 file>"
```

You can also pass a Certificate Authority file (CA) if your certificate and key combination requires it:

```sh
mvn compile exec:java -pl samples/Pkcs12Connect -Dexec.mainClass=pkcs12connect.Pkcs12Connect -Dexec.args="--endpoint <endpoint> --pkcs12_file <path to PKCS12 file> --pkcs12_file <password for PKCS12 file> --ca_file <path to CA file>"
```

### How to setup and run

To use the certificate and key files provided by AWS IoT Core, you will need to convert them into PKCS#12 format and then import them into your Java keystore. You can convert the certificate and key file to PKCS12 using the following command:

```sh
openssl pkcs12 -export -in <my-certificate.pem.crt> -inkey <my-private-key.pem.key> -out <my-pkcs12-key.pem.key> -name <alias here> -password pass:<password here>
```

Once converted, you can then run the PKCS12 connect sample with the following:

```sh
mvn compile exec:java -pl samples/Pkcs12Connect -Dexec.mainClass=pkcs12connect.Pkcs12Connect -Dexec.args="--endpoint <endpoint> --pkcs12_file <my-pkcs12-key.pem.key> --pkcs12_file <password here>"
```
72 changes: 72 additions & 0 deletions samples/Pkcs12Connect/pom.xml
Original file line number Diff line number Diff line change
@@ -0,0 +1,72 @@
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/maven-v4_0_0.xsd">
<modelVersion>4.0.0</modelVersion>
<groupId>software.amazon.awssdk.iotdevicesdk</groupId>
<artifactId>Pkcs12Connect</artifactId>
<packaging>jar</packaging>
<version>1.0-SNAPSHOT</version>
<name>${project.groupId}:${project.artifactId}</name>
<description>Java bindings for the AWS IoT Core Service</description>
<url>https://github.com/awslabs/aws-iot-device-sdk-java-v2</url>
<properties>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<project.build.sourceEncoding>UTF-8</project.build.sourceEncoding>
</properties>
<profiles>
<profile>
<id>latest-release</id>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk.iotdevicesdk</groupId>
<artifactId>aws-iot-device-sdk</artifactId>
<version>1.11.5</version>
</dependency>
</dependencies>
</profile>
<profile>
<id>default</id>
<activation>
<activeByDefault>true</activeByDefault>
</activation>
<dependencies>
<dependency>
<groupId>software.amazon.awssdk.iotdevicesdk</groupId>
<artifactId>aws-iot-device-sdk</artifactId>
<version>1.0.0-SNAPSHOT </version>
</dependency>
</dependencies>
</profile>
</profiles>
<build>
<plugins>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>exec-maven-plugin</artifactId>
<version>1.4.0</version>
<configuration>
<mainClass>pkcs12connect.Pkcs12Connect</mainClass>
</configuration>
</plugin>
<plugin>
<groupId>org.codehaus.mojo</groupId>
<artifactId>build-helper-maven-plugin</artifactId>
<version>3.2.0</version>
<executions>
<execution>
<id>add-source</id>
<phase>generate-sources</phase>
<goals>
<goal>add-source</goal>
</goals>
<configuration>
<sources>
<source>../Utils/CommandLineUtils</source>
</sources>
</configuration>
</execution>
</executions>
</plugin>
</plugins>
</build>
</project>
119 changes: 119 additions & 0 deletions samples/Pkcs12Connect/src/main/java/pkcs12connect/Pkcs12Connect.java
Original file line number Diff line number Diff line change
@@ -0,0 +1,119 @@
/**
* Copyright Amazon.com, Inc. or its affiliates. All Rights Reserved.
* SPDX-License-Identifier: Apache-2.0.
*/

package pkcs12connect;

import software.amazon.awssdk.crt.CRT;
import software.amazon.awssdk.crt.CrtResource;
import software.amazon.awssdk.crt.CrtRuntimeException;
import software.amazon.awssdk.crt.mqtt.MqttClientConnection;
import software.amazon.awssdk.crt.mqtt.MqttClientConnectionEvents;
import software.amazon.awssdk.crt.http.HttpProxyOptions;
import software.amazon.awssdk.iot.AwsIotMqttConnectionBuilder;

import java.util.concurrent.ExecutionException;
import java.util.concurrent.CompletableFuture;

import utils.commandlineutils.CommandLineUtils;

public class Pkcs12Connect {
// 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 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("Pkcs12Connect execution failure", cause);
} else if (cause != null) {
System.out.println("Exception encountered: " + cause.toString());
}
}

public static void main(String[] args) {

/**
* cmdData is the arguments/input from the command line placed into a single struct for
* use in this sample. This handles all of the command line parsing, validating, etc.
* See the Utils/CommandLineUtils for more information.
*/
CommandLineUtils.SampleCommandLineData cmdData = CommandLineUtils.getInputForIoTSample("Pkcs12Connect", args);

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 {

/**
* Create the MQTT connection from the builder
*/
AwsIotMqttConnectionBuilder builder = AwsIotMqttConnectionBuilder.newMtlsPkcs12Builder(cmdData.input_pkcs12File, cmdData.input_pkcs12Password);
if (cmdData.input_ca != "") {
builder.withCertificateAuthorityFromPath(null, cmdData.input_ca);
}
builder.withConnectionEventCallbacks(callbacks)
.withClientId(cmdData.input_clientId)
.withEndpoint(cmdData.input_endpoint)
.withPort((short)cmdData.input_port)
.withCleanSession(true)
.withProtocolOperationTimeoutMs(60000);
MqttClientConnection connection = builder.build();
builder.close();

/**
* Verify the connection was created
*/
if (connection == null)
{
onApplicationFailure(new RuntimeException("MQTT connection creation failed!"));
}

/**
* Connect and disconnect
*/
CompletableFuture<Boolean> 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);
}
System.out.println("Disconnecting...");
CompletableFuture<Void> disconnected = connection.disconnect();
disconnected.get();
System.out.println("Disconnected.");

/**
* Close the connection now that it is complete
*/
connection.close();

} catch (CrtRuntimeException | InterruptedException | ExecutionException ex) {
onApplicationFailure(ex);
}

CrtResource.waitForNoResources();
System.out.println("Complete!");
}

}
1 change: 1 addition & 0 deletions samples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@
* [Basic Connect](./BasicConnect/README.md)
* [Websocket Connect](./WebsocketConnect/README.md)
* [Pkcs11 Connect](./Pkcs11Connect/README.md)
* [Pkcs12 Connect](./Pkcs12Connect/README.md)
* [WindowsCert Connect](./WindowsCertConnect/README.md)
* [X509 Connect](./X509CredentialsProviderConnect/README.md)
* [CustomAuthorizer Connect](./CustomAuthorizerConnect/README.md)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -238,6 +238,9 @@ public class SampleCommandLineData
public String input_x509Cert;
public String input_x509Key;
public String input_x509Ca;
// PKCS12
public String input_pkcs12File;
public String input_pkcs12Password;
}

// Helper function for getting the message and topic
Expand Down Expand Up @@ -606,6 +609,25 @@ public SampleCommandLineData parseSampleInputX509Connect(String [] args)
return returnData;
}

public SampleCommandLineData parseSampleInputPkcs12Connect(String[] args)
{
addCommonMQTTCommands();
registerCommand(m_cmd_pkcs12_file, "<path>", "Path to your client PKCS12 certificate.");
registerCommand(m_cmd_pkcs12_password, "<path>", "Path to your client certificate in PEM format.");
registerCommand(m_cmd_client_id, "<int>", "Client id to use (optional, default='test-*').");
registerCommand(m_cmd_port, "<int>", "Port to connect to on the endpoint (optional, default='8883').");
sendArguments(args);

SampleCommandLineData returnData = new SampleCommandLineData();
returnData.input_endpoint = getCommandRequired(m_cmd_endpoint, "");
returnData.input_pkcs12File = getCommandRequired(m_cmd_pkcs12_file, "");
returnData.input_pkcs12Password = getCommandRequired(m_cmd_pkcs12_password, "");
returnData.input_ca = getCommandOrDefault(m_cmd_ca_file, "");
returnData.input_clientId = getCommandOrDefault(m_cmd_client_id, "test-" + UUID.randomUUID().toString());
returnData.input_port = Integer.parseInt(getCommandOrDefault(m_cmd_port, "8883"));
return returnData;
}

/**
* Based on the sample string: sets up the arguments, parses the arguments, and returns the command line data all in one go
*/
Expand Down Expand Up @@ -646,6 +668,8 @@ public static SampleCommandLineData getInputForIoTSample(String sampleName, Stri
return cmdUtils.parseSampleInputWindowsCertConnect(args);
} else if (sampleName.equals("x509CredentialsProviderConnect")) {
return cmdUtils.parseSampleInputX509Connect(args);
} else if (sampleName.equals("Pkcs12Connect")) {
return cmdUtils.parseSampleInputPkcs12Connect(args);
} else {
throw new RuntimeException("Unknown sample name!");
}
Expand Down Expand Up @@ -699,6 +723,8 @@ public static SampleCommandLineData getInputForIoTSample(String sampleName, Stri
private static final String m_cmd_password = "password";
private static final String m_cmd_protocol = "protocol";
private static final String m_cmd_auth_params = "auth_params";
private static final String m_cmd_pkcs12_file = "pkcs12_file";
private static final String m_cmd_pkcs12_password = "pkcs12_password";
}

class CommandLineOption {
Expand Down
Loading