diff --git a/demos/fleet_provisioning/fleet_provisioning_csr/CMakeLists.txt b/demos/fleet_provisioning/fleet_provisioning_csr/CMakeLists.txt new file mode 100644 index 0000000000..a15e8bfa37 --- /dev/null +++ b/demos/fleet_provisioning/fleet_provisioning_csr/CMakeLists.txt @@ -0,0 +1,57 @@ +set( DEMO_NAME "fleet_provisioning_with_csr_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 ) + +# 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} + ${FLEET_PROVISIONING_SOURCES} ) + +target_link_libraries( ${DEMO_NAME} PRIVATE + tinycbor + mbedtls + clock_posix + transport_mbedtls_posix ) + +target_include_directories( ${DEMO_NAME} + PUBLIC + ${LOGGING_INCLUDE_DIRS} + ${MQTT_INCLUDE_PUBLIC_DIRS} + ${BACKOFF_ALGORITHM_INCLUDE_PUBLIC_DIRS} + ${AWS_DEMO_INCLUDE_DIRS} + ${FLEET_PROVISIONING_INCLUDE_PUBLIC_DIRS} + ${CMAKE_SOURCE_DIR}/platform/include + ${CMAKE_CURRENT_LIST_DIR} ) + +set_macro_definitions(TARGETS ${DEMO_NAME} + OPTIONAL + "DOWNLOADED_CERT_WRITE_PATH" + "GENERATED_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" + "CLIENT_PRIVATE_KEY_PATH" + "CLIENT_CERT_PATH") diff --git a/demos/fleet_provisioning/fleet_provisioning_csr/core_mqtt_config.h b/demos/fleet_provisioning/fleet_provisioning_csr/core_mqtt_config.h new file mode 100644 index 0000000000..629e7dc688 --- /dev/null +++ b/demos/fleet_provisioning/fleet_provisioning_csr/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_csr/demo_config.h b/demos/fleet_provisioning/fleet_provisioning_csr/demo_config.h new file mode 100644 index 0000000000..95005e3bcf --- /dev/null +++ b/demos/fleet_provisioning/fleet_provisioning_csr/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 ( 2048U ) + +/** + * @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_csr/example_claim_policy.json b/demos/fleet_provisioning/fleet_provisioning_csr/example_claim_policy.json new file mode 100644 index 0000000000..637ec03620 --- /dev/null +++ b/demos/fleet_provisioning/fleet_provisioning_csr/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-from-csr/*", + "arn:aws:iot:::topic/$aws/provisioning-templates//provision/*" + ] + }, + { + "Effect": "Allow", + "Action": "iot:Subscribe", + "Resource": [ + "arn:aws:iot:::topicfilter/$aws/certificates/create-from-csr/*", + "arn:aws:iot:::topicfilter/$aws/provisioning-templates//provision/*" + ] + } + ] + } diff --git a/demos/fleet_provisioning/fleet_provisioning_csr/example_demo_template.json b/demos/fleet_provisioning/fleet_provisioning_csr/example_demo_template.json new file mode 100644 index 0000000000..78d7fca2a8 --- /dev/null +++ b/demos/fleet_provisioning/fleet_provisioning_csr/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_csr/fleet_provisioning_config.h b/demos/fleet_provisioning/fleet_provisioning_csr/fleet_provisioning_config.h new file mode 100644 index 0000000000..aa7ef33a3f --- /dev/null +++ b/demos/fleet_provisioning/fleet_provisioning_csr/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_csr/fleet_provisioning_serializer.c b/demos/fleet_provisioning/fleet_provisioning_csr/fleet_provisioning_serializer.c new file mode 100644 index 0000000000..9458a25a20 --- /dev/null +++ b/demos/fleet_provisioning/fleet_provisioning_csr/fleet_provisioning_serializer.c @@ -0,0 +1,485 @@ +/* + * 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 generateCsrRequest( uint8_t * pBuffer, + size_t bufferLength, + const char * pCsr, + size_t csrLength, + size_t * pOutLengthWritten ) +{ + CborEncoder encoder, mapEncoder; + CborError cborRet; + + assert( pBuffer != NULL ); + assert( pCsr != NULL ); + assert( pOutLengthWritten != NULL ); + + /* For details on the CreateCertificatefromCsr request payload format, see: + * https://docs.aws.amazon.com/iot/latest/developerguide/fleet-provision-api.html#create-cert-csr-request-payload + */ + cbor_encoder_init( &encoder, pBuffer, bufferLength, 0 ); + + /* The request document is a map with 1 key value pair. */ + cborRet = cbor_encoder_create_map( &encoder, &mapEncoder, 1 ); + + if( cborRet == CborNoError ) + { + cborRet = cbor_encode_text_stringz( &mapEncoder, "certificateSigningRequest" ); + } + + if( cborRet == CborNoError ) + { + cborRet = cbor_encode_text_string( &mapEncoder, pCsr, csrLength ); + } + + 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 CreateCertificateFromCsr request payload into buffer." ) ); + } + } + + return( cborRet == CborNoError ); +} +/*-----------------------------------------------------------*/ + +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 parseCsrResponse( const uint8_t * pResponse, + size_t length, + char * pCertificateBuffer, + size_t * pCertificateBufferLength, + 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( 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 CreateCertificateFromCsr response: %s.", cbor_error_string( cborRet ) ) ); + } + else if( !cbor_value_is_map( &map ) ) + { + LogError( ( "CreateCertificateFromCsr response is not a valid map container type." ) ); + } + else + { + cborRet = cbor_value_map_find_value( &map, "certificatePem", &value ); + + if( cborRet != CborNoError ) + { + LogError( ( "Error searching CreateCertificateFromCsr response: %s.", cbor_error_string( cborRet ) ) ); + } + else if( value.type == CborInvalidType ) + { + LogError( ( "\"certificatePem\" not found in CreateCertificateFromCsr response." ) ); + } + else if( value.type != CborTextStringType ) + { + LogError( ( "Value for \"certificatePem\" key in CreateCertificateFromCsr 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 CreateCertificateFromCsr response: %s.", cbor_error_string( cborRet ) ) ); + } + } + } + + if( cborRet == CborNoError ) + { + cborRet = cbor_value_map_find_value( &map, "certificateId", &value ); + + if( cborRet != CborNoError ) + { + LogError( ( "Error searching CreateCertificateFromCsr response: %s.", cbor_error_string( cborRet ) ) ); + } + else if( value.type == CborInvalidType ) + { + LogError( ( "\"certificateId\" not found in CreateCertificateFromCsr response." ) ); + } + else if( value.type != CborTextStringType ) + { + LogError( ( "\"certificateId\" is an unexpected type in CreateCertificateFromCsr 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 CreateCertificateFromCsr response: %s.", cbor_error_string( cborRet ) ) ); + } + } + } + + if( cborRet == CborNoError ) + { + cborRet = cbor_value_map_find_value( &map, "certificateOwnershipToken", &value ); + + if( cborRet != CborNoError ) + { + LogError( ( "Error searching CreateCertificateFromCsr response: %s.", cbor_error_string( cborRet ) ) ); + } + else if( value.type == CborInvalidType ) + { + LogError( ( "\"certificateOwnershipToken\" not found in CreateCertificateFromCsr response." ) ); + } + else if( value.type != CborTextStringType ) + { + LogError( ( "\"certificateOwnershipToken\" is an unexpected type in CreateCertificateFromCsr 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 CreateCertificateFromCsr 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_csr/fleet_provisioning_serializer.h b/demos/fleet_provisioning/fleet_provisioning_csr/fleet_provisioning_serializer.h new file mode 100644 index 0000000000..e83f7e9f38 --- /dev/null +++ b/demos/fleet_provisioning/fleet_provisioning_csr/fleet_provisioning_serializer.h @@ -0,0 +1,123 @@ +/* + * 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 + * CreateCertificateFromCsr API in order to request a certificate from AWS IoT + * for the included Certificate Signing Request (CSR). + * + * @param[in] pBuffer Buffer into which to write the publish request payload. + * @param[in] bufferLength Length of #pBuffer. + * @param[in] pCsr The CSR to include in the request payload. + * @param[in] csrLength The length of #pCsr. + * @param[out] pOutLengthWritten The length of the publish request payload. + */ +bool generateCsrRequest( uint8_t * pBuffer, + size_t bufferLength, + const char * pCsr, + size_t csrLength, + size_t * pOutLengthWritten ); + +/** + * @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 parseCsrResponse( const uint8_t * pResponse, + size_t length, + char * pCertificateBuffer, + size_t * pCertificateBufferLength, + 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_csr/fleet_provisioning_with_csr_demo.c b/demos/fleet_provisioning/fleet_provisioning_csr/fleet_provisioning_with_csr_demo.c new file mode 100644 index 0000000000..ac27cebe63 --- /dev/null +++ b/demos/fleet_provisioning/fleet_provisioning_csr/fleet_provisioning_with_csr_demo.c @@ -0,0 +1,797 @@ +/* + * 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 Certificate Signing Request (CSR). 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 + * CreateCertificateFromCsr topics, and obtains a 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 + +#if defined( DOWNLOADED_CERT_WRITE_PATH ) + #include +#endif // DOWNLOADED_CERT_WRITE_PATH + +/* Demo config. */ +#include "demo_config.h" + +/* AWS IoT Fleet Provisioning Library. */ +#include "fleet_provisioning.h" + +/* Demo includes. */ +#include "mqtt_operations.h" +#include "fleet_provisioning_serializer.h" +#include "mbedtls_posix.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 CSR_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 CreateCertificateFromCsr 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 CreateCertificateFromCsr accepted and rejected topics. + */ +static bool subscribeToCsrResponseTopics( void ); + +/** + * @brief Unsubscribe from the CreateCertificateFromCsr accepted and rejected topics. + */ +static bool unsubscribeFromCsrResponseTopics( 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 == FleetProvCborCreateCertFromCsrAccepted ) + { + LogInfo( ( "Received accepted response from Fleet Provisioning CreateCertificateFromCsr 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 == FleetProvCborCreateCertFromCsrRejected ) + { + LogError( ( "Received rejected response from Fleet Provisioning CreateCertificateFromCsr 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 subscribeToCsrResponseTopics( void ) +{ + bool status; + + status = SubscribeToTopic( FP_CBOR_CREATE_CERT_ACCEPTED_TOPIC, + FP_CBOR_CREATE_CERT_ACCEPTED_LENGTH ); + + if( status == false ) + { + LogError( ( "Failed to subscribe to fleet provisioning topic: %.*s.", + FP_CBOR_CREATE_CERT_ACCEPTED_LENGTH, + FP_CBOR_CREATE_CERT_ACCEPTED_TOPIC ) ); + } + + if( status == true ) + { + status = SubscribeToTopic( FP_CBOR_CREATE_CERT_REJECTED_TOPIC, + FP_CBOR_CREATE_CERT_REJECTED_LENGTH ); + + if( status == false ) + { + LogError( ( "Failed to subscribe to fleet provisioning topic: %.*s.", + FP_CBOR_CREATE_CERT_REJECTED_LENGTH, + FP_CBOR_CREATE_CERT_REJECTED_TOPIC ) ); + } + } + + return status; +} +/*-----------------------------------------------------------*/ + +static bool unsubscribeFromCsrResponseTopics( void ) +{ + bool status; + + status = UnsubscribeFromTopic( FP_CBOR_CREATE_CERT_ACCEPTED_TOPIC, + FP_CBOR_CREATE_CERT_ACCEPTED_LENGTH ); + + if( status == false ) + { + LogError( ( "Failed to unsubscribe from fleet provisioning topic: %.*s.", + FP_CBOR_CREATE_CERT_ACCEPTED_LENGTH, + FP_CBOR_CREATE_CERT_ACCEPTED_TOPIC ) ); + } + + if( status == true ) + { + status = UnsubscribeFromTopic( FP_CBOR_CREATE_CERT_REJECTED_TOPIC, + FP_CBOR_CREATE_CERT_REJECTED_LENGTH ); + + if( status == false ) + { + LogError( ( "Failed to unsubscribe from fleet provisioning topic: %.*s.", + FP_CBOR_CREATE_CERT_REJECTED_LENGTH, + FP_CBOR_CREATE_CERT_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 the CSR. */ + char csr[ CSR_BUFFER_LENGTH ] = { 0 }; + size_t csrLength = 0; + /* Buffer for holding received certificate until it is saved. */ + char certificate[ CERT_BUFFER_LENGTH ]; + size_t certificateLength; + /* 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; + int demoRunCount = 0; + + /* Silence compiler warnings about unused variables. */ + ( void ) argc; + ( void ) argv; + + do + { + /* Initialize the buffer lengths to their max lengths. */ + certificateLength = CERT_BUFFER_LENGTH; + certificateIdLength = CERT_ID_BUFFER_LENGTH; + ownershipTokenLength = OWNERSHIP_TOKEN_BUFFER_LENGTH; + + /**** 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 CreateCertificatefromCsr. */ + + 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, + CLAIM_CERT_PATH, + CLAIM_PRIVATE_KEY_PATH ); + + if( status == false ) + { + LogError( ( "Failed to establish MQTT session." ) ); + } + else + { + LogInfo( ( "Established connection with claim credentials." ) ); + connectionEstablished = true; + } + } + + /**** Call the CreateCertificateFromCsr API ***************************/ + + /* We use the CreateCertificatefromCsr API to obtain a client certificate + * for a key on the device by means of sending a certificate signing + * request (CSR). */ + if( status == true ) + { + /* Subscribe to the CreateCertificateFromCsr accepted and rejected + * topics. In this demo we use CBOR encoding for the payloads, + * so we use the CBOR variants of the topics. */ + status = subscribeToCsrResponseTopics(); + } + + if( status == true ) + { + if( Mbedtls_GenerateECKey( CLIENT_PRIVATE_KEY_PATH ) != MBEDTLS_SUCCESS ) + { + status = false; + } + } + + if( status == true ) + { + if( Mbedtls_GenerateCSR( CLIENT_PRIVATE_KEY_PATH, csr, CSR_BUFFER_LENGTH ) != MBEDTLS_SUCCESS ) + { + status = false; + } + } + + if( status == true ) + { + /* Create the request payload containing the CSR to publish to the + * CreateCertificateFromCsr APIs. */ + status = generateCsrRequest( payloadBuffer, + NETWORK_BUFFER_SIZE, + csr, + csrLength, + &payloadLength ); + } + + if( status == true ) + { + /* Publish the CSR to the CreateCertificatefromCsr API. */ + status = PublishToTopic( FP_CBOR_CREATE_CERT_PUBLISH_TOPIC, + FP_CBOR_CREATE_CERT_PUBLISH_LENGTH, + ( char * ) payloadBuffer, + payloadLength ); + + if( status == false ) + { + LogError( ( "Failed to publish to fleet provisioning topic: %.*s.", + FP_CBOR_CREATE_CERT_PUBLISH_LENGTH, + FP_CBOR_CREATE_CERT_PUBLISH_TOPIC ) ); + } + } + + if( status == true ) + { + /* Get the response to the CreateCertificatefromCsr request. */ + status = waitForResponse(); + } + + if( status == true ) + { + /* From the response, extract the certificate, certificate ID, and + * certificate ownership token. */ + status = parseCsrResponse( payloadBuffer, + payloadLength, + certificate, + &certificateLength, + certificateId, + &certificateIdLength, + ownershipToken, + &ownershipTokenLength ); + + if( status == true ) + { + LogInfo( ( "Received certificate with Id: %.*s", ( int ) certificateIdLength, certificateId ) ); + } + } + + if( status == true ) + { + FILE * pFile = fopen(CLIENT_CERT_PATH, "w"); + + if( pFile == NULL ) + { + status = false; + } + else + { + size_t itemsWritten = fwrite( certificate, certificateLength, 1, pFile ); + if( itemsWritten != 1 ) + { + status = false; + } + } + } + + if( status == true ) + { + /* Unsubscribe from the CreateCertificateFromCsr topics. */ + status = unsubscribeFromCsrResponseTopics(); + } + + /**** 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, + CLIENT_CERT_PATH, + CLIENT_PRIVATE_KEY_PATH ); + + 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; + } + + /**** 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 + } + + return ( status == true ) ? EXIT_SUCCESS : EXIT_FAILURE; +} +/*-----------------------------------------------------------*/ diff --git a/demos/fleet_provisioning/fleet_provisioning_csr/mqtt_operations.c b/demos/fleet_provisioning/fleet_provisioning_csr/mqtt_operations.c new file mode 100644 index 0000000000..87c9b43f40 --- /dev/null +++ b/demos/fleet_provisioning/fleet_provisioning_csr/mqtt_operations.c @@ -0,0 +1,1114 @@ +/* + * 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_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 +{ + MbedtlsContext_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 MbedtlsContext_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 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] pClientCertPath Path to the client certificate to use. + * @param[in] pPrivateKeyPath Path to the private key of the client certificate. + * + * @return false on failure; true on successful connection. + */ +static bool connectToBrokerWithBackoffRetries( NetworkContext_t * pNetworkContext, + const char * pClientCertPath, + const char * pPrivateKeyPath ); + +/** + * @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 bool connectToBrokerWithBackoffRetries( NetworkContext_t * pNetworkContext, + const char * pClientCertPath, + const char * pPrivateKeyPath ) +{ + bool returnStatus = false; + BackoffAlgorithmStatus_t backoffAlgStatus = BackoffAlgorithmSuccess; + MbedtlsStatus_t tlsStatus = MBEDTLS_SUCCESS; + BackoffAlgorithmContext_t reconnectParams; + MbedtlsCredentials_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.pClientCertPath = pClientCertPath; + tlsCredentials.pPrivateKeyPath = pPrivateKeyPath; + + /* 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_Connect( pNetworkContext, + AWS_IOT_ENDPOINT, + AWS_MQTT_PORT, + &tlsCredentials, + TRANSPORT_SEND_RECV_TIMEOUT_MS ); + + if( tlsStatus == MBEDTLS_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, ( uint32_t ) rand(), &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_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, + char * pClientCertPath, + char * pPrivateKeyPath ) +{ + bool returnStatus = false; + MQTTStatus_t mqttStatus; + MQTTConnectInfo_t connectInfo; + MQTTFixedBuffer_t networkBuffer; + TransportInterface_t transport = { NULL }; + 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, + pClientCertPath, + pPrivateKeyPath ); + + 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_Send; + transport.recv = Mbedtls_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. */ + + connectInfo.cleanSession = false; + + /* 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_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_csr/mqtt_operations.h b/demos/fleet_provisioning/fleet_provisioning_csr/mqtt_operations.h new file mode 100644 index 0000000000..bdd15eed94 --- /dev/null +++ b/demos/fleet_provisioning/fleet_provisioning_csr/mqtt_operations.h @@ -0,0 +1,109 @@ +/* + * 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" + +/** + * @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] pClientCertPath The client certificate PKCS #11 label to use. + * @param[in] pPrivateKeyPath 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, + char * pClientCertPath, + char * pPrivateKeyPath ); + +/** + * @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/libraries/CMakeLists.txt b/libraries/CMakeLists.txt index 05a9ffdf93..063d501953 100644 --- a/libraries/CMakeLists.txt +++ b/libraries/CMakeLists.txt @@ -15,7 +15,6 @@ endmacro() # Clone the submodules only if this is a Git repo. if( EXISTS ${ROOT_DIR}/.git ) clone_path( ${MODULES_DIR} ) - clone_path( ${MODULES_DIR}/standard/corePKCS11 "--checkout" ) endif() # Add build configuration for all 3rd party modules. diff --git a/manifest.yml b/manifest.yml index 005ca373b6..1f1bd57725 100644 --- a/manifest.yml +++ b/manifest.yml @@ -55,12 +55,6 @@ dependencies: type: "git" url: "https://github.com/FreeRTOS/backoffAlgorithm" path: "libraries/standard/backoffAlgorithm" - - name: "corePKCS11" - version: "c671c11f2de13c31e8eb9563858fb513b4c5b678" - repository: - type: "git" - url: "https://github.com/FreeRTOS/corePKCS11" - path: "libraries/standard/corePKCS11" - name: "Fleet-Provisioning-for-AWS-IoT-embedded-sdk" version: "8ce2b28325efb917c2e357aed2361e3fa6162ecf" repository: diff --git a/platform/CMakeLists.txt b/platform/CMakeLists.txt index 6f1e95faf9..5847dfb06c 100644 --- a/platform/CMakeLists.txt +++ b/platform/CMakeLists.txt @@ -1,7 +1,7 @@ # For static library builds, link against static library of OpenSSL -if(NOT BUILD_SHARED_LIBS) - set( OPENSSL_USE_STATIC_LIBS TRUE ) -endif() +# if(NOT BUILD_SHARED_LIBS) +# set( OPENSSL_USE_STATIC_LIBS TRUE ) +# endif() find_package(OpenSSL) # Verify the minimum OpenSSL version required if( ${OPENSSL_FOUND} AND ( OPENSSL_VERSION MATCHES "0.9$" OR OPENSSL_VERSION MATCHES "1.0$" ) ) diff --git a/platform/posix/posixFilePaths.cmake b/platform/posix/posixFilePaths.cmake index 732a18116a..b9cc0cbbd6 100644 --- a/platform/posix/posixFilePaths.cmake +++ b/platform/posix/posixFilePaths.cmake @@ -18,6 +18,10 @@ set( OPENSSL_TRANSPORT_SOURCES ${CMAKE_CURRENT_LIST_DIR}/transport/src/openssl_posix.c ) # MbedTLS transport source files. +set( MBEDTLS_TRANSPORT_SOURCES + ${CMAKE_CURRENT_LIST_DIR}/transport/src/mbedtls_posix.c ) + +# MbedTLS corePKCS11 transport source files. set( MBEDTLS_PKCS11_TRANSPORT_SOURCES ${CMAKE_CURRENT_LIST_DIR}/transport/src/mbedtls_pkcs11_posix.c ) diff --git a/platform/posix/transport/CMakeLists.txt b/platform/posix/transport/CMakeLists.txt index f5ee9ddbb9..192db642a0 100644 --- a/platform/posix/transport/CMakeLists.txt +++ b/platform/posix/transport/CMakeLists.txt @@ -40,40 +40,25 @@ target_link_libraries( openssl_posix # requires explicit linking. ${CMAKE_DL_LIBS} ) -# 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 - "${COREPKCS11_LOCATION}/source/portable/os/posix/core_pkcs11_pal.c" - "${COREPKCS11_LOCATION}/source/portable/os/core_pkcs11_pal_utils.c" - "${CORE_PKCS11_3RDPARTY_LOCATION}/mbedtls_utils/mbedtls_utils.c" +target_include_directories( openssl_posix + PUBLIC + ${OPENSSL_INCLUDE_DIR} ) -# Create target for POSIX implementation of MbedTLS transport with PKCS #11. -add_library( transport_mbedtls_pkcs11_posix - ${MBEDTLS_PKCS11_TRANSPORT_SOURCES} - ${PKCS_SOURCES} ) +# Create target for POSIX implementation of MbedTLS transport +add_library( transport_mbedtls_posix + ${MBEDTLS_TRANSPORT_SOURCES} ) -target_link_libraries( transport_mbedtls_pkcs11_posix +target_link_libraries( transport_mbedtls_posix PRIVATE - mbedtls ) + mbedtls ) target_include_directories( - transport_mbedtls_pkcs11_posix + transport_mbedtls_posix PUBLIC ${COMMON_TRANSPORT_INCLUDE_PUBLIC_DIRS} ${LOGGING_INCLUDE_DIRS} ${TRANSPORT_INTERFACE_INCLUDE_DIR} - ${PKCS_INCLUDE_PUBLIC_DIRS} - "${COREPKCS11_LOCATION}/source/portable/os" - ${CORE_PKCS11_3RDPARTY_LOCATION}/pkcs11 - ${DEMOS_DIR}/pkcs11/common/include - PRIVATE - ${CORE_PKCS11_3RDPARTY_LOCATION}/mbedtls_utils ) # Install transport implementations as both shared and static libraries. @@ -82,7 +67,7 @@ if(INSTALL_PLATFORM_ABSTRACTIONS) openssl_posix plaintext_posix sockets_posix - transport_mbedtls_pkcs11_posix + transport_mbedtls_posix LIBRARY DESTINATION "${CSDK_LIB_INSTALL_PATH}" ARCHIVE DESTINATION "${CSDK_LIB_INSTALL_PATH}") endif() diff --git a/platform/posix/transport/include/mbedtls_posix.h b/platform/posix/transport/include/mbedtls_posix.h new file mode 100644 index 0000000000..943c72bc12 --- /dev/null +++ b/platform/posix/transport/include/mbedtls_posix.h @@ -0,0 +1,239 @@ +/* + * 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 MBEDTLS_POSIX_H_ +#define MBEDTLS_POSIX_H_ + +/** + * @file mbedtls_posix.h + * + * @brief Implementation for the transport interface using a mutually + * authenticated TLS connection with MbedTLS for TLS and core for secure + * credential management. + */ + +/**************************************************/ +/******* DO NOT CHANGE the following order ********/ +/**************************************************/ + +/* Logging related header files are required to be included 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 transport interface implementation which uses + * MbedTLS and Sockets. */ +#ifndef LIBRARY_LOG_NAME + #define LIBRARY_LOG_NAME "Transport_MbedTLS_" +#endif +#ifndef LIBRARY_LOG_LEVEL + #define LIBRARY_LOG_LEVEL LOG_WARN +#endif + +#include "logging_stack.h" + +/************ End of logging configuration ****************/ + +/* *INDENT-OFF* */ +#ifdef __cplusplus + extern "C" { +#endif +/* *INDENT-ON* */ + +/* Standard includes. */ +#include + +#define MBEDTLS_ALLOW_PRIVATE_ACCESS + +/* MbedTLS includes. */ +#include "mbedtls/net_sockets.h" +#include "mbedtls/ssl.h" +#include "mbedtls/pk.h" +#include "mbedtls/entropy.h" +#include "mbedtls/ctr_drbg.h" + +/* Transport interface include. */ +#include "transport_interface.h" + +/** + * @brief Debug logging level to use for MbedTLS. + * + * @note The default value of 0 disables MbedTLS logging. + * See https://tls.mbed.org/api/debug_8h.html#a6629362e96b43725ace95c8ff01d9985 + * for valid values. + */ +#define MBEDTLS_DEBUG_LOG_LEVEL 4 + +/** + * @brief Context containing state for the MbedTLS and core based + * transport interface implementation. + * + * @note Applications using this transport interface implementation should use + * this struct as the #NetworkContext_t for the transport interface + * configuration passed to coreMQTT or coreHTTP. + */ +typedef struct MbedtlsContext +{ + mbedtls_net_context socketContext; /**< @brief MbedTLS socket context. */ + mbedtls_ssl_config config; /**< @brief SSL connection configuration. */ + mbedtls_ssl_context context; /**< @brief SSL connection context */ + mbedtls_x509_crt_profile certProfile; /**< @brief Certificate security profile for this connection. */ + mbedtls_x509_crt rootCa; /**< @brief Root CA certificate context. */ + mbedtls_x509_crt clientCert; /**< @brief Client certificate context. */ + mbedtls_pk_context privKey; /**< @brief Client private key context. */ + mbedtls_entropy_context entropyCtx; /**< @brief Entropy context */ + mbedtls_ctr_drbg_context ctrDrbgCtx; /**< @brief Random number generator context */ +} MbedtlsContext_t; + +/** + * @brief TLS Connect / Disconnect return status. + */ +typedef enum MbedtlsStatus +{ + MBEDTLS_SUCCESS = 0, /**< Function successfully completed. */ + MBEDTLS_INVALID_PARAMETER, /**< At least one parameter was invalid. */ + MBEDTLS_INSUFFICIENT_MEMORY, /**< Insufficient memory required to establish connection. */ + MBEDTLS_INVALID_CREDENTIALS, /**< Provided credentials were invalid. */ + MBEDTLS_HANDSHAKE_FAILED, /**< Performing TLS handshake with server failed. */ + MBEDTLS_INTERNAL_ERROR, /**< A call to a system API resulted in an internal error. */ + MBEDTLS_CONNECT_FAILURE /**< Initial connection to the server failed. */ +} MbedtlsStatus_t; + +/** + * @brief Contains the credentials necessary for tls connection setup. + */ +typedef struct MbedtlsCredentials +{ + /** + * @brief To use ALPN, set this to a NULL-terminated list of supported + * protocols in decreasing order of preference. + * + * See [this link] + * (https://aws.amazon.com/blogs/iot/mqtt-with-tls-client-authentication-on-port-443-why-it-is-useful-and-how-it-works/) + * for more information. + */ + const char ** pAlpnProtos; + + /** + * @brief Disable server name indication (SNI) for a TLS session. + */ + bool disableSni; + + const char * pRootCaPath; /**< @brief String representing a trusted server root certificate. */ + const char * pClientCertPath; /**< @brief String representing the PKCS #11 label for the client certificate. */ + const char * pPrivateKeyPath; /**< @brief String representing the PKCS #11 label for the private key. */ +} MbedtlsCredentials_t; + +/** + * @brief Sets up a mutually authenticated TLS session on top of a TCP + * connection using the MbedTLS library for TLS and the core library for + * credential management. + * + * @param[out] pNetworkContext The output parameter to return the created network context. + * @param[in] pHostName The hostname of the remote endpoint. + * @param[in] port The destination port. + * @param[in] pMbedtlsCredentials Credentials for the TLS connection. + * @param[in] recvTimeoutMs The timeout for socket receive operations. + * + * @note #recvTimeoutMs sets the maximum blocking time of the #Mbedtls_Recv function. + * + * @return #MBEDTLS_SUCCESS on success; + * #MBEDTLS_INSUFFICIENT_MEMORY, #MBEDTLS_INVALID_CREDENTIALS, + * #MBEDTLS_HANDSHAKE_FAILED, #MBEDTLS_INTERNAL_ERROR, + * or #MBEDTLS_CONNECT_FAILURE on failure. + */ +MbedtlsStatus_t Mbedtls_Connect( NetworkContext_t * pNetworkContext, + const char * pHostName, + uint16_t port, + const MbedtlsCredentials_t * pMbedtlsCredentials, + uint32_t recvTimeoutMs ); + +/** + * @brief Gracefully disconnect an established TLS connection. + * + * @param[in] pNetworkContext Network context. + */ +void Mbedtls_Disconnect( NetworkContext_t * pNetworkContext ); + +/** + * @brief Receives data over an established TLS session using the MbedTLS API. + * + * This function can be used as the #TransportInterface.recv implementation of + * the transport interface to receive data from the network. + * + * @param[in] pNetworkContext The network context created using Mbedtls_Connect API. + * @param[out] pBuffer Buffer to receive network data into. + * @param[in] bytesToRecv Number of bytes requested from the network. + * + * @return Number of bytes received if successful; negative value to indicate failure. + * A return value of zero represents that the receive operation can be retried. + */ +int32_t Mbedtls_Recv( NetworkContext_t * pNetworkContext, + void * pBuffer, + size_t bytesToRecv ); + +/** + * @brief Sends data over an established TLS session using the MbedTLS API. + * + * This function can be used as the #TransportInterface.send implementation of + * the transport interface to send data over the network. + * + * @param[in] pNetworkContext The network context created using Mbedtls_Connect API. + * @param[in] pBuffer Buffer containing the bytes to send over the network stack. + * @param[in] bytesToSend Number of bytes to send over the network. + * + * @return Number of bytes sent if successful; negative value on error. + * + * @note This function does not return zero value because it cannot be retried + * on send operation failure. + */ +int32_t Mbedtls_Send( NetworkContext_t * pNetworkContext, + const void * pBuffer, + size_t bytesToSend ); + +/** + * @brief Generate a new EC Private Key + * This function generates a new EC private key and writes the resulting key + * in DER format to the provided path. + * + * @param[in] pPrivateKeyPath Path to store the resulting private key. + * + * @return #MBEDTLS_SUCCESS on success + */ +MbedtlsStatus_t Mbedtls_GenerateECKey( const char * pPrivateKeyPath ); + + +MbedtlsStatus_t Mbedtls_GenerateCSR( const char * pPrivateKeyPath, + char * pCsrPemBuffer, + size_t csrPemBufferLength ); + +/* *INDENT-OFF* */ +#ifdef __cplusplus + } +#endif +/* *INDENT-ON* */ + +#endif /* ifndef MBEDTLS_POSIX_H_ */ diff --git a/platform/posix/transport/src/mbedtls_posix.c b/platform/posix/transport/src/mbedtls_posix.c new file mode 100644 index 0000000000..a3caec773b --- /dev/null +++ b/platform/posix/transport/src/mbedtls_posix.c @@ -0,0 +1,931 @@ +/* + * 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. + */ + +/* Standard includes. */ +#include +#include +#include +#include +#include + +/* TLS transport header. */ +#include "mbedtls_posix.h" + +/* MbedTLS includes. */ +#include "mbedtls/debug.h" +#include "mbedtls/error.h" +#include "mbedtls/x509_csr.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief Each compilation unit that consumes the NetworkContext must define it. + * It should contain a single pointer as seen below whenever the header file + * of this transport implementation is included to your project. + * + * @note When using multiple transports in the same compilation unit, + * define this pointer as void *. + */ +struct NetworkContext +{ + MbedtlsContext_t * pParams; +}; + +/*-----------------------------------------------------------*/ + +/** + * @brief Represents string to be logged when mbedTLS returned error + * does not contain a high-level code. + */ +static const char * pNoHighLevelMbedTlsCodeStr = ""; + +/** + * @brief Represents string to be logged when mbedTLS returned error + * does not contain a low-level code. + */ +static const char * pNoLowLevelMbedTlsCodeStr = ""; + +/** + * @brief Utility for converting the high-level code in an mbedTLS error to string, + * if the code-contains a high-level code; otherwise, using a default string. + */ +#define mbedtlsHighLevelCodeOrDefault( mbedTlsCode ) \ + ( mbedtls_high_level_strerr( mbedTlsCode ) != NULL ) ? \ + mbedtls_high_level_strerr( mbedTlsCode ) : pNoHighLevelMbedTlsCodeStr + +/** + * @brief Utility for converting the level-level code in an mbedTLS error to string, + * if the code-contains a level-level code; otherwise, using a default string. + */ +#define mbedtlsLowLevelCodeOrDefault( mbedTlsCode ) \ + ( mbedtls_low_level_strerr( mbedTlsCode ) != NULL ) ? \ + mbedtls_low_level_strerr( mbedTlsCode ) : pNoLowLevelMbedTlsCodeStr + +/*-----------------------------------------------------------*/ + +/** + * @brief Initialize the MbedTLS structures in a network connection. + * + * @param[in] pContext The SSL context to initialize. + */ +static void contextInit( MbedtlsContext_t * pContext ); + +/** + * @brief Free the MbedTLS structures in a network connection. + * + * @param[in] pContext The SSL context to free. + */ +static void contextFree( MbedtlsContext_t * pContext ); + +/** + * @brief Configure MbedTLS for TLS on a TCP connection using PKCS #11 for the + * client credentials. + * + * @param[in] pMbedtlsContext Network context. + * @param[in] pHostName Remote host name, used for server name indication. + * @param[in] pMbedtlsCredentials TLS setup parameters. + * @param[in] recvTimeoutMs Receive timeout for network socket. + * + * @return #MBEDTLS_SUCCESS, #MBEDTLS_INSUFFICIENT_MEMORY, #MBEDTLS_INVALID_CREDENTIALS, + * #MBEDTLS_HANDSHAKE_FAILED, or #MBEDTLS_INTERNAL_ERROR. + */ +static MbedtlsStatus_t configureMbedtls( MbedtlsContext_t * pMbedtlsContext, + const char * pHostName, + const MbedtlsCredentials_t * pMbedtlsCredentials, + uint32_t recvTimeoutMs ); + +/** + * @brief Configure the client and Root CA in the MbedTLS SSL context. + * + * @param[in] pMbedtlsContext Network context. + * @param[in] pMbedtlsCredentials TLS setup parameters. + * + * @return #MBEDTLS_SUCCESS on success, + * #MBEDTLS_INVALID_CREDENTIALS on error. + */ +static MbedtlsStatus_t configureMbedtlsCertificates( MbedtlsContext_t * pMbedtlsContext, + const MbedtlsCredentials_t * pMbedtlsCredentials ); + +/** + * @brief Configure the SNI and ALPN in the MbedTLS SSL context. + * + * @param[in] pMbedtlsContext Network context. + * @param[in] pMbedtlsCredentials TLS setup parameters. + * @param[in] pHostName Remote host name, used for server name indication. + * + * @return #MBEDTLS_SUCCESS on success, + * #MBEDTLS_INVALID_CREDENTIALS on error. + */ +static MbedtlsStatus_t configureMbedtlsSniAlpn( MbedtlsContext_t * pMbedtlsContext, + const MbedtlsCredentials_t * pMbedtlsCredentials, + const char * pHostName ); + +/** + * @brief Configure the Maximum Fragment Length in the MbedTLS SSL context. + * + * @param[in] pMbedtlsContext Network context. + * + * @return #MBEDTLS_SUCCESS on success, + * #MBEDTLS_INVALID_CREDENTIALS on error. + */ +static MbedtlsStatus_t configureMbedtlsFragmentLength( MbedtlsContext_t * pMbedtlsContext ); + +/*-----------------------------------------------------------*/ + +static void contextInit( MbedtlsContext_t * pContext ) +{ + assert( pContext != NULL ); + psa_crypto_init(); + + mbedtls_net_init( &( pContext->socketContext ) ); + mbedtls_ssl_init( &( pContext->context ) ); + mbedtls_ssl_config_init( &( pContext->config ) ); + mbedtls_x509_crt_init( &( pContext->rootCa ) ); + mbedtls_x509_crt_init( &( pContext->clientCert ) ); + mbedtls_pk_init( &( pContext->privKey ) ); + mbedtls_entropy_init( &( pContext->entropyCtx ) ); + mbedtls_ctr_drbg_init( &( pContext->ctrDrbgCtx ) ); +} +/*-----------------------------------------------------------*/ + +static void contextFree( MbedtlsContext_t * pContext ) +{ + if( pContext != NULL ) + { + mbedtls_net_free( &( pContext->socketContext ) ); + mbedtls_ssl_free( &( pContext->context ) ); + mbedtls_ssl_config_free( &( pContext->config ) ); + mbedtls_x509_crt_free( &( pContext->rootCa ) ); + mbedtls_x509_crt_free( &( pContext->clientCert ) ); + mbedtls_pk_free( &( pContext->privKey ) ); + mbedtls_entropy_free( &( pContext->entropyCtx ) ); + mbedtls_ctr_drbg_free( &( pContext->ctrDrbgCtx ) ); + } +} + +/*-----------------------------------------------------------*/ + +static void mbedtlsDebugPrint( void * ctx, + int level, + const char * pFile, + int line, + const char * pStr ) +{ + /* Unused parameters. */ + ( void ) ctx; + ( void ) pFile; + ( void ) line; + + /* Send the debug string to the portable logger. */ + fprintf( stderr, "mbedTLS: |%d| %s", level, pStr ); +} + +/*-----------------------------------------------------------*/ + +static MbedtlsStatus_t configureMbedtls( MbedtlsContext_t * pMbedtlsContext, + const char * pHostName, + const MbedtlsCredentials_t * pMbedtlsCredentials, + uint32_t recvTimeoutMs ) +{ + MbedtlsStatus_t returnStatus = MBEDTLS_SUCCESS; + int mbedtlsError = 0; + + assert( pMbedtlsContext != NULL ); + assert( pHostName != NULL ); + assert( pMbedtlsCredentials != NULL ); + assert( pMbedtlsCredentials->pRootCaPath != NULL ); + + /* Initialize the MbedTLS context structures. */ + contextInit( pMbedtlsContext ); + + mbedtls_ctr_drbg_set_prediction_resistance( &( pMbedtlsContext->ctrDrbgCtx ), + MBEDTLS_CTR_DRBG_PR_ON ); + + mbedtlsError = mbedtls_ctr_drbg_seed( &( pMbedtlsContext->ctrDrbgCtx ), + mbedtls_entropy_func, + &( pMbedtlsContext->entropyCtx ), + NULL, + 0 ); + + if( mbedtlsError != 0 ) + { + LogError( ( "Failed to seed mbedtls ctr-drgb random number generator: mbedTLSError= %s : %s.", + mbedtlsHighLevelCodeOrDefault( mbedtlsError ), + mbedtlsLowLevelCodeOrDefault( mbedtlsError ) ) ); + + returnStatus = MBEDTLS_INTERNAL_ERROR; + } + + if( returnStatus == MBEDTLS_SUCCESS ) + { + mbedtlsError = mbedtls_ssl_config_defaults( &( pMbedtlsContext->config ), + MBEDTLS_SSL_IS_CLIENT, + MBEDTLS_SSL_TRANSPORT_STREAM, + MBEDTLS_SSL_PRESET_DEFAULT ); + + + if( mbedtlsError != 0 ) + { + LogError( ( "Failed to set default SSL configuration: mbedTLSError= %s : %s.", + mbedtlsHighLevelCodeOrDefault( mbedtlsError ), + mbedtlsLowLevelCodeOrDefault( mbedtlsError ) ) ); + + /* Per MbedTLS docs, mbedtls_ssl_config_defaults only fails on memory allocation. */ + returnStatus = MBEDTLS_INSUFFICIENT_MEMORY; + } + } + + if( returnStatus == MBEDTLS_SUCCESS ) + { + /* Set up the certificate security profile, starting from the default value. */ + pMbedtlsContext->certProfile = mbedtls_x509_crt_profile_default; + + /* Set SSL authmode and the RNG context. */ + mbedtls_ssl_conf_authmode( &( pMbedtlsContext->config ), + MBEDTLS_SSL_VERIFY_REQUIRED ); + + mbedtls_ssl_conf_rng( &( pMbedtlsContext->config ), + mbedtls_ctr_drbg_random, + &( pMbedtlsContext->ctrDrbgCtx ) ); + + mbedtls_ssl_conf_cert_profile( &( pMbedtlsContext->config ), + &( pMbedtlsContext->certProfile ) ); + + mbedtls_ssl_conf_read_timeout( &( pMbedtlsContext->config ), + recvTimeoutMs ); + + mbedtls_ssl_conf_dbg( &pMbedtlsContext->config, + mbedtlsDebugPrint, + NULL ); + + mbedtls_debug_set_threshold( MBEDTLS_DEBUG_LOG_LEVEL ); + + returnStatus = configureMbedtlsCertificates( pMbedtlsContext, + pMbedtlsCredentials ); + } + + if( returnStatus == MBEDTLS_SUCCESS ) + { + returnStatus = configureMbedtlsSniAlpn( pMbedtlsContext, pMbedtlsCredentials, pHostName ); + } + + if( returnStatus == MBEDTLS_SUCCESS ) + { + /* Initialize the MbedTLS secured connection context. */ + mbedtlsError = mbedtls_ssl_setup( &( pMbedtlsContext->context ), + &( pMbedtlsContext->config ) ); + + if( mbedtlsError != 0 ) + { + LogError( ( "Failed to set up MbedTLS SSL context: mbedTLSError= %s : %s.", + mbedtlsHighLevelCodeOrDefault( mbedtlsError ), + mbedtlsLowLevelCodeOrDefault( mbedtlsError ) ) ); + returnStatus = MBEDTLS_INTERNAL_ERROR; + } + } + + if( returnStatus == MBEDTLS_SUCCESS ) + { + /* Set the underlying IO for the TLS connection. */ + mbedtls_ssl_set_bio( &( pMbedtlsContext->context ), + ( void * ) &( pMbedtlsContext->socketContext ), + mbedtls_net_send, + mbedtls_net_recv, + mbedtls_net_recv_timeout ); + + returnStatus = configureMbedtlsFragmentLength( pMbedtlsContext ); + } + + if( returnStatus != MBEDTLS_SUCCESS ) + { + contextFree( pMbedtlsContext ); + } + else + { + LogDebug( ( "Configured MbedTLS context." ) ); + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +static MbedtlsStatus_t configureMbedtlsCertificates( MbedtlsContext_t * pMbedtlsContext, + const MbedtlsCredentials_t * pMbedtlsCredentials ) + +{ + MbedtlsStatus_t returnStatus = MBEDTLS_SUCCESS; + int mbedtlsError = 0; + + assert( pMbedtlsContext != NULL ); + assert( pMbedtlsCredentials != NULL ); + assert( pMbedtlsCredentials->pRootCaPath != NULL ); + + /* Parse the server root CA certificate into the SSL context. */ + mbedtlsError = mbedtls_x509_crt_parse_file( &( pMbedtlsContext->rootCa ), + pMbedtlsCredentials->pRootCaPath ); + + if( mbedtlsError != 0 ) + { + LogError( ( "Failed to parse server root CA certificate: mbedTLSError= %s : %s.", + mbedtlsHighLevelCodeOrDefault( mbedtlsError ), + mbedtlsLowLevelCodeOrDefault( mbedtlsError ) ) ); + returnStatus = MBEDTLS_INVALID_CREDENTIALS; + } + else + { + mbedtls_ssl_conf_ca_chain( &( pMbedtlsContext->config ), + &( pMbedtlsContext->rootCa ), + NULL ); + + mbedtlsError = mbedtls_pk_parse_keyfile( + &( pMbedtlsContext->privKey ), + pMbedtlsCredentials->pPrivateKeyPath, + NULL, + mbedtls_ctr_drbg_random, + &( pMbedtlsContext->ctrDrbgCtx ) + ); + + if( mbedtlsError != 0 ) + { + LogError( ( "Failed to load private key from path %s", pMbedtlsCredentials->pPrivateKeyPath ) ); + returnStatus = MBEDTLS_INVALID_CREDENTIALS; + } + } + + if( returnStatus == MBEDTLS_SUCCESS ) + { + /* Setup the client certificate. */ + mbedtlsError = mbedtls_x509_crt_parse_file( + &( pMbedtlsContext->clientCert ), + pMbedtlsCredentials->pClientCertPath + ); + + if( mbedtlsError != 0 ) + { + LogError( ( "Failed to load client certificate from file: %s.", pMbedtlsCredentials->pClientCertPath ) ); + returnStatus = MBEDTLS_INVALID_CREDENTIALS; + } + } + + if( returnStatus == MBEDTLS_SUCCESS ) + { + ( void ) mbedtls_ssl_conf_own_cert( &( pMbedtlsContext->config ), + &( pMbedtlsContext->clientCert ), + &( pMbedtlsContext->privKey ) ); + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +static MbedtlsStatus_t configureMbedtlsSniAlpn( MbedtlsContext_t * pMbedtlsContext, + const MbedtlsCredentials_t * pMbedtlsCredentials, + const char * pHostName ) +{ + MbedtlsStatus_t returnStatus = MBEDTLS_SUCCESS; + int mbedtlsError = 0; + + assert( pMbedtlsContext != NULL ); + assert( pHostName != NULL ); + assert( pMbedtlsCredentials != NULL ); + assert( pMbedtlsCredentials->pRootCaPath != NULL ); + + if( pMbedtlsCredentials->pAlpnProtos != NULL ) + { + /* Include an application protocol list in the TLS ClientHello message. */ + mbedtlsError = mbedtls_ssl_conf_alpn_protocols( &( pMbedtlsContext->config ), + pMbedtlsCredentials->pAlpnProtos ); + + if( mbedtlsError != 0 ) + { + LogError( ( "Failed to configure ALPN protocol in MbedTLS: mbedTLSError= %s : %s.", + mbedtlsHighLevelCodeOrDefault( mbedtlsError ), + mbedtlsLowLevelCodeOrDefault( mbedtlsError ) ) ); + returnStatus = MBEDTLS_INTERNAL_ERROR; + } + } + + /* Enable SNI if requested. */ + if( ( returnStatus == MBEDTLS_SUCCESS ) && + ( pMbedtlsCredentials->disableSni == false ) ) + { + mbedtlsError = mbedtls_ssl_set_hostname( &( pMbedtlsContext->context ), + pHostName ); + + if( mbedtlsError != 0 ) + { + LogError( ( "Failed to set server name: mbedTLSError= %s : %s.", + mbedtlsHighLevelCodeOrDefault( mbedtlsError ), + mbedtlsLowLevelCodeOrDefault( mbedtlsError ) ) ); + returnStatus = MBEDTLS_INTERNAL_ERROR; + } + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +static MbedtlsStatus_t configureMbedtlsFragmentLength( MbedtlsContext_t * pMbedtlsContext ) +{ + MbedtlsStatus_t returnStatus = MBEDTLS_SUCCESS; + int mbedtlsError = 0; + + assert( pMbedtlsContext != NULL ); + + /* Set Maximum Fragment Length if enabled. */ + #ifdef MBEDTLS_SSL_MAX_FRAGMENT_LENGTH + + /* Enable the max fragment extension. 4096 bytes is currently the largest fragment size permitted. + * See RFC 6066 https://tools.ietf.org/html/rfc6066#page-8 for more information. + * + * Smaller values can be found in "mbedtls/include/ssl.h". + */ + mbedtlsError = mbedtls_ssl_conf_max_frag_len( &( pMbedtlsContext->config ), MBEDTLS_SSL_MAX_FRAG_LEN_4096 ); + + if( mbedtlsError != 0 ) + { + LogError( ( "Failed to maximum fragment length extension: mbedTLSError= %s : %s.", + mbedtlsHighLevelCodeOrDefault( mbedtlsError ), + mbedtlsLowLevelCodeOrDefault( mbedtlsError ) ) ); + returnStatus = MBEDTLS_INTERNAL_ERROR; + } + #endif /* ifdef MBEDTLS_SSL_MAX_FRAGMENT_LENGTH */ + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +MbedtlsStatus_t Mbedtls_Connect( NetworkContext_t * pNetworkContext, + const char * pHostName, + uint16_t port, + const MbedtlsCredentials_t * pMbedtlsCredentials, + uint32_t recvTimeoutMs ) +{ + MbedtlsContext_t * pMbedtlsContext = NULL; + MbedtlsStatus_t returnStatus = MBEDTLS_SUCCESS; + int mbedtlsError = 0; + char portStr[ 6 ] = { 0 }; + + if( ( pNetworkContext == NULL ) || + ( pNetworkContext->pParams == NULL ) || + ( pHostName == NULL ) || + ( pMbedtlsCredentials == NULL ) || + ( pMbedtlsCredentials->pRootCaPath == NULL ) || + ( pMbedtlsCredentials->pClientCertPath == NULL ) || + ( pMbedtlsCredentials->pPrivateKeyPath == NULL ) ) + { + LogError( ( "Invalid input parameter(s): Arguments cannot be NULL. pNetworkContext=%p, " + "pHostName=%p, pMbedtlsCredentials=%p.", + ( void * ) pNetworkContext, + ( const void * ) pHostName, + ( const void * ) pMbedtlsCredentials ) ); + returnStatus = MBEDTLS_INVALID_PARAMETER; + } + else + { + snprintf( portStr, sizeof( portStr ), "%u", port ); + pMbedtlsContext = pNetworkContext->pParams; + + /* Configure MbedTLS. */ + returnStatus = configureMbedtls( pMbedtlsContext, pHostName, pMbedtlsCredentials, recvTimeoutMs ); + } + + /* Establish a TCP connection with the server. */ + if( returnStatus == MBEDTLS_SUCCESS ) + { + mbedtlsError = mbedtls_net_connect( &( pMbedtlsContext->socketContext ), + pHostName, + portStr, + MBEDTLS_NET_PROTO_TCP ); + + if( mbedtlsError != 0 ) + { + LogError( ( "Failed to connect to %s with error %d.", pHostName, mbedtlsError ) ); + returnStatus = MBEDTLS_CONNECT_FAILURE; + } + } + + if( returnStatus == MBEDTLS_SUCCESS ) + { + /* Perform the TLS handshake. */ + do + { + mbedtlsError = mbedtls_ssl_handshake( &( pMbedtlsContext->context ) ); + } while( ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_READ ) || + ( mbedtlsError == MBEDTLS_ERR_SSL_WANT_WRITE ) ); + + if( ( mbedtlsError != 0 ) || ( mbedtls_ssl_get_verify_result( &( pMbedtlsContext->context ) ) != 0U ) ) + { + LogError( ( "Failed to perform TLS handshake: mbedTLSError= %s : %s.", + mbedtlsHighLevelCodeOrDefault( mbedtlsError ), + mbedtlsLowLevelCodeOrDefault( mbedtlsError ) ) ); + returnStatus = MBEDTLS_HANDSHAKE_FAILED; + } + } + + /* Clean up on failure. */ + if( returnStatus != MBEDTLS_SUCCESS ) + { + contextFree( pMbedtlsContext ); + } + else + { + LogInfo( ( "TLS Connection to %s established.", pHostName ) ); + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +void Mbedtls_Disconnect( NetworkContext_t * pNetworkContext ) +{ + MbedtlsContext_t * pMbedtlsContext = NULL; + int tlsStatus = 0; + + if( ( pNetworkContext != NULL ) && ( pNetworkContext->pParams != NULL ) ) + { + pMbedtlsContext = pNetworkContext->pParams; + /* Attempting to terminate TLS connection. */ + tlsStatus = mbedtls_ssl_close_notify( &( pMbedtlsContext->context ) ); + + if( tlsStatus == 0 ) + { + LogInfo( ( "Closing TLS connection: TLS close-notify sent." ) ); + } + else if( ( tlsStatus == MBEDTLS_ERR_SSL_WANT_READ ) || + ( tlsStatus == MBEDTLS_ERR_SSL_WANT_WRITE ) ) + { + /* WANT_READ or WANT_WRITE can be ignored. Logging for debugging purposes. */ + LogInfo( ( "TLS close-notify sent; " + "received %s as the TLS status which can be ignored for close-notify.", + ( tlsStatus == MBEDTLS_ERR_SSL_WANT_READ ) ? "WANT_READ" : "WANT_WRITE" ) ); + } + else + { + /* Ignore the WANT_READ or WANT_WRITE return values. */ + LogError( ( "Failed to send TLS close-notify: mbedTLSError= %s : %s.", + mbedtlsHighLevelCodeOrDefault( tlsStatus ), + mbedtlsLowLevelCodeOrDefault( tlsStatus ) ) ); + } + + /* Free contexts. */ + contextFree( pMbedtlsContext ); + } +} + +/*-----------------------------------------------------------*/ + +int Mbedtls_Recv( NetworkContext_t * pNetworkContext, + void * pBuffer, + size_t bytesToRecv ) +{ + MbedtlsContext_t * pMbedtlsContext = NULL; + int tlsStatus = 0; + + assert( ( pNetworkContext != NULL ) && ( pNetworkContext->pParams != NULL ) ); + + pMbedtlsContext = pNetworkContext->pParams; + tlsStatus = ( int ) mbedtls_ssl_read( &( pMbedtlsContext->context ), + pBuffer, + bytesToRecv ); + + if( ( tlsStatus == MBEDTLS_ERR_SSL_TIMEOUT ) || + ( tlsStatus == MBEDTLS_ERR_SSL_WANT_READ ) || + ( tlsStatus == MBEDTLS_ERR_SSL_WANT_WRITE ) ) + { + LogDebug( ( "Failed to read data. However, a read can be retried on this error. " + "mbedTLSError= %s : %s.", + mbedtlsHighLevelCodeOrDefault( tlsStatus ), + mbedtlsLowLevelCodeOrDefault( tlsStatus ) ) ); + + /* Mark these set of errors as a timeout. The libraries may retry read + * on these errors. */ + tlsStatus = 0; + } + else if( tlsStatus < 0 ) + { + LogError( ( "Failed to read data: mbedTLSError= %s : %s.", + mbedtlsHighLevelCodeOrDefault( tlsStatus ), + mbedtlsLowLevelCodeOrDefault( tlsStatus ) ) ); + } + else + { + /* Empty else marker. */ + } + + return tlsStatus; +} + +/*-----------------------------------------------------------*/ + +int Mbedtls_Send( NetworkContext_t * pNetworkContext, + const void * pBuffer, + size_t bytesToSend ) +{ + MbedtlsContext_t * pMbedtlsContext = NULL; + int tlsStatus = 0; + + assert( ( pNetworkContext != NULL ) && ( pNetworkContext->pParams != NULL ) ); + + pMbedtlsContext = pNetworkContext->pParams; + tlsStatus = ( int ) mbedtls_ssl_write( &( pMbedtlsContext->context ), + pBuffer, + bytesToSend ); + + if( ( tlsStatus == MBEDTLS_ERR_SSL_TIMEOUT ) || + ( tlsStatus == MBEDTLS_ERR_SSL_WANT_READ ) || + ( tlsStatus == MBEDTLS_ERR_SSL_WANT_WRITE ) ) + { + LogDebug( ( "Failed to send data. However, send can be retried on this error. " + "mbedTLSError= %s : %s.", + mbedtlsHighLevelCodeOrDefault( tlsStatus ), + mbedtlsLowLevelCodeOrDefault( tlsStatus ) ) ); + + /* Mark these set of errors as a timeout. The libraries may retry send + * on these errors. */ + tlsStatus = 0; + } + else if( tlsStatus < 0 ) + { + LogError( ( "Failed to send data: mbedTLSError= %s : %s.", + mbedtlsHighLevelCodeOrDefault( tlsStatus ), + mbedtlsLowLevelCodeOrDefault( tlsStatus ) ) ); + } + else + { + /* Empty else marker. */ + } + + return tlsStatus; +} + +/*-----------------------------------------------------------*/ + +static MbedtlsStatus_t writeKeyToFile( const char * pKeyPath, + mbedtls_pk_context * pPkCtx ) +{ + unsigned char pKeyBuffer[ 128 ]; + MbedtlsStatus_t status = MBEDTLS_SUCCESS; + int result = 0; + + /* Serialize the private to a buffer */ + result = mbedtls_pk_write_key_der( pPkCtx, + pKeyBuffer, + 128 ); + + if( result < 0 ) + { + LogError( ( "Failed to serialize EC key pair: result= %s : %s.", + mbedtlsHighLevelCodeOrDefault( result ), + mbedtlsLowLevelCodeOrDefault( result ) ) ); + + status = MBEDTLS_INTERNAL_ERROR; + } + else + { + FILE * pKeyFile; + unsigned char * pKeyStart = &( pKeyBuffer[ result ] ); + size_t derKeyLength = 128 - result; + + pKeyFile = fopen( pKeyPath, "wb" ); + if( pKeyFile == NULL ) + { + LogError( ( "Failed to open path: %s, result: %s.", + pKeyPath, + strerror( errno ) ) ); + + status = MBEDTLS_INVALID_PARAMETER; + } + else + { + if( fwrite( pKeyStart, derKeyLength, 1, pKeyFile ) != 1 ) + { + LogError( ( "Failed to write %lu bytes path: %s, result: %s.", + ( unsigned long ) derKeyLength, + pKeyPath, + strerror( errno ) ) ); + + status = MBEDTLS_INTERNAL_ERROR; + } + + if( fclose( pKeyFile ) != 0 ) + { + LogError( ( "Failed to close file path: %s, result: %s.", + pKeyPath, + strerror( errno ) ) ); + + status = MBEDTLS_INTERNAL_ERROR; + } + } + } + memset( pKeyBuffer, 0, 128 ); + + return status; +} + +/*-----------------------------------------------------------*/ + +MbedtlsStatus_t Mbedtls_GenerateECKey( const char * pPrivateKeyPath ) +{ + MbedtlsStatus_t status = MBEDTLS_SUCCESS; + mbedtls_entropy_context entropyCtx; + mbedtls_ctr_drbg_context ctrDrbgCtx; + mbedtls_pk_context pkCtx; + int result = 0; + + assert( pPrivateKeyPath != NULL ); + assert( access( pPrivateKeyPath, W_OK ) == 0 ); + + /* Initialize mbedtls entropy, prng, and pk contexts */ + mbedtls_entropy_init( &entropyCtx ); + mbedtls_ctr_drbg_init( &ctrDrbgCtx ); + mbedtls_pk_init( &pkCtx ); + + /* Seed the CTR-DRBG Pesudo Random Number Generator */ + mbedtls_ctr_drbg_set_prediction_resistance( &ctrDrbgCtx, MBEDTLS_CTR_DRBG_PR_ON ); + result = mbedtls_ctr_drbg_seed( &ctrDrbgCtx, + mbedtls_entropy_func, + &entropyCtx, + NULL, + 0 ); + if( result != 0 ) + { + LogError( ( "Failed to seed CTR-DRBG: result= %s : %s.", + mbedtlsHighLevelCodeOrDefault( result ), + mbedtlsLowLevelCodeOrDefault( result ) ) ); + status = MBEDTLS_INTERNAL_ERROR; + } + + if( status == MBEDTLS_SUCCESS ) + { + /* Setup / allocate the mbedtls PK context */ + result = mbedtls_pk_setup( &pkCtx, + mbedtls_pk_info_from_type( MBEDTLS_PK_ECKEY ) ); + + if( result != 0 ) + { + LogError( ( "Failed to setup mbedtls pk context: result= %s : %s.", + mbedtlsHighLevelCodeOrDefault( result ), + mbedtlsLowLevelCodeOrDefault( result ) ) ); + status = MBEDTLS_INTERNAL_ERROR; + } + } + + if( status == MBEDTLS_SUCCESS ) + { + /* Generate a new ECP key pair */ + result = mbedtls_ecp_gen_key( MBEDTLS_ECP_DP_SECP256R1, + mbedtls_pk_ec( pkCtx ), + mbedtls_ctr_drbg_random, + &ctrDrbgCtx ); + + if( result != 0 ) + { + LogError( ( "Failed to generate an EC key: result= %s : %s.", + mbedtlsHighLevelCodeOrDefault( result ), + mbedtlsLowLevelCodeOrDefault( result ) ) ); + status = MBEDTLS_INTERNAL_ERROR; + } + } + + if( status == MBEDTLS_SUCCESS ) + { + status = writeKeyToFile( pPrivateKeyPath, &pkCtx ); + } + + mbedtls_pk_free( &pkCtx ); + mbedtls_ctr_drbg_free( &ctrDrbgCtx ); + mbedtls_entropy_free( &entropyCtx ); + + return status; +} + +/*-----------------------------------------------------------*/ + +MbedtlsStatus_t Mbedtls_GenerateCSR( const char * pPrivateKeyPath, + char * pCsrPemBuffer, + size_t csrPemBufferLength ) +{ + MbedtlsStatus_t status = MBEDTLS_SUCCESS; + mbedtls_entropy_context entropyCtx; + mbedtls_ctr_drbg_context ctrDrbgCtx; + mbedtls_pk_context pkCtx; + mbedtls_x509write_csr csrCtx; + int result = 0; + + if( pPrivateKeyPath == NULL || pCsrPemBuffer == NULL ) + { + return MBEDTLS_INVALID_PARAMETER; + } + + /* Initialize mbedtls entropy, prng, pk, and csr contexts */ + mbedtls_entropy_init( &entropyCtx ); + mbedtls_ctr_drbg_init( &ctrDrbgCtx ); + mbedtls_pk_init( &pkCtx ); + mbedtls_x509write_csr_init( &csrCtx ); + + /* Seed the CTR-DRBG Pesudo Random Number Generator */ + mbedtls_ctr_drbg_set_prediction_resistance( &ctrDrbgCtx, MBEDTLS_CTR_DRBG_PR_ON ); + result = mbedtls_ctr_drbg_seed( &ctrDrbgCtx, + mbedtls_entropy_func, + &entropyCtx, + NULL, + 0 ); + if( result != 0 ) + { + LogError( ( "Failed to seed CTR-DRBG: result= %s : %s.", + mbedtlsHighLevelCodeOrDefault( result ), + mbedtlsLowLevelCodeOrDefault( result ) ) ); + status = MBEDTLS_INTERNAL_ERROR; + } + + if( status == MBEDTLS_SUCCESS ) + { + /* Load private key from file */ + result = mbedtls_pk_parse_keyfile( &pkCtx, + pPrivateKeyPath, + NULL, + mbedtls_ctr_drbg_random, + &ctrDrbgCtx ); + + if( result != 0 ) + { + LogError( ( "Failed to read key from file: %s, result= %s : %s.", + pPrivateKeyPath, + mbedtlsHighLevelCodeOrDefault( result ), + mbedtlsLowLevelCodeOrDefault( result ) ) ); + status = MBEDTLS_INTERNAL_ERROR; + } + } + + if( status == MBEDTLS_SUCCESS ) + { + /* Create CSR */ + mbedtls_x509write_csr_set_md_alg( &csrCtx, MBEDTLS_MD_SHA256 ); + mbedtls_x509write_csr_set_key( &csrCtx, &pkCtx ); + + result = mbedtls_x509write_csr_set_key_usage( &csrCtx, MBEDTLS_X509_KU_DIGITAL_SIGNATURE ); + + if( result == 0 ) + { + result = mbedtls_x509write_csr_set_ns_cert_type( &csrCtx, MBEDTLS_X509_NS_CERT_TYPE_SSL_CLIENT ); + } + + if( result == 0 ) + { + //TODO: Set this to device serial number / thing name + result = mbedtls_x509write_csr_set_subject_name( &csrCtx, "CN=MyDevice" ); + } + + if( result == 0 ) + { + result = mbedtls_x509write_csr_pem( &csrCtx, + ( unsigned char * ) pCsrPemBuffer, + csrPemBufferLength, + mbedtls_ctr_drbg_random, + &ctrDrbgCtx ); + } + + if( result < 0 ) + { + LogError( ( "Failed to generate CSR. result= %s : %s.", + mbedtlsHighLevelCodeOrDefault( result ), + mbedtlsLowLevelCodeOrDefault( result ) ) ); + status = MBEDTLS_INTERNAL_ERROR; + } + } + + mbedtls_x509write_csr_free( &csrCtx ); + mbedtls_pk_free( &pkCtx ); + mbedtls_ctr_drbg_free( &ctrDrbgCtx ); + mbedtls_entropy_free( &entropyCtx ); + + return status; +} + +/*-----------------------------------------------------------*/ + + diff --git a/tools/cmake/install.cmake b/tools/cmake/install.cmake index 157ebe8842..bbea2e38b8 100644 --- a/tools/cmake/install.cmake +++ b/tools/cmake/install.cmake @@ -9,7 +9,6 @@ set(FILEPATH_LOCATIONS ${MODULES_DIR}/standard/coreHTTP/httpFilePaths.cmake ${MODULES_DIR}/standard/coreJSON/jsonFilePaths.cmake ${MODULES_DIR}/standard/coreMQTT/mqttFilePaths.cmake - ${MODULES_DIR}/standard/corePKCS11/pkcsFilePaths.cmake ${PLATFORM_DIR}/posix/posixFilePaths.cmake ) @@ -30,23 +29,11 @@ set(LIBRARY_PREFIXES "OTA_MQTT" "BACKOFF_ALGORITHM" "HTTP" - "MQTT" - "PKCS") - -set(COREPKCS11_LOCATION "${MODULES_DIR}/standard/corePKCS11") -set(CORE_PKCS11_3RDPARTY_LOCATION "${COREPKCS11_LOCATION}/source/dependency/3rdparty") + "MQTT") # Define any extra sources or includes outside the standard, making sure to use the same prefix. set(MQTT_EXTRA_SOURCES ${MQTT_SERIALIZER_SOURCES}) -set(PKCS_EXTRA_SOURCES - "${COREPKCS11_LOCATION}/source/portable/os/posix/core_pkcs11_pal.c" - "${COREPKCS11_LOCATION}/source/portable/os/core_pkcs11_pal_utils.c" - "${CORE_PKCS11_3RDPARTY_LOCATION}/mbedtls_utils/mbedtls_utils.c") -set(PKCS_EXTRA_INCLUDE_PRIVATE_DIRS - PRIVATE - "${CORE_PKCS11_3RDPARTY_LOCATION}/mbedtls_utils" - "${COREPKCS11_LOCATION}/source/portable/os") set(OTA_BACKENDS "OTA_HTTP" "OTA_MQTT") foreach(ota_backend ${OTA_BACKENDS}) set("${ota_backend}_EXTRA_INCLUDE_PUBLIC_DIRS" @@ -106,13 +93,6 @@ foreach(library_prefix ${LIBRARY_PREFIXES}) PRIVATE ${${config_prefix}_CUSTOM_CONFIG_DIR}) else() target_compile_definitions("${library_name}" PRIVATE -D${config_prefix}_DO_NOT_USE_CUSTOM_CONFIG) - # PKCS11 requires a config so include the one from the demos by default. - if(${config_prefix} STREQUAL "PKCS") - target_include_directories("${library_name}" PRIVATE - ${DEMOS_DIR}/pkcs11/common/include - ${LOGGING_INCLUDE_DIRS}) - target_link_libraries("${library_name}" PRIVATE mbedtls ) - endif() endif() # Add public include directories to library target.