Skip to content

Commit 17f5d3f

Browse files
authored
Support PKCS#11 for mutual TLS on Unix platforms (#356)
- Update to latest `aws-crt-cpp`, which exposes PKCS#11 functionality (see awslabs/aws-crt-cpp#315) - Add `pkcs11-pub-sub` sample, demonstrating an MQTT connection where the private key is stored in PKCS#11 token. - Add docs for sample - Sample runs in CI
1 parent 4dc84d4 commit 17f5d3f

File tree

11 files changed

+538
-18
lines changed

11 files changed

+538
-18
lines changed

.builder/actions/build_samples.py

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ def run(self, env):
1212
steps = []
1313
samples = [
1414
'samples/mqtt/basic_pub_sub',
15+
'samples/mqtt/pkcs11_pub_sub',
1516
'samples/mqtt/raw_pub_sub',
1617
'samples/shadow/shadow_sync',
1718
'samples/greengrass/basic_discovery',

codebuild/samples/linux-smoke-tests.yml

Lines changed: 2 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,7 +6,7 @@ phases:
66
- add-apt-repository ppa:ubuntu-toolchain-r/test
77
- apt-add-repository "deb http://apt.llvm.org/bionic/ llvm-toolchain-bionic-8 main"
88
- apt-get update -y
9-
- apt-get install clang-8 cmake -y -f
9+
- apt-get install clang-8 cmake softhsm -y -f
1010
pre_build:
1111
commands:
1212
- export CC=clang-8
@@ -16,6 +16,7 @@ phases:
1616
- echo Build started on `date`
1717
- $CODEBUILD_SRC_DIR/codebuild/samples/setup-linux.sh
1818
- $CODEBUILD_SRC_DIR/codebuild/samples/pubsub-linux.sh
19+
- $CODEBUILD_SRC_DIR/codebuild/samples/pkcs11-pubsub-linux.sh
1920
- $CODEBUILD_SRC_DIR/codebuild/samples/securetunnel-linux.sh
2021
post_build:
2122
commands:
Lines changed: 42 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,42 @@
1+
#!/bin/bash
2+
3+
set -e
4+
set -o pipefail
5+
6+
ENDPOINT=$(aws secretsmanager get-secret-value --secret-id "unit-test/endpoint" --query "SecretString" | cut -f2 -d":" | sed -e 's/[\\\"\}]//g')
7+
8+
# from hereon commands are echoed. don't leak secrets
9+
set -x
10+
11+
softhsm2-util --version
12+
13+
# SoftHSM2's default tokendir path might be invalid on this machine
14+
# so set up a conf file that specifies a known good tokendir path
15+
mkdir -p /tmp/tokens
16+
export SOFTHSM2_CONF=/tmp/softhsm2.conf
17+
echo "directories.tokendir = /tmp/tokens" > /tmp/softhsm2.conf
18+
19+
# create token
20+
softhsm2-util --init-token --free --label my-token --pin 0000 --so-pin 0000
21+
22+
# add private key to token (must be in PKCS#8 format)
23+
openssl pkcs8 -topk8 -in /tmp/privatekey.pem -out /tmp/privatekey.p8.pem -nocrypt
24+
softhsm2-util --import /tmp/privatekey.p8.pem --token my-token --label my-key --id BEEFCAFE --pin 0000
25+
26+
# build and run sample
27+
pushd $CODEBUILD_SRC_DIR/samples/mqtt/pkcs11_pub_sub
28+
29+
mkdir _build
30+
cd _build
31+
cmake -DCMAKE_PREFIX_PATH=/tmp/install ..
32+
make -j
33+
34+
./pkcs11-pub-sub \
35+
--endpoint $ENDPOINT \
36+
--cert /tmp/certificate.pem \
37+
--pkcs11_lib /usr/lib/softhsm/libsofthsm2.so \
38+
--pin 0000 \
39+
--token_label my-token \
40+
--key_label my-key
41+
42+
popd

crt/aws-crt-cpp

Submodule aws-crt-cpp updated 132 files

eventstream_rpc/source/EventStreamClient.cpp

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1302,7 +1302,7 @@ namespace Aws
13021302
const Crt::Optional<Crt::ByteBuf> &payload,
13031303
uint32_t messageFlags)
13041304
{
1305-
bool streamAlreadyTerminated = messageFlags & AWS_EVENT_STREAM_RPC_MESSAGE_FLAG_TERMINATE_STREAM;
1305+
bool streamAlreadyTerminated = (messageFlags & AWS_EVENT_STREAM_RPC_MESSAGE_FLAG_TERMINATE_STREAM) != 0;
13061306

13071307
Crt::StringView payloadStringView;
13081308
if (payload.has_value())

eventstream_rpc/tests/EventStreamClientTest.cpp

Lines changed: 6 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,12 @@
77
#include <awstest/EchoTestRpcClient.h>
88

99
#include <aws/testing/aws_test_harness.h>
10+
#if defined(_WIN32)
11+
// aws_test_harness.h includes Windows.h, which is an abomination.
12+
// undef macros with clashing names...
13+
# undef SetPort
14+
# undef GetMessage
15+
#endif
1016

1117
#include <iostream>
1218
#include <queue>

greengrass_ipc/CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -53,6 +53,13 @@ else ()
5353
target_compile_options(GreengrassIpc-cpp PRIVATE -Wall -Wno-long-long -pedantic -Werror)
5454
endif ()
5555

56+
if (MSVC)
57+
# Generated code files can be very big. Set /bigobj to avoid the following error:
58+
# > fatal error C1128: number of sections exceeded object file format limit: compile with /bigobj
59+
target_compile_options(GreengrassIpc-cpp PRIVATE /bigobj)
60+
endif ()
61+
62+
5663
if (CMAKE_BUILD_TYPE STREQUAL "" OR CMAKE_BUILD_TYPE MATCHES Debug)
5764
target_compile_definitions(GreengrassIpc-cpp PRIVATE "-DDEBUG_BUILD")
5865
endif ()

samples/README.md

Lines changed: 67 additions & 12 deletions
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,7 @@
11
# Sample apps for the AWS IoT Device SDK for C++ v2
22

33
* [Basic MQTT Pub-Sub](#basic-mqtt-pub-sub)
4+
* [PKCS#11 MQTT Pub-Sub](#pkcs11-mqtt-pub-sub)
45
* [Raw MQTT Pub-Sub](#raw-mqtt-pub-sub)
56
* [Fleet provisioning](#fleet-provisioning)
67
* [Shadow](#shadow)
@@ -96,9 +97,63 @@ To run the basic MQTT Pub-Sub use the following command:
9697
--topic <topic name>
9798
```
9899

100+
## PKCS#11 MQTT Pub-Sub
101+
102+
This sample is similar to the [Basic Pub-Sub](#basic-mqtt-pub-sub),
103+
but the private key for mutual TLS is stored on a PKCS#11 compatible smart card or Hardware Security Module (HSM)
104+
105+
WARNING: Unix only. Currently, TLS integration with PKCS#11 is only available on Unix devices.
106+
107+
source: `samples/mqtt/pkcs11_pub_sub/main/cpp`
108+
109+
To run this sample using [SoftHSM2](https://www.opendnssec.org/softhsm/) as the PKCS#11 device:
110+
111+
1) Create an IoT Thing with a certificate and key if you haven't already.
112+
113+
2) Convert the private key into PKCS#8 format
114+
```sh
115+
openssl pkcs8 -topk8 -in <private.pem.key> -out <private.p8.key> -nocrypt
116+
```
117+
118+
3) Install [SoftHSM2](https://www.opendnssec.org/softhsm/):
119+
```sh
120+
sudo apt install softhsm
121+
```
122+
123+
Check that it's working:
124+
```sh
125+
softhsm2-util --show-slots
126+
```
127+
128+
If this spits out an error message, create a config file:
129+
* Default location: `~/.config/softhsm2/softhsm2.conf`
130+
* This file must specify token dir, default value is:
131+
```
132+
directories.tokendir = /usr/local/var/lib/softhsm/tokens/
133+
```
134+
135+
4) Create token and import private key.
136+
137+
You can use any values for the labels, PINs, etc
138+
```sh
139+
softhsm2-util --init-token --free --label <token-label> --pin <user-pin> --so-pin <so-pin>
140+
```
141+
142+
Note which slot the token ended up in
143+
144+
```sh
145+
softhsm2-util --import <private.p8.key> --slot <slot-with-token> --label <key-label> --id <hex-chars> --pin <user-pin>
146+
```
147+
148+
5) Now you can run the sample:
149+
```sh
150+
./pkcs11-pub-sub --endpoint <xxxx-ats.iot.xxxx.amazonaws.com> --ca_file <AmazonRootCA1.pem> --cert <certificate.pem.crt> --pkcs11_lib <libsofthsm2.so> --pin <user-pin> --token_label <token-label> --key_label <key-label>
151+
```
152+
153+
99154
## Raw MQTT Pub-Sub
100155
101-
This sample is similar to the Basic Pub-Sub, but the connection setup is more manual.
156+
This sample is similar to the [Basic Pub-Sub](#basic-mqtt-pub-sub), but the connection setup is more manual.
102157
This is a starting point for using custom
103158
[Configurable Endpoints](https://docs.aws.amazon.com/iot/latest/developerguide/iot-custom-endpoints-configurable.html).
104159
@@ -339,7 +394,7 @@ get the sample up and running. These steps assume you have the AWS CLI installed
339394
sufficient permission to perform all of the listed operations. You will also need python3 to be able to run parse_cert_set_result.py. These steps are based on provisioning setup steps
340395
that can be found at [Embedded C SDK Setup](https://docs.aws.amazon.com/freertos/latest/lib-ref/c-sdk/provisioning/provisioning_tests.html#provisioning_system_tests_setup).
341396
342-
First, create the IAM role that will be needed by the fleet provisioning template. Replace `RoleName` with a name of the role you want to create.
397+
First, create the IAM role that will be needed by the fleet provisioning template. Replace `RoleName` with a name of the role you want to create.
343398
``` sh
344399
aws iam create-role \
345400
--role-name [RoleName] \
@@ -351,17 +406,17 @@ aws iam attach-role-policy \
351406
--role-name [RoleName] \
352407
--policy-arn arn:aws:iam::aws:policy/service-role/AWSIoTThingsRegistration
353408
```
354-
Finally, create the template resource which will be used for provisioning by the demo application. This needs to be done only
355-
once. To create a template, the following AWS CLI command may be used. Replace `TemplateName` with the name of the fleet
356-
provisioning template you want to create. Replace `RoleName` with the name of the role you created previously. Replace
357-
`TemplateJSON` with the template body as a JSON string (containing escape characters). Replace `account` with your AWS
358-
account number.
409+
Finally, create the template resource which will be used for provisioning by the demo application. This needs to be done only
410+
once. To create a template, the following AWS CLI command may be used. Replace `TemplateName` with the name of the fleet
411+
provisioning template you want to create. Replace `RoleName` with the name of the role you created previously. Replace
412+
`TemplateJSON` with the template body as a JSON string (containing escape characters). Replace `account` with your AWS
413+
account number.
359414
``` sh
360415
aws iot create-provisioning-template \
361416
--template-name [TemplateName] \
362417
--provisioning-role-arn arn:aws:iam::[account]:role/[RoleName] \
363418
--template-body "[TemplateJSON]" \
364-
--enabled
419+
--enabled
365420
```
366421
The rest of the instructions assume you have used the following for the template body:
367422
``` sh
@@ -371,13 +426,13 @@ If you use a different body, you may need to pass in different template paramete
371426
372427
#### Running the sample and provisioning using a certificate-key set from a provisioning claim
373428
374-
To run the provisioning sample, you'll need a certificate and key set with sufficient permissions. Provisioning certificates are normally
429+
To run the provisioning sample, you'll need a certificate and key set with sufficient permissions. Provisioning certificates are normally
375430
created ahead of time and placed on your device, but for this sample, we will just create them on the fly. You can also
376431
use any certificate set you've already created if it has sufficient IoT permissions and in doing so, you can skip the step
377432
that calls `create-provisioning-claim`.
378-
433+
379434
We've included a script in the utils folder that creates certificate and key files from the response of calling
380-
`create-provisioning-claim`. These dynamically sourced certificates are only valid for five minutes. When running the command,
435+
`create-provisioning-claim`. These dynamically sourced certificates are only valid for five minutes. When running the command,
381436
you'll need to substitute the name of the template you previously created, and on Windows, replace the paths with something appropriate.
382437
383438
(Optional) Create a temporary provisioning claim certificate set. This command is executed in the debug folder(`aws-iot-device-sdk-cpp-v2-build\samples\identity\fleet_provisioning\Debug`):
@@ -390,7 +445,7 @@ aws iot create-provisioning-claim \
390445
```
391446
392447
The provisioning claim's cert and key set have been written to `/tmp/provision*`. Now you can use these temporary keys
393-
to perform the actual provisioning. If you are not using the temporary provisioning certificate, replace the paths for `--cert`
448+
to perform the actual provisioning. If you are not using the temporary provisioning certificate, replace the paths for `--cert`
394449
and `--key` appropriately:
395450

396451
``` sh

samples/mqtt/basic_pub_sub/main.cpp

Lines changed: 1 addition & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -481,11 +481,9 @@ int main(int argc, char *argv[])
481481

482482
/*
483483
* Actually perform the connect dance.
484-
* This will use default ping behavior of 1 hour and 3 second timeouts.
485-
* If you want different behavior, those arguments go into slots 3 & 4.
486484
*/
487485
fprintf(stdout, "Connecting...\n");
488-
if (!connection->Connect(clientId.c_str(), false, 1000))
486+
if (!connection->Connect(clientId.c_str(), false /*cleanSession*/, 1000 /*keepAliveTimeSecs*/))
489487
{
490488
fprintf(stderr, "MQTT Connection failed with error %s\n", ErrorDebugString(connection->LastError()));
491489
exit(-1);
Lines changed: 23 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,23 @@
1+
cmake_minimum_required(VERSION 3.1)
2+
# note: cxx-17 requires cmake 3.8, cxx-20 requires cmake 3.12
3+
project(pkcs11-pub-sub CXX)
4+
5+
file(GLOB SRC_FILES
6+
"*.cpp"
7+
)
8+
9+
add_executable(${PROJECT_NAME} ${SRC_FILES})
10+
11+
set_target_properties(${PROJECT_NAME} PROPERTIES
12+
CXX_STANDARD 14)
13+
14+
# set warnings
15+
if (MSVC)
16+
target_compile_options(${PROJECT_NAME} PRIVATE /W4 /WX /wd4068)
17+
else ()
18+
target_compile_options(${PROJECT_NAME} PRIVATE -Wall -Wno-long-long -pedantic -Werror)
19+
endif ()
20+
21+
find_package(aws-crt-cpp REQUIRED)
22+
23+
target_link_libraries(${PROJECT_NAME} AWS::aws-crt-cpp)

0 commit comments

Comments
 (0)