diff --git a/source/include/sigv4.h b/source/include/sigv4.h new file mode 100644 index 00000000..93d664f8 --- /dev/null +++ b/source/include/sigv4.h @@ -0,0 +1,392 @@ +/* + * SigV4 Utility Library v1.0.0 + * 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 sigv4.h + * @brief Interface for the SigV4 Client Utility Library. + */ + +#ifndef SIGV4_H_ +#define SIGV4_H_ + +/* Standard includes. */ +#include +#include + +/* SIGV4_DO_NOT_USE_CUSTOM_CONFIG allows building of the SigV4 library without a + * config file. If a config file is provided, the SIGV4_DO_NOT_USE_CUSTOM_CONFIG + * macro must not be defined. + */ +#ifndef SIGV4_DO_NOT_USE_CUSTOM_CONFIG + #include "sigv4_config.h" +#endif + +/* Include config defaults header to get default values of configurations not + * defined in sigv4_config.h file. */ +#include "sigv4_config_defaults.h" + +/* Convenience macros for library optimization */ + +/** @addtogroup sigv4_constants + * @{ + */ +/**< AWS identifier for this utility's SigV4 signing algorithms. */ +#define SIGV4_AWS4_HMAC_SHA256 "AWS4-HMAC-SHA256" + +/**< AWS identifier for S3 SigV4 streaming upload. */ +#define SIGV4_STREAMING_AWS4_HMAC_SHA256_PAYLOAD "STREAMING-AWS4-HMAC-SHA256-PAYLOAD" + +/**< AWS identifiers for Amazon specific HTTP header fields. */ +#define SIGV4_HTTP_X_AMZ_DATE_HEADER "x-amz-date" +#define SIGV4_HTTP_X_AMZ_SECURITY_TOKEN_HEADER "x-amz-security-token" +#define SIGV4_HTTP_X_AMZ_CONTENT_SHA256_HEADER "x-amz-content-sha256" +#define SIGV4_HTTP_X_AMZ_STORAGE_CLASS_HEADER "x-amz-storage-class" + +/**< Lengths of the security tokens. */ +#define SIGV4_ACCESS_KEY_ID_LENGTH 20U +#define SIGV4_SECRET_ACCESS_KEY_LENGTH 40U + +/**< Set this to one to statically enable support for canonicalizing the URI, + * headers, and query in this utility.*/ +#define SIGV4_USE_CANONICAL_SUPPORT 1 +/** @}*/ + +/** + * @defgroup sigv4_canonical_flags SigV4HttpParameters_t Flags + * @brief Flags for SigV4HttpParameters_t.flags. These flags inform the library + * of parameters already in canonical form. + * + * Flags should be bitwise-ORed with each other to change the behavior of + * #SigV4_GenerateHTTPAuthorization. + */ + +/** + * @ingroup sigv4_canonical_flags + * @brief Set this flag to indicate that the HTTP request path input is already + * canonicalized. + * + * This flag is valid only for #SigV4HttpParameters_t.flags. + */ +#define SIGV4_HTTP_PATH_IS_CANONICAL_FLAG 0x1U + +/** + * @ingroup sigv4_canonical_flags + * @brief Set this flag to indicate that the HTTP request query input is already + * canonicalized. + * + * This flag is valid only for #SigV4HttpParameters_t.flags. + */ +#define SIGV4_HTTP_QUERY_IS_CANONICAL_FLAG 0x2U + +/** + * @ingroup sigv4_canonical_flags + * @brief Set this flag to indicate that the HTTP request headers input is + * already canonicalized. + * + * This flag is valid only for #SigV4HttpParameters_t.flags. + */ +#define SIGV4_HTTP_HEADERS_ARE_CANONICAL_FLAG 0x4U + +/** + * @ingroup sigv4_canonical_flags + * @brief Set this flag to indicate that the HTTP request path, query, and + * headers are all already canonicalized. + * + * This flag is valid only for #SigV4HttpParameters_t.flags. + */ +#define SIGV4_HTTP_ALL_ARE_CANONICAL_FLAG 0x8U + +/** + * @ingroup sigv4_enum_types + * @brief Return status of the SigV4 Utility Library. + */ +typedef enum SigV4Status +{ + /** + * @brief The SigV4 Utility library function completed successfully. + * + * Functions that may return this value: + * - #SigV4_GenerateHTTPAuthorization + * - #SigV4_AwsIotDateToIso8601 + */ + SigV4Success, + + /** + * @brief The SigV4 Utility library function received an invalid input + * parameter. + * + * Functions that may return this value: + * - #SigV4_GenerateHTTPAuthorization + * - #SigV4_AwsIotDateToIso8601 + */ + SigV4InvalidParameter, + + /** + * @brief The application buffer was not large enough for the specified hash + * function. + * + * Functions that may return this value: + * - #SigV4_GenerateHTTPAuthorization + */ + SigV4InsufficientMemory, + + /** + * @brief An error occurred while formatting the provided date header. + * + * Functions that may return this value: + * - #SigV4_AwsIotDateToIso8601 + */ + SigV4ISOFormattingError +} SigV4Status_t; + +/** + * @ingroup sigv4_struct_types + * @brief The cryptography interface used to supply the user-defined hash + * implementation. + */ +typedef struct SigV4CryptoInterface +{ + /** + * @brief Initializes the @p pHashContext. + * + * @param[in] pHashContext Context used to maintain the hash's current state + * during incremental updates. + * + * @return Zero on success, all other return values are failures. + */ + int32_t ( * hashInit )( void * pHashContext ); + + /** + * @brief Calculates an ongoing hash update (SHA-256, for example). + * + * @param[in] pHashContext Context used to maintain the hash's current state + * during incremental updates. + * @param[in] pInput Buffer holding the data to hash. + * @param[in] inputLen length of the input buffer data. + * + * @return Zero on success, all other return values are failures. + */ + int32_t ( * hashUpdate )( void * pHashContext, + const uint8_t * pInput, + size_t inputLen ); + + /** + * @brief Calculates the final binary digest of the hash from the context. + * + * @param[in] pHashContext Context used to maintain the hash's current state + * during incremental updates. + * @param[out] pOutput The buffer used to place final hash binary digest + * output. + * @param[in] outputLen The length of the pOutput buffer. This is defined in + * #SIGV4_HASH_DIGEST_LENGTH. + * + * @return Zero on success, all other return values are failures. + */ + int32_t ( * hashFinal )( void * pHashContext, + uint8_t * pOutput, + size_t outputLen ); + + /* Context for the hashInit, hashUpdate, and hashFinal interfaces. */ + void * pHashContext; +} SigV4CryptoInterface_t; + +/** + * @ingroup sigv4_struct_types + * @brief Configurations of the HTTP request used to create the Canonical + * Request. + */ +typedef struct SigV4HttpParameters +{ + /** + * @brief The HTTP method: GET, POST, PUT, etc. + */ + const char * pHttpMethod; + size_t httpMethodLen; + + /** + * @brief These flags are used to indicate if the path, query, or headers are already + * in the canonical form. This is to bypass the internal sorting, white space + * trimming, and encoding done by the library. This is a performance optimization + * option. Please see https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html + * for information on generating a canonical path, query, and headers string. + * #define SIGV4_HTTP_PATH_IS_CANONICAL_FLAG 0x1 + * #define SIGV4_HTTP_QUERY_IS_CANONICAL_FLAG 0x2 + * #define SIGV4_HTTP_HEADERS_ARE_CANONICAL_FLAG 0x4 + * #define SIGV4_HTTP_ALL_ARE_CANONICAL_FLAG 0x8 + */ + uint32_t flags; + + /** + * @brief The path in the HTTP request. This is the absolute request URI, + * which contains everything in the URI following the HTTP host until the + * question mark character ("?") that begins any query string parameters + * (e.g. "/path/to/item.txt"). If #SIGV4_HTTP_PATH_IS_CANONICAL_FLAG is set, + * then this input must already be in canonical form. + */ + const char * pPath; + size_t pathLen; + + /** + * @brief The HTTP request query from the URL. This contains all characters + * following character that denotes the start of the query. If + * #SIGV4_HTTP_QUERY_IS_CANONICAL_FLAG is set, then this input must already + * be in canonical form. + */ + const char * pQuery; + size_t queryLen; + + /** + * @brief The headers from the HTTP request that we want to sign. This + * should be the raw headers in HTTP request format. If + * #SIGV4_HTTP_HEADERS_IS_CANONICAL_FLAG is set, then this input must + * already be in canonical form. + */ + const char * pHeaders; + size_t headersLen; + + /** + * @brief The HTTP response body, if one exists (ex. PUT request). If this + * body is chunked, then this field should be set with + * STREAMING-AWS4-HMAC-SHA256-PAYLOAD. + */ + const char * pPayload; + size_t payloadLen; +} SigV4HttpParameters_t; + +/** + * @ingroup sigv4_struct_types + * @brief Configurations for the AWS credentials used to generate the Signing + * Key. + */ +typedef struct SigV4Credentials +{ + /** + * @brief The pAccessKeyId MUST be 20 characters long. + */ + const char * pAccessKeyId; + size_t accessKeyLen; + + /** + * @brief The pSecretAccessKey MUST be 40 characters long. + */ + const char * pSecretAccessKey; + size_t secretAccessKeyLen; + + /** + * @brief The Security Token from STS is of varying length. This can be NULL + * if the access key id and secret access key were not retrieved from a + * temporary token service. + */ + const char * pSecurityToken; + size_t securityTokenLen; + + /** + * @brief The expiration time for the pAccessKeyId, pSecretAccessKey, and + * pSecurityToken if the credentials were retrieved from a temporary token + * service. This is in ISO8601 date and time format. + */ + const char * pExpiration; + size_t expirationLen; +} SigV4Credentials_t; + +/** + * @ingroup sigv4_struct_types + * @brief Nested struct containing the above parameters required to create the + * String to Sign and the Signing Key. + */ +typedef struct SigV4Parameters +{ + /** + * @brief The AccessKeyId, SecretAccessKey, and SecurityToken used to + * generate the Authorization header. + */ + SigV4Credentials_t * pCredentials; + + /** + * @brief The date in ISO 8601 format, e.g. "20150830T123600Z". This is + * always 16 characters long. + */ + const char * pDateIso8601; + + /** + * @brief The target AWS region for the request. Please see + * https://docs.aws.amazon.com/general/latest/gr/rande.html for a list of + * region names and codes. + */ + const char * pRegion; + size_t regionLen; + + /** + * @brief The target AWS service for the request. The service name can be + * found as the first segment of the service endpoint. Please see + * https://docs.aws.amazon.com/general/latest/gr/aws-service-information.html + * (https://docs.aws.amazon.com/general/latest/gr/aws-service-information.html) + * for your service of interest. + */ + const char * pService; + size_t serviceLen; + + /** + * @brief The cryptography interface. + */ + SigV4CryptoInterface_t * pCryptoInterface; + + /** + * @brief HTTP specific SigV4 parameters for canonical request calculation. + */ + SigV4HttpParameters_t * pHttpParameters; +} SigV4Parameters_t; + +/** + * @brief Generates the HTTP Authorization header value. + * + * @param[in] pParams Parameters for generating the SigV4 signature. + * @param[out] pAuthBuf Buffer to hold the generated Authorization header value. + * @param[in, out] authBufLen Input: the length of pAuthBuf, output: the length + * of the authorization value written to the buffer. + * @param[out] pSignature Location of the signature in the authorization string. + * @param[out] signatureLen The length of pSignature. + * + * @return #SigV4Success if successful, error code otherwise. + */ +SigV4Status_t SigV4_GenerateHTTPAuthorization( const SigV4Parameters_t * pParams, + char * pAuthBuf, + size_t * authBufLen, + char ** pSignature, + size_t * signatureLen ); + +/** + * @brief Parse the date header value from the AWS IoT response. + * The AWS IoT response date is of the form: 2018-01-18T09:18:06Z. + * + * @param[in] pDate The date header value. + * @param[in] dateLen length of the pDate header value. + * @param[out] pDateISO8601 The ISO8601 format compliant date. This buffer must + * be 17 characters in length to account for the null terminator. + * + * @return #SigV4Success code if successful, error code otherwise. + */ +SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, + size_t dateLen, + char pDateISO8601[ 17 ] ); + +#endif /* SIGV4_H_ */ diff --git a/source/include/sigv4_config_defaults.h b/source/include/sigv4_config_defaults.h new file mode 100644 index 00000000..f98c55d6 --- /dev/null +++ b/source/include/sigv4_config_defaults.h @@ -0,0 +1,183 @@ +/* + * SigV4 Utility Library v1.0.0 + * 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 sigv4_config_defaults.h + * @brief The default values for configuration macros used by the SigV4 Utility + * Library. + * + * @note This file should NOT be modified. If custom values are needed for any + * configuration macros, a sigv4_config.h file should be provided to the SigV4 + * Utility Library to override the default values defined in this file. To use + * the custom config file, the preprocessor macro SIGV4_DO_NOT_USE_CUSTOM_CONFIG + * should NOT be set. + */ + +#ifndef SIGV4_CONFIG_DEFAULTS_H_ +#define SIGV4_CONFIG_DEFAULTS_H_ + +/** + * @brief Macro defining the size of the internal buffer used for incremental + * canonicalization and hashing. + * + * A buffer of this size in bytes is declared on the stack. It should be be + * large enough for the digest output of the specified hash function. + * + * Possible values: Any positive 32 bit integer.
+ * Default value: `1024` + */ +#ifndef SIGV4_PROCESSING_BUFFER_LENGTH + #define SIGV4_PROCESSING_BUFFER_LENGTH 1024U +#endif + +/** + * @brief Macro defining the maximum number of headers in the request, used to + * assist the library in sorting header fields during canonicalization. + * + * This macro should be updated if the number of request headers the application + * wishes to sign is higher or lower than the default value (100). + * + * Possible values: Any positive 32 bit integer.
+ * Default value: `100` + */ +#ifndef SIGV4_MAX_HTTP_HEADER_COUNT + #define SIGV4_MAX_HTTP_HEADER_COUNT 100U +#endif + +/** + * @brief Macro defining the maximum number of query key/value pairs, used to + * assist the library in sorting query keys during canonicalization. + * + * This macro should be updated if the number of query key/value pairs the + * application wishes to sign is higher or lower than the default value (100). + * + * Possible values: Any positive 32 bit integer.
+ * Default value: `100` + */ +#ifndef SIGV4_MAX_QUERY_PAIR_COUNT + #define SIGV4_MAX_QUERY_PAIR_COUNT 100U +#endif + +/** + * @brief Macro defining the digest length of the specified hash function, used + * to determine the length of the output buffer. + * + * This macro should be updated if using a hashing algorithm other than SHA256 + * (32 byte digest length). For example, SHA512 would require this macro to be + * updated to 64 to accurately reflect its digest length. + * + * Possible values: Any positive 32 bit integer.
+ * Default value: `32` + */ +#ifndef SIGV4_HASH_DIGEST_LENGTH + #define SIGV4_HASH_DIGEST_LENGTH 32U +#endif + +/** + * @brief Macro called by the SigV4 Utility library for logging "Error" level + * messages. + * + * To enable error level logging in the SigV4 Utility library, this macro should + * be mapped to the application-specific logging implementation that supports + * error logging. + * + * @note This logging macro is called in the SigV4 Utility library with + * parameters wrapped in double parentheses to be ISO C89/C90 standard + * compliant. For a reference POSIX implementation of the logging macros, refer + * to sigv4_config.h files, and the logging-stack in demos folder of the [AWS + * IoT Embedded C SDK + * repository](https://github.com/aws/aws-iot-device-sdk-embedded-C). + * + * Default value: Error logging is turned off, and no code is generated + * for calls to the macro in the SigV4 Utility library on compilation. + */ +#ifndef LogError + #define LogError( message ) +#endif + +/** + * @brief Macro called by the the SigV4 Utility library for logging "Warning" + * level messages. + * + * To enable warning level logging in the SigV4 Utility library, this macro + * should be mapped to the application-specific logging implementation that + * supports warning logging. + * + * @note This logging macro is called in the SigV4 Utility library with + * parameters wrapped in double parentheses to be ISO C89/C90 standard + * compliant. For a reference POSIX implementation of the logging macros, refer + * to sigv4_config.h files, and the logging-stack in demos folder of the [AWS + * IoT Embedded C SDK + * repository](https://github.com/aws/aws-iot-device-sdk-embedded-C). + * + * Default value: Warning logs are turned off, and no code is generated + * for calls to the macro in the SigV4 Utility library on compilation. + */ +#ifndef LogWarn + #define LogWarn( message ) +#endif + +/** + * @brief Macro called by the the SigV4 Utility library for logging "Info" level + * messages. + * + * To enable info level logging in the SigV4 Utility library, this macro should + * be mapped to the application-specific logging implementation that supports + * info logging. + * + * @note This logging macro is called in the SigV4 Utility library with + * parameters wrapped in double parentheses to be ISO C89/C90 standard + * compliant. For a reference POSIX implementation of the logging macros, refer + * to sigv4_config.h files, and the logging-stack in demos folder of the [AWS + * IoT Embedded C SDK + * repository](https://github.com/aws/aws-iot-device-sdk-embedded-C). + * + * Default value: Info logging is turned off, and no code is generated + * for calls to the macro in the SigV4 Utility library on compilation. + */ +#ifndef LogInfo + #define LogInfo( message ) +#endif + +/** + * @brief Macro called by the the SigV4 Utility library for logging "Debug" + * level messages. + * + * To enable debug level logging from SigV4 Utility library, this macro should + * be mapped to the application-specific logging implementation that supports + * debug logging. + * + * @note This logging macro is called in the SigV4 Utility library with + * parameters wrapped in double parentheses to be ISO C89/C90 standard + * compliant. For a reference POSIX implementation of the logging macros, refer + * to sigv4_config.h files, and the logging-stack in demos folder of the [AWS + * IoT Embedded C SDK + * repository](https://github.com/aws/aws-iot-device-sdk-embedded-C). + * + * Default value: Debug logging is turned off, and no code is generated + * for calls to the macro in the SigV4 Utility library on compilation. + */ +#ifndef LogDebug + #define LogDebug( message ) +#endif + +#endif /* ifndef SIGV4_CONFIG_DEFAULTS_H_ */ diff --git a/source/sigv4.c b/source/sigv4.c new file mode 100644 index 00000000..ebb16fd0 --- /dev/null +++ b/source/sigv4.c @@ -0,0 +1,445 @@ +/* + * SigV4 Utility Library v1.0.0 + * 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 sigv4.c + * @brief Implements the user-facing functions in sigv4.h + */ + +#include +#include +#include +#include + +#include "sigv4.h" +#include "sigv4_internal.h" + +/*-----------------------------------------------------------*/ + +/** + * @brief Verifies parameters in @p pParams and its sub-members. + * + * @param[in] pParams Complete SigV4 configurations passed by application. + * + * @return #SigV4Success if successful, #SigV4InvalidParameters otherwise. + */ +static SigV4Status_t verifySigV4Parameters( const SigV4Parameters_t * pParams ); + + +#if ( SIGV4_USE_CANONICAL_SUPPORT == 1 ) + + /** + * @brief Normalize a URI string according to RFC 3986 and fill destination + * buffer with the formatted string. + * + * @param[in] pURI The URI string to encode. + * @param[in] uriLen Length of pURI. + * @param[out] pCanonicalURI The resulting canonicalized URI. + * @param[in, out] canonicalURILen input: the length of pCanonicalURI, + * output: the length of the generated canonical URI. + * @param[in] encodeSlash Option to indicate if slashes should be encoded. + * @param[in] nullTerminate Option to indicate if a null character should be + * added to the end of the canonical URI. + */ + static void encodeURI( const char * pURI, + size_t uriLen, + char * pCanonicalURI, + size_t * canonicalURILen, + bool encodeSlash, + bool nullTerminate ); + + /** + * @brief Canonicalize the full URI path. The input URI starts after the + * HTTP host and ends at the question mark character ("?") that begins the + * query string parameters (if any). Example: folder/subfolder/item.txt" + * + * @param[in] pUri HTTP request URI, also known that the request absolute + * path. + * @param[in] uriLen Length of pURI. + * @param[in] encodeOnce Service-dependent option to indicate whether + * encoding should be done once or twice. For example, S3 requires that the + * URI is encoded only once, while other services encode twice. + * @param[in, out] canonicalRequest Struct to maintain intermediary buffer + * and state of canonicalization. + */ + static void generateCanonicalURI( const char * pURI, + size_t uriLen, + bool encodeOnce, + canonicalContext_t * canonicalRequest ); + + /** + * @brief Canonicalize the query string HTTP URL, beginning (but not + * including) at the "?" character. Does not include "/". + * + * @param[in] pQuery HTTP request query. + * @param[in] queryLen Length of pQuery. + * @param[in, out] canonicalRequest Struct to maintain intermediary buffer + * and state of canonicalization. + */ + static void generateCanonicalQuery( const char * pQuery, + size_t queryLen, + canonicalContext_t * canonicalRequest ); + + /** + * @brief Canonicalize the HTTP request headers. + * + * @param[in] pHeaders The raw HTTP headers. + * @param[in] headerLen Length of pHeaders. + * @param[in, out] canonicalRequest Struct to maintain intermediary buffer + * and state of canonicalization. + */ + static void generateCanonicalHeaders( const char * pHeaders, + size_t headerLen, + canonicalContext_t * canonicalRequest ); + + +#endif /* if ( SIGV4_USE_CANONICAL_SUPPORT == 1 ) */ + +/*-----------------------------------------------------------*/ + +/* Converts a hex character to its integer value */ +static char hexToInt( char pHex ) +{ + return isdigit( pHex ) ? pHex - '0' : tolower( pHex ) - 'a' + 10; +} + +/* Converts an integer value to its hex character */ +static char intToHex( char pInt ) +{ + static char hex[] = "0123456789abcdef"; + + return hex[ pInt & 15 ]; +} + +static void encodeURI( const char * pURI, + size_t uriLen, + char * pCanonicalURI, + size_t * canonicalURILen, + bool encodeSlash, + bool nullTerminate ) +{ + char * pURILoc = pURI; + char * pBufLoc = pCanonicalURI; + size_t index = 0U; + + assert( pURI != NULL ); + assert( pCanonicalURI != NULL ); + assert( canonicalURILen != NULL ); + assert( *canonicalURILen > 0U ); + + while( index < uriLen && *pURILoc ) + { + if( isalnum( *pURILoc ) || ( *pURILoc == '-' ) || ( *pURILoc == '_' ) || ( *pURILoc == '.' ) || ( *pURILoc == '~' ) ) + { + *pBufLoc++ = *pURILoc; + } + else if( *pURILoc == '/' ) + { + *pBufLoc++ = encodeSlash ? '%2F' : *pURILoc; + } + else + { + *pBufLoc++ = '%', *pBufLoc++ = intToHex( *pURILoc >> 4 ), *pBufLoc++ = intToHex( *pURILoc & 15 ); + } + + pURILoc++; + index++; + } + + if( nullTerminate ) + { + *pBufLoc++ = '\0'; + index++; + } + + *canonicalURILen = index; +} + +/*-----------------------------------------------------------*/ + +static void generateCanonicalURI( const char * pURI, + size_t uriLen, + bool encodeOnce, + canonicalContext_t * canonicalRequest ) +{ + size_t encodedLen, remainingLen = canonicalRequest->bufRemaining; + char * pBufLoc = canonicalRequest->pBufCur; + + assert( pURI != NULL ); + assert( canonicalRequest != NULL ); + + encodeURI( pURI, uriLen, pBufLoc, &encodedLen, false, true ); + + remainingLen -= encodedLen; + + if( !encodeOnce ) + { + encodeURI( pBufLoc, encodedLen, pBufLoc + encodedLen, &remainingLen, false, true ); + memmove( canonicalRequest->pBufCur + encodedLen, canonicalRequest->pBufCur, remainingLen ); + } + + canonicalRequest->pBufCur += remainingLen; + *( canonicalRequest->pBufCur++ ) = '\n'; + + canonicalRequest->bufRemaining -= remainingLen + 1; +} + +/*-----------------------------------------------------------*/ + +static int cmpFun( const void * a, + const void * b ) +{ + char * token_a = strtok( ( char * ) a, "=" ); + char * token_b = strtok( ( char * ) b, "=" ); + + int compare = strcmp( token_a, token_b ); + + if( strcmp == 0 ) + { + token_a = strtok( NULL, "=" ); + token_b = strtok( NULL, "=" ); + + assert( token_a != NULL ); + assert( token_b != NULL ); + + compare = strcmp( token_a, token_b ); + } + + return compare; +} + +static void generateCanonicalQuery( const char * pQuery, + size_t queryLen, + canonicalContext_t * canonicalRequest ) +{ + size_t index = 0U; + size_t remainingLen = canonicalRequest->bufRemaining; + char * pBufLoc = canonicalRequest->pBufCur; + char * tokenQueries, tokenParams; + + assert( pQuery != NULL ); + assert( canonicalRequest != NULL ); + + tokenQueries = strtok( pQuery, "&" ); + + while( tokenQueries != NULL ) + { + canonicalRequest->pQueryLoc[ index ] = &tokenQueries[ 0 ]; + tokenQueries = strtok( NULL, "&" ); + + index++; + } + + qsort( canonicalRequest->pQueryLoc, index, cmpFun ); + + for( int i = 0; i < index; i++ ) + { + tokenParams = strtok( canonicalRequest->pQueryLoc[ i ], "=" ); + + if( tokenParams != NULL ) + { + encodeURI( tokenParams, strlen( tokenParams ), pBufLoc, &remainingLen, true, false ); + pBufLoc += remainingLen; + *pBufLoc = '='; /* Overwrite null character. */ + + canonicalRequest->bufRemaining -= remainingLen; + remainingLen = canonicalRequest->bufRemaining; + } + + tokenParams = strtok( NULL, "=" ); + + if( tokenParams != NULL ) + { + encodeURI( tokenParams, strlen( tokenParams ), pBufLoc, &remainingLen, true, false ); + pBufLoc += remainingLen; + + canonicalRequest->bufRemaining -= remainingLen; + remainingLen = canonicalRequest->bufRemaining; + } + + if( index != i + 1 ) + { + *pBufLoc++ = '&'; + *pBufLoc++ = '\0'; + *pBufLoc++ = '\n'; + canonicalRequest->bufRemaining -= 3; + } + } + + canonicalRequest->pBufCur = pBufLoc; +} + +/*-----------------------------------------------------------*/ + +static SigV4Status_t verifySigV4Parameters( const SigV4Parameters_t * pParams ) +{ + SigV4Status_t returnStatus = SigV4Success; + + /* Check for NULL members of struct pParams */ + if( pParams == NULL ) + { + LogError( ( "Parameter check failed: pParams is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + else if( pParams->pCredentials == NULL ) + { + LogError( ( "Parameter check failed: pParams->pCredentials is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + else if( pParams->pCredentials->pAccessKeyId == NULL ) + { + LogError( ( "Parameter check failed: pParams->pCredentials->pAccessKeyId is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + else if( pParams->pCredentials->pSecretAccessKey == NULL ) + { + LogError( ( "Parameter check failed: pParams->pCredentials->pSecretAccessKey is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + else if( pParams->pCredentials->pSecurityToken == NULL ) + { + LogError( ( "Parameter check failed: pParams->pCredentials->pSecurityToken is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + else if( pParams->pCredentials->pExpiration == NULL ) + { + LogError( ( "Parameter check failed: pParams->pCredentials->pExpiration is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + else if( pParams->pDateIso8601 == NULL ) + { + LogError( ( "Parameter check failed: pParams->pDateIso8601 is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + else if( pParams->pRegion == NULL ) + { + LogError( ( "Parameter check failed: pParams->pRegion is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + else if( pParams->pService == NULL ) + { + LogError( ( "Parameter check failed: pParams->pService is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + else if( pParams->pCryptoInterface == NULL ) + { + LogError( ( "Parameter check failed: pParams->pCryptoInterface is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + else if( pParams->pCryptoInterface->pHashContext == NULL ) + { + LogError( ( "Parameter check failed: pParams->pCryptoInterface->pHashContext is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + else if( pParams->pHttpParameters == NULL ) + { + LogError( ( "Parameter check failed: pParams->pHttpParameters is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + else if( pParams->pHttpParameters->pHttpMethod == NULL ) + { + LogError( ( "Parameter check failed: pParams->pHttpParameters->pHttpMethod is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + else if( pParams->pHttpParameters->pPath == NULL ) + { + LogError( ( "Parameter check failed: pParams->pHttpParameters->pPath is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + else if( pParams->pHttpParameters->pQuery == NULL ) + { + LogError( ( "Parameter check failed: pParams->pHttpParameters->pQuery is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + else if( pParams->pHttpParameters->pHeaders == NULL ) + { + LogError( ( "Parameter check failed: pParams->pHttpParameters->pHeaders is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + else if( pParams->pHttpParameters->pPayload == NULL ) + { + LogError( ( "Parameter check failed: pParams->pHttpParameters->pPayload is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + + return returnStatus; +} + + +/*-----------------------------------------------------------*/ + +SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, + size_t dateLen, + char pDateISO8601[ 17 ] ) +{ + SigV4Status_t returnStatus = SigV4Success; + size_t lenFormatted = 0U; + char * pLastChar = NULL; + struct tm tm; + + /* Check for NULL parameters. */ + if( pDate == NULL ) + { + LogError( ( "Parameter check failed: pDate is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + else if( pDateISO8601 == NULL ) + { + LogError( ( "Parameter check failed: pDateISO8601 is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + /* Check validity of the date header size provided. */ + else if( dateLen == 0U ) + { + LogError( ( "Parameter check failed: dateLen must be greater than 0." ) ); + returnStatus = SigV4InvalidParameter; + } + + if( returnStatus == SigV4Success ) + { + memset( &tm, 0, sizeof( struct tm ) ); + pLastChar = strptime( pDate, "%Y-%m-%dT%H:%M:%SZ", &tm ); + + if( pLastChar == NULL ) + { + LogError( ( "Error matching input to ISO8601 format string." ) ); + returnStatus == SigV4ISOFormattingError; + } + else if( pLastChar != '\0' ) + { + LogWarn( ( "Input contained more characters than expected." ) ); + } + } + + if( returnStatus == SigV4Success ) + { + lenFormatted = strftime( pDateISO8601, 17, "%Y%m%dT%H%M%SZ", &tm ); + + if( lenFormatted != 16 ) + { + LogError( ( "Formatted string is not of expected length 16." ) ); + returnStatus = SigV4ISOFormattingError; + } + } + + return returnStatus; +}