summaryrefslogtreecommitdiff
path: root/FreeRTOS-Plus/Demo/coreHTTP_Windows_Simulator/HTTP_S3_Download/DemoTasks/S3DownloadHTTPExample.c
diff options
context:
space:
mode:
Diffstat (limited to 'FreeRTOS-Plus/Demo/coreHTTP_Windows_Simulator/HTTP_S3_Download/DemoTasks/S3DownloadHTTPExample.c')
-rw-r--r--FreeRTOS-Plus/Demo/coreHTTP_Windows_Simulator/HTTP_S3_Download/DemoTasks/S3DownloadHTTPExample.c1217
1 files changed, 936 insertions, 281 deletions
diff --git a/FreeRTOS-Plus/Demo/coreHTTP_Windows_Simulator/HTTP_S3_Download/DemoTasks/S3DownloadHTTPExample.c b/FreeRTOS-Plus/Demo/coreHTTP_Windows_Simulator/HTTP_S3_Download/DemoTasks/S3DownloadHTTPExample.c
index 3ec4dbb69..8c5b66460 100644
--- a/FreeRTOS-Plus/Demo/coreHTTP_Windows_Simulator/HTTP_S3_Download/DemoTasks/S3DownloadHTTPExample.c
+++ b/FreeRTOS-Plus/Demo/coreHTTP_Windows_Simulator/HTTP_S3_Download/DemoTasks/S3DownloadHTTPExample.c
@@ -58,6 +58,7 @@
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
+#include <stdbool.h>
/* Kernel includes. */
#include "FreeRTOS.h"
@@ -75,16 +76,55 @@
/* Common HTTP demo utilities. */
#include "http_demo_utils.h"
+/* JSON API header. */
+#include "core_json.h"
+
+/* SIGV4 API header. */
+#include "sigv4.h"
+
+/* MBEDTLS API header. */
+#include "mbedtls/sha256.h"
+
/*------------- Demo configurations -------------------------*/
-/* Check that the root CA certificate is defined. */
-#ifndef democonfigROOT_CA_PEM
- #error "Please define democonfigROOT_CA_PEM in demo_config.h."
+/* Check that the root CA certificate for S3 is defined. */
+#ifndef democonfigS3_ROOT_CA_PEM
+ #error "Please define democonfigS3_ROOT_CA_PEM in demo_config.h."
+#endif
+
+/* Check that the root CA certificate for IoT credential provider is defined. */
+#ifndef democonfigIOT_CRED_PROVIDER_ROOT_CA_PEM
+ #error "Please define democonfigIOT_CRED_PROVIDER_ROOT_CA_PEM in demo_config.h."
+#endif
+
+/* Check that AWS IOT Thing Name is defined. */
+#ifndef democonfigIOT_THING_NAME
+ #error "Please define the democonfigIOT_THING_NAME macro in demo_config.h."
+#endif
+
+/* Check that AWS IOT credential provider endpoint is defined. */
+#ifndef democonfigIOT_CREDENTIAL_PROVIDER_ENDPOINT
+ #error "Please define the democonfigIOT_CREDENTIAL_PROVIDER_ENDPOINT macro in demo_config.h."
+#endif
+
+/* Check that AWS IOT credential provider role is defined. */
+#ifndef democonfigIOT_CREDENTIAL_PROVIDER_ROLE
+ #error "Please define the democonfigIOT_CREDENTIAL_PROVIDER_ROLE macro in demo_config.h."
+#endif
+
+/* Check that AWS S3 BUCKET NAME is defined. */
+#ifndef democonfigS3_BUCKET_NAME
+ #error "Please define the democonfigS3_BUCKET_NAME macro in demo_config.h."
+#endif
+
+/* Check that AWS S3 OBJECT NAME is defined. */
+#ifndef democonfigS3_OBJECT_NAME
+ #error "Please define the democonfigS3_OBJECT_NAME macro in demo_config.h."
#endif
-/* Check that the pre-signed GET URL is defined. */
-#ifndef democonfigS3_PRESIGNED_GET_URL
- #error "Please define democonfigS3_PRESIGNED_GET_URL in demo_config.h."
+/* Check that AWS S3 BUCKET REGION is defined. */
+#ifndef democonfigS3_BUCKET_REGION
+ #error "Please define the democonfigS3_BUCKET_REGION macro in which bucket resides in demo_config.h."
#endif
/* Check that a TLS port for AWS IoT Core is defined. */
@@ -108,11 +148,6 @@
#endif
/**
- * @brief Length of the pre-signed GET URL defined in demo_config.h.
- */
-#define httpexampleS3_PRESIGNED_GET_URL_LENGTH ( sizeof( democonfigS3_PRESIGNED_GET_URL ) - 1 )
-
-/**
* @brief The length of the HTTP GET method.
*/
#define httpexampleHTTP_METHOD_GET_LENGTH ( sizeof( HTTP_METHOD_GET ) - 1 )
@@ -133,6 +168,101 @@
#define httpexampleHTTP_STATUS_CODE_PARTIAL_CONTENT 206
/**
+ * @brief Buffer Length for storing the AWS IoT Credentials retrieved from
+ * AWS IoT credential provider which includes the following:
+ * 1. Access Key ID
+ * 2. Secret Access key
+ * 3. Session Token
+ * 4. Expiration Date
+ */
+#define CREDENTIAL_BUFFER_LENGTH 1500U
+
+/**
+ * @brief AWS Service name to send HTTP request using SigV4 library.
+ */
+#define AWS_S3_SERVICE_NAME "s3"
+
+/**
+ * @brief AWS S3 Endpoint.
+ */
+#define AWS_S3_ENDPOINT \
+ democonfigS3_BUCKET_NAME "." AWS_S3_SERVICE_NAME "." \
+ democonfigS3_BUCKET_REGION ".amazonaws.com"
+
+/**
+ * @brief AWS S3 URI PATH.
+ */
+#define AWS_S3_URI_PATH \
+ "/" democonfigS3_OBJECT_NAME
+
+/**
+ * @brief The URI path for HTTP requests to AWS IoT Credential provider.
+ */
+#define AWS_IOT_CREDENTIAL_PROVIDER_URI_PATH \
+ "/role-aliases/" \
+ democonfigIOT_CREDENTIAL_PROVIDER_ROLE "/credentials"
+
+/**
+ * @brief HTTP header name for specifying the IOT Thing resource name in request to AWS S3.
+ */
+#define AWS_IOT_THING_NAME_HEADER_FIELD "x-amz-iot-thing-name"
+
+/**
+ * @brief Field name of the HTTP date header to read from the AWS IOT credential provider server response.
+ */
+#define AWS_IOT_CRED_PROVIDER_RESPONSE_DATE_HEADER "date"
+
+/**
+ * @brief Field name of the HTTP Authorization header to add to the request headers.
+ */
+#define SIGV4_AUTH_HEADER_FIELD_NAME "Authorization"
+
+/**
+ * @brief IS8601 formatted date length.
+ */
+#define SIGV4_ISO_STRING_LEN 16U
+
+/**
+ * @brief Length of AWS HTTP Authorization header value generated using SigV4 library.
+ */
+#define AWS_HTTP_AUTH_HEADER_VALUE_LEN 2048U
+
+/**
+ * @brief Length in bytes of hex encoded hash digest.
+ */
+#define HEX_ENCODED_SHA256_HASH_DIGEST_LENGTH ( ( ( uint16_t ) 64 ) )
+
+/**
+ * @brief Length in bytes of SHA256 hash digest.
+ */
+#define SHA256_HASH_DIGEST_LENGTH ( HEX_ENCODED_SHA256_HASH_DIGEST_LENGTH / 2 )
+
+/**
+ * @brief Access Key Id key to be searched in the IoT credentials response.
+ */
+#define CREDENTIALS_RESPONSE_ACCESS_KEY_ID_KEY "credentials.accessKeyId"
+
+/**
+ * @brief Secret Access key to be searched in the IoT credentials response.
+ */
+#define CREDENTIALS_RESPONSE_SECRET_ACCESS_KEY "credentials.secretAccessKey"
+
+/**
+ * @brief Session Token key to be searched in the IoT credentials response.
+ */
+#define CREDENTIALS_RESPONSE_SESSION_TOKEN_KEY "credentials.sessionToken"
+
+/**
+ * @brief Expiration Date key to be searched in the IoT credentials response.
+ */
+#define CREDENTIALS_RESPONSE_EXPIRATION_DATE_KEY "credentials.expiration"
+
+/**
+ * @brief Represents empty payload for HTTP GET request sent to AWS S3.
+ */
+#define S3_REQUEST_EMPTY_PAYLOAD ""
+
+/**
* @brief The maximum number of times to run the loop in this demo.
*
* @note The demo loop is re-run only if the demo fails initially. Once the demo
@@ -171,38 +301,58 @@ struct NetworkContext
static uint8_t ucUserBuffer[ democonfigUSER_BUFFER_LENGTH ];
/**
- * @brief Header data sent as part of an HTTP request to the server.
+ * @brief Configurations of the initial request headers that are passed to
+ * #HTTPClient_InitializeRequestHeaders.
*/
-static HTTPRequestHeaders_t xRequestHeaders;
+
/**
- * @brief Configurations of the initial request headers that are passed to
- * #HTTPClient_InitializeRequestHeaders.
+ * @brief The location of the path within the pre-signed URL.
*/
-static HTTPRequestInfo_t xRequestInfo;
+static const char * pcRequestURI;
/**
- * @brief Response returned from the HTTP server.
+ * @brief mbedTLS Hash Context passed to SigV4 cryptointerface for generating the hash digest.
*/
-static HTTPResponse_t xResponse;
+static mbedtls_sha256_context xHashContext = { 0 };
/**
- * @brief The host address string extracted from the pre-signed URL.
- *
- * @note httpexampleS3_PRESIGNED_GET_URL_LENGTH is set as the array length here as the
- * length of the host name string cannot exceed this value.
+ * @brief Configurations of the AWS credentials sent to sigV4 library for generating the Authorization Header.
*/
-static char cServerHost[ httpexampleS3_PRESIGNED_GET_URL_LENGTH ];
+static SigV4Credentials_t xSigvCreds = { 0 };
/**
- * @brief The length of the host address found in the pre-signed URL.
+ * @brief Buffer used in the demo for storing temporary credentials
+ * received from AWS TOT credential provider.
*/
-static size_t xServerHostLength;
+static uint8_t ucCredBuffer[ CREDENTIAL_BUFFER_LENGTH ] = { 0 };
/**
- * @brief The location of the path within the pre-signed URL.
+ * @brief Represents date in ISO8601 format used in the HTTP requests sent to AWS S3.
*/
-static const char * pcRequestURI;
+static char cDateISO8601[ SIGV4_ISO_STRING_LEN ] = { 0 };
+
+/**
+ * @brief Represents Authorization header value generated using SigV4 library.
+ */
+static char cSigv4Auth[ AWS_HTTP_AUTH_HEADER_VALUE_LEN ];
+
+/**
+ * @brief Represents Length of Authorization header value generated using SigV4 library.
+ */
+static size_t xSigv4AuthLen = AWS_HTTP_AUTH_HEADER_VALUE_LEN;
+
+/**
+ * @brief The security token retrieved from AWS IoT credential provider
+ * required for making HTTP requests to AWS S3.
+ */
+static const char * pcSecurityToken;
+
+/**
+ * @brief Length of security token retrieved from AWS IoT credential provider
+ * required for making HTTP requests to AWS S3.
+ */
+static size_t xSecurityTokenLen;
/*-----------------------------------------------------------*/
@@ -214,15 +364,54 @@ static const char * pcRequestURI;
*/
static void prvHTTPDemoTask( void * pvParameters );
+
+/**
+ * @brief Establish a HTTP connection with AWS S3 server.
+ *
+ * @param[in] pxNetworkContext The network context for communication.
+ * @param[in] pcServer Address of the server to connect to.
+ * @param[in] pxNetworkCredentials Credentials for connecting to the server.
+ *
+ * @return pdPASS on success, pdFAIL on failure.
+ */
+static BaseType_t prvConnectToServer( NetworkContext_t * pxNetworkContext,
+ const char * pcServer,
+ NetworkCredentials_t * pxNetworkCredentials );
+/**
+ * @brief Establish a HTTP connection with AWS S3 server.
+ *
+ * @param[in] pxNetworkContext The network context for communication.
+ *
+ * @return pdPASS on success, pdFAIL on failure.
+ */
+static BaseType_t prvConnectToS3Server( NetworkContext_t * pxNetworkContext );
+
+/**
+ * @brief Establish a HTTP connection with AWS IoT credential provider server.
+ *
+ * @param[in] pxNetworkContext The network context for communication.
+ *
+ * @return pdPASS on success, pdFAIL on failure.
+ */
+static BaseType_t prvConnectToIotServer( NetworkContext_t * pxNetworkContext );
+
/**
- * @brief Connect to HTTP server with reconnection retries.
+ * @brief Send a HTTP GET request with empty body and Range header.
*
- * @param[out] pxNetworkContext The output parameter to return the created
- * network context.
+ * @param[in] pxTransportInterface The transport interface for making network calls.
+ * @param[in] ulRangeStart Start byte for Range header.
+ * @param[in] ulRangeEnd End byte for Range header.
+ * @param[in] pcPath The Request-URI to the objects of interest. This string
+ * should be null-terminated.
+ * @param[out] pxResponse Response from the GET request.
*
- * @return pdPASS on successful connection; pdFAIL otherwise.
+ * @return pdPASS on success, pdFAIL on failure.
*/
-static BaseType_t prvConnectToServer( NetworkContext_t * pxNetworkContext );
+static BaseType_t prvSendS3HttpEmptyGet( const TransportInterface_t * pxTransportInterface,
+ uint32_t ulRangeStart,
+ uint32_t ulRangeEnd,
+ const char * pcPath,
+ HTTPResponse_t * pxResponse );
/**
* @brief Retrieve the size of the S3 object that is specified in pcPath.
@@ -260,6 +449,117 @@ static BaseType_t prvGetS3ObjectFileSize( size_t * pxFileSize,
static BaseType_t prvDownloadS3ObjectFile( const TransportInterface_t * pxTransportInterface,
const char * pcPath );
+/**
+ * @brief Parse the credentials retrieved from AWS IOT Credential Provider using coreJSON API.
+ *
+ * @param[in] pxResponse HTTP response which needs to be parsed to get the credentials.
+ * @param[out] pxSigvCreds Buffer passed to store the parsed credentials.
+ *
+ * @return #JSONSuccess if the credentials are parsed successfully;
+ * #JSONNullParameter if any pointer parameters are NULL;
+ * #JSONBadParameter if any of the response parameters that needs to be parsed are empty;
+ * #JSONNotFound if the key to be parsed is not in the response.
+ */
+static JSONStatus_t prvParseCredentials( HTTPResponse_t * pxResponse,
+ SigV4Credentials_t * pxSigvCreds );
+
+/**
+ * @brief Retrieve the temporary credentials from AWS IOT Credential Provider.
+ *
+ * @param[in] pxTransportInterface The transport interface for performing network send/recv operations.
+ * @param[out] pcDateISO8601 Buffer to store the ISO8601 formatted date.
+ * @param[in] xDateISO8601Len Length of the buffer provided to store ISO8601 formatted date.
+ * @param[in,out] pxResponse Response buffer to store the HTTP response received.
+ * @param[out] pxSigvCreds Buffer to store the parsed credentials.
+ *
+ * @return pdPASS on success, pdFAIL on failure.
+ */
+static BaseType_t prvGetTemporaryCredentials( TransportInterface_t * pxTransportInterface,
+ const char * pcDateISO8601,
+ size_t xDateISO8601Len,
+ HTTPResponse_t * pxResponse,
+ SigV4Credentials_t * pxSigvCreds );
+
+/**
+ * @brief Skip over request line and get the starting address of key-value pair
+ * HTTP headers in an HTTP request.
+ *
+ * @param[in] pxRequestHeaders Pointer to HTTP request headers that contains the HTTP request information.
+ * @param[out] pcStartHeaderLoc Buffer to store the start Location of the HTTP header.
+ * @param[out] pxHeadersDataLen Length of @p pStartHeaderLoc.
+ */
+static void prvGetHeaderStartLocFromHttpRequest( HTTPRequestHeaders_t * pxRequestHeaders,
+ char ** pcStartHeaderLoc,
+ size_t * pxHeadersDataLen );
+
+/**
+ * @brief Compute the SHA256 hash of input and output hex representation.
+ *
+ * @param[in] pcInputStr Input string to compute SHA256
+ * @param[in] xInputStrLen Length of `pcInputStr`.
+ * @param[out] pcHexOutput Output buffer for hex encoded SHA256 hash.
+ *
+ * @note The size of `pcHexOutput` should be at least HEX_ENCODED_SHA256_HASH_DIGEST_LENGTH.
+ */
+static void prvSha256Encode( const char * pcInputStr,
+ size_t xInputStrLen,
+ char * pcHexOutput );
+
+/**
+ * @brief Application-defined Hash Initialization function provided
+ * to the SigV4 library.
+ *
+ * @note Refer to SigV4CryptoInterface_t interface documentation for this function.
+ */
+static int32_t prvSha256Init( void * pxHashContext );
+
+/**
+ * @brief Application-defined Hash Update function provided to the SigV4 library.
+ *
+ * @note Refer to SigV4CryptoInterface_t interface documentation for this function.
+ */
+static int32_t prvSha256Update( void * pxHashContext,
+ const uint8_t * pucInput,
+ size_t xInputLen );
+
+/**
+ * @brief Application-defined Hash Final function provided to the SigV4 library.
+ *
+ * @note Refer to SigV4CryptoInterface_t interface documentation for this function.
+ */
+static int32_t prvSha256Final( void * pxHashContext,
+ uint8_t * pucOutput,
+ size_t xOutputLen );
+
+/**
+ * @brief CryptoInterface provided to SigV4 library for generating the hash digest.
+ */
+static SigV4CryptoInterface_t cryptoInterface =
+{
+ .hashInit = prvSha256Init,
+ .hashUpdate = prvSha256Update,
+ .hashFinal = prvSha256Final,
+ .pHashContext = &xHashContext,
+ .hashBlockLen = HEX_ENCODED_SHA256_HASH_DIGEST_LENGTH,
+ .hashDigestLen = SHA256_HASH_DIGEST_LENGTH,
+};
+
+/**
+ * @brief SigV4 parameters provided to SigV4 library by the application for generating
+ * the Authorization header.
+ */
+static SigV4Parameters_t xSigv4Params =
+{
+ .pCredentials = &xSigvCreds,
+ .pDateIso8601 = cDateISO8601,
+ .pRegion = democonfigS3_BUCKET_REGION,
+ .regionLen = sizeof( democonfigS3_BUCKET_REGION ) - 1,
+ .pService = AWS_S3_SERVICE_NAME,
+ .serviceLen = sizeof( AWS_S3_SERVICE_NAME ) - 1,
+ .pCryptoInterface = &cryptoInterface,
+ .pHttpParameters = NULL
+};
+
/*-----------------------------------------------------------*/
/*
@@ -284,18 +584,10 @@ void vStartSimpleHTTPDemo( void )
/**
* @brief Entry point of the demo.
*
- * This example, using a pre-signed URL, resolves a S3 domain, establishes a TCP
- * connection, validates the server's certificate using the root CA certificate
- * defined in the config header, and then finally performs a TLS handshake with
- * the HTTP server so that all communication is encrypted. After which, the HTTP
- * Client library API is used to download the S3 file (by sending multiple GET
- * requests, filling up the response buffer each time until all parts are
- * downloaded). If any request fails, an error code is returned.
- *
- * @note This demo requires user-generated pre-signed URLs to be pasted into
- * demo_config.h. Please use the provided script "presigned_urls_gen.py"
- * (located in located in coreHTTP_Windows_Simulator/Common) to generate these
- * URLs. For detailed instructions, see the accompanied README.md.
+ * This demo demonstrates downloading a file from S3 using SigV4 authentication.
+ * First the demo establishes a TLS connection with IoT credential provider
+ * server to obtain temporary credentials. Then it connects to S3 server and
+ * sends HTTP requests to download the file.
*
* @note This example is single-threaded.
*
@@ -308,28 +600,20 @@ static void prvHTTPDemoTask( void * pvParameters )
NetworkContext_t xNetworkContext = { 0 };
TlsTransportParams_t xTlsTransportParams = { 0 };
BaseType_t xIsConnectionEstablished = pdFALSE;
- /* HTTP Client library return status. */
- HTTPStatus_t xHTTPStatus = HTTPSuccess;
UBaseType_t uxDemoRunCount = 0UL;
+ /* Response from IoT credential provider */
+ HTTPResponse_t xCredentialResponse = { 0 };
/* The user of this demo must check the logs for any failure codes. */
BaseType_t xDemoStatus = pdPASS;
- /* The length of the path within the pre-signed URL. This variable is
- * defined in order to store the length returned from parsing the URL, but
- * it is unused. The path used for the requests in this demo needs all the
- * query information following the location of the object, to the end of the
- * S3 presigned URL. */
- size_t xPathLen = 0;
-
/* Remove compiler warnings about unused parameters. */
( void ) pvParameters;
/* Set the pParams member of the network context with desired transport. */
xNetworkContext.pParams = &xTlsTransportParams;
- LogInfo( ( "HTTP Client Synchronous S3 download demo using pre-signed URL:\n%s",
- democonfigS3_PRESIGNED_GET_URL ) );
+ LogInfo( ( "HTTP Client Synchronous S3 download demo using temporary credentials fetched from iot credential provider" ) );
/* This demo runs once, unless there are failures in the demo execution. In
* case of failures, the demo loop will run up to HTTP_MAX_DEMO_LOOP_COUNT
@@ -343,7 +627,7 @@ static void prvHTTPDemoTask( void * pvParameters )
* maximum number of attempts or the maximum timeout value is reached. The
* function returns pdFAIL if the TCP connection cannot be established with
* the server after configured number of attempts. */
- xDemoStatus = connectToServerWithBackoffRetries( prvConnectToServer,
+ xDemoStatus = connectToServerWithBackoffRetries( prvConnectToIotServer,
&xNetworkContext );
if( xDemoStatus == pdPASS )
@@ -360,29 +644,54 @@ static void prvHTTPDemoTask( void * pvParameters )
{
/* Log an error to indicate connection failure after all
* reconnect attempts are over. */
- LogError( ( "Failed to connect to HTTP server %s.",
- cServerHost ) );
+ LogError( ( "Failed to connect to IoT server: %d.", xDemoStatus ) );
}
- /******************** Download S3 Object File. **********************/
+ /******************* Get temp credential from IoT ********************/
+ if( xDemoStatus == pdPASS )
+ {
+ xCredentialResponse.pBuffer = ucCredBuffer;
+ xCredentialResponse.bufferLen = CREDENTIAL_BUFFER_LENGTH;
+ xDemoStatus = prvGetTemporaryCredentials( &xTransportInterface, cDateISO8601, sizeof( cDateISO8601 ), &xCredentialResponse, &xSigvCreds );
+ if( xDemoStatus != pdPASS )
+ {
+ LogError( ( "Failed to get credential from credential provider: %d.", xDemoStatus ) );
+ }
+ }
+ if( xIsConnectionEstablished == pdTRUE )
+ {
+ /* Close the connection with IoT credential provider. */
+ TLS_FreeRTOS_Disconnect( &xNetworkContext );
+ xIsConnectionEstablished = pdFALSE;
+ }
+ /************************ Connect to S3 server ************************/
if( xDemoStatus == pdPASS )
{
- /* Retrieve the path location from democonfigS3_PRESIGNED_GET_URL. This
- * function returns the length of the path without the query into
- * xPathLen, which is left unused in this demo. */
- xHTTPStatus = getUrlPath( democonfigS3_PRESIGNED_GET_URL,
- httpexampleS3_PRESIGNED_GET_URL_LENGTH,
- &pcRequestURI,
- &xPathLen );
+ xDemoStatus = connectToServerWithBackoffRetries( prvConnectToS3Server,
+ &xNetworkContext );
+ if( xDemoStatus != pdPASS )
+ {
+ LogError( ( "Failed to connect to AWS S3 server: %d.", xDemoStatus ) );
+ }
- xDemoStatus = ( xHTTPStatus == HTTPSuccess ) ? pdPASS : pdFAIL;
}
if( xDemoStatus == pdPASS )
{
+ memset( &xTransportInterface, 0, sizeof( xTransportInterface ) );
+ xTransportInterface.pNetworkContext = &xNetworkContext;
+ xTransportInterface.send = TLS_FreeRTOS_send;
+ xTransportInterface.recv = TLS_FreeRTOS_recv;
+ xIsConnectionEstablished = pdTRUE;
+ }
+
+ /******************** Download S3 Object File. **********************/
+
+ if( xDemoStatus == pdPASS )
+ {
xDemoStatus = prvDownloadS3ObjectFile( &xTransportInterface,
- pcRequestURI );
+ AWS_S3_URI_PATH );
}
/************************** Disconnect. *****************************/
@@ -430,63 +739,55 @@ static void prvHTTPDemoTask( void * pvParameters )
/*-----------------------------------------------------------*/
-static BaseType_t prvConnectToServer( NetworkContext_t * pxNetworkContext )
+static BaseType_t prvConnectToServer( NetworkContext_t * pxNetworkContext,
+ const char * pcServer,
+ NetworkCredentials_t * pxNetworkCredentials )
{
TlsTransportStatus_t xNetworkStatus;
- NetworkCredentials_t xNetworkCredentials = { 0 };
- BaseType_t xStatus = pdPASS;
- HTTPStatus_t xHTTPStatus = HTTPSuccess;
-
- /* The location of the host address within the pre-signed URL. */
- const char * pcAddress = NULL;
configASSERT( pxNetworkContext != NULL );
- /* Retrieve the address location and length from democonfigS3_PRESIGNED_GET_URL. */
- xHTTPStatus = getUrlAddress( democonfigS3_PRESIGNED_GET_URL,
- httpexampleS3_PRESIGNED_GET_URL_LENGTH,
- &pcAddress,
- &xServerHostLength );
+ LogInfo( ( "Establishing a TLS session with %s:%d.",
+ pcServer,
+ democonfigHTTPS_PORT ) );
- xStatus = ( xHTTPStatus == HTTPSuccess ) ? pdPASS : pdFAIL;
+ /* Attempt to create a server-authenticated TLS connection. */
+ xNetworkStatus = TLS_FreeRTOS_Connect( pxNetworkContext,
+ pcServer,
+ democonfigHTTPS_PORT,
+ pxNetworkCredentials,
+ democonfigTRANSPORT_SEND_RECV_TIMEOUT_MS,
+ democonfigTRANSPORT_SEND_RECV_TIMEOUT_MS );
- if( xStatus == pdPASS )
- {
- /* cServerHost should consist only of the host address located in
- * democonfigS3_PRESIGNED_GET_URL. */
- memcpy( cServerHost, pcAddress, xServerHostLength );
- cServerHost[ xServerHostLength ] = '\0';
-
- xNetworkCredentials.disableSni = democonfigDISABLE_SNI;
- /* Set the credentials for establishing a TLS connection. */
- xNetworkCredentials.pRootCa = ( const unsigned char * ) democonfigROOT_CA_PEM;
- xNetworkCredentials.rootCaSize = sizeof( democonfigROOT_CA_PEM );
-
- /* Establish a TLS session with the HTTP server. This example connects
- * to the server host located in democonfigPRESIGNED_GET_URL and
- * democonfigHTTPS_PORT in demo_config.h. */
- LogInfo( ( "Establishing a TLS session with %s:%d.",
- cServerHost,
- democonfigHTTPS_PORT ) );
-
- /* Attempt to create a server-authenticated TLS connection. */
- xNetworkStatus = TLS_FreeRTOS_Connect( pxNetworkContext,
- cServerHost,
- democonfigHTTPS_PORT,
- &xNetworkCredentials,
- democonfigTRANSPORT_SEND_RECV_TIMEOUT_MS,
- democonfigTRANSPORT_SEND_RECV_TIMEOUT_MS );
-
- if( xNetworkStatus != TLS_TRANSPORT_SUCCESS )
- {
- xStatus = pdFAIL;
- }
- }
+ return xNetworkStatus == TLS_TRANSPORT_SUCCESS? pdPASS : pdFAIL;
- return xStatus;
}
-/*-----------------------------------------------------------*/
+static BaseType_t prvConnectToS3Server( NetworkContext_t * pxNetworkContext )
+{
+ NetworkCredentials_t xNetworkCredentials = { 0 };
+ xNetworkCredentials.disableSni = democonfigDISABLE_SNI;
+ /* Set the credentials for establishing a TLS connection. */
+ xNetworkCredentials.pRootCa = ( uint8_t * )democonfigS3_ROOT_CA_PEM;
+ xNetworkCredentials.rootCaSize = sizeof( democonfigS3_ROOT_CA_PEM );
+
+ return prvConnectToServer( pxNetworkContext, AWS_S3_ENDPOINT, &xNetworkCredentials );
+}
+
+static BaseType_t prvConnectToIotServer( NetworkContext_t * pxNetworkContext )
+{
+ NetworkCredentials_t xNetworkCredentials = { 0 };
+ xNetworkCredentials.disableSni = democonfigDISABLE_SNI;
+ /* Set the credentials for establishing a TLS connection. */
+ xNetworkCredentials.pRootCa = ( uint8_t * )democonfigIOT_CRED_PROVIDER_ROOT_CA_PEM;
+ xNetworkCredentials.rootCaSize = sizeof( democonfigIOT_CRED_PROVIDER_ROOT_CA_PEM );
+ xNetworkCredentials.pClientCert = ( uint8_t * )democonfigCLIENT_CERTIFICATE_PEM;
+ xNetworkCredentials.clientCertSize = sizeof( democonfigCLIENT_CERTIFICATE_PEM );
+ xNetworkCredentials.pPrivateKey = ( uint8_t * )democonfigCLIENT_PRIVATE_KEY_PEM;
+ xNetworkCredentials.privateKeySize = sizeof( democonfigCLIENT_PRIVATE_KEY_PEM );
+
+ return prvConnectToServer( pxNetworkContext, democonfigIOT_CREDENTIAL_PROVIDER_ENDPOINT, &xNetworkCredentials );
+}
static BaseType_t prvGetS3ObjectFileSize( size_t * pxFileSize,
const TransportInterface_t * pxTransportInterface,
@@ -495,7 +796,8 @@ static BaseType_t prvGetS3ObjectFileSize( size_t * pxFileSize,
const char * pcPath )
{
BaseType_t xStatus = pdPASS;
- HTTPStatus_t xHTTPStatus = HTTPSuccess;
+ HTTPStatus_t xHTTPStatus;
+ HTTPResponse_t xResponse;
/* The location of the file size in pcContentRangeValStr. */
char * pcFileSizeStr = NULL;
@@ -508,98 +810,13 @@ static BaseType_t prvGetS3ObjectFileSize( size_t * pxFileSize,
configASSERT( pcHost != NULL );
configASSERT( pcPath != NULL );
- /* Initialize all HTTP Client library API structs to 0. */
- ( void ) memset( &xRequestHeaders, 0, sizeof( xRequestHeaders ) );
- ( void ) memset( &xRequestInfo, 0, sizeof( xRequestInfo ) );
- ( void ) memset( &xResponse, 0, sizeof( xResponse ) );
-
- /* Initialize the request object. */
- xRequestInfo.pHost = pcHost;
- xRequestInfo.hostLen = xHostLen;
- xRequestInfo.pMethod = HTTP_METHOD_GET;
- xRequestInfo.methodLen = sizeof( HTTP_METHOD_GET ) - 1;
- xRequestInfo.pPath = pcPath;
- xRequestInfo.pathLen = strlen( pcPath );
-
- /* Set "Connection" HTTP header to "keep-alive" so that multiple requests
- * can be sent over the same established TCP connection. This is done in
- * order to download the file in parts. */
- xRequestInfo.reqFlags = HTTP_REQUEST_KEEP_ALIVE_FLAG;
-
- /* Set the buffer used for storing request headers. */
- xRequestHeaders.pBuffer = ucUserBuffer;
- xRequestHeaders.bufferLen = democonfigUSER_BUFFER_LENGTH;
-
- /* Initialize the response object. The same buffer used for storing request
- * headers is reused here. */
- xResponse.pBuffer = ucUserBuffer;
- xResponse.bufferLen = democonfigUSER_BUFFER_LENGTH;
-
LogInfo( ( "Getting file object size from host..." ) );
- xHTTPStatus = HTTPClient_InitializeRequestHeaders( &xRequestHeaders,
- &xRequestInfo );
-
- if( xHTTPStatus != HTTPSuccess )
- {
- LogError( ( "Failed to initialize HTTP request headers: Error=%s.",
- HTTPClient_strerror( xHTTPStatus ) ) );
- xStatus = pdFAIL;
- }
-
- if( xStatus == pdPASS )
- {
- /* Add the header to get bytes=0-0. S3 will respond with a Content-Range
- * header that contains the size of the file in it. This header will
- * look like: "Content-Range: bytes 0-0/FILESIZE". The body will have a
- * single byte that we are ignoring. */
- xHTTPStatus = HTTPClient_AddRangeHeader( &xRequestHeaders, 0, 0 );
-
- if( xHTTPStatus != HTTPSuccess )
- {
- LogError( ( "Failed to add range header to request headers: Error=%s.",
- HTTPClient_strerror( xHTTPStatus ) ) );
- xStatus = pdFAIL;
- }
- }
-
- if( xStatus == pdPASS )
- {
- /* Send the request and receive the response. */
- xHTTPStatus = HTTPClient_Send( pxTransportInterface,
- &xRequestHeaders,
- NULL,
- 0,
- &xResponse,
- 0 );
-
- if( xHTTPStatus != HTTPSuccess )
- {
- LogError( ( "Failed to send HTTP GET request to %s%s: Error=%s.",
- pcHost, pcPath, HTTPClient_strerror( xHTTPStatus ) ) );
- xStatus = pdFAIL;
- }
- }
-
- if( xStatus == pdPASS )
- {
- LogDebug( ( "Received HTTP response from %s%s...",
- pcHost, pcPath ) );
- LogDebug( ( "Response Headers:\n%.*s",
- ( int32_t ) xResponse.headersLen,
- xResponse.pHeaders ) );
- LogDebug( ( "Response Body:\n%.*s\n",
- ( int32_t ) xResponse.bodyLen,
- xResponse.pBody ) );
-
- if( xResponse.statusCode != httpexampleHTTP_STATUS_CODE_PARTIAL_CONTENT )
- {
- LogError( ( "Received an invalid response from the server "
- "(Status Code: %u).",
- xResponse.statusCode ) );
- xStatus = pdFAIL;
- }
- }
+ /* Add the header to get bytes=0-0. S3 will respond with a Content-Range
+ * header that contains the size of the file in it. This header will
+ * look like: "Content-Range: bytes 0-0/FILESIZE". The body will have a
+ * single byte that we are ignoring. */
+ xStatus = prvSendS3HttpEmptyGet( pxTransportInterface, 0, 0, pcPath, &xResponse );
if( xStatus == pdPASS )
{
@@ -657,33 +874,38 @@ static BaseType_t prvGetS3ObjectFileSize( size_t * pxFileSize,
/*-----------------------------------------------------------*/
-static BaseType_t prvDownloadS3ObjectFile( const TransportInterface_t * pxTransportInterface,
- const char * pcPath )
+static BaseType_t prvSendS3HttpEmptyGet( const TransportInterface_t * pxTransportInterface,
+ uint32_t ulRangeStart,
+ uint32_t ulRangeEnd,
+ const char * pcPath,
+ HTTPResponse_t * pxResponse )
{
- /* Return value of this method. */
- BaseType_t xStatus = pdFAIL;
- HTTPStatus_t xHTTPStatus = HTTPSuccess;
-
- /* The size of the file we are trying to download in S3. */
- size_t xFileSize = 0;
-
- /* The number of bytes we want to request with in each range of the file
- * bytes. */
- size_t xNumReqBytes = 0;
- /* xCurByte indicates which starting byte we want to download next. */
- size_t xCurByte = 0;
+ BaseType_t xStatus = pdPASS;
+ HTTPStatus_t xHttpStatus;
+ SigV4Status_t xSigv4Status;
+ SigV4HttpParameters_t xSigv4HttpParams;
+
+ HTTPRequestHeaders_t xRequestHeaders = { 0 };
+ HTTPRequestInfo_t xRequestInfo = { 0 };
+
+ /* Store Signature used in AWS HTTP requests generated using SigV4 library. */
+ char * pcSignature = NULL;
+ size_t xSignatureLen = 0;
+ /* Pointer to start of key-value pair buffer in request buffer. This is
+ * used for Sigv4 signing */
+ char * pcHeaderStart;
+ size_t xHeadersLen;
+ static char cPayloadSha256[ HEX_ENCODED_SHA256_HASH_DIGEST_LENGTH ];
configASSERT( pxTransportInterface != NULL );
configASSERT( pcPath != NULL );
/* Initialize all HTTP Client library API structs to 0. */
- ( void ) memset( &xRequestHeaders, 0, sizeof( xRequestHeaders ) );
- ( void ) memset( &xRequestInfo, 0, sizeof( xRequestInfo ) );
- ( void ) memset( &xResponse, 0, sizeof( xResponse ) );
+ ( void ) memset( pxResponse, 0, sizeof( HTTPResponse_t ) );
/* Initialize the request object. */
- xRequestInfo.pHost = cServerHost;
- xRequestInfo.hostLen = xServerHostLength;
+ xRequestInfo.pHost = AWS_S3_ENDPOINT;
+ xRequestInfo.hostLen = strlen( AWS_S3_ENDPOINT );
xRequestInfo.pMethod = HTTP_METHOD_GET;
xRequestInfo.methodLen = httpexampleHTTP_METHOD_GET_LENGTH;
xRequestInfo.pPath = pcPath;
@@ -700,14 +922,189 @@ static BaseType_t prvDownloadS3ObjectFile( const TransportInterface_t * pxTransp
/* Initialize the response object. The same buffer used for storing request
* headers is reused here. */
- xResponse.pBuffer = ucUserBuffer;
- xResponse.bufferLen = democonfigUSER_BUFFER_LENGTH;
+ pxResponse->pBuffer = ucUserBuffer;
+ pxResponse->bufferLen = democonfigUSER_BUFFER_LENGTH;
+
+ xHttpStatus = HTTPClient_InitializeRequestHeaders( &xRequestHeaders,
+ &xRequestInfo );
+ if( xHttpStatus != HTTPSuccess )
+ {
+ LogError( ( "Failed initialize HTTP headers: Error=%s.",
+ HTTPClient_strerror( xHttpStatus ) ) );
+ xStatus = pdFAIL;
+ }
+
+ if( xStatus == pdPASS )
+ {
+ /* Add the X-AMZ-DATE required headers to the request. */
+ xHttpStatus = HTTPClient_AddHeader( &xRequestHeaders,
+ SIGV4_HTTP_X_AMZ_DATE_HEADER,
+ sizeof( SIGV4_HTTP_X_AMZ_DATE_HEADER ) - 1,
+ cDateISO8601,
+ SIGV4_ISO_STRING_LEN );
+ if( xHttpStatus != HTTPSuccess )
+ {
+ LogError( ( "Failed to add X-AMZ-DATE to request headers: Error=%s.",
+ HTTPClient_strerror( xHttpStatus ) ) );
+ xStatus = pdFAIL;
+ }
+ }
+
+ if( xStatus == pdPASS )
+ {
+ /* S3 requires the security token as part of the canonical headers. */
+ xHttpStatus = HTTPClient_AddHeader( &xRequestHeaders,
+ SIGV4_HTTP_X_AMZ_SECURITY_TOKEN_HEADER,
+ sizeof( SIGV4_HTTP_X_AMZ_SECURITY_TOKEN_HEADER ) - 1,
+ pcSecurityToken,
+ xSecurityTokenLen );
+ if( xHttpStatus != HTTPSuccess )
+ {
+ LogError( ( "Failed to add X-AMZ-SECURITY-TOKEN to request headers: Error=%s.",
+ HTTPClient_strerror( xHttpStatus ) ) );
+ xStatus = pdFAIL;
+ }
+ }
+
+ if( xStatus == pdPASS )
+ {
+ /* Add Range header */
+ xHttpStatus = HTTPClient_AddRangeHeader( &xRequestHeaders,
+ ulRangeStart,
+ ulRangeEnd );
+ if( xHttpStatus != HTTPSuccess )
+ {
+ LogError( ( "Failed to add range to request headers: Error=%s.",
+ HTTPClient_strerror( xHttpStatus ) ) );
+ xStatus = pdFAIL;
+ }
+ }
+
+ if( xStatus == pdPASS )
+ {
+ /* Add the SHA256 of an empty payload. */
+ prvSha256Encode( S3_REQUEST_EMPTY_PAYLOAD, sizeof(S3_REQUEST_EMPTY_PAYLOAD) - 1, cPayloadSha256 );
+ xHttpStatus = HTTPClient_AddHeader( &xRequestHeaders,
+ SIGV4_HTTP_X_AMZ_CONTENT_SHA256_HEADER,
+ sizeof( SIGV4_HTTP_X_AMZ_CONTENT_SHA256_HEADER ) - 1,
+ cPayloadSha256,
+ sizeof( cPayloadSha256 ) );
+ if( xHttpStatus != HTTPSuccess )
+ {
+ LogError( ( "Failed to add X-AMZ-CONTENT-SHA256-HEADER to request headers: Error=%s.",
+ HTTPClient_strerror( xHttpStatus ) ) );
+ xStatus = pdFAIL;
+ }
+ }
+
+ if( xStatus == pdPASS )
+ {
+ /* Find the start key-value pairs for sigv4 signing. */
+ prvGetHeaderStartLocFromHttpRequest( &xRequestHeaders, &pcHeaderStart, &xHeadersLen );
+
+ /* Setup the HTTP parameters. */
+ xSigv4HttpParams.pHttpMethod = xRequestInfo.pMethod;
+ xSigv4HttpParams.httpMethodLen = xRequestInfo.methodLen;
+ /* None of the requests parameters below are pre-canonicalized */
+ xSigv4HttpParams.flags = 0;
+ xSigv4HttpParams.pPath = xRequestInfo.pPath;
+ xSigv4HttpParams.pathLen = xRequestInfo.pathLen;
+ /* AWS S3 request does not require any Query parameters. */
+ xSigv4HttpParams.pQuery = NULL;
+ xSigv4HttpParams.queryLen = 0;
+ xSigv4HttpParams.pHeaders = pcHeaderStart;
+ xSigv4HttpParams.headersLen = xHeadersLen;
+ xSigv4HttpParams.pPayload = S3_REQUEST_EMPTY_PAYLOAD;
+ xSigv4HttpParams.payloadLen = sizeof( S3_REQUEST_EMPTY_PAYLOAD ) - 1U;
+
+ /* Initializing sigv4Params with Http parameters required for the HTTP request. */
+ xSigv4Params.pHttpParameters = &xSigv4HttpParams;
+
+ /* Generate HTTP Authorization header using SigV4_GenerateHTTPAuthorization API. */
+ xSigv4Status = SigV4_GenerateHTTPAuthorization( &xSigv4Params, cSigv4Auth, &xSigv4AuthLen, &pcSignature, &xSignatureLen );
+
+ if( xSigv4Status != SigV4Success )
+ {
+ LogError( ( "Failed to generate HTTP AUTHORIZATION Header: %d", xSigv4Status ) );
+ xStatus = pdFAIL;
+ }
+ }
+
+ if( xStatus == pdPASS )
+ {
+ /* Add the authorization header to the HTTP request headers. */
+ xHttpStatus = HTTPClient_AddHeader( &xRequestHeaders,
+ SIGV4_AUTH_HEADER_FIELD_NAME,
+ sizeof( SIGV4_AUTH_HEADER_FIELD_NAME ) - 1,
+ cSigv4Auth,
+ xSigv4AuthLen );
+
+ if( xHttpStatus != HTTPSuccess )
+ {
+ LogError( ( "Failed to add Sigv4 auth header. Error=%s.",
+ HTTPClient_strerror( xHttpStatus ) ) );
+ xStatus = pdFAIL;
+ }
+ }
+
+ if( xStatus == pdPASS )
+ {
+ /* Send out the http request */
+ xHttpStatus = HTTPClient_Send( pxTransportInterface,
+ &xRequestHeaders,
+ NULL,
+ 0,
+ pxResponse,
+ 0 );
+ if( xHttpStatus != HTTPSuccess )
+ {
+ LogError( ( "Failed to send HTTP GET request to %s%s: Error=%s.",
+ xRequestInfo.pHost, xRequestInfo.pPath, HTTPClient_strerror( xHttpStatus ) ) );
+ xStatus = pdFAIL;
+ }
+ }
+
+ if( xStatus == pdPASS )
+ {
+ LogDebug( ( "Received HTTP response from %s%s...",
+ xRequestInfo.pHost, xRequestInfo.pPath ) );
+ LogDebug( ( "Response Headers:\n%.*s",
+ pxResponse->headersLen,
+ pxResponse->pHeaders ) );
+ LogDebug( ( "Response Body:\n%.*s\n",
+ pxResponse->bodyLen,
+ pxResponse->pBody ) );
+
+ /* Since Range is set in the header, the success status is 206 Partial Content. */
+ if( pxResponse->statusCode != httpexampleHTTP_STATUS_CODE_PARTIAL_CONTENT )
+ {
+ LogError( ( "Received an invalid response from the server "
+ "(Status Code: %u).",
+ pxResponse->statusCode ) );
+ xStatus = pdFAIL;
+ }
+ }
+
+ return xStatus;
+}
+
+static BaseType_t prvDownloadS3ObjectFile( const TransportInterface_t * pxTransportInterface,
+ const char * pcPath )
+{
+ BaseType_t xStatus = pdFAIL;
+ HTTPResponse_t xResponse;
+ /* The size of the file we are trying to download in S3. */
+ size_t xFileSize = 0;
+ /* The number of bytes we want to request with in each range of the file bytes. */
+ size_t xNumReqBytes = 0;
+ /* curByte indicates which starting byte we want to download next. */
+ size_t xCurByte = 0;
/* Verify the file exists by retrieving the file size. */
xStatus = prvGetS3ObjectFileSize( &xFileSize,
pxTransportInterface,
- cServerHost,
- xServerHostLength,
+ AWS_S3_ENDPOINT,
+ sizeof( AWS_S3_ENDPOINT ) - 1,
pcPath );
if( xFileSize < democonfigRANGE_REQUEST_LENGTH )
@@ -722,82 +1119,340 @@ static BaseType_t prvDownloadS3ObjectFile( const TransportInterface_t * pxTransp
/* Here we iterate sending byte range requests until the full file has been
* downloaded. We keep track of the next byte to download with xCurByte. When
* this reaches the xFileSize we stop downloading. */
- while( ( xStatus == pdPASS ) && ( xHTTPStatus == HTTPSuccess ) && ( xCurByte < xFileSize ) )
+ while( ( xStatus == pdPASS ) && ( xCurByte < xFileSize ) )
{
- xHTTPStatus = HTTPClient_InitializeRequestHeaders( &xRequestHeaders,
- &xRequestInfo );
+ xStatus = prvSendS3HttpEmptyGet( pxTransportInterface,
+ xCurByte,
+ xCurByte + xNumReqBytes - 1,
+ pcPath,
+ &xResponse );
- if( xHTTPStatus == HTTPSuccess )
+ if( xStatus == pdPASS )
{
- xHTTPStatus = HTTPClient_AddRangeHeader( &xRequestHeaders,
- xCurByte,
- xCurByte + xNumReqBytes - 1 );
+ /* We increment by the content length because the server may not
+ * have sent us the range we request. */
+ xCurByte += xResponse.contentLength;
+
+ if( ( xFileSize - xCurByte ) < xNumReqBytes )
+ {
+ xNumReqBytes = xFileSize - xCurByte;
+ }
+
+ xStatus = ( xResponse.statusCode == httpexampleHTTP_STATUS_CODE_PARTIAL_CONTENT ) ? pdPASS : pdFAIL;
}
else
{
- LogError( ( "Failed to initialize HTTP request headers: Error=%s.",
- HTTPClient_strerror( xHTTPStatus ) ) );
+ LogError( ( "An error occurred in downloading the file. "
+ "Failed to send HTTP GET request to %s%s: Error=%u.",
+ AWS_S3_ENDPOINT, pcPath, xResponse.statusCode ) );
}
+ }
+
+ return xStatus;
+}
+
+static BaseType_t prvGetTemporaryCredentials( TransportInterface_t * pxTransportInterface,
+ const char * pcDateISO8601,
+ size_t xDateISO8601Len,
+ HTTPResponse_t * pxResponse,
+ SigV4Credentials_t * pxSigvCreds )
+{
+ BaseType_t xStatus = pdPASS;
+ HTTPStatus_t xHttpStatus = HTTPSuccess;
+ SigV4Status_t xSigv4Status = SigV4Success;
+ JSONStatus_t xJsonStatus = JSONSuccess;
+ HTTPRequestHeaders_t xRequestHeaders = { 0 };
+ HTTPRequestInfo_t xRequestInfo = { 0 };
+ const char * pcCredServer = democonfigIOT_CREDENTIAL_PROVIDER_ENDPOINT;
+ size_t xCredServerLen = strlen( democonfigIOT_CREDENTIAL_PROVIDER_ENDPOINT );
+ const char * pcPath = AWS_IOT_CREDENTIAL_PROVIDER_URI_PATH;
+ size_t xPathLen = strlen( AWS_IOT_CREDENTIAL_PROVIDER_URI_PATH );
+ const char * pDate = NULL;
+ size_t xDateLen;
- if( xHTTPStatus == HTTPSuccess )
+ configASSERT( pxTransportInterface != NULL );
+ configASSERT( pxSigvCreds != NULL );
+ configASSERT( pcDateISO8601 != NULL );
+ configASSERT( xDateISO8601Len > 0 );
+
+ /* Initialize Request header buffer. */
+ xRequestHeaders.pBuffer = ucUserBuffer;
+ xRequestHeaders.bufferLen = democonfigUSER_BUFFER_LENGTH;
+
+ /* Set HTTP request parameters to get temporary AWS IoT credentials. */
+ xRequestInfo.pMethod = HTTP_METHOD_GET;
+ xRequestInfo.methodLen = sizeof( HTTP_METHOD_GET ) - 1;
+ xRequestInfo.pPath = pcPath;
+ xRequestInfo.pathLen = xPathLen;
+ xRequestInfo.pHost = pcCredServer;
+ xRequestInfo.hostLen = xCredServerLen;
+ xRequestInfo.reqFlags = 0;
+
+ pxResponse->pHeaderParsingCallback = NULL;
+
+ /* Initialize request headers. */
+ xHttpStatus = HTTPClient_InitializeRequestHeaders( &xRequestHeaders, &xRequestInfo );
+
+ if( xHttpStatus != HTTPSuccess )
+ {
+ LogError( ( "Failed to initialize request headers: Error=%s.",
+ HTTPClient_strerror( xHttpStatus ) ) );
+ xStatus = pdFAIL;
+ }
+
+ if( xStatus == pdPASS )
+ {
+ /* Add AWS_IOT_THING_NAME_HEADER_FIELD header to the HTTP request headers. */
+ xHttpStatus = HTTPClient_AddHeader( &xRequestHeaders,
+ AWS_IOT_THING_NAME_HEADER_FIELD,
+ sizeof( AWS_IOT_THING_NAME_HEADER_FIELD ) - 1U,
+ democonfigIOT_THING_NAME,
+ sizeof( democonfigIOT_THING_NAME ) - 1U );
+
+ if( xHttpStatus != HTTPSuccess )
{
- LogInfo( ( "Downloading bytes %d-%d, out of %d total bytes, from %s...: ",
- ( int32_t ) ( xCurByte ),
- ( int32_t ) ( xCurByte + xNumReqBytes - 1 ),
- ( int32_t ) xFileSize,
- cServerHost ) );
- LogDebug( ( "Request Headers:\n%.*s",
- ( int32_t ) xRequestHeaders.headersLen,
- ( char * ) xRequestHeaders.pBuffer ) );
- xHTTPStatus = HTTPClient_Send( pxTransportInterface,
- &xRequestHeaders,
- NULL,
- 0,
- &xResponse,
- 0 );
+ LogError( ( "Failed to add x-amz-iot-thing-name header to request headers: Error=%s.",
+ HTTPClient_strerror( xHttpStatus ) ) );
+ xStatus = pdFAIL;
}
- else
+ }
+
+ if( xStatus == pdPASS )
+ {
+ /* Send the request to AWS IoT Credentials Provider to obtain temporary credentials
+ * so that the demo application can access configured S3 bucket thereafter. */
+ xHttpStatus = HTTPClient_Send( pxTransportInterface,
+ &xRequestHeaders,
+ NULL,
+ 0,
+ pxResponse,
+ 0 );
+
+ if( xHttpStatus != HTTPSuccess )
{
- LogError( ( "Failed to add Range header to request headers: Error=%s.",
- HTTPClient_strerror( xHTTPStatus ) ) );
+ LogError( ( "Failed to send HTTP GET request to %s%s for obtaining temporary credentials: Error=%s.",
+ pcCredServer, pcPath, HTTPClient_strerror( xHttpStatus ) ) );
+ xStatus = pdFAIL;
+ }
+ }
+
+ if( xStatus == pdPASS )
+ {
+ LogDebug( ( "AWS IoT credential provider response: %.*s.",
+ ( int32_t ) pxResponse->bufferLen, pxResponse->pBuffer ) );
+
+ /* Parse the credentials received in the response. */
+ xJsonStatus = prvParseCredentials( pxResponse, pxSigvCreds );
+
+ if( xJsonStatus != JSONSuccess )
+ {
+ LogError( ( "Failed to parse temporary IoT credentials retrieved from AWS IoT credential provider" ) );
+ xStatus = pdFAIL;
}
+ }
+
+ /* Get the AWS IoT date from the http response. */
+ if( xStatus == pdPASS )
+ {
+ xHttpStatus = HTTPClient_ReadHeader( pxResponse,
+ AWS_IOT_CRED_PROVIDER_RESPONSE_DATE_HEADER,
+ sizeof( AWS_IOT_CRED_PROVIDER_RESPONSE_DATE_HEADER ) - 1,
+ &pDate,
+ &xDateLen );
- if( xHTTPStatus == HTTPSuccess )
+ if( xHttpStatus != HTTPSuccess )
{
- LogDebug( ( "Received HTTP response from %s%s...",
- cServerHost, pcPath ) );
- LogDebug( ( "Response Headers:\n%.*s",
- ( int32_t ) xResponse.headersLen,
- xResponse.pHeaders ) );
- LogInfo( ( "Response Body:\n%.*s\n",
- ( int32_t ) xResponse.bodyLen,
- xResponse.pBody ) );
+ LogError( ( "Failed to retrieve \"%s\" header from response: Error=%s.",
+ AWS_IOT_CRED_PROVIDER_RESPONSE_DATE_HEADER, HTTPClient_strerror( xHttpStatus ) ) );
+ xStatus = pdFAIL;
+ }
+ }
- /* We increment by the content length because the server may not
- * have sent us the range we request. */
- xCurByte += xResponse.contentLength;
+ if( xStatus == pdPASS )
+ {
+ /* Convert AWS IoT date retrieved from IoT server to ISO 8601 date format. */
+ xSigv4Status = SigV4_AwsIotDateToIso8601( pDate, xDateLen, pcDateISO8601, xDateISO8601Len );
- if( ( xFileSize - xCurByte ) < xNumReqBytes )
- {
- xNumReqBytes = xFileSize - xCurByte;
- }
+ if( xSigv4Status != SigV4Success )
+ {
+ LogError( ( "Failed to convert AWS IoT date to ISO 8601 format." ) );
+ xStatus = pdFAIL;
+ }
+ }
- xStatus = ( xResponse.statusCode == httpexampleHTTP_STATUS_CODE_PARTIAL_CONTENT ) ? pdPASS : pdFAIL;
+ return xStatus;
+}
+
+/*-----------------------------------------------------------*/
+
+static JSONStatus_t prvParseCredentials( HTTPResponse_t * pxResponse,
+ SigV4Credentials_t * pxSigvCreds )
+{
+ JSONStatus_t xJsonStatus = JSONSuccess;
+ /* Expiration time for temporary credentials */
+ char * pcExpiration;
+ size_t xExpirationLen;
+
+ configASSERT( pxResponse != NULL );
+ configASSERT( pxSigvCreds != NULL );
+
+ if( xJsonStatus == JSONSuccess )
+ {
+ /* Get accessKeyId from HTTP response. */
+ xJsonStatus = JSON_Search( ( char * ) pxResponse->pBody,
+ pxResponse->bodyLen,
+ CREDENTIALS_RESPONSE_ACCESS_KEY_ID_KEY,
+ strlen( CREDENTIALS_RESPONSE_ACCESS_KEY_ID_KEY ),
+ ( char ** ) &( pxSigvCreds->pAccessKeyId ),
+ &( pxSigvCreds->accessKeyIdLen ) );
+
+ if( xJsonStatus != JSONSuccess )
+ {
+ LogError( ( "Error parsing accessKeyId in the credentials." ) );
+ }
+ }
+
+ if( xJsonStatus == JSONSuccess )
+ {
+ /* Get secretAccessKey from HTTP response. */
+ xJsonStatus = JSON_Search( ( char * ) pxResponse->pBody,
+ pxResponse->bodyLen,
+ CREDENTIALS_RESPONSE_SECRET_ACCESS_KEY,
+ strlen( CREDENTIALS_RESPONSE_SECRET_ACCESS_KEY ),
+ ( char ** ) &( pxSigvCreds->pSecretAccessKey ),
+ &( pxSigvCreds->secretAccessKeyLen ) );
+
+ if( xJsonStatus != JSONSuccess )
+ {
+ LogError( ( "Error parsing secretAccessKey in the credentials." ) );
+ }
+ }
+
+ if( xJsonStatus == JSONSuccess )
+ {
+ /* Get sessionToken from HTTP response. */
+ xJsonStatus = JSON_Search( ( char * ) pxResponse->pBody,
+ pxResponse->bodyLen,
+ CREDENTIALS_RESPONSE_SESSION_TOKEN_KEY,
+ strlen( CREDENTIALS_RESPONSE_SESSION_TOKEN_KEY ),
+ ( char ** ) &( pcSecurityToken ),
+ &( xSecurityTokenLen ) );
+
+ if( xJsonStatus != JSONSuccess )
+ {
+ LogError( ( "Error parsing sessionToken in the credentials." ) );
+ }
+ }
+
+ if( xJsonStatus == JSONSuccess )
+ {
+ /* Get expiration date from HTTP response. */
+ xJsonStatus = JSON_Search( ( char * ) pxResponse->pBody,
+ pxResponse->bodyLen,
+ CREDENTIALS_RESPONSE_EXPIRATION_DATE_KEY,
+ strlen( CREDENTIALS_RESPONSE_EXPIRATION_DATE_KEY ),
+ ( char ** ) &( pcExpiration ),
+ &( xExpirationLen ) );
+
+ if( xJsonStatus != JSONSuccess )
+ {
+ LogError( ( "Error parsing expiration date in the credentials." ) );
}
else
{
- LogError( ( "An error occurred in downloading the file. "
- "Failed to send HTTP GET request to %s%s: Error=%s.",
- cServerHost, pcPath, HTTPClient_strerror( xHTTPStatus ) ) );
+ LogInfo( ( "AWS IoT credentials will expire after this timestamp: %.*s.", xExpirationLen, pcExpiration ) );
}
+ }
+
+ return xJsonStatus;
+}
+
+static int32_t prvSha256Init( void * pxHashContext )
+{
+ mbedtls_sha256_init( ( mbedtls_sha256_context * ) pxHashContext );
+ return mbedtls_sha256_starts_ret( ( mbedtls_sha256_context * ) pxHashContext, 0 );
+}
+
+/*-----------------------------------------------------------*/
+
+static int32_t prvSha256Update( void * pxHashContext,
+ const uint8_t * pucInput,
+ size_t xInputLen )
+{
+ return mbedtls_sha256_update_ret( ( mbedtls_sha256_context * ) pxHashContext,
+ ( const unsigned char * ) pucInput,
+ xInputLen );
+}
+
+/*-----------------------------------------------------------*/
+
+static int32_t prvSha256Final( void * pxHashContext,
+ uint8_t * pucOutput,
+ size_t xOutputLen )
+{
+ configASSERT( xOutputLen >= SHA256_HASH_DIGEST_LENGTH );
- if( xStatus != pdPASS )
+ ( void ) xOutputLen;
+
+ return mbedtls_sha256_finish_ret( ( mbedtls_sha256_context * ) pxHashContext,
+ ( unsigned char * ) pucOutput );
+}
+
+static void prvGetHeaderStartLocFromHttpRequest( HTTPRequestHeaders_t * pxRequestHeaders,
+ char ** pcStartHeaderLoc,
+ size_t * pxHeadersDataLen )
+{
+ size_t xHeaderLen = pxRequestHeaders->headersLen;
+ char * pcHeaders = ( char * ) pxRequestHeaders->pBuffer;
+ bool xNewLineFound = false;
+
+ configASSERT( pcStartHeaderLoc != NULL );
+ configASSERT( pxHeadersDataLen != NULL );
+
+ while( xHeaderLen >= 2 )
+ {
+ /* The request line ends in \r\n. Look for \r\n. */
+ if( 0 == strncmp( pcHeaders, "\r\n", strlen( "\r\n" ) ) )
{
- LogError( ( "Received an invalid response from the server "
- "(Status Code: %u).",
- xResponse.statusCode ) );
+ xNewLineFound = true;
+ break;
}
+
+ pcHeaders++;
+ xHeaderLen--;
+ }
+
+ if( xNewLineFound == false )
+ {
+ LogError( ( "Failed to find starting location of HTTP headers in HTTP request: \"\\r\\n\" missing before start of HTTP headers." ) );
}
- return( ( xStatus == pdPASS ) && ( xHTTPStatus == HTTPSuccess ) );
+ configASSERT( xNewLineFound != false );
+
+ /* Moving header pointer past "\r\n" .*/
+ *pxHeadersDataLen = xHeaderLen - 2;
+ *pcStartHeaderLoc = pcHeaders + 2;
+}
+
+static void prvSha256Encode( const char * pcInputStr,
+ size_t xInputStrLen,
+ char * pcHexOutput )
+{
+ configASSERT( pcInputStr != NULL );
+ configASSERT( xInputStrLen >= 0 );
+ configASSERT( pcHexOutput != NULL );
+
+ const char cHexChars[] = "0123456789abcdef";
+ char * pcOutputChar = pcHexOutput;
+ static uint8_t ucSha256[ SHA256_HASH_DIGEST_LENGTH ];
+
+ mbedtls_sha256_ret( pcInputStr, xInputStrLen, ucSha256, 0 );
+
+ for(size_t i = 0; i < SHA256_HASH_DIGEST_LENGTH; i++ )
+ {
+ *pcOutputChar = cHexChars[ ( ucSha256[ i ] & 0xF0 ) >> 4 ];
+ pcOutputChar++;
+ *pcOutputChar = cHexChars[ ( ucSha256[ i ] & 0x0F ) ];
+ pcOutputChar++;
+ }
}