diff --git a/demos/fleet_provisioning/fleet_provisioning_keys_cert/CMakeLists.txt b/demos/fleet_provisioning/fleet_provisioning_keys_cert/CMakeLists.txt new file mode 100644 index 0000000000..1a2dd0a33a --- /dev/null +++ b/demos/fleet_provisioning/fleet_provisioning_keys_cert/CMakeLists.txt @@ -0,0 +1,73 @@ +set( DEMO_NAME "fleet_provisioning_keys_cert_demo" ) + +# Include MQTT library's source and header path variables. +include( ${CMAKE_SOURCE_DIR}/libraries/standard/coreMQTT/mqttFilePaths.cmake ) + +# Include backoffAlgorithm library file path configuration. +include( ${CMAKE_SOURCE_DIR}/libraries/standard/backoffAlgorithm/backoffAlgorithmFilePaths.cmake ) + +# Include Fleet Provisioning library's source and header path variables. +include( + ${CMAKE_SOURCE_DIR}/libraries/aws/fleet-provisioning-for-aws-iot-embedded-sdk/fleetprovisioningFilePaths.cmake ) + +# Set path to corePKCS11 and it's third party libraries. +set(COREPKCS11_LOCATION "${CMAKE_SOURCE_DIR}/libraries/standard/corePKCS11") +set(CORE_PKCS11_3RDPARTY_LOCATION "${COREPKCS11_LOCATION}/source/dependency/3rdparty") + +# Include PKCS #11 library's source and header path variables. +include( ${COREPKCS11_LOCATION}/pkcsFilePaths.cmake ) + +list(APPEND PKCS_SOURCES + "${CORE_PKCS11_3RDPARTY_LOCATION}/mbedtls_utils/mbedtls_utils.c" +) + +# CPP files are searched for supporting CI build checks that verify C++ linkage of the Fleet Provisioning library +file( GLOB DEMO_SRCS "*.c*" ) + +# Demo target. +add_executable( ${DEMO_NAME} + ${DEMO_SRCS} + ${MQTT_SOURCES} + ${MQTT_SERIALIZER_SOURCES} + ${BACKOFF_ALGORITHM_SOURCES} + ${PKCS_SOURCES} + ${PKCS_PAL_POSIX_SOURCES} + ${FLEET_PROVISIONING_SOURCES} ) + +target_link_libraries( ${DEMO_NAME} PRIVATE + tinycbor + mbedtls + clock_posix + transport_mbedtls_pkcs11_posix ) + +target_include_directories( ${DEMO_NAME} + PUBLIC + ${LOGGING_INCLUDE_DIRS} + ${MQTT_INCLUDE_PUBLIC_DIRS} + ${BACKOFF_ALGORITHM_INCLUDE_PUBLIC_DIRS} + ${PKCS_INCLUDE_PUBLIC_DIRS} + ${PKCS_PAL_INCLUDE_PUBLIC_DIRS} + ${AWS_DEMO_INCLUDE_DIRS} + "${FLEET_PROVISIONING_INCLUDE_PUBLIC_DIRS}" + "${DEMOS_DIR}/pkcs11/common/include" # corePKCS11 config + "${CMAKE_SOURCE_DIR}/platform/include" + "${CMAKE_CURRENT_LIST_DIR}" + PRIVATE + "${CORE_PKCS11_3RDPARTY_LOCATION}/mbedtls_utils" ) + +set_macro_definitions(TARGETS ${DEMO_NAME} + OPTIONAL + "DOWNLOADED_CERT_WRITE_PATH" + "DOWNLOADED_PRIVATE_KEY_WRITE_PATH" + REQUIRED + "AWS_IOT_ENDPOINT" + "ROOT_CA_CERT_PATH" + "CLAIM_CERT_PATH" + "CLAIM_PRIVATE_KEY_PATH" + "PROVISIONING_TEMPLATE_NAME" + "DEVICE_SERIAL_NUMBER" + "CSR_SUBJECT_NAME" + "CLIENT_IDENTIFIER" + "OS_NAME" + "OS_VERSION" + "HARDWARE_PLATFORM_NAME") diff --git a/demos/fleet_provisioning/fleet_provisioning_keys_cert/core_mqtt_config.h b/demos/fleet_provisioning/fleet_provisioning_keys_cert/core_mqtt_config.h new file mode 100644 index 0000000000..629e7dc688 --- /dev/null +++ b/demos/fleet_provisioning/fleet_provisioning_keys_cert/core_mqtt_config.h @@ -0,0 +1,78 @@ +/* + * AWS IoT Device SDK for Embedded C 202211.00 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef CORE_MQTT_CONFIG_H_ +#define CORE_MQTT_CONFIG_H_ + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Include logging header files and define logging macros in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define the LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL macros. + * 3. Include the header file "logging_stack.h". + */ + +#include "logging_levels.h" + +/* Logging configuration for the MQTT library. */ +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "MQTT" +#endif + +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_WARN +#endif + +#include "logging_stack.h" + +/************ End of logging configuration ****************/ + +/** + * @brief Determines the maximum number of MQTT PUBLISH messages, pending + * acknowledgement at a time, that are supported for incoming and outgoing + * direction of messages, separately. + * + * QoS 1 and 2 MQTT PUBLISHes require acknowledgement from the server before + * they can be completed. While they are awaiting the acknowledgement, the + * client must maintain information about their state. The value of this + * macro sets the limit on how many simultaneous PUBLISH states an MQTT + * context maintains, separately, for both incoming and outgoing direction of + * PUBLISHes. + * + * @note The MQTT context maintains separate state records for outgoing + * and incoming PUBLISHes, and thus, 2 * MQTT_STATE_ARRAY_MAX_COUNT amount + * of memory is statically allocated for the state records. + */ +#define MQTT_STATE_ARRAY_MAX_COUNT ( 10U ) + +/** + * @brief Number of milliseconds to wait for a ping response to a ping + * request as part of the keep-alive mechanism. + * + * If a ping response is not received before this timeout, then + * #MQTT_ProcessLoop will return #MQTTKeepAliveTimeout. + */ +#define MQTT_PINGRESP_TIMEOUT_MS ( 5000U ) + +#endif /* ifndef CORE_MQTT_CONFIG_H_ */ diff --git a/demos/fleet_provisioning/fleet_provisioning_keys_cert/demo_config.h b/demos/fleet_provisioning/fleet_provisioning_keys_cert/demo_config.h new file mode 100644 index 0000000000..e095e21592 --- /dev/null +++ b/demos/fleet_provisioning/fleet_provisioning_keys_cert/demo_config.h @@ -0,0 +1,214 @@ +/* + * AWS IoT Device SDK for Embedded C 202211.00 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef DEMO_CONFIG_H_ +#define DEMO_CONFIG_H_ + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Include logging header files and define logging macros in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL. + * 3. Include the header file "logging_stack.h". + */ + +/* Include header that defines log levels. */ +#include "logging_levels.h" + +/* Logging configuration for the Demo. */ +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "FLEET_PROVISIONING_DEMO" +#endif + +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_INFO +#endif + +#include "logging_stack.h" + +/************ End of logging configuration ****************/ + +/** + * @brief Details of the MQTT broker to connect to. + * + * This is the Thing's Rest API Endpoint for AWS IoT. + * + * @note Your AWS IoT Core endpoint can be found in the AWS IoT console under + * Settings/Custom Endpoint, or using the describe-endpoint API. + * + * #define AWS_IOT_ENDPOINT "...insert here..." + */ + +/** + * @brief AWS IoT MQTT broker port number. + * + * In general, port 8883 is for secured MQTT connections. + * + * @note Port 443 requires use of the ALPN TLS extension with the ALPN protocol + * name. When using port 8883, ALPN is not required. + */ +#define AWS_MQTT_PORT ( 8883 ) + +/** + * @brief Path of the file containing the server's root CA certificate. + * + * This certificate is used to identify the AWS IoT server and is publicly + * available. Refer to the AWS documentation available in the link below + * https://docs.aws.amazon.com/iot/latest/developerguide/server-authentication.html#server-authentication-certs + * + * Amazon's root CA certificate is automatically downloaded to the certificates + * directory from @ref https://www.amazontrust.com/repository/AmazonRootCA1.pem + * using the CMake build system. + * + * @note This certificate should be PEM-encoded. + * @note This path is relative from the demo binary created. Update + * ROOT_CA_CERT_PATH to the absolute path if this demo is executed from elsewhere. + */ +#ifndef ROOT_CA_CERT_PATH + #define ROOT_CA_CERT_PATH "certificates/AmazonRootCA1.crt" +#endif + +/** + * @brief Path of the file containing the provisioning claim certificate. This + * certificate is used to connect to AWS IoT Core and use Fleet Provisioning + * APIs to provision the client device. This is used for the "Provisioning by + * Claim" provisioning workflow. + * + * For information about provisioning by claim, see the following AWS documentation: + * https://docs.aws.amazon.com/iot/latest/developerguide/provision-wo-cert.html#claim-based + * + * @note This certificate should be PEM-encoded. The certificate should be + * registered on AWS IoT Core beforehand. It should have an AWS IoT policy to + * allow it to access only the Fleet Provisioning APIs. An example policy for + * the claim certificates for this demo is available in the + * example_claim_policy.json file in the demo directory. In the example, + * replace with your AWS region, with your + * account ID, and with the name of your provisioning template. + * + * #define CLAIM_CERT_PATH "...insert here..." + */ + +/** + * @brief Path of the file containing the provisioning claim private key. This + * key corresponds to the provisioning claim certificate and is used to + * authenticate with AWS IoT for provisioning by claim. + * + * For information about provisioning by claim, see the following AWS documentation: + * https://docs.aws.amazon.com/iot/latest/developerguide/provision-wo-cert.html#claim-based + * + * @note This private key should be PEM-encoded. + * + * #define CLAIM_PRIVATE_KEY_PATH "...insert here..." + */ + +/** + * @brief Name of the provisioning template to use for the RegisterThing + * portion of the Fleet Provisioning workflow. + * + * For information about provisioning templates, see the following AWS documentation: + * https://docs.aws.amazon.com/iot/latest/developerguide/provision-template.html#fleet-provision-template + * + * The example template used for this demo is available in the + * example_demo_template.json file in the demo directory. In the example, + * replace with the policy provisioned devices + * should have. The demo template uses Fn::Join to construct the Thing name by + * concatenating fp_demo_ and the serial number sent by the demo. + * + * @note The provisioning template MUST be created in AWS IoT before running the + * demo. + * + * #define PROVISIONING_TEMPLATE_NAME "...insert here..." + */ + +/** + * @brief Serial number to send in the request to the Fleet Provisioning + * RegisterThing API. + * + * This is sent as a parameter to the provisioning template, which uses it to + * generate a unique Thing name. This should be unique per device. + * + * #define DEVICE_SERIAL_NUMBER "...insert here..." + */ + +/** + * @brief Subject name to use when creating the certificate signing request (CSR) + * for provisioning the demo client with using the Fleet Provisioning + * CreateCertificateFromCsr APIs. + * + * This is passed to MbedTLS; see https://tls.mbed.org/api/x509__csr_8h.html#a954eae166b125cea2115b7db8c896e90 + */ +#ifndef CSR_SUBJECT_NAME + #define CSR_SUBJECT_NAME "CN=Fleet Provisioning Demo" +#endif + +/** + * @brief MQTT client identifier. + * + * No two clients may use the same client identifier simultaneously. + * + * @note The client identifier should match the Thing name per + * AWS IoT Security best practices: + * https://docs.aws.amazon.com/iot/latest/developerguide/security-best-practices.html + * However, it is not required for the demo to run. + */ +#ifndef CLIENT_IDENTIFIER + #define CLIENT_IDENTIFIER DEVICE_SERIAL_NUMBER +#endif + +/** + * @brief Size of the network buffer for MQTT packets. Must be large enough to + * hold the GetCertificateFromCsr response, which, among other things, includes + * a PEM encoded certificate. + */ +#define NETWORK_BUFFER_SIZE ( 4096U ) + +/** + * @brief The name of the operating system that the application is running on. + * The current value is given as an example. Please update for your specific + * operating system. + */ +#define OS_NAME "Ubuntu" + +/** + * @brief The version of the operating system that the application is running + * on. The current value is given as an example. Please update for your specific + * operating system version. + */ +#define OS_VERSION "18.04 LTS" + +/** + * @brief The name of the hardware platform the application is running on. The + * current value is given as an example. Please update for your specific + * hardware platform. + */ +#define HARDWARE_PLATFORM_NAME "PC" + +/** + * @brief The name of the MQTT library used and its version, following an "@" + * symbol. + */ +#include "core_mqtt.h" +#define MQTT_LIB "core-mqtt@" MQTT_LIBRARY_VERSION + +#endif /* ifndef DEMO_CONFIG_H_ */ diff --git a/demos/fleet_provisioning/fleet_provisioning_keys_cert/example_claim_policy.json b/demos/fleet_provisioning/fleet_provisioning_keys_cert/example_claim_policy.json new file mode 100644 index 0000000000..ec2b52eef9 --- /dev/null +++ b/demos/fleet_provisioning/fleet_provisioning_keys_cert/example_claim_policy.json @@ -0,0 +1,31 @@ + { + "Version": "2012-10-17", + "Statement": [ + { + "Effect": "Allow", + "Action": [ + "iot:Connect" + ], + "Resource": "*" + }, + { + "Effect": "Allow", + "Action": [ + "iot:Publish", + "iot:Receive" + ], + "Resource": [ + "arn:aws:iot:::topic/$aws/certificates/create/*", + "arn:aws:iot:::topic/$aws/provisioning-templates//provision/*" + ] + }, + { + "Effect": "Allow", + "Action": "iot:Subscribe", + "Resource": [ + "arn:aws:iot:::topicfilter/$aws/certificates/create/*", + "arn:aws:iot:::topicfilter/$aws/provisioning-templates//provision/*" + ] + } + ] + } diff --git a/demos/fleet_provisioning/fleet_provisioning_keys_cert/example_demo_template.json b/demos/fleet_provisioning/fleet_provisioning_keys_cert/example_demo_template.json new file mode 100644 index 0000000000..78d7fca2a8 --- /dev/null +++ b/demos/fleet_provisioning/fleet_provisioning_keys_cert/example_demo_template.json @@ -0,0 +1,54 @@ + { + "Parameters": { + "SerialNumber": { + "Type": "String" + }, + "AWS::IoT::Certificate::Id": { + "Type": "String" + } + }, + "Resources": { + "certificate": { + "Properties": { + "CertificateId": { + "Ref": "AWS::IoT::Certificate::Id" + }, + "Status": "Active" + }, + "Type": "AWS::IoT::Certificate" + }, + "policy": { + "Properties": { + "PolicyName": "" + }, + "Type": "AWS::IoT::Policy" + }, + "thing": { + "OverrideSettings": { + "AttributePayload": "MERGE", + "ThingGroups": "DO_NOTHING", + "ThingTypeName": "REPLACE" + }, + "Properties": { + "AttributePayload": {}, + "ThingGroups": [], + "ThingName": { + "Fn::Join": [ + "", + [ + "fp_demo_", + { + "Ref": "SerialNumber" + } + ] + ] + }, + "ThingTypeName": "fp_demo_things" + }, + "Type": "AWS::IoT::Thing" + } + }, + "DeviceConfiguration": { + "Foo": "Bar" + } + } diff --git a/demos/fleet_provisioning/fleet_provisioning_keys_cert/fleet_provisioning_config.h b/demos/fleet_provisioning/fleet_provisioning_keys_cert/fleet_provisioning_config.h new file mode 100644 index 0000000000..aa7ef33a3f --- /dev/null +++ b/demos/fleet_provisioning/fleet_provisioning_keys_cert/fleet_provisioning_config.h @@ -0,0 +1,51 @@ +/* + * AWS IoT Device SDK for Embedded C 202211.00 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef FLEET_PROVISIONING_CONFIG_H_ +#define FLEET_PROVISIONING_CONFIG_H_ + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Include logging header files and define logging macros in the following order: + * 1. Include the header file "logging_levels.h". + * 2. Define the LIBRARY_LOG_NAME and LIBRARY_LOG_LEVEL macros. + * 3. Include the header file "logging_stack.h". + */ + +#include "logging_levels.h" + +/* Logging configuration for the Fleet Provisioning library. */ +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "FleetProvisioning" +#endif + +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_INFO +#endif + +#include "logging_stack.h" + +/************ End of logging configuration ****************/ + +#endif /* ifndef FLEET_PROVISIONING_CONFIG_H_ */ diff --git a/demos/fleet_provisioning/fleet_provisioning_keys_cert/fleet_provisioning_keys_cert_demo.c b/demos/fleet_provisioning/fleet_provisioning_keys_cert/fleet_provisioning_keys_cert_demo.c new file mode 100644 index 0000000000..40a331cdd8 --- /dev/null +++ b/demos/fleet_provisioning/fleet_provisioning_keys_cert/fleet_provisioning_keys_cert_demo.c @@ -0,0 +1,830 @@ +/* + * AWS IoT Device SDK for Embedded C 202211.00 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* + * Demo for showing use of the Fleet Provisioning library to use the Fleet + * Provisioning feature of AWS IoT Core for provisioning devices with + * credentials. This demo shows how a device can be provisioned with AWS IoT + * Core using the Certificate Signing Request workflow of the Fleet + * Provisioning feature. + * + * The Fleet Provisioning library provides macros and helper functions for + * assembling MQTT topics strings, and for determining whether an incoming MQTT + * message is related to the Fleet Provisioning API of AWS IoT Core. The Fleet + * Provisioning library does not depend on any particular MQTT library, + * therefore the functionality for MQTT operations is placed in another file + * (mqtt_operations.c). This demo uses the coreMQTT library. If needed, + * mqtt_operations.c can be modified to replace coreMQTT with another MQTT + * library. This demo requires using the AWS IoT Core broker as Fleet + * Provisioning is an AWS IoT Core feature. + * + * This demo provisions a device certificate using the provisioning by claim + * workflow with a Keys and Certificate Request. The demo connects to AWS + * IoT Core using provided claim credentials (whose certificate needs to be + * registered with IoT Core before running this demo), subscribes to the + * CreateKeysAndCertificate topics, and obtains keys and certificate. It then + * subscribes to the RegisterThing topics and activates the certificate and + * obtains a Thing using the provisioning template. Finally, it reconnects to + * AWS IoT Core using the new credentials. + */ + +/* Standard includes. */ +#include +#include +#include +#include + +/* POSIX includes. */ +#include +#include +#include + +/* Demo config. */ +#include "demo_config.h" + +/* corePKCS11 includes. */ +#include "core_pkcs11.h" +#include "core_pkcs11_config.h" + +/* AWS IoT Fleet Provisioning Library. */ +#include "fleet_provisioning.h" + +/* Demo includes. */ +#include "mqtt_operations.h" +#include "pkcs11_operations.h" +#include "fleet_provisioning_serializer.h" + +/** + * These configurations are required. Throw compilation error if it is not + * defined. + */ +#ifndef PROVISIONING_TEMPLATE_NAME + #error "Please define PROVISIONING_TEMPLATE_NAME to the template name registered with AWS IoT Core in demo_config.h." +#endif +#ifndef CLAIM_CERT_PATH + #error "Please define path to claim certificate (CLAIM_CERT_PATH) in demo_config.h." +#endif +#ifndef CLAIM_PRIVATE_KEY_PATH + #error "Please define path to claim private key (CLAIM_PRIVATE_KEY_PATH) in demo_config.h." +#endif +#ifndef DEVICE_SERIAL_NUMBER + #error "Please define a serial number (DEVICE_SERIAL_NUMBER) in demo_config.h." +#endif + +/** + * @brief The length of #PROVISIONING_TEMPLATE_NAME. + */ +#define PROVISIONING_TEMPLATE_NAME_LENGTH ( ( uint16_t ) ( sizeof( PROVISIONING_TEMPLATE_NAME ) - 1 ) ) + +/** + * @brief The length of #DEVICE_SERIAL_NUMBER. + */ +#define DEVICE_SERIAL_NUMBER_LENGTH ( ( uint16_t ) ( sizeof( DEVICE_SERIAL_NUMBER ) - 1 ) ) + +/** + * @brief Size of AWS IoT Thing name buffer. + * + * See https://docs.aws.amazon.com/iot/latest/apireference/API_CreateThing.html#iot-CreateThing-request-thingName + */ +#define MAX_THING_NAME_LENGTH 128 + +/** + * @brief The maximum number of times to run the loop in this demo. + * + * @note The demo loop is attempted to re-run only if it fails in an iteration. + * Once the demo loop succeeds in an iteration, the demo exits successfully. + */ +#ifndef FLEET_PROV_MAX_DEMO_LOOP_COUNT + #define FLEET_PROV_MAX_DEMO_LOOP_COUNT ( 3 ) +#endif + +/** + * @brief Time in seconds to wait between retries of the demo loop if + * demo loop fails. + */ +#define DELAY_BETWEEN_DEMO_RETRY_ITERATIONS_SECONDS ( 5 ) + +/** + * @brief Size of buffer in which to hold the certificate signing request (CSR). + */ +#define PRIV_KEY_BUFFER_LENGTH 2048 + +/** + * @brief Size of buffer in which to hold the certificate. + */ +#define CERT_BUFFER_LENGTH 2048 + +/** + * @brief Size of buffer in which to hold the certificate id. + * + * See https://docs.aws.amazon.com/iot/latest/apireference/API_Certificate.html#iot-Type-Certificate-certificateId + */ +#define CERT_ID_BUFFER_LENGTH 64 + +/** + * @brief Size of buffer in which to hold the certificate ownership token. + */ +#define OWNERSHIP_TOKEN_BUFFER_LENGTH 512 + +/** + * @brief Status values of the Fleet Provisioning response. + */ +typedef enum +{ + ResponseNotReceived, + ResponseAccepted, + ResponseRejected +} ResponseStatus_t; + +/*-----------------------------------------------------------*/ + +/** + * @brief Status reported from the MQTT publish callback. + */ +static ResponseStatus_t responseStatus; + +/** + * @brief Buffer to hold the provisioned AWS IoT Thing name. + */ +static char thingName[ MAX_THING_NAME_LENGTH ]; + +/** + * @brief Length of the AWS IoT Thing name. + */ +static size_t thingNameLength; + +/** + * @brief Buffer to hold responses received from the AWS IoT Fleet Provisioning + * APIs. When the MQTT publish callback receives an expected Fleet Provisioning + * accepted payload, it copies it into this buffer. + */ +static uint8_t payloadBuffer[ NETWORK_BUFFER_SIZE ]; + +/** + * @brief Length of the payload stored in #payloadBuffer. This is set by the + * MQTT publish callback when it copies a received payload into #payloadBuffer. + */ +static size_t payloadLength; + +/*-----------------------------------------------------------*/ + +/** + * @brief Callback to receive the incoming publish messages from the MQTT + * broker. Sets responseStatus if an expected CreateKeysAndCertificate or + * RegisterThing response is received, and copies the response into + * responseBuffer if the response is an accepted one. + * + * @param[in] pPublishInfo Pointer to publish info of the incoming publish. + * @param[in] packetIdentifier Packet identifier of the incoming publish. + */ +static void provisioningPublishCallback( MQTTPublishInfo_t * pPublishInfo, + uint16_t packetIdentifier ); + +/** + * @brief Run the MQTT process loop to get a response. + */ +static bool waitForResponse( void ); + +/** + * @brief Subscribe to the CreateKeysAndCertificate accepted and rejected topics. + */ +static bool subscribeToKeysCertResponseTopics( void ); + +/** + * @brief Unsubscribe from the CreateKeysAndCertificate accepted and rejected topics. + */ +static bool unsubscribeFromKeysCertResponseTopics( void ); + +/** + * @brief Subscribe to the RegisterThing accepted and rejected topics. + */ +static bool subscribeToRegisterThingResponseTopics( void ); + +/** + * @brief Unsubscribe from the RegisterThing accepted and rejected topics. + */ +static bool unsubscribeFromRegisterThingResponseTopics( void ); + +/*-----------------------------------------------------------*/ + +static void provisioningPublishCallback( MQTTPublishInfo_t * pPublishInfo, + uint16_t packetIdentifier ) +{ + FleetProvisioningStatus_t status; + FleetProvisioningTopic_t api; + const char * cborDump; + + /* Silence compiler warnings about unused variables. */ + ( void ) packetIdentifier; + + status = FleetProvisioning_MatchTopic( pPublishInfo->pTopicName, + pPublishInfo->topicNameLength, &api ); + + if( status != FleetProvisioningSuccess ) + { + LogWarn( ( "Unexpected publish message received. Topic: %.*s.", + ( int ) pPublishInfo->topicNameLength, + ( const char * ) pPublishInfo->pTopicName ) ); + } + else + { + if( api == FleetProvCborCreateKeysAndCertAccepted ) + { + LogInfo( ( "Received accepted response from Fleet Provisioning CreateKeysAndCertificate API." ) ); + + cborDump = getStringFromCbor( ( const uint8_t * ) pPublishInfo->pPayload, pPublishInfo->payloadLength ); + LogDebug( ( "Payload: %s", cborDump ) ); + free( ( void * ) cborDump ); + + responseStatus = ResponseAccepted; + + /* Copy the payload from the MQTT library's buffer to #payloadBuffer. */ + ( void ) memcpy( ( void * ) payloadBuffer, + ( const void * ) pPublishInfo->pPayload, + ( size_t ) pPublishInfo->payloadLength ); + + payloadLength = pPublishInfo->payloadLength; + } + else if( api == FleetProvCborCreateKeysAndCertRejected ) + { + LogError( ( "Received rejected response from Fleet Provisioning CreateKeysAndCertificate API." ) ); + + cborDump = getStringFromCbor( ( const uint8_t * ) pPublishInfo->pPayload, pPublishInfo->payloadLength ); + LogError( ( "Payload: %s", cborDump ) ); + free( ( void * ) cborDump ); + + responseStatus = ResponseRejected; + } + else if( api == FleetProvCborRegisterThingAccepted ) + { + LogInfo( ( "Received accepted response from Fleet Provisioning RegisterThing API." ) ); + + cborDump = getStringFromCbor( ( const uint8_t * ) pPublishInfo->pPayload, pPublishInfo->payloadLength ); + LogDebug( ( "Payload: %s", cborDump ) ); + free( ( void * ) cborDump ); + + responseStatus = ResponseAccepted; + + /* Copy the payload from the MQTT library's buffer to #payloadBuffer. */ + ( void ) memcpy( ( void * ) payloadBuffer, + ( const void * ) pPublishInfo->pPayload, + ( size_t ) pPublishInfo->payloadLength ); + + payloadLength = pPublishInfo->payloadLength; + } + else if( api == FleetProvCborRegisterThingRejected ) + { + LogError( ( "Received rejected response from Fleet Provisioning RegisterThing API." ) ); + + cborDump = getStringFromCbor( ( const uint8_t * ) pPublishInfo->pPayload, pPublishInfo->payloadLength ); + LogError( ( "Payload: %s", cborDump ) ); + free( ( void * ) cborDump ); + + responseStatus = ResponseRejected; + } + else + { + LogError( ( "Received message on unexpected Fleet Provisioning topic. Topic: %.*s.", + ( int ) pPublishInfo->topicNameLength, + ( const char * ) pPublishInfo->pTopicName ) ); + } + } +} +/*-----------------------------------------------------------*/ + +static bool waitForResponse( void ) +{ + bool status = false; + + responseStatus = ResponseNotReceived; + + /* responseStatus is updated from the MQTT publish callback. */ + ( void ) ProcessLoopWithTimeout(); + + if( responseStatus == ResponseNotReceived ) + { + LogError( ( "Timed out waiting for response." ) ); + } + + if( responseStatus == ResponseAccepted ) + { + status = true; + } + + return status; +} +/*-----------------------------------------------------------*/ + +static bool subscribeToKeysCertResponseTopics( void ) +{ + bool status; + + status = SubscribeToTopic( FP_CBOR_CREATE_KEYS_ACCEPTED_TOPIC, + FP_CBOR_CREATE_KEYS_ACCEPTED_LENGTH ); + + if( status == false ) + { + LogError( ( "Failed to subscribe to fleet provisioning topic: %.*s.", + FP_CBOR_CREATE_KEYS_ACCEPTED_LENGTH, + FP_CBOR_CREATE_KEYS_ACCEPTED_TOPIC ) ); + } + + if( status == true ) + { + status = SubscribeToTopic( FP_CBOR_CREATE_KEYS_REJECTED_TOPIC, + FP_CBOR_CREATE_KEYS_REJECTED_LENGTH ); + + if( status == false ) + { + LogError( ( "Failed to subscribe to fleet provisioning topic: %.*s.", + FP_CBOR_CREATE_KEYS_REJECTED_LENGTH, + FP_CBOR_CREATE_KEYS_REJECTED_TOPIC ) ); + } + } + + return status; +} +/*-----------------------------------------------------------*/ + +static bool unsubscribeFromKeysCertResponseTopics( void ) +{ + bool status; + + status = UnsubscribeFromTopic( FP_CBOR_CREATE_KEYS_ACCEPTED_TOPIC, + FP_CBOR_CREATE_KEYS_ACCEPTED_LENGTH ); + + if( status == false ) + { + LogError( ( "Failed to unsubscribe from fleet provisioning topic: %.*s.", + FP_CBOR_CREATE_KEYS_ACCEPTED_LENGTH, + FP_CBOR_CREATE_KEYS_ACCEPTED_TOPIC ) ); + } + + if( status == true ) + { + status = UnsubscribeFromTopic( FP_CBOR_CREATE_KEYS_REJECTED_TOPIC, + FP_CBOR_CREATE_KEYS_REJECTED_LENGTH ); + + if( status == false ) + { + LogError( ( "Failed to unsubscribe from fleet provisioning topic: %.*s.", + FP_CBOR_CREATE_KEYS_REJECTED_LENGTH, + FP_CBOR_CREATE_KEYS_REJECTED_TOPIC ) ); + } + } + + return status; +} +/*-----------------------------------------------------------*/ + +static bool subscribeToRegisterThingResponseTopics( void ) +{ + bool status; + + status = SubscribeToTopic( FP_CBOR_REGISTER_ACCEPTED_TOPIC( PROVISIONING_TEMPLATE_NAME ), + FP_CBOR_REGISTER_ACCEPTED_LENGTH( PROVISIONING_TEMPLATE_NAME_LENGTH ) ); + + if( status == false ) + { + LogError( ( "Failed to subscribe to fleet provisioning topic: %.*s.", + FP_CBOR_REGISTER_ACCEPTED_LENGTH( PROVISIONING_TEMPLATE_NAME_LENGTH ), + FP_CBOR_REGISTER_ACCEPTED_TOPIC( PROVISIONING_TEMPLATE_NAME ) ) ); + } + + if( status == true ) + { + status = SubscribeToTopic( FP_CBOR_REGISTER_REJECTED_TOPIC( PROVISIONING_TEMPLATE_NAME ), + FP_CBOR_REGISTER_REJECTED_LENGTH( PROVISIONING_TEMPLATE_NAME_LENGTH ) ); + + if( status == false ) + { + LogError( ( "Failed to subscribe to fleet provisioning topic: %.*s.", + FP_CBOR_REGISTER_REJECTED_LENGTH( PROVISIONING_TEMPLATE_NAME_LENGTH ), + FP_CBOR_REGISTER_REJECTED_TOPIC( PROVISIONING_TEMPLATE_NAME ) ) ); + } + } + + return status; +} +/*-----------------------------------------------------------*/ + +static bool unsubscribeFromRegisterThingResponseTopics( void ) +{ + bool status; + + status = UnsubscribeFromTopic( FP_CBOR_REGISTER_ACCEPTED_TOPIC( PROVISIONING_TEMPLATE_NAME ), + FP_CBOR_REGISTER_ACCEPTED_LENGTH( PROVISIONING_TEMPLATE_NAME_LENGTH ) ); + + if( status == false ) + { + LogError( ( "Failed to unsubscribe from fleet provisioning topic: %.*s.", + FP_CBOR_REGISTER_ACCEPTED_LENGTH( PROVISIONING_TEMPLATE_NAME_LENGTH ), + FP_CBOR_REGISTER_ACCEPTED_TOPIC( PROVISIONING_TEMPLATE_NAME ) ) ); + } + + if( status == true ) + { + status = UnsubscribeFromTopic( FP_CBOR_REGISTER_REJECTED_TOPIC( PROVISIONING_TEMPLATE_NAME ), + FP_CBOR_REGISTER_REJECTED_LENGTH( PROVISIONING_TEMPLATE_NAME_LENGTH ) ); + + if( status == false ) + { + LogError( ( "Failed to unsubscribe from fleet provisioning topic: %.*s.", + FP_CBOR_REGISTER_REJECTED_LENGTH( PROVISIONING_TEMPLATE_NAME_LENGTH ), + FP_CBOR_REGISTER_REJECTED_TOPIC( PROVISIONING_TEMPLATE_NAME ) ) ); + } + } + + return status; +} +/*-----------------------------------------------------------*/ + +/* This example uses a single application task, which shows that how to use + * the Fleet Provisioning library to generate and validate AWS IoT Fleet + * Provisioning MQTT topics, and use the coreMQTT library to communicate with + * the AWS IoT Fleet Provisioning APIs. */ +int main( int argc, + char ** argv ) +{ + bool status = false; + /* Buffer for holding received certificate until it is saved. */ + char certificate[ CERT_BUFFER_LENGTH ]; + size_t certificateLength; + /* Buffer for holding received private key until it is saved. */ + char privatekey[ PRIV_KEY_BUFFER_LENGTH ]; + size_t privatekeyLength; + /* Buffer for holding the certificate ID. */ + char certificateId[ CERT_ID_BUFFER_LENGTH ]; + size_t certificateIdLength; + /* Buffer for holding the certificate ownership token. */ + char ownershipToken[ OWNERSHIP_TOKEN_BUFFER_LENGTH ]; + size_t ownershipTokenLength; + bool connectionEstablished = false; + CK_SESSION_HANDLE p11Session; + int demoRunCount = 0; + CK_RV pkcs11ret = CKR_OK; + + /* Silence compiler warnings about unused variables. */ + ( void ) argc; + ( void ) argv; + + do + { + /* Initialize the buffer lengths to their max lengths. */ + certificateLength = CERT_BUFFER_LENGTH; + privatekeyLength = PRIV_KEY_BUFFER_LENGTH; + certificateIdLength = CERT_ID_BUFFER_LENGTH; + ownershipTokenLength = OWNERSHIP_TOKEN_BUFFER_LENGTH; + + /* Initialize the PKCS #11 module */ + pkcs11ret = xInitializePkcs11Session( &p11Session ); + + if( pkcs11ret != CKR_OK ) + { + LogError( ( "Failed to initialize PKCS #11." ) ); + status = false; + } + else + { + /* Insert the claim credentials into the PKCS #11 module */ + status = loadClaimCredentials( p11Session, + CLAIM_CERT_PATH, + pkcs11configLABEL_CLAIM_CERTIFICATE, + CLAIM_PRIVATE_KEY_PATH, + pkcs11configLABEL_CLAIM_PRIVATE_KEY ); + + if( status == false ) + { + LogError( ( "Failed to provision PKCS #11 with claim credentials." ) ); + } + } + + /**** Connect to AWS IoT Core with provisioning claim credentials *****/ + + /* We first use the claim credentials to connect to the broker. These + * credentials should allow use of the RegisterThing API and one of the + * CreateCertificatefromCsr or CreateKeysAndCertificate. + * In this demo we use CreateKeysAndCertificate. */ + + if( status == true ) + { + /* Attempts to connect to the AWS IoT MQTT broker. If the + * connection fails, retries after a timeout. Timeout value will + * exponentially increase until maximum attempts are reached. */ + LogInfo( ( "Establishing MQTT session with claim certificate..." ) ); + status = EstablishMqttSession( provisioningPublishCallback, + p11Session, + pkcs11configLABEL_CLAIM_CERTIFICATE, + pkcs11configLABEL_CLAIM_PRIVATE_KEY ); + + if( status == false ) + { + LogError( ( "Failed to establish MQTT session." ) ); + } + else + { + LogInfo( ( "Established connection with claim credentials." ) ); + connectionEstablished = true; + } + } + + /**** Call the CreateKeysAndCertificate API ***************************/ + + /* We use the CreateKeysAndCertificate API to obtain a client certificate + * and private key. */ + if( status == true ) + { + /* Subscribe to the CreateKeysAndCertificate accepted and rejected + * topics. In this demo we use CBOR encoding for the payloads, + * so we use the CBOR variants of the topics. */ + status = subscribeToKeysCertResponseTopics(); + } + + if( status == true ) + { + /* Publish an empty payload to the CreateKeysAndCertificate API. */ + status = PublishToTopic( FP_CBOR_CREATE_KEYS_PUBLISH_TOPIC, + FP_CBOR_CREATE_KEYS_PUBLISH_LENGTH, + "", + 0 ); + + if( status == false ) + { + LogError( ( "Failed to publish to fleet provisioning topic: %.*s.", + FP_CBOR_CREATE_KEYS_PUBLISH_LENGTH, + FP_CBOR_CREATE_KEYS_PUBLISH_TOPIC ) ); + } + } + + if( status == true ) + { + /* Get the response to the CreateKeysAndCertificate request. */ + status = waitForResponse(); + } + + if( status == true ) + { + /* From the response, extract the certificate, certificate ID, and + * certificate ownership token. */ + status = parseKeyCertResponse( payloadBuffer, + payloadLength, + certificate, + &certificateLength, + privatekey, + &privatekeyLength, + certificateId, + &certificateIdLength, + ownershipToken, + &ownershipTokenLength ); + + if( status == true ) + { + LogInfo( ( "Received privatekey and certificate with Id: %.*s", ( int ) certificateIdLength, certificateId ) ); + } + } + + if( status == true ) + { + /* Save the certificate into PKCS #11. */ + status = loadPrivateKey( p11Session, + privatekey, + pkcs11configLABEL_DEVICE_PRIVATE_KEY_FOR_TLS, + privatekeyLength ); + } + + if( status == true ) + { + /* Save the certificate into PKCS #11. */ + status = loadCertificate( p11Session, + certificate, + pkcs11configLABEL_DEVICE_CERTIFICATE_FOR_TLS, + certificateLength ); + } + + if( status == true ) + { + /* Unsubscribe from the CreateKeysAndCertificate topics. */ + status = unsubscribeFromKeysCertResponseTopics(); + } + + /**** Call the RegisterThing API **************************************/ + + /* We then use the RegisterThing API to activate the received certificate, + * provision AWS IoT resources according to the provisioning template, and + * receive device configuration. */ + if( status == true ) + { + /* Create the request payload to publish to the RegisterThing API. */ + status = generateRegisterThingRequest( payloadBuffer, + NETWORK_BUFFER_SIZE, + ownershipToken, + ownershipTokenLength, + DEVICE_SERIAL_NUMBER, + DEVICE_SERIAL_NUMBER_LENGTH, + &payloadLength ); + } + + if( status == true ) + { + /* Subscribe to the RegisterThing response topics. */ + status = subscribeToRegisterThingResponseTopics(); + } + + if( status == true ) + { + /* Publish the RegisterThing request. */ + status = PublishToTopic( FP_CBOR_REGISTER_PUBLISH_TOPIC( PROVISIONING_TEMPLATE_NAME ), + FP_CBOR_REGISTER_PUBLISH_LENGTH( PROVISIONING_TEMPLATE_NAME_LENGTH ), + ( char * ) payloadBuffer, + payloadLength ); + + if( status == false ) + { + LogError( ( "Failed to publish to fleet provisioning topic: %.*s.", + FP_CBOR_REGISTER_PUBLISH_LENGTH( PROVISIONING_TEMPLATE_NAME_LENGTH ), + FP_CBOR_REGISTER_PUBLISH_TOPIC( PROVISIONING_TEMPLATE_NAME ) ) ); + } + } + + if( status == true ) + { + /* Get the response to the RegisterThing request. */ + status = waitForResponse(); + } + + if( status == true ) + { + /* Extract the Thing name from the response. */ + thingNameLength = MAX_THING_NAME_LENGTH; + status = parseRegisterThingResponse( payloadBuffer, + payloadLength, + thingName, + &thingNameLength ); + + if( status == true ) + { + LogInfo( ( "Received AWS IoT Thing name: %.*s", ( int ) thingNameLength, thingName ) ); + } + } + + if( status == true ) + { + /* Unsubscribe from the RegisterThing topics. */ + unsubscribeFromRegisterThingResponseTopics(); + } + + /**** Disconnect from AWS IoT Core ************************************/ + + /* As we have completed the provisioning workflow, we disconnect from + * the connection using the provisioning claim credentials. We will + * establish a new MQTT connection with the newly provisioned + * credentials. */ + if( connectionEstablished == true ) + { + DisconnectMqttSession(); + connectionEstablished = false; + } + + /**** Connect to AWS IoT Core with provisioned certificate ************/ + + if( status == true ) + { + LogInfo( ( "Establishing MQTT session with provisioned certificate..." ) ); + status = EstablishMqttSession( provisioningPublishCallback, + p11Session, + pkcs11configLABEL_DEVICE_CERTIFICATE_FOR_TLS, + pkcs11configLABEL_DEVICE_PRIVATE_KEY_FOR_TLS ); + + if( status != true ) + { + LogError( ( "Failed to establish MQTT session with provisioned " + "credentials. Verify on your AWS account that the " + "new certificate is active and has an attached IoT " + "Policy that allows the \"iot:Connect\" action." ) ); + } + else + { + LogInfo( ( "Sucessfully established connection with provisioned credentials." ) ); + connectionEstablished = true; + } + } + + /**** Finish **********************************************************/ + + if( connectionEstablished == true ) + { + /* Close the connection. */ + DisconnectMqttSession(); + connectionEstablished = false; + } + + pkcs11CloseSession( p11Session ); + + /**** Retry in case of failure ****************************************/ + + /* Increment the demo run count. */ + demoRunCount++; + + if( status == true ) + { + LogInfo( ( "Demo iteration %d is successful.", demoRunCount ) ); + } + /* Attempt to retry a failed iteration of demo for up to #FLEET_PROV_MAX_DEMO_LOOP_COUNT times. */ + else if( demoRunCount < FLEET_PROV_MAX_DEMO_LOOP_COUNT ) + { + LogWarn( ( "Demo iteration %d failed. Retrying...", demoRunCount ) ); + sleep( DELAY_BETWEEN_DEMO_RETRY_ITERATIONS_SECONDS ); + } + /* Failed all #FLEET_PROV_MAX_DEMO_LOOP_COUNT demo iterations. */ + else + { + LogError( ( "All %d demo iterations failed.", FLEET_PROV_MAX_DEMO_LOOP_COUNT ) ); + break; + } + } while( status != true ); + + /* Log demo success. */ + if( status == true ) + { + LogInfo( ( "Demo completed successfully." ) ); + + #if defined( DOWNLOADED_CERT_WRITE_PATH ) + { + int fd = open( DOWNLOADED_CERT_WRITE_PATH, O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR ); + + if( -1 != fd ) + { + const ssize_t writtenBytes = write( fd, certificate, certificateLength ); + + if( writtenBytes == certificateLength ) + { + LogInfo( ( "Written %s successfully.", DOWNLOADED_CERT_WRITE_PATH ) ); + } + else + { + LogError( ( "Could not write to %s. Error: %s.", DOWNLOADED_CERT_WRITE_PATH, strerror( errno ) ) ); + } + + close( fd ); + } + else + { + LogError( ( "Could not open %s. Error: %s.", DOWNLOADED_CERT_WRITE_PATH, strerror( errno ) ) ); + } + } + #else /* if defined( DOWNLOADED_CERT_WRITE_PATH ) */ + LogInfo( ( "NOTE: define DOWNLOADED_CERT_WRITE_PATH in order to have the certificate written to disk." ) ); + #endif // DOWNLOADED_CERT_WRITE_PATH + + #if defined( DOWNLOADED_PRIVATE_KEY_WRITE_PATH ) + { + int fd = open( DOWNLOADED_PRIVATE_KEY_WRITE_PATH, O_CREAT | O_WRONLY | O_TRUNC, S_IRUSR | S_IWUSR ); + + if( -1 != fd ) + { + const ssize_t writtenBytes = write( fd, privatekey, privatekeyLength ); + + if( writtenBytes == privatekeyLength ) + { + LogInfo( ( "Written %s successfully.", DOWNLOADED_PRIVATE_KEY_WRITE_PATH ) ); + } + else + { + LogError( ( "Could not write to %s. Error: %s.", DOWNLOADED_PRIVATE_KEY_WRITE_PATH, strerror( errno ) ) ); + } + + close( fd ); + } + else + { + LogError( ( "Could not open %s. Error: %s.", DOWNLOADED_PRIVATE_KEY_WRITE_PATH, strerror( errno ) ) ); + } + } + #else /* if defined( DOWNLOADED_PRIVATE_KEY_WRITE_PATH ) */ + LogInfo( ( "NOTE: define DOWNLOADED_PRIVATE_KEY_WRITE_PATH in order to have the private key written to disk." ) ); + #endif // DOWNLOADED_PRIVATE_KEY_WRITE_PATH + } + + return ( status == true ) ? EXIT_SUCCESS : EXIT_FAILURE; +} +/*-----------------------------------------------------------*/ diff --git a/demos/fleet_provisioning/fleet_provisioning_keys_cert/fleet_provisioning_serializer.c b/demos/fleet_provisioning/fleet_provisioning_keys_cert/fleet_provisioning_serializer.c new file mode 100644 index 0000000000..b3f1e9fab6 --- /dev/null +++ b/demos/fleet_provisioning/fleet_provisioning_keys_cert/fleet_provisioning_serializer.c @@ -0,0 +1,468 @@ +/* + * AWS IoT Device SDK for Embedded C 202211.00 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/* Standard includes */ +#include + +/* TinyCBOR library for CBOR encoding and decoding operations. */ +#include "cbor.h" + +/* Demo config. */ +#include "demo_config.h" + +/* AWS IoT Fleet Provisioning Library. */ +#include "fleet_provisioning.h" + +/* Header include. */ +#include "fleet_provisioning_serializer.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief Context passed to tinyCBOR for #cborPrinter. Initial + * state should be zeroed. + */ +typedef struct +{ + const char * str; + size_t length; +} CborPrintContext_t; + +/*-----------------------------------------------------------*/ + +/** + * @brief Printing function to pass to tinyCBOR. + * + * cbor_value_to_pretty_stream calls it multiple times to print a textual CBOR + * representation. + * + * @param token Context for the function. + * @param fmt Printf style format string. + * @param ... Printf style args after format string. + */ +static CborError cborPrinter( void * token, + const char * fmt, + ... ); + +/*-----------------------------------------------------------*/ + +bool generateRegisterThingRequest( uint8_t * pBuffer, + size_t bufferLength, + const char * pCertificateOwnershipToken, + size_t certificateOwnershipTokenLength, + const char * pSerial, + size_t serialLength, + size_t * pOutLengthWritten ) +{ + CborEncoder encoder, mapEncoder, parametersEncoder; + CborError cborRet; + + assert( pBuffer != NULL ); + assert( pCertificateOwnershipToken != NULL ); + assert( pSerial != NULL ); + assert( pOutLengthWritten != NULL ); + + /* For details on the RegisterThing request payload format, see: + * https://docs.aws.amazon.com/iot/latest/developerguide/fleet-provision-api.html#register-thing-request-payload + */ + cbor_encoder_init( &encoder, pBuffer, bufferLength, 0 ); + /* The RegisterThing request payload is a map with two keys. */ + cborRet = cbor_encoder_create_map( &encoder, &mapEncoder, 2 ); + + if( cborRet == CborNoError ) + { + cborRet = cbor_encode_text_stringz( &mapEncoder, "certificateOwnershipToken" ); + } + + if( cborRet == CborNoError ) + { + cborRet = cbor_encode_text_string( &mapEncoder, pCertificateOwnershipToken, certificateOwnershipTokenLength ); + } + + if( cborRet == CborNoError ) + { + cborRet = cbor_encode_text_stringz( &mapEncoder, "parameters" ); + } + + if( cborRet == CborNoError ) + { + /* Parameters in this example is length 1. */ + cborRet = cbor_encoder_create_map( &mapEncoder, ¶metersEncoder, 1 ); + } + + if( cborRet == CborNoError ) + { + cborRet = cbor_encode_text_stringz( ¶metersEncoder, "SerialNumber" ); + } + + if( cborRet == CborNoError ) + { + cborRet = cbor_encode_text_string( ¶metersEncoder, pSerial, serialLength ); + } + + if( cborRet == CborNoError ) + { + cborRet = cbor_encoder_close_container( &mapEncoder, ¶metersEncoder ); + } + + if( cborRet == CborNoError ) + { + cborRet = cbor_encoder_close_container( &encoder, &mapEncoder ); + } + + if( cborRet == CborNoError ) + { + *pOutLengthWritten = cbor_encoder_get_buffer_size( &encoder, ( uint8_t * ) pBuffer ); + } + else + { + LogError( ( "Error during CBOR encoding: %s", cbor_error_string( cborRet ) ) ); + + if( ( cborRet & CborErrorOutOfMemory ) != 0 ) + { + LogError( ( "Cannot fit RegisterThing request payload into buffer." ) ); + } + } + + return( cborRet == CborNoError ); +} +/*-----------------------------------------------------------*/ + +bool parseKeyCertResponse( const uint8_t * pResponse, + size_t length, + char * pCertificateBuffer, + size_t * pCertificateBufferLength, + char * pPrivateKeyBuffer, + size_t * pPrivateKeyBufferLength, + char * pCertificateIdBuffer, + size_t * pCertificateIdBufferLength, + char * pOwnershipTokenBuffer, + size_t * pOwnershipTokenBufferLength ) +{ + CborError cborRet; + CborParser parser; + CborValue map; + CborValue value; + + assert( pResponse != NULL ); + assert( pCertificateBuffer != NULL ); + assert( pCertificateBufferLength != NULL ); + assert( pPrivateKeyBuffer != NULL ); + assert( pPrivateKeyBufferLength != NULL ); + assert( pCertificateIdBuffer != NULL ); + assert( pCertificateIdBufferLength != NULL ); + assert( *pCertificateIdBufferLength >= 64 ); + assert( pOwnershipTokenBuffer != NULL ); + assert( pOwnershipTokenBufferLength != NULL ); + + /* For details on the CreateCertificatefromCsr response payload format, see: + * https://docs.aws.amazon.com/iot/latest/developerguide/fleet-provision-api.html#register-thing-response-payload + */ + cborRet = cbor_parser_init( pResponse, length, 0, &parser, &map ); + + if( cborRet != CborNoError ) + { + LogError( ( "Error initializing parser for CreateKeysAndCertificate response: %s.", cbor_error_string( cborRet ) ) ); + } + else if( !cbor_value_is_map( &map ) ) + { + LogError( ( "CreateKeysAndCertificate response is not a valid map container type." ) ); + } + else + { + cborRet = cbor_value_map_find_value( &map, "certificatePem", &value ); + + if( cborRet != CborNoError ) + { + LogError( ( "Error searching CreateKeysAndCertificate response: %s.", cbor_error_string( cborRet ) ) ); + } + else if( value.type == CborInvalidType ) + { + LogError( ( "\"certificatePem\" not found in CreateKeysAndCertificate response." ) ); + } + else if( value.type != CborTextStringType ) + { + LogError( ( "Value for \"certificatePem\" key in CreateKeysAndCertificate response is not a text string type." ) ); + } + else + { + cborRet = cbor_value_copy_text_string( &value, pCertificateBuffer, pCertificateBufferLength, NULL ); + + if( cborRet == CborErrorOutOfMemory ) + { + size_t requiredLen = 0; + ( void ) cbor_value_calculate_string_length( &value, &requiredLen ); + LogError( ( "Certificate buffer insufficiently large. Certificate length: %lu", ( unsigned long ) requiredLen ) ); + } + else if( cborRet != CborNoError ) + { + LogError( ( "Failed to parse \"certificatePem\" value from CreateKeysAndCertificate response: %s.", cbor_error_string( cborRet ) ) ); + } + } + } + + if( cborRet == CborNoError ) + { + cborRet = cbor_value_map_find_value( &map, "certificateId", &value ); + + if( cborRet != CborNoError ) + { + LogError( ( "Error searching CreateKeysAndCertificate response: %s.", cbor_error_string( cborRet ) ) ); + } + else if( value.type == CborInvalidType ) + { + LogError( ( "\"certificateId\" not found in CreateKeysAndCertificate response." ) ); + } + else if( value.type != CborTextStringType ) + { + LogError( ( "\"certificateId\" is an unexpected type in CreateKeysAndCertificate response." ) ); + } + else + { + cborRet = cbor_value_copy_text_string( &value, pCertificateIdBuffer, pCertificateIdBufferLength, NULL ); + + if( cborRet == CborErrorOutOfMemory ) + { + size_t requiredLen = 0; + ( void ) cbor_value_calculate_string_length( &value, &requiredLen ); + LogError( ( "Certificate ID buffer insufficiently large. Certificate ID length: %lu", ( unsigned long ) requiredLen ) ); + } + else if( cborRet != CborNoError ) + { + LogError( ( "Failed to parse \"certificateId\" value from CreateKeysAndCertificate response: %s.", cbor_error_string( cborRet ) ) ); + } + } + } + + if( cborRet == CborNoError ) + { + cborRet = cbor_value_map_find_value( &map, "privateKey", &value ); + + if( cborRet != CborNoError ) + { + LogError( ( "Error searching CreateKeysAndCertificate response: %s.", cbor_error_string( cborRet ) ) ); + } + else if( value.type == CborInvalidType ) + { + LogError( ( "\"privateKey\" not found in CreateKeysAndCertificate response." ) ); + } + else if( value.type != CborTextStringType ) + { + LogError( ( "\"privateKey\" is an unexpected type in CreateKeysAndCertificate response." ) ); + } + else + { + cborRet = cbor_value_copy_text_string( &value, pPrivateKeyBuffer, pPrivateKeyBufferLength, NULL ); + + if( cborRet == CborErrorOutOfMemory ) + { + size_t requiredLen = 0; + ( void ) cbor_value_calculate_string_length( &value, &requiredLen ); + LogError( ( "Certificate ID buffer insufficiently large. Certificate ID length: %lu", ( unsigned long ) requiredLen ) ); + } + else if( cborRet != CborNoError ) + { + LogError( ( "Failed to parse \"certificateId\" value from CreateKeysAndCertificate response: %s.", cbor_error_string( cborRet ) ) ); + } + } + } + + if( cborRet == CborNoError ) + { + cborRet = cbor_value_map_find_value( &map, "certificateOwnershipToken", &value ); + + if( cborRet != CborNoError ) + { + LogError( ( "Error searching CreateKeysAndCertificate response: %s.", cbor_error_string( cborRet ) ) ); + } + else if( value.type == CborInvalidType ) + { + LogError( ( "\"certificateOwnershipToken\" not found in CreateKeysAndCertificate response." ) ); + } + else if( value.type != CborTextStringType ) + { + LogError( ( "\"certificateOwnershipToken\" is an unexpected type in CreateKeysAndCertificate response." ) ); + } + else + { + cborRet = cbor_value_copy_text_string( &value, pOwnershipTokenBuffer, pOwnershipTokenBufferLength, NULL ); + + if( cborRet == CborErrorOutOfMemory ) + { + size_t requiredLen = 0; + ( void ) cbor_value_calculate_string_length( &value, &requiredLen ); + LogError( ( "Certificate ownership token buffer insufficiently large. Certificate ownership token buffer length: %lu", ( unsigned long ) requiredLen ) ); + } + else if( cborRet != CborNoError ) + { + LogError( ( "Failed to parse \"certificateOwnershipToken\" value from CreateKeysAndCertificate response: %s.", cbor_error_string( cborRet ) ) ); + } + } + } + + return( cborRet == CborNoError ); +} +/*-----------------------------------------------------------*/ + +bool parseRegisterThingResponse( const uint8_t * pResponse, + size_t length, + char * pThingNameBuffer, + size_t * pThingNameBufferLength ) +{ + CborError cborRet; + CborParser parser; + CborValue map; + CborValue value; + + assert( pResponse != NULL ); + assert( pThingNameBuffer != NULL ); + assert( pThingNameBufferLength != NULL ); + + /* For details on the RegisterThing response payload format, see: + * https://docs.aws.amazon.com/iot/latest/developerguide/fleet-provision-api.html#register-thing-response-payload + */ + cborRet = cbor_parser_init( pResponse, length, 0, &parser, &map ); + + if( cborRet != CborNoError ) + { + LogError( ( "Error initializing parser for RegisterThing response: %s.", cbor_error_string( cborRet ) ) ); + } + else if( !cbor_value_is_map( &map ) ) + { + LogError( ( "RegisterThing response not a map type." ) ); + } + else + { + cborRet = cbor_value_map_find_value( &map, "thingName", &value ); + + if( cborRet != CborNoError ) + { + LogError( ( "Error searching RegisterThing response: %s.", cbor_error_string( cborRet ) ) ); + } + else if( value.type == CborInvalidType ) + { + LogError( ( "\"thingName\" not found in RegisterThing response." ) ); + } + else if( value.type != CborTextStringType ) + { + LogError( ( "\"thingName\" is an unexpected type in RegisterThing response." ) ); + } + else + { + cborRet = cbor_value_copy_text_string( &value, pThingNameBuffer, pThingNameBufferLength, NULL ); + + if( cborRet == CborErrorOutOfMemory ) + { + size_t requiredLen = 0; + ( void ) cbor_value_calculate_string_length( &value, &requiredLen ); + LogError( ( "Thing name buffer insufficiently large. Thing name length: %lu", ( unsigned long ) requiredLen ) ); + } + else if( cborRet != CborNoError ) + { + LogError( ( "Failed to parse \"thingName\" value from RegisterThing response: %s.", cbor_error_string( cborRet ) ) ); + } + } + } + + return( cborRet == CborNoError ); +} +/*-----------------------------------------------------------*/ + +static CborError cborPrinter( void * token, + const char * fmt, + ... ) +{ + int result; + va_list args; + CborPrintContext_t * ctx = ( CborPrintContext_t * ) token; + + va_start( args, fmt ); + + /* Compute length to write. */ + result = vsnprintf( NULL, 0, fmt, args ); + + va_end( args ); + + if( result < 0 ) + { + LogError( ( "Error formatting CBOR string." ) ); + } + else + { + size_t newLen = ( unsigned ) result; + size_t oldLen = ctx->length; + char * newPtr; + + ctx->length = oldLen + newLen; + newPtr = ( char * ) realloc( ( void * ) ctx->str, ctx->length + 1 ); + + if( newPtr == NULL ) + { + LogError( ( "Failed to reallocate CBOR string." ) ); + result = -1; + } + else + { + va_start( args, fmt ); + + result = vsnprintf( newPtr + oldLen, newLen + 1, fmt, args ); + + va_end( args ); + + ctx->str = newPtr; + + if( result < 0 ) + { + LogError( ( "Error printing CBOR string." ) ); + } + } + } + + return ( result < 0 ) ? CborErrorIO : CborNoError; +} +/*-----------------------------------------------------------*/ + +const char * getStringFromCbor( const uint8_t * cbor, + size_t length ) +{ + CborPrintContext_t printCtx = { 0 }; + CborParser parser; + CborValue value; + CborError error; + + error = cbor_parser_init( cbor, length, 0, &parser, &value ); + + if( error == CborNoError ) + { + error = cbor_value_to_pretty_stream( cborPrinter, &printCtx, &value, CborPrettyDefaultFlags ); + } + + if( error != CborNoError ) + { + LogError( ( "Error printing CBOR payload." ) ); + printCtx.str = ""; + } + + return printCtx.str; +} +/*-----------------------------------------------------------*/ diff --git a/demos/fleet_provisioning/fleet_provisioning_keys_cert/fleet_provisioning_serializer.h b/demos/fleet_provisioning/fleet_provisioning_keys_cert/fleet_provisioning_serializer.h new file mode 100644 index 0000000000..c47dc9cfe0 --- /dev/null +++ b/demos/fleet_provisioning/fleet_provisioning_keys_cert/fleet_provisioning_serializer.h @@ -0,0 +1,108 @@ +/* + * AWS IoT Device SDK for Embedded C 202211.00 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy + * of this software and associated documentation files (the "Software"), to deal + * in the Software without restriction, including without limitation the rights + * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell + * copies of the Software, and to permit persons to whom the Software is + * furnished to do so, subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in + * all copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, + * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE + * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER + * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, + * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE + * SOFTWARE. + */ + +/** + * This file declares functions for serializing and parsing CBOR encoded Fleet + * Provisioning API payloads. + */ + +/* Standard includes. */ +#include +#include +#include + +/** + * @brief Creates the request payload to be published to the RegisterThing API + * in order to activate the provisioned certificate and receive a Thing name. + * + * @param[in] pBuffer Buffer into which to write the publish request payload. + * @param[in] bufferLength Length of #buffer. + * @param[in] pCertificateOwnershipToken The certificate's certificate + * ownership token. + * @param[in] certificateOwnershipTokenLength Length of + * #certificateOwnershipToken. + * @param[out] pOutLengthWritten The length of the publish request payload. + */ +bool generateRegisterThingRequest( uint8_t * pBuffer, + size_t bufferLength, + const char * pCertificateOwnershipToken, + size_t certificateOwnershipTokenLength, + const char * pSerial, + size_t serialLength, + size_t * pOutLengthWritten ); + +/** + * @brief Extracts the certificate, certificate ID, and certificate ownership + * token from a CreateCertificateFromCsr accepted response. These are copied + * to the provided buffers so that they can outlive the data in the response + * buffer and as CBOR strings may be chunked. + * + * @param[in] pResponse The response payload. + * @param[in] length Length of #pResponse. + * @param[in] pCertificateBuffer The buffer to which to write the certificate. + * @param[in,out] pCertificateBufferLength The length of #pCertificateBuffer. + * The length written is output here. + * @param[in] pCertificateIdBuffer The buffer to which to write the certificate + * ID. + * @param[in,out] pCertificateIdBufferLength The length of + * #pCertificateIdBuffer. The length written is output here. + * @param[in] pOwnershipTokenBuffer The buffer to which to write the + * certificate ownership token. + * @param[in,out] pOwnershipTokenBufferLength The length of + * #pOwnershipTokenBuffer. The length written is output here. + */ +bool parseKeyCertResponse( const uint8_t * pResponse, + size_t length, + char * pCertificateBuffer, + size_t * pCertificateBufferLength, + char * pPrivateKeyBuffer, + size_t * pPrivateKeyBufferLength, + char * pCertificateIdBuffer, + size_t * pCertificateIdBufferLength, + char * pOwnershipTokenBuffer, + size_t * pOwnershipTokenBufferLength ); + +/** + * @brief Extracts the Thing name from a RegisterThing accepted response. + * + * @param[in] pResponse The response document. + * @param[in] length Length of #pResponse. + * @param[in] pThingNameBuffer The buffer to which to write the Thing name. + * @param[in,out] pThingNameBufferLength The length of #pThingNameBuffer. The + * written length is output here. + */ +bool parseRegisterThingResponse( const uint8_t * pResponse, + size_t length, + char * pThingNameBuffer, + size_t * pThingNameBufferLength ); + +/** + * @brief Converts a CBOR document into a pretty printed string. + * + * @param[in] cbor The CBOR document. + * @param[in] length The length of the CBOR document. + * + * @returns The pretty printed string on success. "" on error. + */ +const char * getStringFromCbor( const uint8_t * cbor, + size_t length ); diff --git a/demos/fleet_provisioning/fleet_provisioning_keys_cert/mqtt_operations.c b/demos/fleet_provisioning/fleet_provisioning_keys_cert/mqtt_operations.c new file mode 100644 index 0000000000..f62e79e0c8 --- /dev/null +++ b/demos/fleet_provisioning/fleet_provisioning_keys_cert/mqtt_operations.c @@ -0,0 +1,1138 @@ +/* + * AWS IoT Device SDK for Embedded C 202211.00 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file mqtt_operations.c + * + * @brief This file provides wrapper functions for MQTT operations on a mutually + * authenticated TLS connection. + * + * A mutually authenticated TLS connection is used to connect to the AWS IoT + * MQTT message broker in this example. Define ROOT_CA_CERT_PATH, + * CLIENT_CERT_PATH, and CLIENT_PRIVATE_KEY_PATH in demo_config.h to achieve + * mutual authentication. + */ + +/* Standard includes. */ +#include +#include +#include +#include + +/* POSIX includes. */ +#include + +/* Config include. */ +#include "demo_config.h" + +/* Interface include. */ +#include "mqtt_operations.h" + +/* MbedTLS transport include. */ +#include "mbedtls_pkcs11_posix.h" + +/*Include backoff algorithm header for retry logic.*/ +#include "backoff_algorithm.h" + +/* Clock for timer. */ +#include "clock.h" + +/* AWS IoT Core TLS ALPN definitions for MQTT authentication */ +#include "aws_iot_alpn_defs.h" + +/** + * These configurations are required. Throw compilation error if the below + * configs are not defined. + */ +#ifndef AWS_IOT_ENDPOINT + #error "Please define AWS IoT MQTT broker endpoint(AWS_IOT_ENDPOINT) in demo_config.h." +#endif +#ifndef ROOT_CA_CERT_PATH + #error "Please define path to Root CA certificate of the MQTT broker(ROOT_CA_CERT_PATH) in demo_config.h." +#endif +#ifndef CLIENT_IDENTIFIER + #error "Please define a unique CLIENT_IDENTIFIER." +#endif + +/** + * Provide default values for undefined configuration settings. + */ +#ifndef AWS_MQTT_PORT + #define AWS_MQTT_PORT ( 8883 ) +#endif + +#ifndef NETWORK_BUFFER_SIZE + #define NETWORK_BUFFER_SIZE ( 1024U ) +#endif + +/** + * @brief Length of the AWS IoT endpoint. + */ +#define AWS_IOT_ENDPOINT_LENGTH ( ( uint16_t ) ( sizeof( AWS_IOT_ENDPOINT ) - 1 ) ) + +/** + * @brief Length of the client identifier. + */ +#define CLIENT_IDENTIFIER_LENGTH ( ( uint16_t ) ( sizeof( CLIENT_IDENTIFIER ) - 1 ) ) + +/** + * @brief The maximum number of retries for connecting to server. + */ +#define CONNECTION_RETRY_MAX_ATTEMPTS ( 5U ) + +/** + * @brief The maximum back-off delay (in milliseconds) for retrying connection to server. + */ +#define CONNECTION_RETRY_MAX_BACKOFF_DELAY_MS ( 5000U ) + +/** + * @brief The base back-off delay (in milliseconds) to use for connection retry attempts. + */ +#define CONNECTION_RETRY_BACKOFF_BASE_MS ( 500U ) + +/** + * @brief Timeout for receiving CONNACK packet in milliseconds. + */ +#define CONNACK_RECV_TIMEOUT_MS ( 1000U ) + +/** + * @brief Maximum number of outgoing publishes maintained in the application + * until an ack is received from the broker. + */ +#define MAX_OUTGOING_PUBLISHES ( 5U ) + +/** + * @brief Invalid packet identifier for the MQTT packets. Zero is always an + * invalid packet identifier as per MQTT 3.1.1 spec. + */ +#define MQTT_PACKET_ID_INVALID ( ( uint16_t ) 0U ) + +/** + * @brief Timeout for MQTT_ProcessLoop function in milliseconds. + */ +#define MQTT_PROCESS_LOOP_TIMEOUT_MS ( 1000U ) + +/** + * @brief The maximum time interval in seconds which is allowed to elapse + * between two Control Packets. + * + * It is the responsibility of the client to ensure that the interval between + * control packets being sent does not exceed the this keep-alive value. In the + * absence of sending any other control packets, the client MUST send a + * PINGREQ packet. + */ +#define MQTT_KEEP_ALIVE_INTERVAL_SECONDS ( 60U ) + +/** + * @brief Timeout in milliseconds for transport send and receive. + */ +#define TRANSPORT_SEND_RECV_TIMEOUT_MS ( 1000U ) + +/** + * @brief The MQTT metrics string expected by AWS IoT MQTT Broker. + */ +#define METRICS_STRING "?SDK=" OS_NAME "&Version=" OS_VERSION "&Platform=" HARDWARE_PLATFORM_NAME "&MQTTLib=" MQTT_LIB + +/** + * @brief The length of the MQTT metrics string. + */ +#define METRICS_STRING_LENGTH ( ( uint16_t ) ( sizeof( METRICS_STRING ) - 1 ) ) + +/** + * @brief The length of the outgoing publish records array used by the coreMQTT + * library to track QoS > 0 packet ACKS for outgoing publishes. + */ +#define OUTGOING_PUBLISH_RECORD_LEN ( 10U ) + +/** + * @brief The length of the incoming publish records array used by the coreMQTT + * library to track QoS > 0 packet ACKS for incoming publishes. + */ +#define INCOMING_PUBLISH_RECORD_LEN ( 10U ) +/*-----------------------------------------------------------*/ + +/** + * @brief Structure to keep the MQTT publish packets until an ack is received + * for QoS1 publishes. + */ +typedef struct PublishPackets +{ + /** + * @brief Packet identifier of the publish packet. + */ + uint16_t packetId; + + /** + * @brief Publish info of the publish packet. + */ + MQTTPublishInfo_t pubInfo; +} PublishPackets_t; + +/* Each compilation unit must define the NetworkContext struct. */ +struct NetworkContext +{ + MbedtlsPkcs11Context_t * pParams; +}; +/*-----------------------------------------------------------*/ + +/** + * @brief Packet Identifier updated when an ACK packet is received. + * + * It is used to match an expected ACK for a transmitted packet. + */ +static uint16_t globalAckPacketIdentifier = 0U; + +/** + * @brief Packet Identifier generated when Subscribe request was sent to the broker. + * + * It is used to match received Subscribe ACK to the transmitted subscribe + * request. + */ +static uint16_t globalSubscribePacketIdentifier = 0U; + +/** + * @brief Packet Identifier generated when Unsubscribe request was sent to the broker. + * + * It is used to match received Unsubscribe ACK to the transmitted unsubscribe + * request. + */ +static uint16_t globalUnsubscribePacketIdentifier = 0U; + +/** + * @brief Array to keep the outgoing publish messages. + * + * These stored outgoing publish messages are kept until a successful ack + * is received. + */ +static PublishPackets_t outgoingPublishPackets[ MAX_OUTGOING_PUBLISHES ] = { 0 }; + +/** + * @brief The network buffer must remain valid for the lifetime of the MQTT context. + */ +static uint8_t buffer[ NETWORK_BUFFER_SIZE ]; + +/** + * @brief The MQTT context used for MQTT operation. + */ +static MQTTContext_t mqttContext = { 0 }; + +/** + * @brief The network context used for MbedTLS operation. + */ +static NetworkContext_t networkContext = { 0 }; + +/** + * @brief The parameters for MbedTLS operation. + */ +static MbedtlsPkcs11Context_t tlsContext = { 0 }; + +/** + * @brief The flag to indicate that the mqtt session is established. + */ +static bool mqttSessionEstablished = false; + +/** + * @brief Callback registered when calling EstablishMqttSession to get incoming + * publish messages. + */ +static MQTTPublishCallback_t appPublishCallback = NULL; + +/** + * @brief Array to track the outgoing publish records for outgoing publishes + * with QoS > 0. + * + * This is passed into #MQTT_InitStatefulQoS to allow for QoS > 0. + * + */ +static MQTTPubAckInfo_t pOutgoingPublishRecords[ OUTGOING_PUBLISH_RECORD_LEN ]; + +/** + * @brief Array to track the incoming publish records for incoming publishes + * with QoS > 0. + * + * This is passed into #MQTT_InitStatefulQoS to allow for QoS > 0. + * + */ +static MQTTPubAckInfo_t pIncomingPublishRecords[ INCOMING_PUBLISH_RECORD_LEN ]; +/*-----------------------------------------------------------*/ + +/** + * @brief The random number generator to use for exponential backoff with + * jitter retry logic. + * + * @return The generated random number. + */ +static uint32_t generateRandomNumber( void ); + +/** + * @brief Connect to the MQTT broker with reconnection retries. + * + * If connection fails, retry is attempted after a timeout. Timeout value + * exponentially increases until maximum timeout value is reached or the number + * of attempts are exhausted. + * + * @param[out] pNetworkContext The created network context. + * @param[in] p11Session The PKCS #11 session to use. + * @param[in] pClientCertLabel The client certificate PKCS #11 label to use. + * @param[in] pPrivateKeyLabel The private key PKCS #11 label for the client certificate. + * + * @return false on failure; true on successful connection. + */ +static bool connectToBrokerWithBackoffRetries( NetworkContext_t * pNetworkContext, + CK_SESSION_HANDLE p11Session, + char * pClientCertLabel, + char * pPrivateKeyLabel ); + +/** + * @brief Get the free index in the #outgoingPublishPackets array at which an + * outgoing publish can be stored. + * + * @param[out] pIndex The index at which an outgoing publish can be stored. + * + * @return false if no more publishes can be stored; + * true if an index to store the next outgoing publish is obtained. + */ +static bool getNextFreeIndexForOutgoingPublishes( uint8_t * pIndex ); + +/** + * @brief Clean up the outgoing publish at given index from the + * #outgoingPublishPackets array. + * + * @param[in] index The index at which a publish message has to be cleaned up. + */ +static void cleanupOutgoingPublishAt( uint8_t index ); + +/** + * @brief Clean up all the outgoing publishes in the #outgoingPublishPackets array. + */ +static void cleanupOutgoingPublishes( void ); + +/** + * @brief Clean up the publish packet with the given packet id in the + * #outgoingPublishPackets array. + * + * @param[in] packetId Packet id of the packet to be clean. + */ +static void cleanupOutgoingPublishWithPacketID( uint16_t packetId ); + +/** + * @brief Callback registered with the MQTT library. + * + * @param[in] pMqttContext MQTT context pointer. + * @param[in] pPacketInfo Packet Info pointer for the incoming packet. + * @param[in] pDeserializedInfo Deserialized information from the incoming packet. + */ +static void mqttCallback( MQTTContext_t * pMqttContext, + MQTTPacketInfo_t * pPacketInfo, + MQTTDeserializedInfo_t * pDeserializedInfo ); + +/** + * @brief Resend the publishes if a session is re-established with the broker. + * + * This function handles the resending of the QoS1 publish packets, which are + * maintained locally. + * + * @param[in] pMqttContext The MQTT context pointer. + * + * @return true if all the unacknowledged QoS1 publishes are re-sent successfully; + * false otherwise. + */ +static bool handlePublishResend( MQTTContext_t * pMqttContext ); + +/** + * @brief Wait for an expected ACK packet to be received. + * + * This function handles waiting for an expected ACK packet by calling + * #MQTT_ProcessLoop and waiting for #mqttCallback to set the global ACK + * packet identifier to the expected ACK packet identifier. + * + * @param[in] pMqttContext MQTT context pointer. + * @param[in] usPacketIdentifier Packet identifier for expected ACK packet. + * @param[in] ulTimeout Maximum duration to wait for expected ACK packet. + * + * @return true if the expected ACK packet was received, false otherwise. + */ +static bool waitForPacketAck( MQTTContext_t * pMqttContext, + uint16_t usPacketIdentifier, + uint32_t ulTimeout ); +/*-----------------------------------------------------------*/ + +static uint32_t generateRandomNumber() +{ + return( ( uint32_t ) rand() ); +} +/*-----------------------------------------------------------*/ + +static bool connectToBrokerWithBackoffRetries( NetworkContext_t * pNetworkContext, + CK_SESSION_HANDLE p11Session, + char * pClientCertLabel, + char * pPrivateKeyLabel ) +{ + bool returnStatus = false; + BackoffAlgorithmStatus_t backoffAlgStatus = BackoffAlgorithmSuccess; + MbedtlsPkcs11Status_t tlsStatus = MBEDTLS_PKCS11_SUCCESS; + BackoffAlgorithmContext_t reconnectParams; + MbedtlsPkcs11Credentials_t tlsCredentials = { 0 }; + uint16_t nextRetryBackOff = 0U; + + /* Set the pParams member of the network context with desired transport. */ + pNetworkContext->pParams = &tlsContext; + + /* Initialize credentials for establishing TLS session. */ + tlsCredentials.pRootCaPath = ROOT_CA_CERT_PATH; + tlsCredentials.pClientCertLabel = pClientCertLabel; + tlsCredentials.pPrivateKeyLabel = pPrivateKeyLabel; + tlsCredentials.p11Session = p11Session; + + /* AWS IoT requires devices to send the Server Name Indication (SNI) + * extension to the Transport Layer Security (TLS) protocol and provide + * the complete endpoint address in the host_name field. Details about + * SNI for AWS IoT can be found in the link below. + * https://docs.aws.amazon.com/iot/latest/developerguide/transport-security.html + */ + tlsCredentials.disableSni = false; + + if( AWS_MQTT_PORT == 443 ) + { + static const char * alpnProtoArray[] = AWS_IOT_ALPN_MQTT_CA_AUTH_MBEDTLS; + + /* Pass the ALPN protocol name depending on the port being used. + * Please see more details about the ALPN protocol for AWS IoT MQTT endpoint + * in the link below. + * https://aws.amazon.com/blogs/iot/mqtt-with-tls-client-authentication-on-port-443-why-it-is-useful-and-how-it-works/ + */ + tlsCredentials.pAlpnProtos = alpnProtoArray; + } + + /* Initialize reconnect attempts and interval */ + BackoffAlgorithm_InitializeParams( &reconnectParams, + CONNECTION_RETRY_BACKOFF_BASE_MS, + CONNECTION_RETRY_MAX_BACKOFF_DELAY_MS, + CONNECTION_RETRY_MAX_ATTEMPTS ); + + do + { + /* Establish a TLS session with the MQTT broker. This example connects + * to the MQTT broker as specified in AWS_IOT_ENDPOINT and AWS_MQTT_PORT + * at the demo config header. */ + LogDebug( ( "Establishing a TLS session to %.*s:%d.", + AWS_IOT_ENDPOINT_LENGTH, + AWS_IOT_ENDPOINT, + AWS_MQTT_PORT ) ); + + tlsStatus = Mbedtls_Pkcs11_Connect( pNetworkContext, + AWS_IOT_ENDPOINT, + AWS_MQTT_PORT, + &tlsCredentials, + TRANSPORT_SEND_RECV_TIMEOUT_MS ); + + if( tlsStatus == MBEDTLS_PKCS11_SUCCESS ) + { + /* Connection successful. */ + returnStatus = true; + } + else + { + /* Generate a random number and get back-off value (in milliseconds) for the next connection retry. */ + backoffAlgStatus = BackoffAlgorithm_GetNextBackoff( &reconnectParams, generateRandomNumber(), &nextRetryBackOff ); + + if( backoffAlgStatus == BackoffAlgorithmRetriesExhausted ) + { + LogError( ( "Connection to the broker failed, all attempts exhausted." ) ); + } + else if( backoffAlgStatus == BackoffAlgorithmSuccess ) + { + LogWarn( ( "Connection to the broker failed. Retrying connection " + "after %hu ms backoff.", + ( unsigned short ) nextRetryBackOff ) ); + Clock_SleepMs( nextRetryBackOff ); + } + } + } while( ( tlsStatus != MBEDTLS_PKCS11_SUCCESS ) && ( backoffAlgStatus == BackoffAlgorithmSuccess ) ); + + return returnStatus; +} +/*-----------------------------------------------------------*/ + +static bool getNextFreeIndexForOutgoingPublishes( uint8_t * pIndex ) +{ + bool returnStatus = false; + uint8_t index = 0; + + assert( outgoingPublishPackets != NULL ); + assert( pIndex != NULL ); + + for( index = 0; index < MAX_OUTGOING_PUBLISHES; index++ ) + { + /* A free index is marked by invalid packet id. Check if the the index + * has a free slot. */ + if( outgoingPublishPackets[ index ].packetId == MQTT_PACKET_ID_INVALID ) + { + returnStatus = true; + break; + } + } + + /* Copy the available index into the output param. */ + if( returnStatus == true ) + { + *pIndex = index; + } + + return returnStatus; +} +/*-----------------------------------------------------------*/ + +static void cleanupOutgoingPublishAt( uint8_t index ) +{ + assert( outgoingPublishPackets != NULL ); + assert( index < MAX_OUTGOING_PUBLISHES ); + + /* Clear the outgoing publish packet. */ + ( void ) memset( &( outgoingPublishPackets[ index ] ), + 0x00, + sizeof( outgoingPublishPackets[ index ] ) ); +} +/*-----------------------------------------------------------*/ + +static void cleanupOutgoingPublishes( void ) +{ + assert( outgoingPublishPackets != NULL ); + + /* Clean up all the outgoing publish packets. */ + ( void ) memset( outgoingPublishPackets, 0x00, sizeof( outgoingPublishPackets ) ); +} +/*-----------------------------------------------------------*/ + +static void cleanupOutgoingPublishWithPacketID( uint16_t packetId ) +{ + uint8_t index = 0; + + assert( outgoingPublishPackets != NULL ); + assert( packetId != MQTT_PACKET_ID_INVALID ); + + /* Clean up the saved outgoing publish with packet Id equal to packetId. */ + for( index = 0; index < MAX_OUTGOING_PUBLISHES; index++ ) + { + if( outgoingPublishPackets[ index ].packetId == packetId ) + { + cleanupOutgoingPublishAt( index ); + + LogDebug( ( "Cleaned up outgoing publish packet with packet id %u.", + packetId ) ); + + break; + } + } +} +/*-----------------------------------------------------------*/ + +static void mqttCallback( MQTTContext_t * pMqttContext, + MQTTPacketInfo_t * pPacketInfo, + MQTTDeserializedInfo_t * pDeserializedInfo ) +{ + uint16_t packetIdentifier; + + assert( pMqttContext != NULL ); + assert( pPacketInfo != NULL ); + assert( pDeserializedInfo != NULL ); + + /* Suppress the unused parameter warning when asserts are disabled in + * build. */ + ( void ) pMqttContext; + + packetIdentifier = pDeserializedInfo->packetIdentifier; + + /* Handle an incoming publish. The lower 4 bits of the publish packet + * type is used for the dup, QoS, and retain flags. Hence masking + * out the lower bits to check if the packet is publish. */ + if( ( pPacketInfo->type & 0xF0U ) == MQTT_PACKET_TYPE_PUBLISH ) + { + assert( pDeserializedInfo->pPublishInfo != NULL ); + + /* Invoke the application callback for incoming publishes. */ + if( appPublishCallback != NULL ) + { + appPublishCallback( pDeserializedInfo->pPublishInfo, packetIdentifier ); + } + } + else + { + /* Handle other packets. */ + switch( pPacketInfo->type ) + { + case MQTT_PACKET_TYPE_SUBACK: + LogDebug( ( "MQTT Packet type SUBACK received." ) ); + + /* Make sure the ACK packet identifier matches with the request + * packet identifier. */ + assert( globalSubscribePacketIdentifier == packetIdentifier ); + + /* Update the global ACK packet identifier. */ + globalAckPacketIdentifier = packetIdentifier; + break; + + case MQTT_PACKET_TYPE_UNSUBACK: + LogDebug( ( "MQTT Packet type UNSUBACK received." ) ); + + /* Make sure the ACK packet identifier matches with the request + * packet identifier. */ + assert( globalUnsubscribePacketIdentifier == packetIdentifier ); + + /* Update the global ACK packet identifier. */ + globalAckPacketIdentifier = packetIdentifier; + break; + + case MQTT_PACKET_TYPE_PINGRESP: + + /* We do not expect to receive PINGRESP as we are using + * MQTT_ProcessLoop. */ + LogWarn( ( "PINGRESP should not be received by the application " + "callback when using MQTT_ProcessLoop." ) ); + break; + + case MQTT_PACKET_TYPE_PUBACK: + LogDebug( ( "PUBACK received for packet id %u.", + packetIdentifier ) ); + + /* Update the global ACK packet identifier. */ + globalAckPacketIdentifier = packetIdentifier; + + /* Cleanup the publish packet from the #outgoingPublishPackets + * array when a PUBACK is received. */ + cleanupOutgoingPublishWithPacketID( packetIdentifier ); + break; + + /* Any other packet type is invalid. */ + default: + LogError( ( "Unknown packet type received:(%02x).", + pPacketInfo->type ) ); + } + } +} +/*-----------------------------------------------------------*/ + +static bool handlePublishResend( MQTTContext_t * pMqttContext ) +{ + bool returnStatus = false; + MQTTStatus_t mqttStatus = MQTTSuccess; + uint8_t index = 0U; + + assert( outgoingPublishPackets != NULL ); + + /* Resend all the QoS1 publishes still in the #outgoingPublishPackets array. + * These are the publishes that haven't received a PUBACK yet. When a PUBACK + * is received, the corresponding publish is removed from the array. */ + for( index = 0U; index < MAX_OUTGOING_PUBLISHES; index++ ) + { + if( outgoingPublishPackets[ index ].packetId != MQTT_PACKET_ID_INVALID ) + { + outgoingPublishPackets[ index ].pubInfo.dup = true; + + LogDebug( ( "Sending duplicate PUBLISH with packet id %u.", + outgoingPublishPackets[ index ].packetId ) ); + mqttStatus = MQTT_Publish( pMqttContext, + &outgoingPublishPackets[ index ].pubInfo, + outgoingPublishPackets[ index ].packetId ); + + if( mqttStatus != MQTTSuccess ) + { + LogError( ( "Sending duplicate PUBLISH for packet id %u " + " failed with status %s.", + outgoingPublishPackets[ index ].packetId, + MQTT_Status_strerror( mqttStatus ) ) ); + break; + } + else + { + LogDebug( ( "Sent duplicate PUBLISH successfully for packet id %u.", + outgoingPublishPackets[ index ].packetId ) ); + } + } + } + + /* Were all the unacknowledged QoS1 publishes successfully re-sent? */ + if( index == MAX_OUTGOING_PUBLISHES ) + { + returnStatus = true; + } + + return returnStatus; +} +/*-----------------------------------------------------------*/ + +static bool waitForPacketAck( MQTTContext_t * pMqttContext, + uint16_t usPacketIdentifier, + uint32_t ulTimeout ) +{ + uint32_t ulMqttProcessLoopEntryTime; + uint32_t ulMqttProcessLoopTimeoutTime; + uint32_t ulCurrentTime; + + MQTTStatus_t eMqttStatus = MQTTSuccess; + bool xStatus = false; + + /* Reset the ACK packet identifier being received. */ + globalAckPacketIdentifier = 0U; + + ulCurrentTime = pMqttContext->getTime(); + ulMqttProcessLoopEntryTime = ulCurrentTime; + ulMqttProcessLoopTimeoutTime = ulCurrentTime + ulTimeout; + + /* Call MQTT_ProcessLoop multiple times until the expected packet ACK + * is received, a timeout happens, or MQTT_ProcessLoop fails. */ + while( ( globalAckPacketIdentifier != usPacketIdentifier ) && + ( ulCurrentTime < ulMqttProcessLoopTimeoutTime ) && + ( eMqttStatus == MQTTSuccess || eMqttStatus == MQTTNeedMoreBytes ) ) + { + /* Event callback will set #globalAckPacketIdentifier when receiving + * appropriate packet. */ + eMqttStatus = MQTT_ProcessLoop( pMqttContext ); + ulCurrentTime = pMqttContext->getTime(); + } + + if( ( ( eMqttStatus != MQTTSuccess ) && ( eMqttStatus != MQTTNeedMoreBytes ) ) || + ( globalAckPacketIdentifier != usPacketIdentifier ) ) + { + LogError( ( "MQTT_ProcessLoop failed to receive ACK packet: Expected ACK Packet ID=%02X, LoopDuration=%u, Status=%s", + usPacketIdentifier, + ( ulCurrentTime - ulMqttProcessLoopEntryTime ), + MQTT_Status_strerror( eMqttStatus ) ) ); + } + else + { + xStatus = true; + } + + return xStatus; +} +/*-----------------------------------------------------------*/ + +bool EstablishMqttSession( MQTTPublishCallback_t publishCallback, + CK_SESSION_HANDLE p11Session, + char * pClientCertLabel, + char * pPrivateKeyLabel ) +{ + bool returnStatus = false; + MQTTStatus_t mqttStatus; + MQTTConnectInfo_t connectInfo; + MQTTFixedBuffer_t networkBuffer; + TransportInterface_t transport = { NULL }; + bool createCleanSession = false; + MQTTContext_t * pMqttContext = &mqttContext; + NetworkContext_t * pNetworkContext = &networkContext; + bool sessionPresent = false; + + assert( pMqttContext != NULL ); + assert( pNetworkContext != NULL ); + + /* Initialize the mqtt context and network context. */ + ( void ) memset( pMqttContext, 0U, sizeof( MQTTContext_t ) ); + ( void ) memset( pNetworkContext, 0U, sizeof( NetworkContext_t ) ); + + returnStatus = connectToBrokerWithBackoffRetries( pNetworkContext, + p11Session, + pClientCertLabel, + pPrivateKeyLabel ); + + if( returnStatus != true ) + { + /* Log an error to indicate connection failure after all + * reconnect attempts are over. */ + LogError( ( "Failed to connect to MQTT broker %.*s.", + AWS_IOT_ENDPOINT_LENGTH, + AWS_IOT_ENDPOINT ) ); + } + else + { + /* Fill in TransportInterface send and receive function pointers. + * For this demo, TCP sockets are used to send and receive data + * from the network. pNetworkContext is an SSL context for OpenSSL.*/ + transport.pNetworkContext = pNetworkContext; + transport.send = Mbedtls_Pkcs11_Send; + transport.recv = Mbedtls_Pkcs11_Recv; + transport.writev = NULL; + + /* Fill the values for network buffer. */ + networkBuffer.pBuffer = buffer; + networkBuffer.size = NETWORK_BUFFER_SIZE; + + /* Remember the publish callback supplied. */ + appPublishCallback = publishCallback; + + /* Initialize the MQTT library. */ + mqttStatus = MQTT_Init( pMqttContext, + &transport, + Clock_GetTimeMs, + mqttCallback, + &networkBuffer ); + + if( mqttStatus != MQTTSuccess ) + { + returnStatus = false; + LogError( ( "MQTT_Init failed with status %s.", + MQTT_Status_strerror( mqttStatus ) ) ); + } + else + { + mqttStatus = MQTT_InitStatefulQoS( pMqttContext, + pOutgoingPublishRecords, + OUTGOING_PUBLISH_RECORD_LEN, + pIncomingPublishRecords, + INCOMING_PUBLISH_RECORD_LEN ); + + if( mqttStatus != MQTTSuccess ) + { + returnStatus = false; + LogError( ( "MQTT_InitStatefulQoS failed with status %s.", + MQTT_Status_strerror( mqttStatus ) ) ); + } + else + { + /* Establish an MQTT session by sending a CONNECT packet. */ + + /* If #createCleanSession is true, start with a clean session + * i.e. direct the MQTT broker to discard any previous session data. + * If #createCleanSession is false, direct the broker to attempt to + * reestablish a session which was already present. */ + connectInfo.cleanSession = createCleanSession; + + /* The client identifier is used to uniquely identify this MQTT client to + * the MQTT broker. In a production device the identifier can be something + * unique, such as a device serial number. */ + connectInfo.pClientIdentifier = CLIENT_IDENTIFIER; + connectInfo.clientIdentifierLength = CLIENT_IDENTIFIER_LENGTH; + + /* The maximum time interval in seconds which is allowed to elapse + * between two Control Packets. + * It is the responsibility of the client to ensure that the interval between + * control packets being sent does not exceed the this keep-alive value. In the + * absence of sending any other control packets, the client MUST send a + * PINGREQ packet. */ + connectInfo.keepAliveSeconds = MQTT_KEEP_ALIVE_INTERVAL_SECONDS; + + /* Username and password for authentication. Not used in this demo. */ + connectInfo.pUserName = METRICS_STRING; + connectInfo.userNameLength = METRICS_STRING_LENGTH; + connectInfo.pPassword = NULL; + connectInfo.passwordLength = 0U; + + /* Send an MQTT CONNECT packet to the broker. */ + mqttStatus = MQTT_Connect( pMqttContext, + &connectInfo, + NULL, + CONNACK_RECV_TIMEOUT_MS, + &sessionPresent ); + + if( mqttStatus != MQTTSuccess ) + { + returnStatus = false; + LogError( ( "Connection with MQTT broker failed with status %s.", + MQTT_Status_strerror( mqttStatus ) ) ); + } + else + { + LogDebug( ( "MQTT connection successfully established with broker." ) ); + } + } + } + + if( returnStatus == true ) + { + /* Keep a flag for indicating if MQTT session is established. This + * flag will mark that an MQTT DISCONNECT has to be sent at the end + * of the demo even if there are intermediate failures. */ + mqttSessionEstablished = true; + } + + if( returnStatus == true ) + { + /* Check if a session is present and if there are any outgoing + * publishes that need to be resent. Resending unacknowledged + * publishes is needed only if the broker is re-establishing a + * session that was already present. */ + if( sessionPresent == true ) + { + LogDebug( ( "An MQTT session with broker is re-established. " + "Resending unacked publishes." ) ); + + /* Handle all the resend of publish messages. */ + returnStatus = handlePublishResend( &mqttContext ); + } + else + { + LogDebug( ( "A clean MQTT connection is established." + " Cleaning up all the stored outgoing publishes." ) ); + + /* Clean up the outgoing publishes waiting for ack as this new + * connection doesn't re-establish an existing session. */ + cleanupOutgoingPublishes(); + } + } + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +bool DisconnectMqttSession( void ) +{ + MQTTStatus_t mqttStatus = MQTTSuccess; + bool returnStatus = false; + MQTTContext_t * pMqttContext = &mqttContext; + NetworkContext_t * pNetworkContext = &networkContext; + + assert( pMqttContext != NULL ); + assert( pNetworkContext != NULL ); + + if( mqttSessionEstablished == true ) + { + /* Send DISCONNECT. */ + mqttStatus = MQTT_Disconnect( pMqttContext ); + + if( mqttStatus != MQTTSuccess ) + { + LogError( ( "Sending MQTT DISCONNECT failed with status=%u.", + mqttStatus ) ); + } + else + { + /* MQTT DISCONNECT sent successfully. */ + returnStatus = true; + } + } + + /* End TLS session, then close TCP connection. */ + ( void ) Mbedtls_Pkcs11_Disconnect( pNetworkContext ); + + return returnStatus; +} +/*-----------------------------------------------------------*/ + +bool SubscribeToTopic( const char * pTopicFilter, + uint16_t topicFilterLength ) +{ + bool returnStatus = false; + MQTTStatus_t mqttStatus; + MQTTContext_t * pMqttContext = &mqttContext; + MQTTSubscribeInfo_t pSubscriptionList[ 1 ]; + + assert( pMqttContext != NULL ); + assert( pTopicFilter != NULL ); + assert( topicFilterLength > 0 ); + + /* Start with everything at 0. */ + ( void ) memset( ( void * ) pSubscriptionList, 0x00, sizeof( pSubscriptionList ) ); + + /* This example subscribes to only one topic and uses QOS1. */ + pSubscriptionList[ 0 ].qos = MQTTQoS1; + pSubscriptionList[ 0 ].pTopicFilter = pTopicFilter; + pSubscriptionList[ 0 ].topicFilterLength = topicFilterLength; + + /* Generate packet identifier for the SUBSCRIBE packet. */ + globalSubscribePacketIdentifier = MQTT_GetPacketId( pMqttContext ); + + /* Send SUBSCRIBE packet. */ + mqttStatus = MQTT_Subscribe( pMqttContext, + pSubscriptionList, + sizeof( pSubscriptionList ) / sizeof( MQTTSubscribeInfo_t ), + globalSubscribePacketIdentifier ); + + if( mqttStatus != MQTTSuccess ) + { + LogError( ( "Failed to send SUBSCRIBE packet to broker with error = %s.", + MQTT_Status_strerror( mqttStatus ) ) ); + } + else + { + LogDebug( ( "SUBSCRIBE topic %.*s to broker.", + topicFilterLength, + pTopicFilter ) ); + + /* Process incoming packet from the broker. Acknowledgment for subscription + * ( SUBACK ) will be received here. However after sending the subscribe, the + * client may receive a publish before it receives a subscribe ack. Since this + * demo is subscribing to the topic to which no one is publishing, probability + * of receiving publish message before subscribe ack is zero; but application + * must be ready to receive any packet. This demo uses MQTT_ProcessLoop to + * receive packet from network. */ + returnStatus = waitForPacketAck( pMqttContext, + globalSubscribePacketIdentifier, + MQTT_PROCESS_LOOP_TIMEOUT_MS ); + } + + return returnStatus; +} +/*-----------------------------------------------------------*/ + +bool UnsubscribeFromTopic( const char * pTopicFilter, + uint16_t topicFilterLength ) +{ + bool returnStatus = false; + MQTTStatus_t mqttStatus; + MQTTContext_t * pMqttContext = &mqttContext; + MQTTSubscribeInfo_t pSubscriptionList[ 1 ]; + + assert( pMqttContext != NULL ); + assert( pTopicFilter != NULL ); + assert( topicFilterLength > 0 ); + + /* Start with everything at 0. */ + ( void ) memset( ( void * ) pSubscriptionList, 0x00, sizeof( pSubscriptionList ) ); + + /* This example subscribes to only one topic and uses QOS1. */ + pSubscriptionList[ 0 ].qos = MQTTQoS1; + pSubscriptionList[ 0 ].pTopicFilter = pTopicFilter; + pSubscriptionList[ 0 ].topicFilterLength = topicFilterLength; + + /* Generate packet identifier for the UNSUBSCRIBE packet. */ + globalUnsubscribePacketIdentifier = MQTT_GetPacketId( pMqttContext ); + + /* Send UNSUBSCRIBE packet. */ + mqttStatus = MQTT_Unsubscribe( pMqttContext, + pSubscriptionList, + sizeof( pSubscriptionList ) / sizeof( MQTTSubscribeInfo_t ), + globalUnsubscribePacketIdentifier ); + + if( mqttStatus != MQTTSuccess ) + { + LogError( ( "Failed to send UNSUBSCRIBE packet to broker with error = %s.", + MQTT_Status_strerror( mqttStatus ) ) ); + } + else + { + LogDebug( ( "UNSUBSCRIBE sent topic %.*s to broker.", + topicFilterLength, + pTopicFilter ) ); + + /* Process incoming packet from the broker. Acknowledgment for unsubscribe + * operation ( UNSUBACK ) will be received here. This demo uses + * MQTT_ProcessLoop to receive packet from network. */ + returnStatus = waitForPacketAck( pMqttContext, + globalUnsubscribePacketIdentifier, + MQTT_PROCESS_LOOP_TIMEOUT_MS ); + } + + return returnStatus; +} +/*-----------------------------------------------------------*/ + +bool PublishToTopic( const char * pTopicFilter, + uint16_t topicFilterLength, + const char * pPayload, + size_t payloadLength ) +{ + bool returnStatus = false; + MQTTStatus_t mqttStatus = MQTTSuccess; + uint8_t publishIndex = MAX_OUTGOING_PUBLISHES; + MQTTContext_t * pMqttContext = &mqttContext; + + assert( pMqttContext != NULL ); + assert( pTopicFilter != NULL ); + assert( topicFilterLength > 0 ); + + /* Get the next free index for the outgoing publish. All QoS1 outgoing + * publishes are stored until a PUBACK is received. These messages are + * stored for supporting a resend if a network connection is broken before + * receiving a PUBACK. */ + returnStatus = getNextFreeIndexForOutgoingPublishes( &publishIndex ); + + if( returnStatus == false ) + { + LogError( ( "Unable to find a free spot for outgoing PUBLISH message." ) ); + } + else + { + LogDebug( ( "Published payload: %.*s", + ( int ) payloadLength, + ( const char * ) pPayload ) ); + + /* This example publishes to only one topic and uses QOS1. */ + outgoingPublishPackets[ publishIndex ].pubInfo.qos = MQTTQoS1; + outgoingPublishPackets[ publishIndex ].pubInfo.pTopicName = pTopicFilter; + outgoingPublishPackets[ publishIndex ].pubInfo.topicNameLength = topicFilterLength; + outgoingPublishPackets[ publishIndex ].pubInfo.pPayload = pPayload; + outgoingPublishPackets[ publishIndex ].pubInfo.payloadLength = payloadLength; + + /* Get a new packet id. */ + outgoingPublishPackets[ publishIndex ].packetId = MQTT_GetPacketId( pMqttContext ); + + /* Send PUBLISH packet. */ + mqttStatus = MQTT_Publish( pMqttContext, + &outgoingPublishPackets[ publishIndex ].pubInfo, + outgoingPublishPackets[ publishIndex ].packetId ); + + if( mqttStatus != MQTTSuccess ) + { + LogError( ( "Failed to send PUBLISH packet to broker with error = %s.", + MQTT_Status_strerror( mqttStatus ) ) ); + cleanupOutgoingPublishAt( publishIndex ); + returnStatus = false; + } + else + { + LogDebug( ( "PUBLISH sent for topic %.*s to broker with packet ID %u.", + topicFilterLength, + pTopicFilter, + outgoingPublishPackets[ publishIndex ].packetId ) ); + } + } + + return returnStatus; +} +/*-----------------------------------------------------------*/ + +bool ProcessLoopWithTimeout( void ) +{ + uint32_t ulMqttProcessLoopTimeoutTime; + uint32_t ulCurrentTime; + + MQTTStatus_t eMqttStatus = MQTTSuccess; + bool returnStatus = false; + + ulCurrentTime = mqttContext.getTime(); + ulMqttProcessLoopTimeoutTime = ulCurrentTime + MQTT_PROCESS_LOOP_TIMEOUT_MS; + + /* Call MQTT_ProcessLoop multiple times until the timeout expires or + * #MQTT_ProcessLoop fails. */ + while( ( ulCurrentTime < ulMqttProcessLoopTimeoutTime ) && + ( eMqttStatus == MQTTSuccess || eMqttStatus == MQTTNeedMoreBytes ) ) + { + eMqttStatus = MQTT_ProcessLoop( &mqttContext ); + ulCurrentTime = mqttContext.getTime(); + } + + if( ( eMqttStatus != MQTTSuccess ) && ( eMqttStatus != MQTTNeedMoreBytes ) ) + { + LogError( ( "MQTT_ProcessLoop returned with status = %s.", + MQTT_Status_strerror( eMqttStatus ) ) ); + } + else + { + LogDebug( ( "MQTT_ProcessLoop successful." ) ); + returnStatus = true; + } + + return returnStatus; +} +/*-----------------------------------------------------------*/ diff --git a/demos/fleet_provisioning/fleet_provisioning_keys_cert/mqtt_operations.h b/demos/fleet_provisioning/fleet_provisioning_keys_cert/mqtt_operations.h new file mode 100644 index 0000000000..d9cdad7528 --- /dev/null +++ b/demos/fleet_provisioning/fleet_provisioning_keys_cert/mqtt_operations.h @@ -0,0 +1,114 @@ +/* + * AWS IoT Device SDK for Embedded C 202211.00 + * Copyright (C) 2020 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef MQTT_OPERATIONS_H_ +#define MQTT_OPERATIONS_H_ + +/* MQTT API header. */ +#include "core_mqtt.h" + +/* corePKCS11 include. */ +#include "core_pkcs11.h" + +/** + * @brief Application callback type to handle the incoming publishes. + * + * @param[in] pPublishInfo Pointer to publish info of the incoming publish. + * @param[in] packetIdentifier Packet identifier of the incoming publish. + */ +typedef void (* MQTTPublishCallback_t )( MQTTPublishInfo_t * pPublishInfo, + uint16_t packetIdentifier ); + +/** + * @brief Establish a MQTT connection. + * + * @param[in] publishCallback The callback function to receive incoming + * publishes from the MQTT broker. + * @param[in] p11Session The PKCS #11 session to use. + * @param[in] pClientCertLabel The client certificate PKCS #11 label to use. + * @param[in] pPrivateKeyLabel The private key PKCS #11 label for the client certificate. + * + * @return true if an MQTT session is established; + * false otherwise. + */ +bool EstablishMqttSession( MQTTPublishCallback_t publishCallback, + CK_SESSION_HANDLE p11Session, + char * pClientCertLabel, + char * pPrivateKeyLabel ); + +/** + * @brief Disconnect the MQTT connection. + * + * @return true if the MQTT session was successfully disconnected; + * false otherwise. + */ +bool DisconnectMqttSession( void ); + +/** + * @brief Subscribe to a MQTT topic filter. + * + * @param[in] pTopicFilter The topic filter to subscribe to. + * @param[in] topicFilterLength Length of the topic buffer. + * + * @return true if subscribe operation was successful; + * false otherwise. + */ +bool SubscribeToTopic( const char * pTopicFilter, + uint16_t topicFilterLength ); + +/** + * @brief Unsubscribe from a MQTT topic filter. + * + * @param[in] pTopicFilter The topic filter to unsubscribe from. + * @param[in] topicFilterLength Length of the topic buffer. + * + * @return true if unsubscribe operation was successful; + * false otherwise. + */ +bool UnsubscribeFromTopic( const char * pTopicFilter, + uint16_t topicFilterLength ); + +/** + * @brief Publish a message to a MQTT topic. + * + * @param[in] pTopic The topic to publish the message on. + * @param[in] topicLength Length of the topic. + * @param[in] pMessage The message to publish. + * @param[in] messageLength Length of the message. + * + * @return true if PUBLISH was successfully sent; + * false otherwise. + */ +bool PublishToTopic( const char * pTopic, + uint16_t topicLength, + const char * pMessage, + size_t messageLength ); + +/** + * @brief Invoke the core MQTT library's process loop function. + * + * @return true if process loop was successful; + * false otherwise. + */ +bool ProcessLoopWithTimeout( void ); + +#endif /* ifndef MQTT_OPERATIONS_H_ */ diff --git a/demos/fleet_provisioning/fleet_provisioning_keys_cert/pkcs11_operations.c b/demos/fleet_provisioning/fleet_provisioning_keys_cert/pkcs11_operations.c new file mode 100644 index 0000000000..a8a853225c --- /dev/null +++ b/demos/fleet_provisioning/fleet_provisioning_keys_cert/pkcs11_operations.c @@ -0,0 +1,797 @@ +/* + * AWS IoT Device SDK for Embedded C 202211.00 + * Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +/** + * @file pkcs11_operations.c + * + * @brief This file provides wrapper functions for PKCS11 operations. + */ + +/* Standard includes. */ +#include +#include + +/* Config include. */ +#include "demo_config.h" + +/* Interface include. */ +#include "pkcs11_operations.h" + +/* PKCS #11 include. */ +#include "core_pkcs11_config.h" +#include "core_pki_utils.h" +#include "mbedtls_utils.h" + +/* MbedTLS include. */ +#include "mbedtls/ctr_drbg.h" +#include "mbedtls/entropy.h" +#include "mbedtls/entropy_poll.h" +#include "mbedtls/error.h" +#include "mbedtls/oid.h" +#include "mbedtls/pk.h" +#include "mbedtls/pk_internal.h" +#include "mbedtls/sha256.h" +#include "mbedtls/x509_crt.h" +#include "mbedtls/x509_csr.h" + +/** + * @brief Size of buffer in which to hold the certificate signing request (CSR). + */ +#define CLAIM_CERT_BUFFER_LENGTH 2048 + +/** + * @brief Size of buffer in which to hold the certificate signing request (CSR). + */ +#define CLAIM_PRIVATE_KEY_BUFFER_LENGTH 2048 + +/* Length parameters for importing RSA-2048 private keys. */ +#define MODULUS_LENGTH pkcs11RSA_2048_MODULUS_BITS / 8 +#define E_LENGTH 3 +#define D_LENGTH pkcs11RSA_2048_MODULUS_BITS / 8 +#define PRIME_1_LENGTH 128 +#define PRIME_2_LENGTH 128 +#define EXPONENT_1_LENGTH 128 +#define EXPONENT_2_LENGTH 128 +#define COEFFICIENT_LENGTH 128 + +#define EC_PARAMS_LENGTH 10 +#define EC_D_LENGTH 32 + +/** + * @brief Struct for holding parsed RSA-2048 private keys. + */ +typedef struct RsaParams_t +{ + CK_BYTE modulus[ MODULUS_LENGTH + 1 ]; + CK_BYTE e[ E_LENGTH + 1 ]; + CK_BYTE d[ D_LENGTH + 1 ]; + CK_BYTE prime1[ PRIME_1_LENGTH + 1 ]; + CK_BYTE prime2[ PRIME_2_LENGTH + 1 ]; + CK_BYTE exponent1[ EXPONENT_1_LENGTH + 1 ]; + CK_BYTE exponent2[ EXPONENT_2_LENGTH + 1 ]; + CK_BYTE coefficient[ COEFFICIENT_LENGTH + 1 ]; +} RsaParams_t; + +/** + * @brief Struct containing parameters needed by the signing callback. + */ +typedef struct SigningCallbackContext +{ + CK_SESSION_HANDLE p11Session; + CK_OBJECT_HANDLE p11PrivateKey; +} SigningCallbackContext_t; + +/*-----------------------------------------------------------*/ + +/** + * @brief Reads a file into the given buffer. + * + * @param[in] path Path of the file. + * @param[out] pBuffer Buffer to read file contents into. + * @param[in] bufferLength Length of #pBuffer. + * @param[out] pOutWrittenLength Length of contents written to #pBuffer. + */ +static bool readFile( const char * path, + char * pBuffer, + size_t bufferLength, + size_t * pOutWrittenLength ); + +/** + * @brief Delete the specified crypto object from storage. + * + * @param[in] session The PKCS #11 session. + * @param[in] pkcsLabelsPtr The list of labels to remove. + * @param[in] pClass The list of corresponding classes. + * @param[in] count The length of #pkcsLabelsPtr and #pClass. + */ +static CK_RV destroyProvidedObjects( CK_SESSION_HANDLE session, + CK_BYTE_PTR * pkcsLabelsPtr, + CK_OBJECT_CLASS * pClass, + CK_ULONG count ); + + +/** + * @brief Import the specified ECDSA private key into storage. + * + * @param[in] session The PKCS #11 session. + * @param[in] label The label to store the key. + * @param[in] mbedPkContext The private key to store. + */ +static CK_RV provisionPrivateECKey( CK_SESSION_HANDLE session, + const char * label, + mbedtls_pk_context * mbedPkContext ); + + + +/** + * @brief Import the specified RSA private key into storage. + * + * @param[in] session The PKCS #11 session. + * @param[in] label The label to store the key. + * @param[in] mbedPkContext The private key to store. + */ +static CK_RV provisionPrivateRSAKey( CK_SESSION_HANDLE session, + const char * label, + mbedtls_pk_context * mbedPkContext ); + + +/** + * @brief Import the specified private key into storage. + * + * @param[in] session The PKCS #11 session. + * @param[in] privateKey The private key to store, in PEM format. + * @param[in] privateKeyLength The length of the key, including null terminator. + * @param[in] label The label to store the key. + */ +static CK_RV provisionPrivateKey( CK_SESSION_HANDLE session, + const char * privateKey, + size_t privateKeyLength, + const char * label ); + +/** + * @brief Import the specified X.509 client certificate into storage. + * + * @param[in] session The PKCS #11 session. + * @param[in] certificate The certificate to store, in PEM format. + * @param[in] certificateLength The length of the certificate, including the null terminator. + * @param[in] label The label to store the certificate. + */ +static CK_RV provisionCertificate( CK_SESSION_HANDLE session, + const char * certificate, + size_t certificateLength, + const char * label ); + +/*-----------------------------------------------------------*/ + +static bool readFile( const char * path, + char * pBuffer, + size_t bufferLength, + size_t * pOutWrittenLength ) +{ + FILE * file; + size_t length = 0; + bool status = true; + + /* Get the file descriptor for the CSR file. */ + file = fopen( path, "rb" ); + + if( file == NULL ) + { + LogError( ( "Error opening file at path: %s. Error: %s.", + path, strerror( errno ) ) ); + status = false; + } + else + { + int result; + /* Seek to the end of the file, so that we can get the file size. */ + result = fseek( file, 0L, SEEK_END ); + + if( result == -1 ) + { + LogError( ( "Failed while moving to end of file. Path: %s. Error: %s.", + path, strerror( errno ) ) ); + status = false; + } + else + { + long lenResult = -1; + /* Get the current position which is the file size. */ + lenResult = ftell( file ); + + if( lenResult == -1 ) + { + LogError( ( "Failed to get length of file. Path: %s. Error: %s.", path, + strerror( errno ) ) ); + status = false; + } + else + { + length = ( size_t ) lenResult; + } + } + + if( status == true ) + { + if( length > bufferLength ) + { + LogError( ( "Buffer too small for file. Buffer size: %ld. Required size: %ld.", + bufferLength, length ) ); + status = false; + } + } + + if( status == true ) + { + /* Return to the beginning of the file. */ + result = fseek( file, 0L, SEEK_SET ); + + if( result == -1 ) + { + LogError( ( "Failed to move to beginning of file. Path: %s. Error: %s.", + path, strerror( errno ) ) ); + status = false; + } + } + + if( status == true ) + { + size_t written = 0; + /* Read the CSR into our buffer. */ + written = fread( pBuffer, 1, length, file ); + + if( written != length ) + { + LogError( ( "Failed reading file. Path: %s. Error: %s.", path, + strerror( errno ) ) ); + status = false; + } + else + { + *pOutWrittenLength = length; + } + } + + fclose( file ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +static CK_RV destroyProvidedObjects( CK_SESSION_HANDLE session, + CK_BYTE_PTR * pkcsLabelsPtr, + CK_OBJECT_CLASS * pClass, + CK_ULONG count ) +{ + CK_RV result; + CK_FUNCTION_LIST_PTR functionList; + CK_OBJECT_HANDLE objectHandle; + CK_BYTE * labelPtr; + CK_ULONG index = 0; + + result = C_GetFunctionList( &functionList ); + + if( result != CKR_OK ) + { + LogError( ( "Could not get a PKCS #11 function pointer." ) ); + } + else + { + for( index = 0; index < count; index++ ) + { + labelPtr = pkcsLabelsPtr[ index ]; + + result = xFindObjectWithLabelAndClass( session, ( char * ) labelPtr, + strlen( ( char * ) labelPtr ), + pClass[ index ], &objectHandle ); + + while( ( result == CKR_OK ) && ( objectHandle != CK_INVALID_HANDLE ) ) + { + result = functionList->C_DestroyObject( session, objectHandle ); + + /* PKCS #11 allows a module to maintain multiple objects with the same + * label and type. The intent of this loop is to try to delete all of + * them. However, to avoid getting stuck, we won't try to find another + * object of the same label/type if the previous delete failed. */ + if( result == CKR_OK ) + { + result = xFindObjectWithLabelAndClass( session, ( char * ) labelPtr, + strlen( ( char * ) labelPtr ), + pClass[ index ], &objectHandle ); + } + else + { + break; + } + } + } + } + + return result; +} + +/*-----------------------------------------------------------*/ + +static CK_RV provisionPrivateECKey( CK_SESSION_HANDLE session, + const char * label, + mbedtls_pk_context * mbedPkContext ) +{ + CK_RV result = CKR_OK; + CK_FUNCTION_LIST_PTR functionList = NULL; + CK_BYTE * DPtr = NULL; /* Private value D. */ + CK_BYTE * ecParamsPtr = NULL; /* DER-encoding of an ANSI X9.62 Parameters value */ + int mbedResult = 0; + CK_BBOOL trueObject = CK_TRUE; + CK_KEY_TYPE privateKeyType = CKK_EC; + CK_OBJECT_CLASS privateKeyClass = CKO_PRIVATE_KEY; + CK_OBJECT_HANDLE objectHandle = CK_INVALID_HANDLE; + mbedtls_ecp_keypair * keyPair = ( mbedtls_ecp_keypair * ) mbedPkContext->pk_ctx; + + result = C_GetFunctionList( &functionList ); + + if( result != CKR_OK ) + { + LogError( ( "Could not get a PKCS #11 function pointer." ) ); + } + else + { + DPtr = ( CK_BYTE * ) malloc( EC_D_LENGTH ); + + if( DPtr == NULL ) + { + result = CKR_HOST_MEMORY; + } + } + + if( result == CKR_OK ) + { + mbedResult = mbedtls_mpi_write_binary( &( keyPair->d ), DPtr, EC_D_LENGTH ); + + if( mbedResult != 0 ) + { + LogError( ( "Failed to parse EC private key components." ) ); + result = CKR_ATTRIBUTE_VALUE_INVALID; + } + } + + if( result == CKR_OK ) + { + if( keyPair->grp.id == MBEDTLS_ECP_DP_SECP256R1 ) + { + ecParamsPtr = ( CK_BYTE * ) ( "\x06\x08" MBEDTLS_OID_EC_GRP_SECP256R1 ); + } + else + { + result = CKR_CURVE_NOT_SUPPORTED; + } + } + + if( result == CKR_OK ) + { + CK_ATTRIBUTE privateKeyTemplate[] = + { + { CKA_CLASS, NULL /* &privateKeyClass*/, sizeof( CK_OBJECT_CLASS ) }, + { CKA_KEY_TYPE, NULL /* &privateKeyType*/, sizeof( CK_KEY_TYPE ) }, + { CKA_LABEL, ( void * ) label, ( CK_ULONG ) strlen( label ) }, + { CKA_TOKEN, NULL /* &trueObject*/, sizeof( CK_BBOOL ) }, + { CKA_SIGN, NULL /* &trueObject*/, sizeof( CK_BBOOL ) }, + { CKA_EC_PARAMS, NULL /* ecParamsPtr*/, EC_PARAMS_LENGTH }, + { CKA_VALUE, NULL /* DPtr*/, EC_D_LENGTH } + }; + + /* Aggregate initializers must not use the address of an automatic variable. */ + privateKeyTemplate[ 0 ].pValue = &privateKeyClass; + privateKeyTemplate[ 1 ].pValue = &privateKeyType; + privateKeyTemplate[ 3 ].pValue = &trueObject; + privateKeyTemplate[ 4 ].pValue = &trueObject; + privateKeyTemplate[ 5 ].pValue = ecParamsPtr; + privateKeyTemplate[ 6 ].pValue = DPtr; + + result = functionList->C_CreateObject( session, + ( CK_ATTRIBUTE_PTR ) &privateKeyTemplate, + sizeof( privateKeyTemplate ) / sizeof( CK_ATTRIBUTE ), + &objectHandle ); + } + + if( DPtr != NULL ) + { + free( DPtr ); + } + + return result; +} + +/*-----------------------------------------------------------*/ + +static CK_RV provisionPrivateRSAKey( CK_SESSION_HANDLE session, + const char * label, + mbedtls_pk_context * mbedPkContext ) +{ + CK_RV result = CKR_OK; + CK_FUNCTION_LIST_PTR functionList = NULL; + int mbedResult = 0; + CK_KEY_TYPE privateKeyType = CKK_RSA; + mbedtls_rsa_context * rsaContext = ( mbedtls_rsa_context * ) mbedPkContext->pk_ctx; + CK_OBJECT_CLASS privateKeyClass = CKO_PRIVATE_KEY; + RsaParams_t * rsaParams = NULL; + CK_BBOOL trueObject = CK_TRUE; + CK_OBJECT_HANDLE objectHandle = CK_INVALID_HANDLE; + + result = C_GetFunctionList( &functionList ); + + if( result != CKR_OK ) + { + LogError( ( "Could not get a PKCS #11 function pointer." ) ); + } + else + { + rsaParams = ( RsaParams_t * ) malloc( sizeof( RsaParams_t ) ); + + if( rsaParams == NULL ) + { + result = CKR_HOST_MEMORY; + } + } + + if( result == CKR_OK ) + { + memset( rsaParams, 0, sizeof( RsaParams_t ) ); + + mbedResult = mbedtls_rsa_export_raw( rsaContext, + rsaParams->modulus, MODULUS_LENGTH + 1, + rsaParams->prime1, PRIME_1_LENGTH + 1, + rsaParams->prime2, PRIME_2_LENGTH + 1, + rsaParams->d, D_LENGTH + 1, + rsaParams->e, E_LENGTH + 1 ); + + if( mbedResult != 0 ) + { + LogError( ( "Failed to parse RSA private key components." ) ); + result = CKR_ATTRIBUTE_VALUE_INVALID; + } + + /* Export Exponent 1, Exponent 2, Coefficient. */ + mbedResult |= mbedtls_mpi_write_binary( ( mbedtls_mpi const * ) &rsaContext->DP, rsaParams->exponent1, EXPONENT_1_LENGTH + 1 ); + mbedResult |= mbedtls_mpi_write_binary( ( mbedtls_mpi const * ) &rsaContext->DQ, rsaParams->exponent2, EXPONENT_2_LENGTH + 1 ); + mbedResult |= mbedtls_mpi_write_binary( ( mbedtls_mpi const * ) &rsaContext->QP, rsaParams->coefficient, COEFFICIENT_LENGTH + 1 ); + + if( mbedResult != 0 ) + { + LogError( ( "Failed to parse RSA private key Chinese Remainder Theorem variables." ) ); + result = CKR_ATTRIBUTE_VALUE_INVALID; + } + } + + if( result == CKR_OK ) + { + /* When importing the fields, the pointer is incremented by 1 + * to remove the leading 0 padding (if it existed) and the original field + * length is used */ + + CK_ATTRIBUTE privateKeyTemplate[] = + { + { CKA_CLASS, NULL /* &privateKeyClass */, sizeof( CK_OBJECT_CLASS ) }, + { CKA_KEY_TYPE, NULL /* &privateKeyType */, sizeof( CK_KEY_TYPE ) }, + { CKA_LABEL, ( void * ) label, ( CK_ULONG ) strlen( label ) }, + { CKA_TOKEN, NULL /* &trueObject */, sizeof( CK_BBOOL ) }, + { CKA_SIGN, NULL /* &trueObject */, sizeof( CK_BBOOL ) }, + { CKA_MODULUS, rsaParams->modulus + 1, MODULUS_LENGTH }, + { CKA_PRIVATE_EXPONENT, rsaParams->d + 1, D_LENGTH }, + { CKA_PUBLIC_EXPONENT, rsaParams->e + 1, E_LENGTH }, + { CKA_PRIME_1, rsaParams->prime1 + 1, PRIME_1_LENGTH }, + { CKA_PRIME_2, rsaParams->prime2 + 1, PRIME_2_LENGTH }, + { CKA_EXPONENT_1, rsaParams->exponent1 + 1, EXPONENT_1_LENGTH }, + { CKA_EXPONENT_2, rsaParams->exponent2 + 1, EXPONENT_2_LENGTH }, + { CKA_COEFFICIENT, rsaParams->coefficient + 1, COEFFICIENT_LENGTH } + }; + + /* Aggregate initializers must not use the address of an automatic variable. */ + privateKeyTemplate[ 0 ].pValue = &privateKeyClass; + privateKeyTemplate[ 1 ].pValue = &privateKeyType; + privateKeyTemplate[ 3 ].pValue = &trueObject; + privateKeyTemplate[ 4 ].pValue = &trueObject; + + result = functionList->C_CreateObject( session, + ( CK_ATTRIBUTE_PTR ) &privateKeyTemplate, + sizeof( privateKeyTemplate ) / sizeof( CK_ATTRIBUTE ), + &objectHandle ); + } + + if( NULL != rsaParams ) + { + free( rsaParams ); + } + + return result; +} + +/*-----------------------------------------------------------*/ + +static CK_RV provisionPrivateKey( CK_SESSION_HANDLE session, + const char * privateKey, + size_t privateKeyLength, + const char * label ) +{ + CK_RV result = CKR_OK; + mbedtls_pk_type_t mbedKeyType = MBEDTLS_PK_NONE; + int mbedResult = 0; + mbedtls_pk_context mbedPkContext = { 0 }; + + mbedtls_pk_init( &mbedPkContext ); + mbedResult = mbedtls_pk_parse_key( &mbedPkContext, ( const uint8_t * ) privateKey, + privateKeyLength, NULL, 0 ); + + if( mbedResult != 0 ) + { + LogError( ( "Unable to parse private key." ) ); + result = CKR_ARGUMENTS_BAD; + } + + /* Determine whether the key to be imported is RSA or EC. */ + if( result == CKR_OK ) + { + mbedKeyType = mbedtls_pk_get_type( &mbedPkContext ); + + if( mbedKeyType == MBEDTLS_PK_RSA ) + { + result = provisionPrivateRSAKey( session, label, &mbedPkContext ); + } + else if( ( mbedKeyType == MBEDTLS_PK_ECDSA ) || + ( mbedKeyType == MBEDTLS_PK_ECKEY ) || + ( mbedKeyType == MBEDTLS_PK_ECKEY_DH ) ) + { + result = provisionPrivateECKey( session, label, &mbedPkContext ); + } + else + { + LogError( ( "Invalid private key type provided. Only RSA-2048 and " + "EC P-256 keys are supported." ) ); + result = CKR_ARGUMENTS_BAD; + } + } + + mbedtls_pk_free( &mbedPkContext ); + + return result; +} + +/*-----------------------------------------------------------*/ + +static CK_RV provisionCertificate( CK_SESSION_HANDLE session, + const char * certificate, + size_t certificateLength, + const char * label ) +{ + PKCS11_CertificateTemplate_t certificateTemplate; + CK_OBJECT_CLASS certificateClass = CKO_CERTIFICATE; + CK_CERTIFICATE_TYPE certificateType = CKC_X_509; + CK_FUNCTION_LIST_PTR functionList = NULL; + CK_RV result = CKR_OK; + uint8_t * derObject = NULL; + int32_t conversion = 0; + size_t derLen = 0; + CK_BBOOL tokenStorage = CK_TRUE; + CK_BYTE subject[] = "TestSubject"; + CK_OBJECT_HANDLE objectHandle = CK_INVALID_HANDLE; + + /* Initialize the client certificate template. */ + certificateTemplate.xObjectClass.type = CKA_CLASS; + certificateTemplate.xObjectClass.pValue = &certificateClass; + certificateTemplate.xObjectClass.ulValueLen = sizeof( certificateClass ); + certificateTemplate.xSubject.type = CKA_SUBJECT; + certificateTemplate.xSubject.pValue = subject; + certificateTemplate.xSubject.ulValueLen = strlen( ( const char * ) subject ); + certificateTemplate.xValue.type = CKA_VALUE; + certificateTemplate.xValue.pValue = ( CK_VOID_PTR ) certificate; + certificateTemplate.xValue.ulValueLen = ( CK_ULONG ) certificateLength; + certificateTemplate.xLabel.type = CKA_LABEL; + certificateTemplate.xLabel.pValue = ( CK_VOID_PTR ) label; + certificateTemplate.xLabel.ulValueLen = strlen( label ); + certificateTemplate.xCertificateType.type = CKA_CERTIFICATE_TYPE; + certificateTemplate.xCertificateType.pValue = &certificateType; + certificateTemplate.xCertificateType.ulValueLen = sizeof( CK_CERTIFICATE_TYPE ); + certificateTemplate.xTokenObject.type = CKA_TOKEN; + certificateTemplate.xTokenObject.pValue = &tokenStorage; + certificateTemplate.xTokenObject.ulValueLen = sizeof( tokenStorage ); + + if( certificate == NULL ) + { + LogError( ( "Certificate cannot be null." ) ); + result = CKR_ATTRIBUTE_VALUE_INVALID; + } + + if( result == CKR_OK ) + { + result = C_GetFunctionList( &functionList ); + + if( result != CKR_OK ) + { + LogError( ( "Could not get a PKCS #11 function pointer." ) ); + } + } + + if( result == CKR_OK ) + { + /* Convert the certificate to DER format from PEM. The DER key should + * be about 3/4 the size of the PEM key, so mallocing the PEM key size + * is sufficient. */ + derObject = ( uint8_t * ) malloc( certificateTemplate.xValue.ulValueLen ); + derLen = certificateTemplate.xValue.ulValueLen; + + if( derObject != NULL ) + { + conversion = convert_pem_to_der( ( unsigned char * ) certificateTemplate.xValue.pValue, + certificateTemplate.xValue.ulValueLen, + derObject, &derLen ); + + if( 0 != conversion ) + { + LogError( ( "Failed to convert provided certificate." ) ); + result = CKR_ARGUMENTS_BAD; + } + } + else + { + LogError( ( "Failed to allocate buffer for converting certificate to DER." ) ); + result = CKR_HOST_MEMORY; + } + } + + if( result == CKR_OK ) + { + /* Set the template pointers to refer to the DER converted objects. */ + certificateTemplate.xValue.pValue = derObject; + certificateTemplate.xValue.ulValueLen = derLen; + + /* Best effort clean-up of the existing object, if it exists. */ + destroyProvidedObjects( session, ( CK_BYTE_PTR * ) &label, &certificateClass, 1 ); + + /* Create an object using the encoded client certificate. */ + LogInfo( ( "Writing certificate into label \"%s\".", label ) ); + + result = functionList->C_CreateObject( session, + ( CK_ATTRIBUTE_PTR ) &certificateTemplate, + sizeof( certificateTemplate ) / sizeof( CK_ATTRIBUTE ), + &objectHandle ); + } + + if( derObject != NULL ) + { + free( derObject ); + } + + return result; +} + +/*-----------------------------------------------------------*/ + +bool loadClaimCredentials( CK_SESSION_HANDLE p11Session, + const char * pClaimCertPath, + const char * pClaimCertLabel, + const char * pClaimPrivKeyPath, + const char * pClaimPrivKeyLabel ) +{ + bool status; + char claimCert[ CLAIM_CERT_BUFFER_LENGTH ] = { 0 }; + size_t claimCertLength = 0; + char claimPrivateKey[ CLAIM_PRIVATE_KEY_BUFFER_LENGTH ] = { 0 }; + size_t claimPrivateKeyLength = 0; + CK_RV ret; + + assert( pClaimCertPath != NULL ); + assert( pClaimCertLabel != NULL ); + assert( pClaimPrivKeyPath != NULL ); + assert( pClaimPrivKeyLabel != NULL ); + + status = readFile( pClaimCertPath, claimCert, CLAIM_CERT_BUFFER_LENGTH, + &claimCertLength ); + + if( status == true ) + { + status = readFile( pClaimPrivKeyPath, claimPrivateKey, + CLAIM_PRIVATE_KEY_BUFFER_LENGTH, &claimPrivateKeyLength ); + } + + if( status == true ) + { + ret = provisionPrivateKey( p11Session, claimPrivateKey, + claimPrivateKeyLength + 1, /* MbedTLS includes null character in length for PEM objects. */ + pClaimPrivKeyLabel ); + status = ( ret == CKR_OK ); + } + + if( status == true ) + { + ret = provisionCertificate( p11Session, claimCert, + claimCertLength + 1, /* MbedTLS includes null character in length for PEM objects. */ + pClaimCertLabel ); + status = ( ret == CKR_OK ); + } + + return status; +} + +/*-----------------------------------------------------------*/ + +bool loadCertificate( CK_SESSION_HANDLE p11Session, + const char * pCertificate, + const char * pLabel, + size_t certificateLength ) +{ + CK_RV ret; + + assert( pCertificate != NULL ); + assert( pLabel != NULL ); + + ret = provisionCertificate( p11Session, + pCertificate, + certificateLength + 1, /* MbedTLS includes null character in length for PEM objects. */ + pLabel ); + + return( ret == CKR_OK ); +} + +/*-----------------------------------------------------------*/ + +bool loadPrivateKey( CK_SESSION_HANDLE p11Session, + const char * pPrivateKey, + const char * pLabel, + size_t privateKeyLength ) +{ + CK_RV ret; + + assert( pPrivateKey != NULL ); + assert( pLabel != NULL ); + + ret = provisionPrivateKey( p11Session, + pPrivateKey, + privateKeyLength + 1, /* MbedTLS includes null character in length for PEM objects. */ + pLabel ); + + return( ret == CKR_OK ); +} + +/*-----------------------------------------------------------*/ + +bool pkcs11CloseSession( CK_SESSION_HANDLE p11Session ) +{ + CK_RV result = CKR_OK; + CK_FUNCTION_LIST_PTR functionList = NULL; + + result = C_GetFunctionList( &functionList ); + + if( result == CKR_OK ) + { + result = functionList->C_CloseSession( p11Session ); + } + + if( result == CKR_OK ) + { + result = functionList->C_Finalize( NULL ); + } + + return( result == CKR_OK ); +} + +/*-----------------------------------------------------------*/ diff --git a/demos/fleet_provisioning/fleet_provisioning_keys_cert/pkcs11_operations.h b/demos/fleet_provisioning/fleet_provisioning_keys_cert/pkcs11_operations.h new file mode 100644 index 0000000000..c22b94f60f --- /dev/null +++ b/demos/fleet_provisioning/fleet_provisioning_keys_cert/pkcs11_operations.h @@ -0,0 +1,97 @@ +/* + * AWS IoT Device SDK for Embedded C 202211.00 + * Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved. + * + * Permission is hereby granted, free of charge, to any person obtaining a copy of + * this software and associated documentation files (the "Software"), to deal in + * the Software without restriction, including without limitation the rights to + * use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of + * the Software, and to permit persons to whom the Software is furnished to do so, + * subject to the following conditions: + * + * The above copyright notice and this permission notice shall be included in all + * copies or substantial portions of the Software. + * + * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR + * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS + * FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR + * COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER + * IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN + * CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE. + */ + +#ifndef PKCS11_OPERATIONS_H_ +#define PKCS11_OPERATIONS_H_ + +/* Standard includes. */ +#include +#include + +/* corePKCS11 include. */ +#include "core_pkcs11.h" + +/** + * @brief Loads the claim credentials into the PKCS #11 module. Claim + * credentials are used in "Provisioning by Claim" workflow of Fleet + * Provisioning feature of AWS IoT Core. For more information, refer to the + * [AWS documentation](https://docs.aws.amazon.com/iot/latest/developerguide/provision-wo-cert.html#claim-based) + * + * Note: This function is for demonstration purposes, and the claim credentials + * should be securely stored in production devices. For example, the + * shared claim credentials could be loaded into a secure element on the devices + * in your fleet at the time of manufacturing. + * + * @param[in] p11Session The PKCS #11 session to use. + * @param[in] pClaimCertPath Path to the claim certificate. + * @param[in] pClaimCertLabel PKCS #11 label for the claim certificate. + * @param[in] pClaimPrivKeyPath Path to the claim private key. + * @param[in] pClaimPrivKeyLabel PKCS #11 label for the claim private key. + * + * @return True on success. + */ +bool loadClaimCredentials( CK_SESSION_HANDLE p11Session, + const char * pClaimCertPath, + const char * pClaimCertLabel, + const char * pClaimPrivKeyPath, + const char * pClaimPrivKeyLabel ); + +/** + * @brief Save the device client certificate into the PKCS #11 module. + * + * @param[in] p11Session The PKCS #11 session to use. + * @param[in] pCertificate The certificate to load. + * @param[in] pLabel PKCS #11 label for the certificate. + * @param[in] certificateLength Length of #pCertificate. + * + * @return True on success. + */ +bool loadCertificate( CK_SESSION_HANDLE p11Session, + const char * pCertificate, + const char * pLabel, + size_t certificateLength ); + +/** + * @brief Save the device client certificate into the PKCS #11 module. + * + * @param[in] p11Session The PKCS #11 session to use. + * @param[in] pPrivateKey The private key to load. + * @param[in] pLabel PKCS #11 label for the private key. + * @param[in] privateKeyLength Length of #pPrivateKey. + * + * @return True on success. + */ +bool loadPrivateKey( CK_SESSION_HANDLE p11Session, + const char * pPrivateKey, + const char * pLabel, + size_t privateKeyLength ); + +/** + * @brief Close the PKCS #11 session. + * + * @param[in] p11Session The PKCS #11 session to use. + * + * @return True on success. + */ +bool pkcs11CloseSession( CK_SESSION_HANDLE p11Session ); + +#endif /* ifndef PKCS11_OPERATIONS_H_ */ diff --git a/demos/lexicon.txt b/demos/lexicon.txt index 741561decf..80367abd85 100644 --- a/demos/lexicon.txt +++ b/demos/lexicon.txt @@ -575,6 +575,7 @@ ppath ppathlen ppayload ppkey +pprivatekey pprivatekeylabel pprivatekeypath pprivkeylabel