From 1b6905b274429defaf2fe2af68283c2a30ab5d76 Mon Sep 17 00:00:00 2001 From: Shivangi <66447522+gshvang@users.noreply.github.com> Date: Mon, 9 Aug 2021 12:10:33 -0700 Subject: [PATCH 01/12] Add Canonical and signed headers functions (#28) * pages.dox update * lexicon.txt change * Formatting changes * Initial code * MISRA Compliance for continue statement * Lexicon update * lexicon update * Refactor complexity * Complexity update * Build fixes * Lexicon update * Remove non working code * memory statistics update --- docs/doxygen/include/size_table.html | 8 +- lexicon.txt | 8 + source/include/sigv4.h | 18 +- source/include/sigv4_internal.h | 128 ++++++- source/sigv4.c | 541 +++++++++++++++++++-------- 5 files changed, 530 insertions(+), 173 deletions(-) diff --git a/docs/doxygen/include/size_table.html b/docs/doxygen/include/size_table.html index e8ae753f..62e8d2fe 100644 --- a/docs/doxygen/include/size_table.html +++ b/docs/doxygen/include/size_table.html @@ -9,12 +9,12 @@ sigv4.c -
1.1K
-
0.8K
+
1.5K
+
1.2K
Total estimates -
1.1K
-
0.8K
+
1.5K
+
1.2K
diff --git a/lexicon.txt b/lexicon.txt index d50b5200..26b7b819 100644 --- a/lexicon.txt +++ b/lexicon.txt @@ -5,6 +5,7 @@ addtodate addtogroup aggregator amz +api apr ascii authbuflen @@ -39,7 +40,10 @@ gr hashfinal hashinit hashupdate +headercount +headerindex headerlen +headersdatalen headerslen hh hhmmss @@ -128,6 +132,7 @@ sep servicelen sha signaturelen +signedheaders sizeof snprintf ss @@ -140,6 +145,8 @@ sts subfolder sublicense thu +trimmable +trimmedlength tm tue txt @@ -148,5 +155,6 @@ uri urilen url utc +vallen yyyy yyyymmdd diff --git a/source/include/sigv4.h b/source/include/sigv4.h index 04c4fc56..a7ff7a71 100644 --- a/source/include/sigv4.h +++ b/source/include/sigv4.h @@ -150,7 +150,19 @@ typedef enum SigV4Status * Functions that may return this value: * - #SigV4_AwsIotDateToIso8601 */ - SigV4ISOFormattingError + SigV4ISOFormattingError, + + /** + * @brief The maximum number of header parameters was exceeded while parsing + * the http header string passed to the library. + * The maximum number of supported HTTP headers can be configured + * with the SIGV4_MAX_HTTP_HEADER_COUNT macro in the library config file + * passed by the application. + * + * Functions that may return this value: + * - #SigV4_GenerateHTTPAuthorization + */ + SigV4MaxHeaderPairCountExceeded } SigV4Status_t; /** @@ -358,6 +370,8 @@ typedef struct SigV4Parameters /** * @brief Generates the HTTP Authorization header value. * + * @note The API does not support HTTP headers containing empty HTTP header keys or values. + * * @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 @@ -423,5 +437,7 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, size_t dateLen, char * pDateISO8601, size_t dateISO8601Len ); + /* @[declare_sigV4_awsIotDateToIso8601_function] */ + #endif /* SIGV4_H_ */ diff --git a/source/include/sigv4_internal.h b/source/include/sigv4_internal.h index 09d8c297..228603d7 100644 --- a/source/include/sigv4_internal.h +++ b/source/include/sigv4_internal.h @@ -28,9 +28,21 @@ #ifndef SIGV4_INTERNAL_H_ #define SIGV4_INTERNAL_H_ +/* 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" + /* Constants for date verification. */ -#define YEAR_MIN 1900L /**< Earliest year accepted. */ -#define MONTH_ASCII_LEN 3U /**< Length of month abbreviations. */ +#define YEAR_MIN 1900L /**< Earliest year accepted. */ +#define MONTH_ASCII_LEN 3U /**< Length of month abbreviations. */ /** * @brief Month name abbreviations for RFC 5322 date parsing. @@ -42,16 +54,66 @@ */ #define MONTH_DAYS { 31, 28, 31, 30, 31, 30, 31, 31, 30, 31, 30, 31 } -#define FORMAT_RFC_3339 "%4Y-%2M-%2DT%2h:%2m:%2sZ" /**< Format string to parse RFC 3339 date. */ -#define FORMAT_RFC_3339_LEN sizeof( FORMAT_RFC_3339 ) - 1U /**< Length of the RFC 3339 format string. */ +#define FORMAT_RFC_3339 "%4Y-%2M-%2DT%2h:%2m:%2sZ" /**< Format string to parse RFC 3339 date. */ +#define FORMAT_RFC_3339_LEN sizeof( FORMAT_RFC_3339 ) - 1U /**< Length of the RFC 3339 format string. */ -#define FORMAT_RFC_5322 "%3*, %2D %3M %4Y %2h:%2m:%2s GMT" /**< Format string to parse RFC 5322 date. */ -#define FORMAT_RFC_5322_LEN sizeof( FORMAT_RFC_5322 ) - 1U /**< Length of the RFC 3339 format string. */ +#define FORMAT_RFC_5322 "%3*, %2D %3M %4Y %2h:%2m:%2s GMT" /**< Format string to parse RFC 5322 date. */ +#define FORMAT_RFC_5322_LEN sizeof( FORMAT_RFC_5322 ) - 1U /**< Length of the RFC 3339 format string. */ -#define ISO_YEAR_LEN 4U /**< Length of year value in ISO 8601 date. */ -#define ISO_NON_YEAR_LEN 2U /**< Length of non-year values in ISO 8601 date. */ +#define ISO_YEAR_LEN 4U /**< Length of year value in ISO 8601 date. */ +#define ISO_NON_YEAR_LEN 2U /**< Length of non-year values in ISO 8601 date. */ -#define ISO_DATE_SCOPE_LEN 8U /**< Length of date substring used in credential scope. */ +#define ISO_DATE_SCOPE_LEN 8U /**< Length of date substring used in credential scope. */ + +/* SigV4 related string literals and lengths. */ + +/** + * @brief The separator between each component of the credential scope. + */ +#define CREDENTIAL_SCOPE_SEPARATOR '/' +#define CREDENTIAL_SCOPE_SEPARATOR_LEN 1U /**< The length of #CREDENTIAL_SCOPE_SEPARATOR. */ + +/** + * @brief The last component that terminates the credential scope. + */ +#define CREDENTIAL_SCOPE_TERMINATOR "aws4_request" +#define CREDENTIAL_SCOPE_TERMINATOR_LEN ( sizeof( CREDENTIAL_SCOPE_TERMINATOR ) - 1U ) /**< The length of #CREDENTIAL_SCOPE_TERMINATOR. */ + +/** + * @brief Default value when HttpParameters_t.pPath == NULL. + */ +#define HTTP_EMPTY_PATH "/" +#define HTTP_EMPTY_PATH_LEN ( sizeof( HTTP_EMPTY_PATH ) - 1U ) /**< The length of #HTTP_EMPTY_PATH. */ + +#define LINEFEED_CHAR '\n' /**< A linefeed character used to build the canonical request. */ +#define LINEFEED_CHAR_LEN 1U /**< The length of #LINEFEED_CHAR. */ + +#define SPACE_CHAR ' ' /**< A linefeed character used to build the Authorization header value. */ +#define SPACE_CHAR_LEN 1U /**< The length of #SPACE_CHAR. */ + +#define S3_SERVICE_NAME "s3" /**< S3 is the only service where the URI must only be encoded once. */ +#define S3_SERVICE_NAME_LEN ( sizeof( S3_SERVICE_NAME ) - 1U ) /**< The length of #S3_SERVICE_NAME. */ + +#define SIGV4_HMAC_SIGNING_KEY_PREFIX "AWS4" /**< HMAC signing key prefix. */ +#define SIGV4_HMAC_SIGNING_KEY_PREFIX_LEN ( sizeof( SIGV4_HMAC_SIGNING_KEY_PREFIX ) - 1U ) /**< The length of #SIGV4_HMAC_SIGNING_KEY_PREFIX. */ + +#define AUTH_CREDENTIAL_PREFIX "Credential=" /**< The prefix that goes before the credential value in the Authorization header value. */ +#define AUTH_CREDENTIAL_PREFIX_LEN ( sizeof( AUTH_CREDENTIAL_PREFIX ) - 1U ) /**< The length of #AUTH_CREDENTIAL_PREFIX. */ +#define AUTH_SEPARATOR ", " /**< The separator between each component in the Authorization header value. */ +#define AUTH_SEPARATOR_LEN ( sizeof( AUTH_SEPARATOR ) - 1U ) /**< The length of #AUTH_SEPARATOR. */ +#define AUTH_SIGNED_HEADERS_PREFIX "SignedHeaders=" /**< The prefix that goes before the signed headers in the Authorization header value. */ +#define AUTH_SIGNED_HEADERS_PREFIX_LEN ( sizeof( AUTH_SIGNED_HEADERS_PREFIX ) - 1U ) /**< The length of #AUTH_SIGNED_HEADERS_PREFIX. */ +#define AUTH_SIGNATURE_PREFIX "Signature=" /**< The prefix that goes before the signature in the Authorization header value. */ +#define AUTH_SIGNATURE_PREFIX_LEN ( sizeof( AUTH_SIGNATURE_PREFIX ) - 1U ) /**< The length of #AUTH_SIGNATURE_PREFIX. */ + +/** + * @brief A helper macro to print insufficient memory errors. + */ +#define LOG_INSUFFICIENT_MEMORY_ERROR( purposeOfWrite, bytesExceeded ) \ + do { \ + LogError( ( "Insufficient memory provided to " purposeOfWrite ", bytesExceeded=%lu", \ + ( unsigned long ) ( bytesExceeded ) ) ); \ + } while( 0 ) /** * @brief An aggregator representing the individually parsed elements of the @@ -79,6 +141,17 @@ typedef struct SigV4String size_t dataLen; /**< Length of pData */ } SigV4String_t; +/** + * @brief A library structure holding the string and length values of parameters to + * be sorted and standardized. This allows for a layer of abstraction during the + * canonicalization step of the V4 signing process. + */ +typedef struct SigV4ConstString +{ + const char * pData; /**< SigV4 string data */ + size_t dataLen; /**< Length of pData */ +} SigV4ConstString_t; + /** * @brief A key-value pair data structure that allows for sorting of SigV4 * string values using internal comparison functions, and provides additional @@ -86,8 +159,8 @@ typedef struct SigV4String */ typedef struct SigV4KeyValuePair { - SigV4String_t key; /**< SigV4 string identifier */ - SigV4String_t value; /**< SigV4 data */ + SigV4ConstString_t key; /**< SigV4 string identifier */ + SigV4ConstString_t value; /**< SigV4 data */ } SigV4KeyValuePair_t; typedef SigV4KeyValuePair_t SigV4Header_t; /**< SigV4 header representation */ @@ -98,12 +171,35 @@ typedef SigV4KeyValuePair_t SigV4Header_t; /**< SigV4 header representation */ */ typedef struct CanonicalContext { - char * pQueryLoc[ SIGV4_MAX_QUERY_PAIR_COUNT ]; /**< Query pointers used during sorting. */ - char * pHeadersLoc[ SIGV4_MAX_HTTP_HEADER_COUNT ]; /**< Header pointers used during sorting. */ + SigV4KeyValuePair_t pQueryLoc[ SIGV4_MAX_QUERY_PAIR_COUNT ]; /**< Query pointers used during sorting. */ + SigV4KeyValuePair_t pHeadersLoc[ SIGV4_MAX_HTTP_HEADER_COUNT ]; /**< Header pointers used during sorting. */ - uint8_t pBufProcessing[ SIGV4_PROCESSING_BUFFER_LENGTH ]; /**< Internal calculation buffer used during canonicalization. */ - char * pBufCur; /**< pBufProcessing cursor */ - size_t bufRemaining; /**< pBufProcessing value used during internal calculation. */ + uint8_t pBufProcessing[ SIGV4_PROCESSING_BUFFER_LENGTH ]; /**< Internal calculation buffer used during canonicalization. */ + char * pBufCur; /**< pBufProcessing cursor. */ + size_t bufRemaining; /**< pBufProcessing value used during internal calculation. */ } CanonicalContext_t; +/** + * @brief An aggregator to maintain the internal state of HMAC + * calculations. + */ +typedef struct HmacContext +{ + /** + * @brief The cryptography interface. + */ + const SigV4CryptoInterface_t * pCryptoInterface; + + /** + * @brief All accumulated key data. + */ + char key[ SIGV4_HASH_DIGEST_LENGTH ]; + + /** + * @brief The length of the accumulated key data. + */ + size_t keyLen; +} HmacContext_t; + + #endif /* ifndef SIGV4_INTERNAL_H_ */ diff --git a/source/sigv4.c b/source/sigv4.c index 1fa69007..22b089df 100644 --- a/source/sigv4.c +++ b/source/sigv4.c @@ -122,8 +122,8 @@ * a value greater than 0 if @pSecondVal < @pFirstVal. 0 is never returned in * order to provide stability to qSort() calls. */ - static int cmpKeyValue( const void * pFirstVal, - const void * pSecondVal ); + static int cmpField( const void * pFirstVal, + const void * pSecondVal ); #endif /* #if (SIGV4_USE_CANONICAL_SUPPORT == 1) */ @@ -246,6 +246,129 @@ static SigV4Status_t verifySigV4Parameters( const SigV4Parameters_t * pParams ); static void hexEncode( SigV4String_t * pInputStr, SigV4String_t * pHexOutput ); +/** + * @brief Extract all header key-value pairs from the passed headers data and add them + * to the canonical request. + * + * @param[in] pHeaders HTTP headers to canonicalize. + * @param[in] headersLen Length of HTTP headers to canonicalize. + * @param[in] flags Flag to indicate if headers are already + * in the canonical form. + * @param[out] canonicalRequest Struct to maintain intermediary buffer + * and state of canonicalization. + * + * @return Following statuses will be returned by the function: + * #SigV4Success if headers are successfully added to the canonical request. + * #SigV4InsufficientMemory if canonical request buffer cannot accommodate the header. + * #SigV4InvalidParameter if HTTP headers are invalid. + * #SigV4MaxHeaderPairCountExceeded if number of headers that needs to be canonicalized + * exceed the SIGV4_MAX_HTTP_HEADER_COUNT macro defined in the config file. + */ +static SigV4Status_t appendAllHeadersToCanonicalRequest( const char * pHeaders, + size_t headersLen, + uint32_t flags, + CanonicalContext_t * canonicalRequest ); + +/** + * @brief Append Signed Headers to the Canonical Request buffer. + * + * @param[in] headerCount Number of headers which needs to be appended. + * @param[in] flags Flag to indicate if headers are already + * in the canonical form. + * @param[in,out] canonicalRequest Struct to maintain intermediary buffer + * and state of canonicalization. + * + * @return Following statuses will be returned by the function: + * #SigV4Success if headers are successfully added to the canonical request. + * #SigV4InsufficientMemory if canonical request buffer cannot accommodate the header. + */ +static SigV4Status_t appendSignedHeaders( size_t headerCount, + uint32_t flags, + CanonicalContext_t * canonicalRequest ); + +/** + * @brief Canonicalize headers and append it to the Canonical Request buffer. + * + * @param[in] headerCount Number of headers which needs to be appended. + * @param[in] flags Flag to indicate if headers are already + * in the canonical form. + * @param[in,out] canonicalRequest Struct to maintain intermediary buffer + * and state of canonicalization. + * + * @return Following statuses will be returned by the function: + * #SigV4Success if headers are successfully added to the canonical request. + * #SigV4InsufficientMemory if canonical request buffer cannot accommodate the header. + */ +static SigV4Status_t appendCanonicalizedHeaders( size_t headerCount, + uint32_t flags, + CanonicalContext_t * canonicalRequest ); + +/** + * @brief Parse each header key and value pair from HTTP headers. + * + * @param[in] pHeaders HTTP headers to parse. + * @param[in] headersDataLen Length of HTTP headers to parse. + * @param[in] flags Flag to indicate if headers are already + * in the canonical form. + * @param[out] headerCount Count of key-value pairs parsed from pData. + * @param[out] canonicalRequest Struct to maintain intermediary buffer + * and state of canonicalization. + * + * @return Following statuses will be returned by the function: + * #SigV4Success if header key or value is successfully added to the canonical request. + * #SigV4InsufficientMemory if canonical request buffer cannot accommodate the header. + * #SigV4MaxHeaderPairCountExceeded if number of key-value entries in the headers data + * exceeds the SIGV4_MAX_HTTP_HEADER_COUNT macro defined in the config file. + */ +SigV4Status_t parseHeaderKeyValueEntries( const char * pHeaders, + size_t headersDataLen, + uint32_t flags, + size_t * headerCount, + CanonicalContext_t * canonicalRequest ); + +/** + * @brief Copy header key or header value to the Canonical Request buffer. + * + * @param[in] pData Header Key or value to be copied to the canonical request. + * @param[in] dataLen Length of Header Key or value. + * @param[in] flags Flag to indicate if headers are already + * in the canonical form. + * @param[in] separator Character separating the multiple key-value pairs or key and values. + * @param[in,out] canonicalRequest Struct to maintain intermediary buffer + * and state of canonicalization. + * + * @return Following statuses will be returned by the function: + * #SigV4Success if the headers are successfully added to the canonical request. + * #SigV4InsufficientMemory if canonical request buffer cannot accommodate the header. + */ +SigV4Status_t copyHeaderStringToCanonicalBuffer( const char * pData, + size_t dataLen, + uint32_t flags, + char separator, + CanonicalContext_t * canonicalRequest ); + +/** + * @brief Helper function to determine whether a header string character represents a space + * that can be trimmed when creating "Canonical Headers". + * All leading and trailing spaces in the header strings need to be trimmed. Also, sequential spaces + * in the header value need to be trimmed to a single space. + * + * Example of modifying header field for Canonical Headers: + * Actual header pair: | Modifier header pair + * My-Header2: "a b c" \n | my-header2:"a b c"\n + * + * @param[in] value Header value or key string to be trimmed. + * @param[in] index Index of current character. + * @param[in] valLen Length of the string. + * @param[in] trimmedLength Current length of trimmed string. + * + * @return `true` if the character needs to be trimmed, else `false`. + */ +static bool isTrimmableSpace( const char * value, + size_t index, + size_t valLen, + size_t trimmedLength ); + /*-----------------------------------------------------------*/ static void intToAscii( int32_t value, @@ -677,8 +800,8 @@ static SigV4Status_t getCredentialScope( SigV4Parameters_t * pSigV4Params, /*-----------------------------------------------------------*/ - static int cmpKeyValue( const void * pFirstVal, - const void * pSecondVal ) + static int cmpField( const void * pFirstVal, + const void * pSecondVal ) { SigV4KeyValuePair_t * pFirst, * pSecond = NULL; size_t lenSmall = 0U; @@ -762,199 +885,313 @@ static SigV4Status_t getCredentialScope( SigV4Parameters_t * pSigV4Params, /*-----------------------------------------------------------*/ - static void generateCanonicalURI( const char * pURI, - size_t uriLen, - bool encodeOnce, - CanonicalContext_t * canonicalRequest ) + static bool isTrimmableSpace( const char * value, + size_t index, + size_t valLen, + size_t trimmedLength ) { - char * pBufLoc = NULL; - size_t encodedLen, remainingLen = 0U; + bool ret = false; - assert( pURI != NULL ); + assert( ( value != NULL ) && ( index < valLen ) ); + + /* Only trim spaces. */ + if( isspace( value[ index ] ) ) + { + /* The last character is a trailing space. */ + if( ( index + 1 ) == valLen ) + { + ret = true; + } + /* Trim if the next character is also a space. */ + else if( isspace( value[ index + 1 ] ) ) + { + ret = true; + } + /* It is a leading space if no characters have been written yet. */ + else if( trimmedLength == 0U ) + { + ret = true; + } + } + + return ret; + } + +/*-----------------------------------------------------------*/ + + SigV4Status_t copyHeaderStringToCanonicalBuffer( const char * pData, + size_t dataLen, + uint32_t flags, + char separator, + CanonicalContext_t * canonicalRequest ) + { + SigV4Status_t status = SigV4Success; + size_t index = 0; + size_t numOfBytesCopied = 0; + size_t buffRemaining; + char * pCurrBufLoc; + + assert( ( pData != NULL ) && ( dataLen > 0 ) ); assert( canonicalRequest != NULL ); assert( canonicalRequest->pBufCur != NULL ); - pBufLoc = ( char * ) canonicalRequest->pBufCur; - encodedLen, remainingLen = canonicalRequest->bufRemaining; - encodeURI( pURI, uriLen, pBufLoc, &encodedLen, false, true ); - - remainingLen -= encodedLen; + buffRemaining = canonicalRequest->bufRemaining; + pCurrBufLoc = canonicalRequest->pBufCur; - if( !encodeOnce ) + for( index = 0; index < dataLen; index++ ) { - encodeURI( pBufLoc, encodedLen, pBufLoc + encodedLen, &remainingLen, false, true ); - memmove( canonicalRequest->pBufCur + encodedLen, canonicalRequest->pBufCur, remainingLen ); + /* If the header field is not in canonical form already, we need to check + * whether this character represents a trimmable space. */ + if( !( flags & SIGV4_HTTP_HEADERS_ARE_CANONICAL_FLAG ) && + isTrimmableSpace( pData, index, dataLen, numOfBytesCopied ) ) + { + /* Cannot copy trimmable space into canonical request buffer. */ + } + /* Remaining buffer space should at least accommodate the character to copy and the trailing separator character. */ + else if( buffRemaining <= 1 ) + { + status = SigV4InsufficientMemory; + break; + } + else + { + /* Lowercase header key only. '\n' character marks the end of the value and header value + * does not need to be lowercased. */ + if( separator == '\n' ) + { + *pCurrBufLoc = ( pData[ index ] ); + } + else + { + *pCurrBufLoc = tolower( pData[ index ] ); + } + + pCurrBufLoc++; + numOfBytesCopied++; + buffRemaining -= 1; + } } - canonicalRequest->pBufCur += remainingLen; - *( canonicalRequest->pBufCur++ ) = '\n'; + /* Add the ending separating character passed to the function. + * Note: Space for the separator character is accounted for while copying + * header field data to canonical request buffer. */ + if( status == SigV4Success ) + { + assert( buffRemaining >= 1 ); + *pCurrBufLoc = separator; + pCurrBufLoc++; + canonicalRequest->pBufCur = pCurrBufLoc; + canonicalRequest->bufRemaining = ( buffRemaining - 1 ); + } - canonicalRequest->bufRemaining -= remainingLen + 1; + return status; } /*-----------------------------------------------------------*/ - static void generateCanonicalQuery( const char * pQuery, - size_t queryLen, - CanonicalContext_t * canonicalRequest ) + static SigV4Status_t appendSignedHeaders( size_t headerCount, + uint32_t flags, + CanonicalContext_t * canonicalRequest ) { - size_t index, remainingLen, i = 0U; - char * pBufLoc, * tokenQueries, * tokenParams = NULL; + size_t headerIndex = 0, keyLen = 0; + SigV4Status_t sigV4Status = SigV4Success; + const char * headerKey; - assert( pQuery != NULL ); - assert( queryLen > 0U ); assert( canonicalRequest != NULL ); assert( canonicalRequest->pBufCur != NULL ); + assert( headerCount > 0 ); - remainingLen = canonicalRequest->bufRemaining; - pBufLoc = ( char * ) canonicalRequest->pBufCur; + for( headerIndex = 0; headerIndex < headerCount; headerIndex++ ) + { + assert( ( canonicalRequest->pHeadersLoc[ headerIndex ].key.pData ) != NULL ); + keyLen = canonicalRequest->pHeadersLoc[ headerIndex ].key.dataLen; - tokenQueries = strtok( ( char * ) pQuery, "&" ); + headerKey = canonicalRequest->pHeadersLoc[ headerIndex ].key.pData; - while( tokenQueries != NULL ) - { - canonicalRequest->pQueryLoc[ index ] = &tokenQueries[ 0 ]; - tokenQueries = strtok( NULL, "&" ); + /* ';' is used to separate signed multiple headers in the canonical request. */ + sigV4Status = copyHeaderStringToCanonicalBuffer( headerKey, keyLen, flags, ';', canonicalRequest ); - index++; + if( sigV4Status != SigV4Success ) + { + break; + } } - qsort( canonicalRequest->pQueryLoc, index, sizeof( char * ), cmpKeyValue ); - - for( i = 0U; i < index; i++ ) + if( sigV4Status == SigV4Success ) { - tokenParams = strtok( canonicalRequest->pQueryLoc[ i ], "=" ); + /* Replacing the last ';' with '\n' as last header does need to have ';'. */ + *( canonicalRequest->pBufCur - 1 ) = '\n'; + } - if( tokenParams != NULL ) - { - encodeURI( tokenParams, strlen( tokenParams ), pBufLoc, &remainingLen, true, false ); - pBufLoc += remainingLen; - *pBufLoc = '='; /* Overwrite null character. */ + return sigV4Status; + } - canonicalRequest->bufRemaining -= remainingLen; - remainingLen = canonicalRequest->bufRemaining; - } +/*-----------------------------------------------------------*/ - tokenParams = strtok( NULL, "=" ); + static SigV4Status_t appendCanonicalizedHeaders( size_t headerCount, + uint32_t flags, + CanonicalContext_t * canonicalRequest ) + { + size_t headerIndex = 0, keyLen = 0, valLen = 0; + const char * value; + const char * headerKey; + SigV4Status_t sigV4Status = SigV4Success; - if( tokenParams != NULL ) - { - encodeURI( tokenParams, strlen( tokenParams ), pBufLoc, &remainingLen, true, false ); - pBufLoc += remainingLen; + assert( canonicalRequest != NULL ); + assert( canonicalRequest->pBufCur != NULL ); + assert( headerCount > 0 ); - canonicalRequest->bufRemaining -= remainingLen; - remainingLen = canonicalRequest->bufRemaining; + for( headerIndex = 0; headerIndex < headerCount; headerIndex++ ) + { + assert( canonicalRequest->pHeadersLoc[ headerIndex ].key.pData != NULL ); + keyLen = canonicalRequest->pHeadersLoc[ headerIndex ].key.dataLen; + valLen = canonicalRequest->pHeadersLoc[ headerIndex ].value.dataLen; + headerKey = canonicalRequest->pHeadersLoc[ headerIndex ].key.pData; + /* ':' is used to separate header key and header value in the canonical request. */ + sigV4Status = copyHeaderStringToCanonicalBuffer( headerKey, keyLen, flags, ':', canonicalRequest ); + + if( sigV4Status == SigV4Success ) + { + value = canonicalRequest->pHeadersLoc[ headerIndex ].value.pData; + /* '\n' is used to separate each key-value pair in the canonical request. */ + sigV4Status = copyHeaderStringToCanonicalBuffer( value, valLen, flags, '\n', canonicalRequest ); } - if( index != i + 1 ) + if( sigV4Status != SigV4Success ) { - *( pBufLoc++ ) = '&'; - *( pBufLoc++ ) = '\0'; - *( pBufLoc++ ) = '\n'; - canonicalRequest->bufRemaining -= 3; + break; } } - canonicalRequest->pBufCur = pBufLoc; + return sigV4Status; } -#endif /* #if ( SIGV4_USE_CANONICAL_SUPPORT == 1 ) */ - /*-----------------------------------------------------------*/ -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 ) + SigV4Status_t parseHeaderKeyValueEntries( const char * pHeaders, + size_t headersDataLen, + uint32_t flags, + size_t * headerCount, + CanonicalContext_t * canonicalRequest ) { - 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; + size_t index = 0, noOfHeaders; + const char * pKeyOrValStartLoc; + const char * pCurrLoc; + bool keyFlag = true; + SigV4Status_t sigV4Status = SigV4Success; + + assert( pHeaders != NULL ); + assert( headersDataLen > 0 ); + assert( canonicalRequest != NULL ); + assert( headerCount != NULL ); + + noOfHeaders = *headerCount; + pKeyOrValStartLoc = pHeaders; + pCurrLoc = pHeaders; + + for( index = 0; index < headersDataLen; index++ ) + { + if( noOfHeaders == SIGV4_MAX_HTTP_HEADER_COUNT ) + { + sigV4Status = SigV4MaxHeaderPairCountExceeded; + break; + } + /* Look for key part of an header field entry. */ + else if( ( keyFlag ) && ( pHeaders[ index ] == ':' ) ) + { + canonicalRequest->pHeadersLoc[ noOfHeaders ].key.pData = pKeyOrValStartLoc; + canonicalRequest->pHeadersLoc[ noOfHeaders ].key.dataLen = ( pCurrLoc - pKeyOrValStartLoc ); + pKeyOrValStartLoc = pCurrLoc + 1U; + keyFlag = false; + } + /* Look for header value part of a header field entry for both canonicalized and non-canonicalized forms. */ + /* Non-canonicalized headers will have header values ending with "\r\n". */ + else if( ( !keyFlag ) && !( flags & SIGV4_HTTP_HEADERS_ARE_CANONICAL_FLAG ) && ( ( index + 1 ) < headersDataLen ) && + ( 0 == strncmp( pCurrLoc, "\r\n", strlen( "\r\n" ) ) ) ) + { + canonicalRequest->pHeadersLoc[ noOfHeaders ].value.pData = pKeyOrValStartLoc; + canonicalRequest->pHeadersLoc[ noOfHeaders ].value.dataLen = ( pCurrLoc - pKeyOrValStartLoc ); + /* Set starting location of the next header key string after the "\r\n". */ + pKeyOrValStartLoc = pCurrLoc + 2U; + keyFlag = true; + noOfHeaders++; + } + /* Canonicalized headers will have header values ending just with "\n". */ + else if( ( !keyFlag ) && ( ( flags & SIGV4_HTTP_HEADERS_ARE_CANONICAL_FLAG ) && ( pHeaders[ index ] == '\n' ) ) ) + { + canonicalRequest->pHeadersLoc[ noOfHeaders ].value.pData = pKeyOrValStartLoc; + canonicalRequest->pHeadersLoc[ noOfHeaders ].value.dataLen = ( pCurrLoc - pKeyOrValStartLoc ); + /* Set starting location of the next header key string after the "\n". */ + pKeyOrValStartLoc = pCurrLoc + 1U; + keyFlag = true; + noOfHeaders++; + } + + pCurrLoc++; + } + + /* Ensure each key has its corresponding value. */ + assert( keyFlag == true ); + + *headerCount = noOfHeaders; + + return sigV4Status; } - else if( pParams->pHttpParameters->pPayload == NULL ) + +/*-----------------------------------------------------------*/ + + static SigV4Status_t appendAllHeadersToCanonicalRequest( const char * pHeaders, + size_t headersLen, + uint32_t flags, + CanonicalContext_t * canonicalRequest ) { - LogError( ( "Parameter check failed: pParams->pHttpParameters->pPayload is NULL." ) ); - returnStatus = SigV4InvalidParameter; + size_t noOfHeaders = 0; + SigV4Status_t sigV4Status = SigV4Success; + + assert( pHeaders != NULL ); + assert( canonicalRequest != NULL ); + assert( canonicalRequest->pBufCur != NULL ); + + /* Parsing header string to extract key and value. */ + sigV4Status = parseHeaderKeyValueEntries( pHeaders, + headersLen, + flags, + &noOfHeaders, + canonicalRequest ); + + if( ( sigV4Status == SigV4Success ) && !( flags & SIGV4_HTTP_HEADERS_ARE_CANONICAL_FLAG ) ) + { + /* Sorting headers based on keys. */ + qsort( canonicalRequest->pHeadersLoc, noOfHeaders, sizeof( SigV4KeyValuePair_t ), cmpField ); + + /* If the headers are canonicalized, we will copy them directly into the buffer as they do not + * need processing, else we need to call the following function. */ + sigV4Status = appendCanonicalizedHeaders( noOfHeaders, flags, canonicalRequest ); + } + + if( ( sigV4Status == SigV4Success ) && !( flags & SIGV4_HTTP_HEADERS_ARE_CANONICAL_FLAG ) ) + { + if( canonicalRequest->bufRemaining < 1 ) + { + sigV4Status = SigV4InsufficientMemory; + } + else + { + *canonicalRequest->pBufCur = '\n'; + canonicalRequest->pBufCur++; + canonicalRequest->bufRemaining--; + } + } + + if( sigV4Status == SigV4Success ) + { + sigV4Status = appendSignedHeaders( noOfHeaders, flags, canonicalRequest ); + } + + return sigV4Status; } - return returnStatus; -} +#endif /* #if ( SIGV4_USE_CANONICAL_SUPPORT == 1 ) */ /*-----------------------------------------------------------*/ From 1e95193a9b4d9a75bed0f26dfacf14938895eab2 Mon Sep 17 00:00:00 2001 From: Amazon GitHub Automation <54958958+amazon-auto@users.noreply.github.com> Date: Tue, 2 Feb 2021 17:39:43 -0800 Subject: [PATCH 02/12] SigV4_GenerateHTTPAuthorization Implementation Update auto-generated .md files Add test and tool directories (#1) Adding test and tools directories, with CMock submodule. CI Actions (#2) Add header files + default configurations (#3) - Add files sigv4.h and sigv4_config_defaults.h - Add public-facing API elements detailed in design doc Format Date Header for ISO8601 Compliance (#4) Add optional utility function to format date header returned from AWS IoT (ex. in temp tokens) for compliance with the ISO8601 format required for authentication Add unit tests for SigV4_AwsIotDateToIso8601() (#8) Setup proof infrastructure for CBMC (#7) * Add Litani and templates for CBMC * Add sample proof * Implement CBMC proof for SigV4_AwsIotDateToIso8601 * Unwind all loops such that no unwinding errors occur Change submodule to use https rather than ssh for aws-templates-for-cbmc-proofs (#12) * CBMC fix test (do not merge) * Change AWS templates to https instead of ssh * Revert README Update README.md and LICENSE files (#14) Update README.md and LICENSE files before changing repo's visibility status (to public). Add remaining doxygen + link verification checks (#15) Add doxygen + link verifier checks (the library-specific doxygen content will be added in a separate PR for further review). [SigV4] CBMC proof for Sigv4_awsIotdatetoISO8601 API (#19) * Sigv4_AWSIOtDateToISO8601 CBMC PROOF * Unit test coverage changes Add release workflow (+ revert to previous license) (#18) change permissions of run_cbmc_proofs.py (#21) [Sigv4] Doxygen content updates (#22) * doxygen doc update * lexicon update Update proof tools (+disable submodule cloning by default) (#20) This commit advances Litani to release 1.10.0, and the starter kit to the tip-of-tree. This brings the following improvements: - Profiling - Litani measures the memory usage of the CBMC safety checking and coverage checking jobs - The dashboard includes box-and-whisker diagrams for memory use per proof - The dashboard includes a graph of how many parallel jobs are running over the whole run, making it easy to choose a CI machine with enough parallelism - It is now possible to designate particular proofs as "EXPENSIVE"; Litani runs expensive proofs serially, ensuring that they do not over-consume resources like RAM. - UI improvements - Each pipeline page includes a table of contents - Each pipeline page includes a dependency graph of the pipeline - Each job on the pipeline page has a hyperlink to that job - The terminal output is now less noisy SigV4_GenerateHTTPAuthorization() API Functionality (#16) * Squash of outdated #13 commits * Hold for checks * Add definitions for sorting structures * Include parsing functions * Fix old commit error * Missing asserts * (temporarily allow warnings) * Spell check + include partial context * More updates to lexicon+doxygen * Add asserts for private func. * Move access after asserts * Clarify pointer increment * Update postfix syntax for correct operator precedence * Feedback changes only * + remove accidental duplicate Implement credential scope Implement generate credential query Validation of parameter count Solution a bit overcomplicated Squash bugs and canonical query parameters should also be sorted by value Finish canonicalize query Fix canonical URI encoding Add hash helper function Add hmac implementation Add newline chars for canonical request Finish writing of canonical request Hex-encoded hash of canonical request matches Write string to sign Fix bug Refactor writeStringToSign for complexity Allow HMAC keys to be passed through separate function calls Add code for generating signing key Fix hmac bug Generate the final signature correctly Fix bug Fix newline not being written Merge Shivangi's code Stylistic changes Link OpenSSL to the test Add unit tests attaining branch coverage of 71% Integrate Shivangi's latest changes Output authBufLen when complete Update logic when headers are precanonicalized. Add additional parameter checks for block/digest len Add documentation Fix test case Get complexity <= 8 for private functions Reduce complexity Remove use of %zu Revert changes to test as it was added to another PR Uncrustify and add doxygen strings. Add docs Resolve doxygen errors and lexicon.txt Document all private functions Fix remaining doxygen errors Update lexicon.txt Remove duplicate declaration Remove assertions on pQuery being NULL Add log messages for insufficient memory errors Uncrustify --- docs/doxygen/pages.dox | 6 + lexicon.txt | 65 +- source/include/sigv4.h | 84 +- source/include/sigv4_config_defaults.h | 25 +- source/include/sigv4_internal.h | 40 +- source/sigv4.c | 2411 +++++++++++++++++++----- 6 files changed, 2136 insertions(+), 495 deletions(-) diff --git a/docs/doxygen/pages.dox b/docs/doxygen/pages.dox index 6fad7fcf..acb0f3d1 100644 --- a/docs/doxygen/pages.dox +++ b/docs/doxygen/pages.dox @@ -3,6 +3,7 @@ @anchor sigv4 @brief AWS Iot SigV4 Utility +<<<<<<< HEAD

The AWS IoT SigV4 Library is a standalone utility for generating a signature and authorization header according to the specifications of the AWS Signature Version 4 @@ -10,6 +11,8 @@ signing process. This utility is an optional addition to applications sending di HTTP requests to AWS services requiring SigV4 authentication. The library is written in C and designed to be compliant with ISO C90 and MISRA C. It has proven safe memory use.

+======= +>>>>>>> bf7559c (SigV4_GenerateHTTPAuthorization Implementation) @section sigv4_memory_requirements Memory Requirements @brief Memory requirements of the SigV4 Utility library. @@ -23,6 +26,7 @@ HTTP requests to AWS services requiring SigV4 authentication. The library is wri All functions in the SigV4 library operate only on the buffers provided and use only local variables on the stack.

+<<<<<<< HEAD

Compliance & Coverage

@@ -32,6 +36,8 @@ All functions are written to have minimal complexity. Unit tests and CBMC proofs are written to cover every path of execution and achieve 100% branch coverage.

+======= +>>>>>>> bf7559c (SigV4_GenerateHTTPAuthorization Implementation) */ /** diff --git a/lexicon.txt b/lexicon.txt index 26b7b819..9d5e66ba 100644 --- a/lexicon.txt +++ b/lexicon.txt @@ -1,32 +1,35 @@ - accesskeyid -accesskeylen +accesskeyidlen addtodate addtogroup aggregator +algorithmlen amz -api apr ascii +auth authbuflen +authorizaton aws br bufferlen bufremaining canonicalrequest canonicalurilen -cbmc chunked com config +completehashandhexencode const +constness copydoc datalen datelen dd deconstructed defgroup -encodeonce +doubleencodeequals +encodetwice encodeslash endif enums @@ -37,17 +40,21 @@ formatlen github gmt gr +hashblocklen +hashdigestlen hashfinal hashinit hashupdate headercount headerindex headerlen -headersdatalen headerslen hh hhmmss hmac +hmacdata +hmacfinal +hmackey html http httpmethodlen @@ -57,13 +64,18 @@ ifndef inc ingroup inputlen +ipad iot iso jan january +keylen +ksecret leninput lentoread +linelen lv +maclen mainpage min misra @@ -72,20 +84,27 @@ mon monthsperday noninfringement nullterminate +opad ored org outputlen paccesskeyid +palgorithm param pathlen pauthbuf +pauthprefixlen payloadlen pbufcur pbuffer pbufloc pbufprocessing +pbufstart +pbytesremaining +pcanonicalcontext pcanonicaluri pcredscope +pcryptointerface pdata pdate pdateelements @@ -96,13 +115,18 @@ phashcontext pheaders pheadersloc phexoutput +phmaccontext phttpmethod pinput pinputstr +pkey +pline +pmac posix poutput poutputexpected poutputleapexpected +poutputlen pparams ppath ppayload @@ -114,6 +138,9 @@ psecretaccesskey psecuritytoken pservice psignature +psignedheaders +psignedheaderslen +psigningkey ptestformatfailure puri qsort @@ -122,6 +149,8 @@ rande readloc regionlen rfc +trimmedlen +trimmedlength sdk sec secretaccesskey @@ -132,22 +161,41 @@ sep servicelen sha signaturelen +<<<<<<< HEAD +signedheaders +======= +<<<<<<< HEAD +======= signedheaders +signedheaderslen +signingkey +>>>>>>> bf7559c (SigV4_GenerateHTTPAuthorization Implementation) +>>>>>>> d393cea (SigV4_GenerateHTTPAuthorization Implementation) sizeof snprintf ss sscanf +<<<<<<< HEAD standalone strftime +======= +strftime +stringtosign +>>>>>>> bf7559c (SigV4_GenerateHTTPAuthorization Implementation) struct structs sts subfolder sublicense thu +<<<<<<< HEAD trimmable trimmedlength tm +======= +tm +trimmedlen +>>>>>>> bf7559c (SigV4_GenerateHTTPAuthorization Implementation) tue txt un @@ -156,5 +204,10 @@ urilen url utc vallen +<<<<<<< HEAD +======= +xor +xy +>>>>>>> bf7559c (SigV4_GenerateHTTPAuthorization Implementation) yyyy yyyymmdd diff --git a/source/include/sigv4.h b/source/include/sigv4.h index a7ff7a71..2bcbb543 100644 --- a/source/include/sigv4.h +++ b/source/include/sigv4.h @@ -49,20 +49,23 @@ /** @addtogroup sigv4_constants * @{ */ -#define SIGV4_AWS4_HMAC_SHA256 "AWS4-HMAC-SHA256" /**< AWS identifier for SHA256 signing algorithm. */ -#define SIGV4_HTTP_X_AMZ_DATE_HEADER "x-amz-date" /**< AWS identifier for HTTP date header. */ -#define SIGV4_HTTP_X_AMZ_SECURITY_TOKEN_HEADER "x-amz-security-token" /**< AWS identifier for security token. */ +#define SIGV4_AWS4_HMAC_SHA256 "AWS4-HMAC-SHA256" /**< AWS identifier for SHA256 signing algorithm. */ +#define SIGV4_AWS4_HMAC_SHA256_LENGTH ( sizeof( SIGV4_AWS4_HMAC_SHA256 ) - 1U ) /**< AWS identifier for SHA256 signing algorithm. */ +#define SIGV4_HTTP_X_AMZ_DATE_HEADER "x-amz-date" /**< AWS identifier for HTTP date header. */ +#define SIGV4_HTTP_X_AMZ_SECURITY_TOKEN_HEADER "x-amz-security-token" /**< AWS identifier for security token. */ -#define SIGV4_STREAMING_AWS4_HMAC_SHA256_PAYLOAD "STREAMING-AWS4-HMAC-SHA256-PAYLOAD" /**< S3 identifier for chunked payloads. */ -#define SIGV4_HTTP_X_AMZ_CONTENT_SHA256_HEADER "x-amz-content-sha256" /**< S3 identifier for streaming requests. */ -#define SIGV4_HTTP_X_AMZ_STORAGE_CLASS_HEADER "x-amz-storage-class" /**< S3 identifier for reduced streaming redundancy. */ +#define SIGV4_STREAMING_AWS4_HMAC_SHA256_PAYLOAD "STREAMING-AWS4-HMAC-SHA256-PAYLOAD" /**< S3 identifier for chunked payloads. */ +#define SIGV4_HTTP_X_AMZ_CONTENT_SHA256_HEADER "x-amz-content-sha256" /**< S3 identifier for streaming requests. */ +#define SIGV4_HTTP_X_AMZ_STORAGE_CLASS_HEADER "x-amz-storage-class" /**< S3 identifier for reduced streaming redundancy. */ -#define SIGV4_ACCESS_KEY_ID_LENGTH 20U /**< Length of access key ID. */ -#define SIGV4_SECRET_ACCESS_KEY_LENGTH 40U /**< Length of secret access key. */ +#define SIGV4_ACCESS_KEY_ID_LENGTH 20U /**< Length of access key ID. */ +#define SIGV4_SECRET_ACCESS_KEY_LENGTH 40U /**< Length of secret access key. */ + +#define SIGV4_ISO_STRING_LEN 16U /**< Length of ISO 8601 date string. */ +#define SIGV4_EXPECTED_LEN_RFC_3339 20U /**< Length of RFC 3339 date input. */ +#define SIGV4_EXPECTED_LEN_RFC_5322 29U +/**< Length of RFC 5322 date input. */ -#define SIGV4_ISO_STRING_LEN 16U /**< Length of ISO 8601 date string. */ -#define SIGV4_EXPECTED_LEN_RFC_3339 20U /**< Length of RFC 3339 date input. */ -#define SIGV4_EXPECTED_LEN_RFC_5322 29U /**< Length of RFC 5322 date input. */ /** @}*/ /** @@ -108,7 +111,7 @@ * * This flag is valid only for #SigV4HttpParameters_t.flags. */ -#define SIGV4_HTTP_ALL_ARE_CANONICAL_FLAG 0x8U +#define SIGV4_HTTP_ALL_ARE_CANONICAL_FLAG 0x7U /** * @ingroup sigv4_enum_types @@ -162,7 +165,24 @@ typedef enum SigV4Status * Functions that may return this value: * - #SigV4_GenerateHTTPAuthorization */ - SigV4MaxHeaderPairCountExceeded + SigV4MaxHeaderPairCountExceeded, + + /** + * @brief The maximum number of query parameters was exceeded while parsing + * the query string input parameter. + * + * Functions that may return this value: + * - #SigV4_GenerateHTTPAuthorization + */ + SigV4MaxQueryPairCountExceeded, + + /** + * @brief An error occurred while performing a hash operation. + * + * Functions that may return this value: + * - #SigV4_GenerateHTTPAuthorization + */ + SigV4HashError, } SigV4Status_t; /** @@ -193,7 +213,7 @@ typedef struct SigV4CryptoInterface * @return Zero on success, all other return values are failures. */ int32_t ( * hashUpdate )( void * pHashContext, - const uint8_t * pInput, + const char * pInput, size_t inputLen ); /** @@ -205,18 +225,28 @@ typedef struct SigV4CryptoInterface * output. * @param[in] outputLen The length of the pOutput buffer, which must be * larger than the hash digest length specified in - * #SIGV4_HASH_DIGEST_LENGTH. + * #SIGV4_HASH_MAX_DIGEST_LENGTH. * * @return Zero on success, all other return values are failures. */ int32_t ( * hashFinal )( void * pHashContext, - uint8_t * pOutput, + char * pOutput, size_t outputLen ); /** * @brief Context for the hashInit, hashUpdate, and hashFinal interfaces. */ void * pHashContext; + + /** + * @brief The block length of the hash function. + */ + size_t hashBlockLen; + + /** + * @brief The digest length of the hash function. + */ + size_t hashDigestLen; } SigV4CryptoInterface_t; /** @@ -238,7 +268,7 @@ typedef struct SigV4HttpParameters * - #SIGV4_HTTP_PATH_IS_CANONICAL_FLAG 0x1 * - #SIGV4_HTTP_QUERY_IS_CANONICAL_FLAG 0x2 * - #SIGV4_HTTP_HEADERS_ARE_CANONICAL_FLAG 0x4 - * - #SIGV4_HTTP_ALL_ARE_CANONICAL_FLAG 0x8 + * - #SIGV4_HTTP_ALL_ARE_CANONICAL_FLAG 0x7 */ uint32_t flags; @@ -287,13 +317,13 @@ typedef struct SigV4HttpParameters typedef struct SigV4Credentials { /** - * @brief The pAccessKeyId MUST be 20 characters long. + * @brief The pAccessKeyId MUST be at least 16 characters long. */ const char * pAccessKeyId; - size_t accessKeyLen; /**< @brief Length of pAccessKeyId. */ + size_t accessKeyIdLen; /**< @brief Length of pAccessKeyId. */ /** - * @brief The pSecretAccessKey MUST be 40 characters long. + * @brief The pSecretAccessKey MUST be at least 40 characters long. */ const char * pSecretAccessKey; size_t secretAccessKeyLen; /**< @brief Length of pSecretAccessKey. */ @@ -338,6 +368,14 @@ typedef struct SigV4Parameters */ const char * pDateIso8601; + /** + * @brief The algorithm used for SigV4 authentication. If set to NULL, + * this will automatically be set to "AWS4-HMAC-SHA256" by default. + */ + const char * pAlgorithm; + + size_t algorithmLen; /**< @brief Length of pAlgorithm. */ + /** * @brief The target AWS region for the request. Please see * https://docs.aws.amazon.com/general/latest/gr/rande.html for a list of @@ -369,15 +407,14 @@ typedef struct SigV4Parameters /** * @brief Generates the HTTP Authorization header value. - * * @note The API does not support HTTP headers containing empty HTTP header keys or values. * * @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 + * @param[in, out] authBufLen Input: the length of @p 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. + * @param[out] signatureLen The length of @p pSignature. * * @return #SigV4Success if successful, error code otherwise. */ @@ -437,7 +474,6 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, size_t dateLen, char * pDateISO8601, size_t dateISO8601Len ); - /* @[declare_sigV4_awsIotDateToIso8601_function] */ #endif /* SIGV4_H_ */ diff --git a/source/include/sigv4_config_defaults.h b/source/include/sigv4_config_defaults.h index ee6e7d38..befdb816 100644 --- a/source/include/sigv4_config_defaults.h +++ b/source/include/sigv4_config_defaults.h @@ -78,18 +78,31 @@ #endif /** - * @brief Macro defining the digest length of the specified hash function, used - * to determine the length of the output buffer. + * @brief Macro indicating the largest block size of any hashing + * algorithm used for SigV4 authentication i.e. the maximum of all + * values specified for the hashBlockLen in #SigV4CryptoInterface_t. + * For example, using SHA-512 would require this value to be at least 128. + * + * Possible values: Any positive 32 bit integer.
+ * Default value: `64` + */ +#ifndef SIGV4_HASH_MAX_BLOCK_LENGTH + #define SIGV4_HASH_MAX_BLOCK_LENGTH 64U +#endif + +/** + * @brief Macro defining the maximum 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. + * (32 byte digest length). For example, using SHA512 would require this + * value to be at least 64. * * Possible values: Any positive 32 bit integer.
* Default value: `32` */ -#ifndef SIGV4_HASH_DIGEST_LENGTH - #define SIGV4_HASH_DIGEST_LENGTH 32U +#ifndef SIGV4_HASH_MAX_DIGEST_LENGTH + #define SIGV4_HASH_MAX_DIGEST_LENGTH 32U #endif /** diff --git a/source/include/sigv4_internal.h b/source/include/sigv4_internal.h index 228603d7..f48d27db 100644 --- a/source/include/sigv4_internal.h +++ b/source/include/sigv4_internal.h @@ -71,40 +71,40 @@ * @brief The separator between each component of the credential scope. */ #define CREDENTIAL_SCOPE_SEPARATOR '/' -#define CREDENTIAL_SCOPE_SEPARATOR_LEN 1U /**< The length of #CREDENTIAL_SCOPE_SEPARATOR. */ +#define CREDENTIAL_SCOPE_SEPARATOR_LEN 1U /**< The length of #CREDENTIAL_SCOPE_SEPARATOR. */ /** * @brief The last component that terminates the credential scope. */ #define CREDENTIAL_SCOPE_TERMINATOR "aws4_request" -#define CREDENTIAL_SCOPE_TERMINATOR_LEN ( sizeof( CREDENTIAL_SCOPE_TERMINATOR ) - 1U ) /**< The length of #CREDENTIAL_SCOPE_TERMINATOR. */ +#define CREDENTIAL_SCOPE_TERMINATOR_LEN ( sizeof( CREDENTIAL_SCOPE_TERMINATOR ) - 1U ) /**< The length of #CREDENTIAL_SCOPE_TERMINATOR. */ /** * @brief Default value when HttpParameters_t.pPath == NULL. */ #define HTTP_EMPTY_PATH "/" -#define HTTP_EMPTY_PATH_LEN ( sizeof( HTTP_EMPTY_PATH ) - 1U ) /**< The length of #HTTP_EMPTY_PATH. */ +#define HTTP_EMPTY_PATH_LEN ( sizeof( HTTP_EMPTY_PATH ) - 1U ) /**< The length of #HTTP_EMPTY_PATH. */ -#define LINEFEED_CHAR '\n' /**< A linefeed character used to build the canonical request. */ -#define LINEFEED_CHAR_LEN 1U /**< The length of #LINEFEED_CHAR. */ +#define LINEFEED_CHAR '\n' /**< A linefeed character used to build the canonical request. */ +#define LINEFEED_CHAR_LEN 1U /**< The length of #LINEFEED_CHAR. */ -#define SPACE_CHAR ' ' /**< A linefeed character used to build the Authorization header value. */ -#define SPACE_CHAR_LEN 1U /**< The length of #SPACE_CHAR. */ +#define SPACE_CHAR ' ' /**< A linefeed character used to build the Authorization header value. */ +#define SPACE_CHAR_LEN 1U /**< The length of #SPACE_CHAR. */ -#define S3_SERVICE_NAME "s3" /**< S3 is the only service where the URI must only be encoded once. */ -#define S3_SERVICE_NAME_LEN ( sizeof( S3_SERVICE_NAME ) - 1U ) /**< The length of #S3_SERVICE_NAME. */ +#define S3_SERVICE_NAME "s3" /**< S3 is the only service where the URI must only be encoded once. */ +#define S3_SERVICE_NAME_LEN ( sizeof( S3_SERVICE_NAME ) - 1U ) /**< The length of #S3_SERVICE_NAME. */ -#define SIGV4_HMAC_SIGNING_KEY_PREFIX "AWS4" /**< HMAC signing key prefix. */ -#define SIGV4_HMAC_SIGNING_KEY_PREFIX_LEN ( sizeof( SIGV4_HMAC_SIGNING_KEY_PREFIX ) - 1U ) /**< The length of #SIGV4_HMAC_SIGNING_KEY_PREFIX. */ +#define SIGV4_HMAC_SIGNING_KEY_PREFIX "AWS4" /**< HMAC signing key prefix. */ +#define SIGV4_HMAC_SIGNING_KEY_PREFIX_LEN ( sizeof( SIGV4_HMAC_SIGNING_KEY_PREFIX ) - 1U ) /**< The length of #SIGV4_HMAC_SIGNING_KEY_PREFIX. */ -#define AUTH_CREDENTIAL_PREFIX "Credential=" /**< The prefix that goes before the credential value in the Authorization header value. */ -#define AUTH_CREDENTIAL_PREFIX_LEN ( sizeof( AUTH_CREDENTIAL_PREFIX ) - 1U ) /**< The length of #AUTH_CREDENTIAL_PREFIX. */ -#define AUTH_SEPARATOR ", " /**< The separator between each component in the Authorization header value. */ -#define AUTH_SEPARATOR_LEN ( sizeof( AUTH_SEPARATOR ) - 1U ) /**< The length of #AUTH_SEPARATOR. */ -#define AUTH_SIGNED_HEADERS_PREFIX "SignedHeaders=" /**< The prefix that goes before the signed headers in the Authorization header value. */ -#define AUTH_SIGNED_HEADERS_PREFIX_LEN ( sizeof( AUTH_SIGNED_HEADERS_PREFIX ) - 1U ) /**< The length of #AUTH_SIGNED_HEADERS_PREFIX. */ -#define AUTH_SIGNATURE_PREFIX "Signature=" /**< The prefix that goes before the signature in the Authorization header value. */ -#define AUTH_SIGNATURE_PREFIX_LEN ( sizeof( AUTH_SIGNATURE_PREFIX ) - 1U ) /**< The length of #AUTH_SIGNATURE_PREFIX. */ +#define AUTH_CREDENTIAL_PREFIX "Credential=" /**< The prefix that goes before the credential value in the Authorization header value. */ +#define AUTH_CREDENTIAL_PREFIX_LEN ( sizeof( AUTH_CREDENTIAL_PREFIX ) - 1U ) /**< The length of #AUTH_CREDENTIAL_PREFIX. */ +#define AUTH_SEPARATOR ", " /**< The separator between each component in the Authorization header value. */ +#define AUTH_SEPARATOR_LEN ( sizeof( AUTH_SEPARATOR ) - 1U ) /**< The length of #AUTH_SEPARATOR. */ +#define AUTH_SIGNED_HEADERS_PREFIX "SignedHeaders=" /**< The prefix that goes before the signed headers in the Authorization header value. */ +#define AUTH_SIGNED_HEADERS_PREFIX_LEN ( sizeof( AUTH_SIGNED_HEADERS_PREFIX ) - 1U ) /**< The length of #AUTH_SIGNED_HEADERS_PREFIX. */ +#define AUTH_SIGNATURE_PREFIX "Signature=" /**< The prefix that goes before the signature in the Authorization header value. */ +#define AUTH_SIGNATURE_PREFIX_LEN ( sizeof( AUTH_SIGNATURE_PREFIX ) - 1U ) /**< The length of #AUTH_SIGNATURE_PREFIX. */ /** * @brief A helper macro to print insufficient memory errors. @@ -193,7 +193,7 @@ typedef struct HmacContext /** * @brief All accumulated key data. */ - char key[ SIGV4_HASH_DIGEST_LENGTH ]; + char key[ SIGV4_HASH_MAX_BLOCK_LENGTH ]; /** * @brief The length of the accumulated key data. diff --git a/source/sigv4.c b/source/sigv4.c index 22b089df..03459c6b 100644 --- a/source/sigv4.c +++ b/source/sigv4.c @@ -39,18 +39,6 @@ #if ( SIGV4_USE_CANONICAL_SUPPORT == 1 ) -/** - * @brief Interpret string parameters into standardized SigV4String_t structs to - * assist in sorting and canonicalization. - * - * @param[in, out] pSigV4Value The SigV4 standardized struct to populate. - * @param[in] pInput String containing sorting parameters. - * @param[in] lenInput Length of string @pInput. - */ - static void stringToSigV4Value( SigV4String_t * pSigV4Value, - const char * pInput, - size_t lenInput ); - /** * @brief Verifies if a SigV4 string value is empty. * @@ -64,21 +52,20 @@ * @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[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. + * @param[in] doubleEncodeEquals Option to indicate if equals should be double-encoded. */ - static void encodeURI( const char * pURI, - size_t uriLen, - char * pCanonicalURI, - size_t * canonicalURILen, - bool encodeSlash, - bool nullTerminate ); + static SigV4Status_t encodeURI( const char * pUri, + size_t uriLen, + char * pCanonicalURI, + size_t * canonicalURILen, + bool encodeSlash, + bool doubleEncodeEquals ); /** * @brief Canonicalize the full URI path. The input URI starts after the @@ -87,17 +74,17 @@ * * @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 + * @param[in] uriLen Length of pUri. + * @param[in] encodeTwice Service-dependent option to indicate whether + * encoding should be done 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 ); + static SigV4Status_t generateCanonicalURI( const char * pUri, + size_t uriLen, + bool encodeTwice, + CanonicalContext_t * canonicalRequest ); /** * @brief Canonicalize the query string HTTP URL, beginning (but not @@ -108,9 +95,9 @@ * @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 ); + static SigV4Status_t generateCanonicalQuery( const char * pQuery, + size_t queryLen, + CanonicalContext_t * canonicalRequest ); /** * @brief Compare two SigV4 data structures lexicographically, without case-sensitivity. @@ -122,8 +109,8 @@ * a value greater than 0 if @pSecondVal < @pFirstVal. 0 is never returned in * order to provide stability to qSort() calls. */ - static int cmpField( const void * pFirstVal, - const void * pSecondVal ); + static int cmpHeaderField( const void * pFirstVal, + const void * pSecondVal ); #endif /* #if (SIGV4_USE_CANONICAL_SUPPORT == 1) */ @@ -141,111 +128,6 @@ static void intToAscii( int32_t value, char ** pBuffer, size_t bufferLen ); -/** - * @brief Format the credential scope of the authorization header using the date, region, and service - * parameters found in #SigV4Parameters_t. - * - * @param[in] pSigV4Params The application parameters defining the credential's scope. - * @param[in, out] pCredScope The credential scope in the V4 required format. - * - * @return SigV4ISOFormattingError if a snprintf() error was encountered, - * SigV4Success otherwise. - */ -static SigV4Status_t getCredentialScope( SigV4Parameters_t * pSigV4Params, - SigV4String_t * pCredScope ); - -/** - * @brief Check if the date represents a valid leap year day. - * - * @param[in] pDateElements The date representation to be verified. - * - * @return #SigV4Success if the date corresponds to a valid leap year, - * #SigV4ISOFormattingError otherwise. - */ -static SigV4Status_t checkLeap( const SigV4DateTime_t * pDateElements ); - -/** - * @brief Verify the date stored in a SigV4DateTime_t date representation. - * - * @param[in] pDateElements The date representation to be verified. - * - * @return #SigV4Success if the date is valid, and #SigV4ISOFormattingError if - * any member of SigV4DateTime_t is invalid or represents an out-of-range date. - */ -static SigV4Status_t validateDateTime( const SigV4DateTime_t * pDateElements ); - -/** - * @brief Append the value of a date element to the internal date representation - * structure. - * - * @param[in] formatChar The specifier identifying the struct member to fill. - * @param[in] result The value to assign to the specified struct member. - * @param[out] pDateElements The date representation structure to modify. - */ -static void addToDate( const char formatChar, - int32_t result, - SigV4DateTime_t * pDateElements ); - -/** - * @brief Interpret the value of the specified characters in date, based on the - * format specifier, and append to the internal date representation. - * - * @param[in] pDate The date to be parsed. - * @param[in] formatChar The format specifier used to interpret characters. - * @param[in] readLoc The index of pDate to read from. - * @param[in] lenToRead The number of characters to read. - * @param[out] pDateElements The date representation to modify. - * - * @return #SigV4Success if parsing succeeded, #SigV4ISOFormattingError if the - * characters read did not match the format specifier. - */ -static SigV4Status_t scanValue( const char * pDate, - const char formatChar, - size_t readLoc, - size_t lenToRead, - SigV4DateTime_t * pDateElements ); - -/** - * @brief Parses date according to format string parameter, and populates date - * representation struct SigV4DateTime_t with its elements. - * - * @param[in] pDate The date to be parsed. - * @param[in] dateLen Length of pDate, the date to be formatted. - * @param[in] pFormat The format string used to extract date pDateElements from - * pDate. This string, among other characters, may contain specifiers of the - * form "%LV", where L is the number of characters to be read, and V is one of - * {Y, M, D, h, m, s, *}, representing a year, month, day, hour, minute, second, - * or skipped (un-parsed) value, respectively. - * @param[in] formatLen Length of the format string pFormat. - * @param[out] pDateElements The deconstructed date representation of pDate. - * - * @return #SigV4Success if all format specifiers were matched successfully, - * #SigV4ISOFormattingError otherwise. - */ -static SigV4Status_t parseDate( const char * pDate, - size_t dateLen, - const char * pFormat, - size_t formatLen, - SigV4DateTime_t * pDateElements ); - -/** - * @brief Verify @p pParams and its sub-members. - * - * @param[in] pParams Complete SigV4 configurations passed by application. - * - * @return #SigV4Success if successful, #SigV4InvalidParameter otherwise. - */ -static SigV4Status_t verifySigV4Parameters( const SigV4Parameters_t * pParams ); - -/** - * @brief Hex digest of provided string parameter. - * - * @param[in] pInputStr String to encode. - * @param[out] pHexOutput Hex representation of @p pInputStr. - */ -static void hexEncode( SigV4String_t * pInputStr, - SigV4String_t * pHexOutput ); - /** * @brief Extract all header key-value pairs from the passed headers data and add them * to the canonical request. @@ -256,6 +138,8 @@ static void hexEncode( SigV4String_t * pInputStr, * in the canonical form. * @param[out] canonicalRequest Struct to maintain intermediary buffer * and state of canonicalization. + * @param[out] pSignedHeaders The starting location of the signed headers. + * @param[out] pSignedHeadersLen The length of the signed headers. * * @return Following statuses will be returned by the function: * #SigV4Success if headers are successfully added to the canonical request. @@ -264,10 +148,12 @@ static void hexEncode( SigV4String_t * pInputStr, * #SigV4MaxHeaderPairCountExceeded if number of headers that needs to be canonicalized * exceed the SIGV4_MAX_HTTP_HEADER_COUNT macro defined in the config file. */ -static SigV4Status_t appendAllHeadersToCanonicalRequest( const char * pHeaders, - size_t headersLen, - uint32_t flags, - CanonicalContext_t * canonicalRequest ); +static SigV4Status_t generateCanonicalAndSignedHeaders( const char * pHeaders, + size_t headersLen, + uint32_t flags, + CanonicalContext_t * canonicalRequest, + char ** pSignedHeaders, + size_t * pSignedHeadersLen ); /** * @brief Append Signed Headers to the Canonical Request buffer. @@ -277,14 +163,14 @@ static SigV4Status_t appendAllHeadersToCanonicalRequest( const char * pHeaders, * in the canonical form. * @param[in,out] canonicalRequest Struct to maintain intermediary buffer * and state of canonicalization. - * - * @return Following statuses will be returned by the function: - * #SigV4Success if headers are successfully added to the canonical request. - * #SigV4InsufficientMemory if canonical request buffer cannot accommodate the header. + * @param[out] pSignedHeaders The starting location of the signed headers. + * @param[out] pSignedHeadersLen The length of the signed headers. */ static SigV4Status_t appendSignedHeaders( size_t headerCount, uint32_t flags, - CanonicalContext_t * canonicalRequest ); + CanonicalContext_t * canonicalRequest, + char ** pSignedHeaders, + size_t * pSignedHeadersLen ); /** * @brief Canonicalize headers and append it to the Canonical Request buffer. @@ -320,11 +206,11 @@ static SigV4Status_t appendCanonicalizedHeaders( size_t headerCount, * #SigV4MaxHeaderPairCountExceeded if number of key-value entries in the headers data * exceeds the SIGV4_MAX_HTTP_HEADER_COUNT macro defined in the config file. */ -SigV4Status_t parseHeaderKeyValueEntries( const char * pHeaders, - size_t headersDataLen, - uint32_t flags, - size_t * headerCount, - CanonicalContext_t * canonicalRequest ); +static SigV4Status_t parseHeaderKeyValueEntries( const char * pHeaders, + size_t headersDataLen, + uint32_t flags, + size_t * headerCount, + CanonicalContext_t * canonicalRequest ); /** * @brief Copy header key or header value to the Canonical Request buffer. @@ -341,11 +227,11 @@ SigV4Status_t parseHeaderKeyValueEntries( const char * pHeaders, * #SigV4Success if the headers are successfully added to the canonical request. * #SigV4InsufficientMemory if canonical request buffer cannot accommodate the header. */ -SigV4Status_t copyHeaderStringToCanonicalBuffer( const char * pData, - size_t dataLen, - uint32_t flags, - char separator, - CanonicalContext_t * canonicalRequest ); +static SigV4Status_t copyHeaderStringToCanonicalBuffer( const char * pData, + size_t dataLen, + uint32_t flags, + char separator, + CanonicalContext_t * canonicalRequest ); /** * @brief Helper function to determine whether a header string character represents a space @@ -369,96 +255,432 @@ static bool isTrimmableSpace( const char * value, size_t valLen, size_t trimmedLength ); -/*-----------------------------------------------------------*/ +/** + * @brief Generate the canonical request but excluding the canonical headers + * and anything that goes after it. Write it onto @p pSignedHeaders and update + * it to point to the next location to write the rest of the canonical request. + * + * @param[in] pParams The application-defined parameters used to + * generate the canonical request. + * @param[in] pCanonicalContext The context of the canonical request. + * @param[in,out] pSignedHeaders The location to start writing the canonical request and + * becomes the location to write the rest of it when this function returns. + * @param[in,out] pSignedHeadersLen The amount of buffer available and becomes the number + * of bytes actually written when this function returns. + * @return SigV4InsufficientMemory if the length of the canonical request output + * buffer cannot fit the actual request before the headers, #SigV4Success otherwise. + */ +static SigV4Status_t generateCanonicalRequestUntilHeaders( const SigV4Parameters_t * pParams, + CanonicalContext_t * pCanonicalContext, + char ** pSignedHeaders, + size_t * pSignedHeadersLen ); -static void intToAscii( int32_t value, - char ** pBuffer, - size_t bufferLen ) -{ - int32_t currentVal = value; - size_t lenRemaining = bufferLen; +/** + * @brief Generate the prefix of the Authorization header equal to + * " Credential=/, SignedHeaders=, Signature=" + * + * @param[in] pParams The application-defined parameters used to + * generate the canonical request. + * @param[in] pAlgorithm The signing algorithm used for SigV4 authentication. + * @param[in] algorithmLen The length of @p pAlgorithm. + * @param[in] pSignedHeaders The signed headers of the SigV4 request. + * @param[in] signedHeadersLen The length of @p pSignedHeaders. + * @param[in,out] pAuthBuf The authorization buffer where to write the prefix. + * Pointer is updated with the next location to write the value of the signature. + * @param[in] pAuthPrefixLen The length of @p pAuthBuf. + * @return #SigV4InsufficientMemory if the length of the canonical request output + * buffer cannot fit the actual request before the headers, #SigV4Success otherwise. + */ +static SigV4Status_t generateAuthorizationValuePrefix( const SigV4Parameters_t * pParams, + const char * pAlgorithm, + size_t algorithmLen, + const char * pSignedHeaders, + size_t signedHeadersLen, + char ** pAuthBuf, + size_t * pAuthPrefixLen ); - assert( pBuffer != NULL ); - assert( bufferLen > 0U ); +/** + * @brief Write a line in the canonical request. + * @note Used whenever there are components of the request that + * are already canonicalized. + * + * @param[in] pLine The line to write to the canonical request. + * @param[in] lineLen The length of @p pLine. + * @param[in,out] pCanonicalContext The canonical context where + * the line should be written. + * @return SigV4InsufficientMemory if the length of the canonical request + * buffer cannot write the desired line, #SigV4Success otherwise. + */ +static SigV4Status_t writeLineToCanonicalRequest( const char * pLine, + size_t lineLen, + CanonicalContext_t * pCanonicalContext ); - /* Write base-10 remainder in its ASCII representation, and fill any - * remaining width with '0' characters. */ - while( lenRemaining > 0U ) - { - lenRemaining--; - ( *pBuffer )[ lenRemaining ] = ( char ) ( ( currentVal % 10 ) + '0' ); - currentVal /= 10; - } +/** + * @brief Update the HMAC using an input key. + * @note This function can be called multiple times before calling + * #hmacData. Appending multiple substrings, then calling #hmacKey + * on the appended string is also equivalent to calling #hmacKey on + * each individual substring. + * + * @param[in] pHmacContext The context used for HMAC calculation. + * @param[in] pKey The key used as input for HMAC calculation. + * @param[in] keyLen The length of @p pKey. + * @return Zero on success, all other return values are failures. + */ +static int32_t hmacKey( HmacContext_t * pHmacContext, + const char * pKey, + size_t keyLen ); - /* Move pointer to follow last written character. */ - *pBuffer += bufferLen; -} +/** + * @brief Update the HMAC using input data. + * @note Must only be called after #hmacKey and leads to undefined + * behavior otherwise. Likewise, one should not call #hmacKey after + * calling #hmacData. One must call #hmacFinal first before calling + * #hmacKey again. + * + * @param[in] pHmacContext The context used for HMAC calculation. + * @param[in] pData The data used as input for HMAC calculation. + * @param[in] dataLen The length of @p pData. + * @return Zero on success, all other return values are failures. + */ +static int32_t hmacData( HmacContext_t * pHmacContext, + const char * pData, + size_t dataLen ); -/*-----------------------------------------------------------*/ +/** + * @brief Write the HMAC digest into the buffer. + * + * @param[in] pHmacContext The context used for HMAC calculation. + * @param[out] pMac The buffer onto which to write the HMAC digest. + * @param[in] macLen The length of @p pMac. + * @return Zero on success, all other return values are failures. + */ +static int32_t hmacFinal( HmacContext_t * pHmacContext, + char * pMac, + size_t macLen ); -static SigV4Status_t checkLeap( const SigV4DateTime_t * pDateElements ) -{ - SigV4Status_t returnStatus = SigV4ISOFormattingError; +/** + * @brief Generate the complete HMAC digest given a key and value, then write + * the digest in some output buffer. + * + * @param[in] pHmacContext The context used for the current HMAC calculation. + * @param[in] pKey The key passed as input to the HMAC function. + * @param[in] keyLen The length of @p pKey. + * @param[in] pData The data passed as input to the HMAC function. + * @param[in] dataLen The length of @p pData. + * @param[out] pOutput The buffer onto which to write the HMAC digest. + * @param[out] outputLen The length of @p pOutput and must be greater + * than pCryptoInterface->hashDigestLen for this function to succeed. + * @param[in] pCryptoInterface The interface used to call hash functions. + * @return Zero on success, all other return values are failures. + */ +static int32_t completeHmac( HmacContext_t * pHmacContext, + const char * pKey, + size_t keyLen, + const char * pData, + size_t dataLen, + char * pOutput, + size_t outputLen, + const SigV4CryptoInterface_t * pCryptoInterface ); - assert( pDateElements != NULL ); +/** + * @brief Generate the complete hash of an input string, then write + * the digest in some output buffer. + * @note Unlike #completeHashAndHexEncode, this function will not + * encode the hash and will simply output the bytes written by the + * hash function. + * + * @param[in] pInput The data passed as input to the hash function. + * @param[in] inputLen The length of @p pInput. + * @param[out] pOutput The buffer onto which to write the hash. + * @param[out] outputLen The length of @p pOutput and must be greater + * than pCryptoInterface->hashDigestLen for this function to succeed. + * @param[in] pCryptoInterface The interface used to call hash functions. + * @return Zero on success, all other return values are failures. + */ +static int32_t completeHash( const char * pInput, + size_t inputLen, + char * pOutput, + size_t outputLen, + const SigV4CryptoInterface_t * pCryptoInterface ); - /* If the date represents a leap day, verify that the leap year is valid. */ - if( ( pDateElements->tm_mon == 2 ) && ( pDateElements->tm_mday == 29 ) ) - { - if( ( ( pDateElements->tm_year % 400 ) != 0 ) && - ( ( ( pDateElements->tm_year % 4 ) != 0 ) || - ( ( pDateElements->tm_year % 100 ) == 0 ) ) ) - { - LogError( ( "%ld is not a valid leap year.", - ( long int ) pDateElements->tm_year ) ); - } - else - { - returnStatus = SigV4Success; - } - } +/** + * @brief Generate the complete hash of an input string, then write + * the digest in an intermediary buffer before hex encoding and + * writing it onto @p pOutput. + * + * @param[in] pInput The data passed as input to the hash function. + * @param[in] inputLen The length of @p pInput. + * @param[out] pOutput The buffer onto which to write the hex-encoded hash. + * @param[out] pOutputLen The length of @p pOutput and must be greater + * than pCryptoInterface->hashDigestLen * 2 for this function to succeed. + * @param[in] pCryptoInterface The interface used to call hash functions. + * @return Zero on success, all other return values are failures. + */ +static SigV4Status_t completeHashAndHexEncode( const char * pInput, + size_t inputLen, + char * pOutput, + size_t * pOutputLen, + const SigV4CryptoInterface_t * pCryptoInterface ); - return returnStatus; -} +/** + * @brief Generate the prefix of the string to sign containing the + * algorithm and date then write it onto @p pBufStart. + * @note This function assumes that enough bytes remain in @p pBufStart in + * order to write the algorithm and date. + * + * @param[in] pBufStart The starting location of the buffer to write the string + * to sign. + * @param[in] pAlgorithm The algorithm used for generating the SigV4 signature. + * @param[in] algorithmLen The length of @p pAlgorithm. + * @param[in] pDateIso8601 The date used as part of the string to sign. + * @return The number of bytes written to @p pBufStart. + */ +static size_t writeStringToSignPrefix( char * pBufStart, + const char * pAlgorithm, + size_t algorithmLen, + const char * pDateIso8601 ); -/*-----------------------------------------------------------*/ +/** + * @brief Generate the string to sign and write it onto a #SigV4String_t. + * + * @param[in] pParams The application-defined parameters used to + * generate the string to sign. + * @param[in] pAlgorithm The algorithm used for generating the SigV4 signature. + * @param[in] algorithmLen The length of @p pAlgorithm. + * @param[in,out] pCanonicalContext The context of the canonical request. + * @return SigV4InsufficientMemory if the length of the canonical request output + * buffer cannot fit the string to sign, #SigV4Success otherwise. + */ +static SigV4Status_t writeStringToSign( const SigV4Parameters_t * pParams, + const char * pAlgorithm, + size_t algorithmLen, + CanonicalContext_t * pCanonicalContext ); -static SigV4Status_t validateDateTime( const SigV4DateTime_t * pDateElements ) -{ - SigV4Status_t returnStatus = SigV4Success; - const int32_t daysPerMonth[] = MONTH_DAYS; +/** + * @brief Generate the signing key and write it onto a #SigV4String_t. + * + * @param[in] pSigV4Params The application-defined parameters used to + * generate the signing key. + * @param[in] pHmacContext The context used for the current HMAC calculation. + * @param[out] pSigningKey The #SigV4String_t onto which the signing key will be written. + * @param[in,out] pBytesRemaining The number of bytes remaining in the canonical buffer. + * @return SigV4InsufficientMemory if the length of @p pSigningKey was insufficient to + * fit the actual signing key, #SigV4Success otherwise. + */ +static SigV4Status_t generateSigningKey( const SigV4Parameters_t * pSigV4Params, + HmacContext_t * pHmacContext, + SigV4String_t * pSigningKey, + size_t * pBytesRemaining ); - assert( pDateElements != NULL ); +/** + * @brief Format the credential scope of the authorization header using the access key ID, + * date, region, and service parameters found in #SigV4Parameters_t and ends with the + * "aws4_request" terminator. + * + * @param[in] pSigV4Params The application parameters defining the credential's scope. + * @param[in, out] pCredScope The credential scope in the V4 required format. + * + * @return SigV4InsufficientMemory if the length of @p pCredScope was insufficient to + * fit the actual credential scope, #SigV4Success otherwise. + */ +static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Params, + SigV4String_t * pCredScope ); - if( pDateElements->tm_year < YEAR_MIN ) - { - LogError( ( "Invalid 'year' value parsed from date string. " - "Expected an integer %ld or greater, received: %ld", - ( long int ) YEAR_MIN, - ( long int ) pDateElements->tm_year ) ); - returnStatus = SigV4ISOFormattingError; - } +/** + * @brief Check if the date represents a valid leap year day. + * + * @param[in] pDateElements The date representation to be verified. + * + * @return #SigV4Success if the date corresponds to a valid leap year, + * #SigV4ISOFormattingError otherwise. + */ +static SigV4Status_t checkLeap( const SigV4DateTime_t * pDateElements ); - if( ( pDateElements->tm_mon < 1 ) || ( pDateElements->tm_mon > 12 ) ) - { - LogError( ( "Invalid 'month' value parsed from date string. " - "Expected an integer between 1 and 12, received: %ld", - ( long int ) pDateElements->tm_mon ) ); - returnStatus = SigV4ISOFormattingError; - } +/** + * @brief Verify the date stored in a SigV4DateTime_t date representation. + * + * @param[in] pDateElements The date representation to be verified. + * + * @return #SigV4Success if the date is valid, and #SigV4ISOFormattingError if + * any member of SigV4DateTime_t is invalid or represents an out-of-range date. + */ +static SigV4Status_t validateDateTime( const SigV4DateTime_t * pDateElements ); - /* Ensure that the day of the month is valid for the relevant month. */ - if( ( returnStatus != SigV4ISOFormattingError ) && - ( ( pDateElements->tm_mday < 1 ) || - ( pDateElements->tm_mday > daysPerMonth[ pDateElements->tm_mon - 1 ] ) ) ) - { - /* Check if the date is a valid leap year day. */ - returnStatus = checkLeap( pDateElements ); +/** + * @brief Append the value of a date element to the internal date representation + * structure. + * + * @param[in] formatChar The specifier identifying the struct member to fill. + * @param[in] result The value to assign to the specified struct member. + * @param[out] pDateElements The date representation structure to modify. + */ +static void addToDate( const char formatChar, + int32_t result, + SigV4DateTime_t * pDateElements ); - if( returnStatus == SigV4ISOFormattingError ) - { - LogError( ( "Invalid 'day' value parsed from date string. " - "Expected an integer between 1 and %ld, received: %ld", +/** + * @brief Interpret the value of the specified characters in date, based on the + * format specifier, and append to the internal date representation. + * + * @param[in] pDate The date to be parsed. + * @param[in] formatChar The format specifier used to interpret characters. + * @param[in] readLoc The index of pDate to read from. + * @param[in] lenToRead The number of characters to read. + * @param[out] pDateElements The date representation to modify. + * + * @return #SigV4Success if parsing succeeded, #SigV4ISOFormattingError if the + * characters read did not match the format specifier. + */ +static SigV4Status_t scanValue( const char * pDate, + const char formatChar, + size_t readLoc, + size_t lenToRead, + SigV4DateTime_t * pDateElements ); + +/** + * @brief Parses date according to format string parameter, and populates date + * representation struct SigV4DateTime_t with its elements. + * + * @param[in] pDate The date to be parsed. + * @param[in] dateLen Length of pDate, the date to be formatted. + * @param[in] pFormat The format string used to extract date pDateElements from + * pDate. This string, among other characters, may contain specifiers of the + * form "%LV", where L is the number of characters to be read, and V is one of + * {Y, M, D, h, m, s, *}, representing a year, month, day, hour, minute, second, + * or skipped (un-parsed) value, respectively. + * @param[in] formatLen Length of the format string pFormat. + * @param[out] pDateElements The deconstructed date representation of pDate. + * + * @return #SigV4Success if all format specifiers were matched successfully, + * #SigV4ISOFormattingError otherwise. + */ +static SigV4Status_t parseDate( const char * pDate, + size_t dateLen, + const char * pFormat, + size_t formatLen, + SigV4DateTime_t * pDateElements ); + +/** + * @brief Verify @p pParams and its sub-members. + * + * @param[in] pParams Complete SigV4 configurations passed by application. + * + * @return #SigV4Success if successful, #SigV4InvalidParameter otherwise. + */ +static SigV4Status_t verifySigV4Parameters( const SigV4Parameters_t * pParams ); + +/** + * @brief Hex digest of provided string parameter. + * + * @param[in] pInputStr String to encode. + * @param[out] pHexOutput Hex representation of @p pInputStr. + * + * @return #SigV4Success if successful, #SigV4InsufficientMemory otherwise. + */ +static SigV4Status_t lowercaseHexEncode( const SigV4String_t * pInputStr, + SigV4String_t * pHexOutput ); + +/** + * @brief Calculate number of bytes needed for the credential scope. + * @note This does not include the linefeed character. + * + * @param[in] pSigV4Params SigV4 configurations passed by application. + * + * @return Number of bytes needed for credential scope. + */ +static size_t sizeNeededForCredentialScope( const SigV4Parameters_t * pSigV4Params ); + +/*-----------------------------------------------------------*/ + +static void intToAscii( int32_t value, + char ** pBuffer, + size_t bufferLen ) +{ + int32_t currentVal = value; + size_t lenRemaining = bufferLen; + + assert( pBuffer != NULL ); + assert( bufferLen > 0U ); + + /* Write base-10 remainder in its ASCII representation, and fill any + * remaining width with '0' characters. */ + while( lenRemaining > 0U ) + { + lenRemaining--; + ( *pBuffer )[ lenRemaining ] = ( char ) ( ( currentVal % 10 ) + '0' ); + currentVal /= 10; + } + + /* Move pointer to follow last written character. */ + *pBuffer += bufferLen; +} + +/*-----------------------------------------------------------*/ + +static SigV4Status_t checkLeap( const SigV4DateTime_t * pDateElements ) +{ + SigV4Status_t returnStatus = SigV4ISOFormattingError; + + assert( pDateElements != NULL ); + + /* If the date represents a leap day, verify that the leap year is valid. */ + if( ( pDateElements->tm_mon == 2 ) && ( pDateElements->tm_mday == 29 ) ) + { + if( ( ( pDateElements->tm_year % 400 ) != 0 ) && + ( ( ( pDateElements->tm_year % 4 ) != 0 ) || + ( ( pDateElements->tm_year % 100 ) == 0 ) ) ) + { + LogError( ( "%ld is not a valid leap year.", + ( long int ) pDateElements->tm_year ) ); + } + else + { + returnStatus = SigV4Success; + } + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +static SigV4Status_t validateDateTime( const SigV4DateTime_t * pDateElements ) +{ + SigV4Status_t returnStatus = SigV4Success; + const int32_t daysPerMonth[] = MONTH_DAYS; + + assert( pDateElements != NULL ); + + if( pDateElements->tm_year < YEAR_MIN ) + { + LogError( ( "Invalid 'year' value parsed from date string. " + "Expected an integer %ld or greater, received: %ld", + ( long int ) YEAR_MIN, + ( long int ) pDateElements->tm_year ) ); + returnStatus = SigV4ISOFormattingError; + } + + if( ( pDateElements->tm_mon < 1 ) || ( pDateElements->tm_mon > 12 ) ) + { + LogError( ( "Invalid 'month' value parsed from date string. " + "Expected an integer between 1 and 12, received: %ld", + ( long int ) pDateElements->tm_mon ) ); + returnStatus = SigV4ISOFormattingError; + } + + /* Ensure that the day of the month is valid for the relevant month. */ + if( ( returnStatus != SigV4ISOFormattingError ) && + ( ( pDateElements->tm_mday < 1 ) || + ( pDateElements->tm_mday > daysPerMonth[ pDateElements->tm_mon - 1 ] ) ) ) + { + /* Check if the date is a valid leap year day. */ + returnStatus = checkLeap( pDateElements ); + + if( returnStatus == SigV4ISOFormattingError ) + { + LogError( ( "Invalid 'day' value parsed from date string. " + "Expected an integer between 1 and %ld, received: %ld", ( long int ) daysPerMonth[ pDateElements->tm_mon - 1 ], ( long int ) pDateElements->tm_mday ) ); } @@ -684,10 +906,10 @@ static SigV4Status_t parseDate( const char * pDate, /*-----------------------------------------------------------*/ -/* Hex digest of provided parameter string. */ -static void hexEncode( SigV4String_t * pInputStr, - SigV4String_t * pHexOutput ) +static SigV4Status_t lowercaseHexEncode( const SigV4String_t * pInputStr, + SigV4String_t * pHexOutput ) { + SigV4Status_t returnStatus = SigV4Success; static const char digitArr[] = "0123456789abcdef"; char * hex = NULL; size_t i = 0U; @@ -699,96 +921,103 @@ static void hexEncode( SigV4String_t * pInputStr, hex = pHexOutput->pData; - for( i = 0; i < pInputStr->dataLen; i++ ) + if( pHexOutput->dataLen < pInputStr->dataLen * 2U ) + { + returnStatus = SigV4InsufficientMemory; + LOG_INSUFFICIENT_MEMORY_ERROR( "hex encode", + ( pInputStr->dataLen * 2U ) - pHexOutput->dataLen ); + } + + if( returnStatus == SigV4Success ) { - *( hex++ ) = digitArr[ ( pInputStr->pData[ i ] & 0xF0 ) >> 4 ]; - *( hex++ ) = digitArr[ ( pInputStr->pData[ i ] & 0xF0 ) ]; + for( i = 0; i < pInputStr->dataLen; i++ ) + { + *hex = digitArr[ ( pInputStr->pData[ i ] & 0xF0 ) >> 4 ]; + hex++; + *hex = digitArr[ ( pInputStr->pData[ i ] & 0x0F ) ]; + hex++; + } + + pHexOutput->dataLen = pInputStr->dataLen * 2U; } - pHexOutput->dataLen = pInputStr->dataLen * 2; + return returnStatus; } /*-----------------------------------------------------------*/ -static SigV4Status_t getCredentialScope( SigV4Parameters_t * pSigV4Params, - SigV4String_t * pCredScope ) +static size_t sizeNeededForCredentialScope( const SigV4Parameters_t * pSigV4Params ) +{ + assert( pSigV4Params != NULL ); + return ISO_DATE_SCOPE_LEN + \ + CREDENTIAL_SCOPE_SEPARATOR_LEN + pSigV4Params->regionLen + \ + CREDENTIAL_SCOPE_SEPARATOR_LEN + pSigV4Params->serviceLen + \ + CREDENTIAL_SCOPE_SEPARATOR_LEN + CREDENTIAL_SCOPE_TERMINATOR_LEN; +} + +static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Params, + SigV4String_t * pCredScope ) { - SigV4Status_t returnVal = SigV4InvalidParameter; + SigV4Status_t returnStatus = SigV4Success; char * pBufWrite = NULL; - int32_t bytesWritten = 0; + size_t sizeNeeded = 0U; assert( pSigV4Params != NULL ); + assert( pSigV4Params->pCredentials != NULL ); + assert( pSigV4Params->pRegion != NULL ); + assert( pSigV4Params->pService != NULL ); assert( pCredScope != NULL ); assert( pCredScope->pData != NULL ); - pBufWrite = pCredScope->pData; - - /* Use only the first 8 characters from the provided ISO 8601 string (YYYYMMDD). */ - bytesWritten = snprintf( ( char * ) pBufWrite, - pCredScope->dataLen + 1U, - "%*s", - ISO_DATE_SCOPE_LEN, - pSigV4Params->pDateIso8601 ); + sizeNeeded = sizeNeededForCredentialScope( pSigV4Params ); - if( bytesWritten == ISO_DATE_SCOPE_LEN ) - { - bytesWritten = snprintf( ( char * ) pBufWrite, - pSigV4Params->regionLen, - "/%s/", - pSigV4Params->pRegion ); + pBufWrite = pCredScope->pData; - if( bytesWritten != pSigV4Params->regionLen ) - { - LogError( ( "Error in formatting provided region string for credential scope." ) ); - returnVal = SigV4ISOFormattingError; - } - } - else + if( pCredScope->dataLen < sizeNeeded ) { - LogError( ( "Error obtaining date for credential scope string." ) ); - returnVal = SigV4ISOFormattingError; + returnStatus = SigV4InsufficientMemory; + LOG_INSUFFICIENT_MEMORY_ERROR( "write the credential scope", + sizeNeeded - pCredScope->dataLen ); } - if( returnVal != SigV4ISOFormattingError ) + /* Each concatenated component is separated by a '/' character. */ + if( returnStatus == SigV4Success ) { - bytesWritten = snprintf( ( char * ) pBufWrite, - pSigV4Params->serviceLen, - "/%s/", - pSigV4Params->pService ); + /* Concatenate first 8 characters from the provided ISO 8601 string (YYYYMMDD). */ + ( void ) memcpy( pBufWrite, pSigV4Params->pDateIso8601, ISO_DATE_SCOPE_LEN ); + pBufWrite += ISO_DATE_SCOPE_LEN; - if( bytesWritten != pSigV4Params->serviceLen ) - { - LogError( ( "Error in formatting provided service string for credential scope." ) ); - returnVal = SigV4ISOFormattingError; - } - } - else - { - pCredScope->dataLen = ISO_DATE_SCOPE_LEN + pSigV4Params->regionLen + pSigV4Params->serviceLen; - returnVal = SigV4Success; - } + *pBufWrite = CREDENTIAL_SCOPE_SEPARATOR; + pBufWrite += CREDENTIAL_SCOPE_SEPARATOR_LEN; - return returnVal; -} + /* Concatenate AWS region. */ + ( void ) memcpy( pBufWrite, pSigV4Params->pRegion, pSigV4Params->regionLen ); + pBufWrite += pSigV4Params->regionLen; -/*-----------------------------------------------------------*/ + *pBufWrite = CREDENTIAL_SCOPE_SEPARATOR; + pBufWrite += CREDENTIAL_SCOPE_SEPARATOR_LEN; -#if ( SIGV4_USE_CANONICAL_SUPPORT == 1 ) + /* Concatenate AWS service. */ + ( void ) memcpy( pBufWrite, pSigV4Params->pService, pSigV4Params->serviceLen ); + pBufWrite += pSigV4Params->serviceLen; - static void stringToSigV4Value( SigV4String_t * pSigV4Value, - const char * pInput, - size_t lenInput ) - { - assert( pSigV4Value != NULL ); - assert( pInput != NULL ); - assert( lenInput > 0U ); + *pBufWrite = CREDENTIAL_SCOPE_SEPARATOR; + pBufWrite += CREDENTIAL_SCOPE_SEPARATOR_LEN; - pSigV4Value->pData = ( char * ) pInput; - pSigV4Value->dataLen = ( size_t ) lenInput; + /* Concatenate terminator. */ + ( void ) memcpy( pBufWrite, CREDENTIAL_SCOPE_TERMINATOR, CREDENTIAL_SCOPE_TERMINATOR_LEN ); + pBufWrite += CREDENTIAL_SCOPE_TERMINATOR_LEN; + + pCredScope->dataLen = sizeNeeded; } + return returnStatus; +} + /*-----------------------------------------------------------*/ +#if ( SIGV4_USE_CANONICAL_SUPPORT == 1 ) + static bool emptySigV4String( SigV4String_t * pInput ) { bool returnVal = true; @@ -800,8 +1029,8 @@ static SigV4Status_t getCredentialScope( SigV4Parameters_t * pSigV4Params, /*-----------------------------------------------------------*/ - static int cmpField( const void * pFirstVal, - const void * pSecondVal ) + static int cmpHeaderField( const void * pFirstVal, + const void * pSecondVal ) { SigV4KeyValuePair_t * pFirst, * pSecond = NULL; size_t lenSmall = 0U; @@ -831,111 +1060,296 @@ static SigV4Status_t getCredentialScope( SigV4Parameters_t * pSigV4Params, /*-----------------------------------------------------------*/ - static void encodeURI( const char * pURI, - size_t uriLen, - char * pCanonicalURI, - size_t * canonicalURILen, - bool encodeSlash, - bool nullTerminate ) + static int cmpQueryFieldValue( const void * pFirstVal, + const void * pSecondVal ) { - const char * pURILoc = NULL; - char * pBufLoc = NULL; - size_t index = 0U; + SigV4KeyValuePair_t * pFirst, * pSecond = NULL; + size_t lenSmall = 0U; + int32_t compResult = -1; - assert( pURI != NULL ); - assert( pCanonicalURI != NULL ); - assert( canonicalURILen != NULL ); - assert( *canonicalURILen > 0U ); + assert( pFirstVal != NULL ); + assert( pSecondVal != NULL ); + + pFirst = ( SigV4KeyValuePair_t * ) pFirstVal; + pSecond = ( SigV4KeyValuePair_t * ) pSecondVal; + + assert( !emptySigV4String( &pFirst->key ) ); + assert( !emptySigV4String( &pSecond->key ) ); - pURILoc = ( const char * ) pURI; - pBufLoc = ( char * ) pCanonicalURI; + lenSmall = ( pFirst->key.dataLen < pSecond->key.dataLen ) ? pFirst->key.dataLen : pSecond->key.dataLen; + compResult = ( int32_t ) strncmp( ( char * ) pFirst->key.pData, + ( char * ) pSecond->key.pData, + lenSmall ); - while( index < uriLen && *pURILoc ) + if( compResult == 0 ) { - if( isalnum( *pURILoc ) || ( *pURILoc == '-' ) || ( *pURILoc == '_' ) || ( *pURILoc == '.' ) || ( *pURILoc == '~' ) ) + if( pFirst->key.dataLen == pSecond->key.dataLen ) { - *( pBufLoc++ ) = *pURILoc; - index++; - } - else if( ( *pURILoc == '/' ) && !encodeSlash ) - { - *( pBufLoc++ ) = *pURILoc; - index++; + /* The fields are equal, so sorting must be done by value. */ + lenSmall = ( pFirst->value.dataLen < pSecond->value.dataLen ) ? pFirst->value.dataLen : pSecond->value.dataLen; + compResult = ( int32_t ) strncmp( ( char * ) pFirst->value.pData, + ( char * ) pSecond->value.pData, + lenSmall ); } else { - *( pBufLoc++ ) = '%'; - *( pBufLoc++ ) = *pURILoc >> 4; - *( pBufLoc++ ) = *pURILoc & 0x0F; - - index += 3; + /* Fields share a common prefix, so the shorter one should come first. */ + compResult = ( pFirst->key.dataLen < pSecond->key.dataLen ) ? -1 : 1; } - - pURILoc++; } - if( nullTerminate ) + if( ( compResult == 0 ) && ( pFirst->value.dataLen != pSecond->value.dataLen ) ) { - *( pBufLoc++ ) = '\0'; - index++; + /* Values share a common prefix, so the shorter one should come first. */ + compResult = ( pFirst->value.dataLen < pSecond->value.dataLen ) ? -1 : 1; } - *canonicalURILen = index; + return compResult; } /*-----------------------------------------------------------*/ - static bool isTrimmableSpace( const char * value, - size_t index, - size_t valLen, - size_t trimmedLength ) + static char toUpperHexChar( char value ) { - bool ret = false; + char hexChar; - assert( ( value != NULL ) && ( index < valLen ) ); + assert( value < 16 ); - /* Only trim spaces. */ - if( isspace( value[ index ] ) ) + if( value < 10 ) { - /* The last character is a trailing space. */ - if( ( index + 1 ) == valLen ) - { - ret = true; - } - /* Trim if the next character is also a space. */ - else if( isspace( value[ index + 1 ] ) ) - { - ret = true; - } - /* It is a leading space if no characters have been written yet. */ - else if( trimmedLength == 0U ) - { - ret = true; - } + hexChar = '0' + value; + } + else + { + hexChar = ( 'A' + value ) - 10; } - return ret; + return hexChar; } -/*-----------------------------------------------------------*/ + static size_t writeHexCodeOfChar( char ** pBuffer, + char code ) + { + **pBuffer = '%'; + ++( *pBuffer ); + **pBuffer = toUpperHexChar( code >> 4 ); + ++( *pBuffer ); + **pBuffer = toUpperHexChar( code & 0x0F ); + ++( *pBuffer ); + + return 3; + } - SigV4Status_t copyHeaderStringToCanonicalBuffer( const char * pData, - size_t dataLen, - uint32_t flags, - char separator, - CanonicalContext_t * canonicalRequest ) + static size_t writeDoubleEncodedEquals( char ** pBuffer ) { - SigV4Status_t status = SigV4Success; - size_t index = 0; - size_t numOfBytesCopied = 0; - size_t buffRemaining; - char * pCurrBufLoc; + **pBuffer = '%'; + ++( *pBuffer ); + **pBuffer = '2'; + ++( *pBuffer ); + **pBuffer = '5'; + ++( *pBuffer ); + **pBuffer = '3'; + ++( *pBuffer ); + **pBuffer = 'D'; + ++( *pBuffer ); + + return 5; + } - assert( ( pData != NULL ) && ( dataLen > 0 ) ); - assert( canonicalRequest != NULL ); - assert( canonicalRequest->pBufCur != NULL ); + static SigV4Status_t encodeURI( const char * pUri, + size_t uriLen, + char * pCanonicalURI, + size_t * canonicalURILen, + bool encodeSlash, + bool doubleEncodeEquals ) + { + const char * pUriLoc = NULL; + char * pBufLoc = NULL; + size_t i = 0U, bytesConsumed = 0U; + SigV4Status_t returnStatus = SigV4Success; - buffRemaining = canonicalRequest->bufRemaining; + assert( pUri != NULL ); + assert( pCanonicalURI != NULL ); + assert( canonicalURILen != NULL ); + assert( *canonicalURILen > 0U ); + + pUriLoc = pUri; + pBufLoc = pCanonicalURI; + + while( i++ < uriLen && *pUriLoc && returnStatus == SigV4Success ) + { + if( doubleEncodeEquals && ( *pUriLoc == '=' ) ) + { + if( ( bytesConsumed > SIZE_MAX - 5U ) || ( bytesConsumed + 5U > *canonicalURILen ) ) + { + returnStatus = SigV4InsufficientMemory; + LOG_INSUFFICIENT_MEMORY_ERROR( "encode the URI", + bytesConsumed + 5U - *canonicalURILen ); + } + else + { + bytesConsumed += writeDoubleEncodedEquals( &pBufLoc ); + } + } + else if( isalnum( *pUriLoc ) || ( *pUriLoc == '-' ) || ( *pUriLoc == '_' ) || ( *pUriLoc == '.' ) || ( *pUriLoc == '~' ) || + ( ( *pUriLoc == '/' ) && !encodeSlash ) ) + { + *pBufLoc = *pUriLoc; + ++pBufLoc; + ++bytesConsumed; + } + else + { + if( ( bytesConsumed > SIZE_MAX - 3U ) || ( bytesConsumed + 3U > *canonicalURILen ) ) + { + returnStatus = SigV4InsufficientMemory; + LOG_INSUFFICIENT_MEMORY_ERROR( "encode the URI", + bytesConsumed + 3U - *canonicalURILen ); + } + else + { + bytesConsumed += writeHexCodeOfChar( &pBufLoc, *pUriLoc ); + } + } + + if( bytesConsumed > *canonicalURILen ) + { + returnStatus = SigV4InsufficientMemory; + LOG_INSUFFICIENT_MEMORY_ERROR( "encode the URI", bytesConsumed - *canonicalURILen ); + } + + pUriLoc++; + } + + *canonicalURILen = bytesConsumed; + + return returnStatus; + } + +/*-----------------------------------------------------------*/ + + static SigV4Status_t generateCanonicalURI( const char * pUri, + size_t uriLen, + bool encodeTwice, + CanonicalContext_t * pCanonicalRequest ) + { + SigV4Status_t returnStatus = SigV4Success; + char * pBufLoc = NULL; + size_t encodedLen = 0U; + + assert( pUri != NULL ); + assert( pCanonicalRequest != NULL ); + assert( pCanonicalRequest->pBufCur != NULL ); + + pBufLoc = pCanonicalRequest->pBufCur; + encodedLen = pCanonicalRequest->bufRemaining; + + /* If the canonical URI needs to be encoded twice, then we encode once here, + * and again at the end of the buffer. Afterwards, the second encode is copied + * to overwrite the first one. */ + returnStatus = encodeURI( pUri, uriLen, pBufLoc, &encodedLen, false, false ); + + if( returnStatus == SigV4Success ) + { + if( encodeTwice ) + { + size_t doubleEncodedLen = pCanonicalRequest->bufRemaining - encodedLen; + + /* Note that the result of encoding the URI a second time must be + * written to a different position in the buffer. It should not be done + * at an overlapping position of the single-encoded URI. Once written, + * the double-encoded URI is moved to the starting location of the single-encoded URI. */ + returnStatus = encodeURI( pBufLoc, + encodedLen, + pBufLoc + encodedLen, + &doubleEncodedLen, + false, + false ); + + if( returnStatus == SigV4Success ) + { + ( void ) memmove( pBufLoc, pBufLoc + encodedLen, doubleEncodedLen ); + pBufLoc += doubleEncodedLen; + pCanonicalRequest->bufRemaining -= doubleEncodedLen; + } + } + else + { + pBufLoc += encodedLen; + pCanonicalRequest->bufRemaining -= encodedLen; + } + } + + if( returnStatus == SigV4Success ) + { + if( pCanonicalRequest->bufRemaining < 1U ) + { + returnStatus = SigV4InsufficientMemory; + LOG_INSUFFICIENT_MEMORY_ERROR( "write the credential scope", 1U ); + } + else + { + *pBufLoc = LINEFEED_CHAR; + pCanonicalRequest->pBufCur = pBufLoc + 1U; + pCanonicalRequest->bufRemaining -= 1U; + } + } + + return returnStatus; + } + +/*-----------------------------------------------------------*/ + static bool isTrimmableSpace( const char * value, + size_t index, + size_t valLen, + size_t trimmedLength ) + { + bool ret = false; + + assert( ( value != NULL ) && ( index < valLen ) ); + + /* Only trim spaces. */ + if( isspace( value[ index ] ) ) + { + /* The last character is a trailing space. */ + if( ( index + 1 ) == valLen ) + { + ret = true; + } + /* Trim if the next character is also a space. */ + else if( isspace( value[ index + 1 ] ) ) + { + ret = true; + } + /* It is a leading space if no characters have been written yet. */ + else if( trimmedLength == 0U ) + { + ret = true; + } + } + + return ret; + } + +/*-----------------------------------------------------------*/ + + static SigV4Status_t copyHeaderStringToCanonicalBuffer( const char * pData, + size_t dataLen, + uint32_t flags, + char separator, + CanonicalContext_t * canonicalRequest ) + { + SigV4Status_t status = SigV4Success; + size_t index = 0; + size_t numOfBytesCopied = 0; + size_t buffRemaining; + char * pCurrBufLoc; + + assert( ( pData != NULL ) && ( dataLen > 0 ) ); + assert( canonicalRequest != NULL ); + assert( canonicalRequest->pBufCur != NULL ); + + buffRemaining = canonicalRequest->bufRemaining; pCurrBufLoc = canonicalRequest->pBufCur; for( index = 0; index < dataLen; index++ ) @@ -991,7 +1405,9 @@ static SigV4Status_t getCredentialScope( SigV4Parameters_t * pSigV4Params, static SigV4Status_t appendSignedHeaders( size_t headerCount, uint32_t flags, - CanonicalContext_t * canonicalRequest ) + CanonicalContext_t * canonicalRequest, + char ** pSignedHeaders, + size_t * pSignedHeadersLen ) { size_t headerIndex = 0, keyLen = 0; SigV4Status_t sigV4Status = SigV4Success; @@ -1001,6 +1417,8 @@ static SigV4Status_t getCredentialScope( SigV4Parameters_t * pSigV4Params, assert( canonicalRequest->pBufCur != NULL ); assert( headerCount > 0 ); + *pSignedHeaders = canonicalRequest->pBufCur; + for( headerIndex = 0; headerIndex < headerCount; headerIndex++ ) { assert( ( canonicalRequest->pHeadersLoc[ headerIndex ].key.pData ) != NULL ); @@ -1017,9 +1435,11 @@ static SigV4Status_t getCredentialScope( SigV4Parameters_t * pSigV4Params, } } + *pSignedHeadersLen = ( size_t ) ( canonicalRequest->pBufCur - *pSignedHeaders - 1U ); + if( sigV4Status == SigV4Success ) { - /* Replacing the last ';' with '\n' as last header does need to have ';'. */ + /* Replacing the last ';' with '\n' as last header should not have ';'. */ *( canonicalRequest->pBufCur - 1 ) = '\n'; } @@ -1068,11 +1488,11 @@ static SigV4Status_t getCredentialScope( SigV4Parameters_t * pSigV4Params, /*-----------------------------------------------------------*/ - SigV4Status_t parseHeaderKeyValueEntries( const char * pHeaders, - size_t headersDataLen, - uint32_t flags, - size_t * headerCount, - CanonicalContext_t * canonicalRequest ) + static SigV4Status_t parseHeaderKeyValueEntries( const char * pHeaders, + size_t headersDataLen, + uint32_t flags, + size_t * headerCount, + CanonicalContext_t * canonicalRequest ) { size_t index = 0, noOfHeaders; const char * pKeyOrValStartLoc; @@ -1140,10 +1560,12 @@ static SigV4Status_t getCredentialScope( SigV4Parameters_t * pSigV4Params, /*-----------------------------------------------------------*/ - static SigV4Status_t appendAllHeadersToCanonicalRequest( const char * pHeaders, - size_t headersLen, - uint32_t flags, - CanonicalContext_t * canonicalRequest ) + static SigV4Status_t generateCanonicalAndSignedHeaders( const char * pHeaders, + size_t headersLen, + uint32_t flags, + CanonicalContext_t * canonicalRequest, + char ** pSignedHeaders, + size_t * pSignedHeadersLen ) { size_t noOfHeaders = 0; SigV4Status_t sigV4Status = SigV4Success; @@ -1162,22 +1584,24 @@ static SigV4Status_t getCredentialScope( SigV4Parameters_t * pSigV4Params, if( ( sigV4Status == SigV4Success ) && !( flags & SIGV4_HTTP_HEADERS_ARE_CANONICAL_FLAG ) ) { /* Sorting headers based on keys. */ - qsort( canonicalRequest->pHeadersLoc, noOfHeaders, sizeof( SigV4KeyValuePair_t ), cmpField ); + qsort( canonicalRequest->pHeadersLoc, noOfHeaders, sizeof( SigV4KeyValuePair_t ), cmpHeaderField ); /* If the headers are canonicalized, we will copy them directly into the buffer as they do not * need processing, else we need to call the following function. */ sigV4Status = appendCanonicalizedHeaders( noOfHeaders, flags, canonicalRequest ); } + /* The \n character must be written if provided headers are not already canonicalized. */ if( ( sigV4Status == SigV4Success ) && !( flags & SIGV4_HTTP_HEADERS_ARE_CANONICAL_FLAG ) ) { if( canonicalRequest->bufRemaining < 1 ) { sigV4Status = SigV4InsufficientMemory; + LOG_INSUFFICIENT_MEMORY_ERROR( "write the canonical headers", 1U ); } else { - *canonicalRequest->pBufCur = '\n'; + *canonicalRequest->pBufCur = LINEFEED_CHAR; canonicalRequest->pBufCur++; canonicalRequest->bufRemaining--; } @@ -1185,88 +1609,1197 @@ static SigV4Status_t getCredentialScope( SigV4Parameters_t * pSigV4Params, if( sigV4Status == SigV4Success ) { - sigV4Status = appendSignedHeaders( noOfHeaders, flags, canonicalRequest ); + sigV4Status = appendSignedHeaders( noOfHeaders, + flags, + canonicalRequest, + pSignedHeaders, + pSignedHeadersLen ); } return sigV4Status; } +/*-----------------------------------------------------------*/ + + static void setQueryStringFieldsAndValues( const char * pQuery, + size_t queryLen, + size_t * pNumberOfParameters, + CanonicalContext_t * pCanonicalRequest ) + { + size_t currentParameter = 0U, i = 0U, startOfFieldOrValue = 0U; + uint8_t fieldHasValue = 0U; + + /* Note: Constness of the query string is casted out here, taking care not to modify + * its contents in any way. */ + + /* Set cursors to each field and value in the query string. */ + for( i = 0U; i < queryLen; i++ ) + { + if( ( pQuery[ i ] == '=' ) && !fieldHasValue ) + { + pCanonicalRequest->pQueryLoc[ currentParameter ].key.pData = ( char * ) &pQuery[ startOfFieldOrValue ]; + pCanonicalRequest->pQueryLoc[ currentParameter ].key.dataLen = i - startOfFieldOrValue; + startOfFieldOrValue = i + 1U; + fieldHasValue = 1U; + } + else if( ( i == queryLen - 1U ) || ( ( pQuery[ i ] == '&' ) && ( i != 0U ) ) ) + { + /* Adjust for the length of the last query parameter. */ + if( i == queryLen - 1U ) + { + i++; + } + + if( i - startOfFieldOrValue == 0U ) + { + /* A field should never be empty, but a value can be empty + * provided a field was specified first. */ + } + else if( !fieldHasValue ) + { + pCanonicalRequest->pQueryLoc[ currentParameter ].key.pData = ( char * ) &pQuery[ startOfFieldOrValue ]; + pCanonicalRequest->pQueryLoc[ currentParameter ].key.dataLen = i - startOfFieldOrValue; + /* The previous field did not have a value set for it, so set its value to NULL. */ + pCanonicalRequest->pQueryLoc[ currentParameter ].value.pData = NULL; + pCanonicalRequest->pQueryLoc[ currentParameter ].value.dataLen = 0U; + startOfFieldOrValue = i + 1U; + currentParameter++; + } + else + { + /* End of value reached, so store a pointer to the previously set value. */ + pCanonicalRequest->pQueryLoc[ currentParameter ].value.pData = ( char * ) &pQuery[ startOfFieldOrValue ]; + pCanonicalRequest->pQueryLoc[ currentParameter ].value.dataLen = i - startOfFieldOrValue; + fieldHasValue = 0U; + startOfFieldOrValue = i + 1U; + currentParameter++; + } + } + else + { + /* Empty else. */ + } + + if( currentParameter > SIGV4_MAX_QUERY_PAIR_COUNT ) + { + break; + } + } + + *pNumberOfParameters = currentParameter; + } + + static SigV4Status_t writeValueInCanonicalizedQueryString( char ** pBufCur, + char * pValue, + size_t valueLen, + size_t * pEncodedLen, + size_t * pRemainingLen ) + { + SigV4Status_t returnStatus = SigV4Success; + + if( *pEncodedLen < 1U ) + { + returnStatus = SigV4InsufficientMemory; + } + else + { + **pBufCur = '='; + ++( *pBufCur ); + *pRemainingLen -= 1U; + *pEncodedLen = *pRemainingLen; + returnStatus = encodeURI( pValue, + valueLen, + *pBufCur, + pEncodedLen, + true, + true ); + } + + if( returnStatus == SigV4Success ) + { + *pBufCur += *pEncodedLen; + *pRemainingLen -= *pEncodedLen; + } + + return returnStatus; + } + + static SigV4Status_t writeCanonicalQueryParameters( CanonicalContext_t * pCanonicalRequest, + size_t numberOfParameters ) + { + SigV4Status_t returnStatus = SigV4Success; + char * pBufLoc = NULL; + size_t encodedLen = 0U, remainingLen = 0U, i = 0U; + + assert( pCanonicalRequest != NULL ); + assert( pCanonicalRequest->pBufCur != NULL ); + assert( pCanonicalRequest->pQueryLoc != NULL ); + + pBufLoc = pCanonicalRequest->pBufCur; + remainingLen = pCanonicalRequest->bufRemaining; + + for( i = 0U; i < numberOfParameters; i++ ) + { + assert( pCanonicalRequest->pQueryLoc[ i ].key.pData != NULL ); + assert( pCanonicalRequest->pQueryLoc[ i ].key.dataLen > 0U ); + + encodedLen = remainingLen; + returnStatus = encodeURI( pCanonicalRequest->pQueryLoc[ i ].key.pData, + pCanonicalRequest->pQueryLoc[ i ].key.dataLen, + pBufLoc, + &encodedLen, + true, + false ); + + if( returnStatus == SigV4Success ) + { + pBufLoc += encodedLen; + remainingLen -= encodedLen; + encodedLen = remainingLen; + + /* An empty value corresponds to an empty string. */ + if( pCanonicalRequest->pQueryLoc[ i ].value.dataLen > 0U ) + { + assert( pCanonicalRequest->pQueryLoc[ i ].value.pData != NULL ); + returnStatus = writeValueInCanonicalizedQueryString( &pBufLoc, + pCanonicalRequest->pQueryLoc[ i ].value.pData, + pCanonicalRequest->pQueryLoc[ i ].value.dataLen, + &encodedLen, + &remainingLen ); + } + } + + if( ( remainingLen < 1U ) && ( numberOfParameters != i + 1 ) ) + { + returnStatus = SigV4InsufficientMemory; + LOG_INSUFFICIENT_MEMORY_ERROR( "write the canonical query", 1U ); + } + else if( ( numberOfParameters != i + 1 ) && ( returnStatus == SigV4Success ) ) + { + *pBufLoc = '&'; + ++pBufLoc; + remainingLen -= 1; + } + else + { + /* Empty else. */ + } + + if( returnStatus != SigV4Success ) + { + break; + } + else + { + pCanonicalRequest->pBufCur = pBufLoc; + pCanonicalRequest->bufRemaining = remainingLen; + } + } + + return returnStatus; + } + + static SigV4Status_t generateCanonicalQuery( const char * pQuery, + size_t queryLen, + CanonicalContext_t * pCanonicalContext ) + { + SigV4Status_t returnStatus = SigV4Success; + size_t numberOfParameters; + + assert( pCanonicalContext != NULL ); + assert( pCanonicalContext->pBufCur != NULL ); + + setQueryStringFieldsAndValues( pQuery, queryLen, &numberOfParameters, pCanonicalContext ); + + if( numberOfParameters > SIGV4_MAX_QUERY_PAIR_COUNT ) + { + LogError( ( "Number of parameters in the query string has exceeded the maximum of %u.", SIGV4_MAX_QUERY_PAIR_COUNT ) ); + returnStatus = SigV4MaxQueryPairCountExceeded; + } + + if( returnStatus == SigV4Success ) + { + /* Sort the parameter names by character code point in ascending order. + * Parameters with duplicate names should be sorted by value. */ + qsort( pCanonicalContext->pQueryLoc, numberOfParameters, sizeof( SigV4KeyValuePair_t ), cmpQueryFieldValue ); + + /* URI-encode each parameter name and value according to the following rules specified for SigV4: + * - Do not URI-encode any of the unreserved characters that RFC 3986 defines: + * A-Z, a-z, 0-9, hyphen ( - ), underscore ( _ ), period ( . ), and tilde ( ~ ). + * - Percent-encode all other characters with %XY, where X and Y are hexadecimal characters (0-9 and uppercase A-F). + * - Double-encode any equals ( = ) characters in parameter values. + */ + returnStatus = writeCanonicalQueryParameters( pCanonicalContext, numberOfParameters ); + } + + if( returnStatus == SigV4Success ) + { + /* Append a linefeed at the end. */ + *pCanonicalContext->pBufCur = LINEFEED_CHAR; + pCanonicalContext->pBufCur += 1U; + pCanonicalContext->bufRemaining -= 1U; + } + + return returnStatus; + } + #endif /* #if ( SIGV4_USE_CANONICAL_SUPPORT == 1 ) */ /*-----------------------------------------------------------*/ -SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, - size_t dateLen, - char * pDateISO8601, - size_t dateISO8601Len ) +static SigV4Status_t verifySigV4Parameters( const SigV4Parameters_t * pParams ) { - SigV4Status_t returnStatus = SigV4InvalidParameter; - SigV4DateTime_t date = { 0 }; - char * pWriteLoc = pDateISO8601; - const char * pFormatStr = NULL; - size_t formatLen = 0U; + SigV4Status_t returnStatus = SigV4Success; - /* Check for NULL parameters. */ - if( pDate == NULL ) + /* Check for NULL members of struct pParams */ + if( pParams == NULL ) { - LogError( ( "Parameter check failed: pDate is NULL." ) ); + LogError( ( "Parameter check failed: pParams is NULL." ) ); + returnStatus = SigV4InvalidParameter; } - else if( pDateISO8601 == NULL ) + else if( pParams->pCredentials == NULL ) { - LogError( ( "Parameter check failed: pDateISO8601 is NULL." ) ); + LogError( ( "Parameter check failed: pParams->pCredentials is NULL." ) ); + returnStatus = SigV4InvalidParameter; } - /* Check that the date provided is of the expected length. */ - else if( ( dateLen != SIGV4_EXPECTED_LEN_RFC_3339 ) && - ( dateLen != SIGV4_EXPECTED_LEN_RFC_5322 ) ) + else if( pParams->pCredentials->pAccessKeyId == NULL ) { - LogError( ( "Parameter check failed: dateLen must be either %u or %u, " - "for RFC 3339 and RFC 5322 formats, respectively.", - SIGV4_EXPECTED_LEN_RFC_3339, - SIGV4_EXPECTED_LEN_RFC_5322 ) ); + LogError( ( "Parameter check failed: pParams->pCredentials->pAccessKeyId is NULL." ) ); + returnStatus = SigV4InvalidParameter; } - - /* Check that the output buffer provided is large enough for the formatted - * string. */ - else if( dateISO8601Len < SIGV4_ISO_STRING_LEN ) + else if( pParams->pCredentials->pSecretAccessKey == NULL ) { - LogError( ( "Parameter check failed: dateISO8601Len must be at least %u.", - SIGV4_ISO_STRING_LEN ) ); + LogError( ( "Parameter check failed: pParams->pCredentials->pSecretAccessKey is NULL." ) ); + returnStatus = SigV4InvalidParameter; } - else + else if( pParams->pCredentials->pSecurityToken == NULL ) { - /* Assign format string according to input type received. */ - pFormatStr = ( dateLen == SIGV4_EXPECTED_LEN_RFC_3339 ) ? - ( FORMAT_RFC_3339 ) : ( FORMAT_RFC_5322 ); - - formatLen = ( dateLen == SIGV4_EXPECTED_LEN_RFC_3339 ) ? - ( FORMAT_RFC_3339_LEN ) : ( FORMAT_RFC_5322_LEN ); - - returnStatus = parseDate( pDate, dateLen, pFormatStr, formatLen, &date ); + LogError( ( "Parameter check failed: pParams->pCredentials->pSecurityToken is NULL." ) ); + returnStatus = SigV4InvalidParameter; } - - if( returnStatus == SigV4Success ) + else if( pParams->pCredentials->pExpiration == NULL ) { - returnStatus = validateDateTime( &date ); + LogError( ( "Parameter check failed: pParams->pCredentials->pExpiration is NULL." ) ); + returnStatus = SigV4InvalidParameter; } - - if( returnStatus == SigV4Success ) + else if( pParams->pDateIso8601 == NULL ) { - /* Combine date elements into complete ASCII representation, and fill - * buffer with result. */ - intToAscii( date.tm_year, &pWriteLoc, ISO_YEAR_LEN ); - intToAscii( date.tm_mon, &pWriteLoc, ISO_NON_YEAR_LEN ); - intToAscii( date.tm_mday, &pWriteLoc, ISO_NON_YEAR_LEN ); - *pWriteLoc = 'T'; - pWriteLoc++; - intToAscii( date.tm_hour, &pWriteLoc, ISO_NON_YEAR_LEN ); - intToAscii( date.tm_min, &pWriteLoc, ISO_NON_YEAR_LEN ); - intToAscii( date.tm_sec, &pWriteLoc, ISO_NON_YEAR_LEN ); - *pWriteLoc = 'Z'; - - LogDebug( ( "Successfully formatted ISO 8601 date: \"%.*s\"", + 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->pCryptoInterface->hashBlockLen > SIGV4_HASH_MAX_BLOCK_LENGTH ) + { + LogError( ( "Parameter check failed: pParams->pCryptoInterface->hashBlockLen is greater than `SIGV4_HASH_MAX_BLOCK_LENGTH`, " + "which can be configured in sigv4_config.h." ) ); + returnStatus = SigV4InvalidParameter; + } + else if( pParams->pCryptoInterface->hashDigestLen > SIGV4_HASH_MAX_DIGEST_LENGTH ) + { + LogError( ( "Parameter check failed: pParams->pCryptoInterface->hashDigestLen is greater than `SIGV4_HASH_MAX_DIGEST_LENGTH`, " + "which can be configured in sigv4_config.h." ) ); + 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->pHeaders == NULL ) + { + LogError( ( "Parameter check failed: pParams->pHttpParameters->pHeaders is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +static int32_t completeHash( const char * pInput, + size_t inputLen, + char * pOutput, + size_t outputLen, + const SigV4CryptoInterface_t * pCryptoInterface ) +{ + int32_t hashStatus = -1; + + hashStatus = pCryptoInterface->hashInit( pCryptoInterface->pHashContext ); + + if( hashStatus == 0 ) + { + hashStatus = pCryptoInterface->hashUpdate( pCryptoInterface->pHashContext, + pInput, inputLen ); + } + + if( hashStatus == 0 ) + { + hashStatus = pCryptoInterface->hashFinal( pCryptoInterface->pHashContext, + pOutput, outputLen ); + } + + return hashStatus; +} + +/*-----------------------------------------------------------*/ + +static SigV4Status_t completeHashAndHexEncode( const char * pInput, + size_t inputLen, + char * pOutput, + size_t * pOutputLen, + const SigV4CryptoInterface_t * pCryptoInterface ) +{ + SigV4Status_t returnStatus = SigV4Success; + /* Used to store the hash of the request payload. */ + char hashedPayload[ SIGV4_HASH_MAX_DIGEST_LENGTH ]; + SigV4String_t originalHash; + SigV4String_t hexEncodedHash; + + assert( pOutput != NULL ); + assert( pOutputLen != NULL ); + assert( pCryptoInterface != NULL ); + + originalHash.pData = hashedPayload; + originalHash.dataLen = pCryptoInterface->hashDigestLen; + hexEncodedHash.pData = pOutput; + hexEncodedHash.dataLen = *pOutputLen; + + if( completeHash( pInput, + inputLen, + hashedPayload, + pCryptoInterface->hashDigestLen, + pCryptoInterface ) != 0 ) + { + returnStatus = SigV4HashError; + } + + if( returnStatus == SigV4Success ) + { + /* Hex-encode the request payload. */ + returnStatus = lowercaseHexEncode( &originalHash, + &hexEncodedHash ); + } + + if( returnStatus == SigV4Success ) + { + *pOutputLen = hexEncodedHash.dataLen; + } + + return returnStatus; +} + +static int32_t hmacKey( HmacContext_t * pHmacContext, + const char * pKey, + size_t keyLen ) +{ + int32_t returnStatus = 0; + const SigV4CryptoInterface_t * pCryptoInterface = NULL; + + assert( pHmacContext != NULL ); + assert( pHmacContext->key != NULL ); + assert( pHmacContext->pCryptoInterface != NULL ); + assert( pHmacContext->pCryptoInterface->hashInit != NULL ); + assert( pHmacContext->pCryptoInterface->hashUpdate != NULL ); + assert( pHmacContext->pCryptoInterface->hashFinal != NULL ); + + pCryptoInterface = pHmacContext->pCryptoInterface; + + /* At the first time this function is called, it is important that pHmacContext->keyLen + * is set to 0U so that the key can be copied to the start of the buffer. */ + if( pHmacContext->keyLen + keyLen <= pCryptoInterface->hashBlockLen ) + { + /* The key fits into the block so just append it. */ + ( void ) memcpy( pHmacContext->key + pHmacContext->keyLen, pKey, keyLen ); + } + else + { + /* Initialize the hash context and hash existing key data. */ + if( pHmacContext->keyLen <= pCryptoInterface->hashBlockLen ) + { + returnStatus = pCryptoInterface->hashInit( pCryptoInterface->pHashContext ); + + if( returnStatus == 0 ) + { + returnStatus = pCryptoInterface->hashUpdate( pCryptoInterface->pHashContext, + pHmacContext->key, + pHmacContext->keyLen ); + } + } + + /* Hash down the key in order to create a block-sized derived key. */ + if( returnStatus == 0 ) + { + returnStatus = pCryptoInterface->hashUpdate( pCryptoInterface->pHashContext, + pKey, + keyLen ); + } + } + + pHmacContext->keyLen += keyLen; + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +static int32_t hmacData( HmacContext_t * pHmacContext, + const char * pData, + size_t dataLen ) +{ + int32_t returnStatus = 0; + size_t i = 0U; + const SigV4CryptoInterface_t * pCryptoInterface = NULL; + + assert( pHmacContext != NULL ); + assert( pHmacContext->key != NULL ); + assert( pHmacContext->pCryptoInterface != NULL ); + assert( pHmacContext->pCryptoInterface->hashInit != NULL ); + assert( pHmacContext->pCryptoInterface->hashUpdate != NULL ); + assert( pHmacContext->pCryptoInterface->hashFinal != NULL ); + + pCryptoInterface = pHmacContext->pCryptoInterface; + + if( pHmacContext->keyLen > pCryptoInterface->hashBlockLen ) + { + /* Store the final block-sized derived key. */ + returnStatus = pCryptoInterface->hashFinal( pCryptoInterface->pHashContext, + pHmacContext->key, + pCryptoInterface->hashBlockLen ); + pHmacContext->keyLen = pCryptoInterface->hashDigestLen; + } + + assert( pCryptoInterface->hashBlockLen >= pHmacContext->keyLen ); + + if( returnStatus == 0 ) + { + /* Zero pad to the right so that the key has the same size as the block size. */ + ( void ) memset( ( void * ) ( pHmacContext->key + pHmacContext->keyLen ), + 0, + pCryptoInterface->hashBlockLen - pHmacContext->keyLen ); + + for( i = 0U; i < pCryptoInterface->hashBlockLen; i++ ) + { + /* XOR the key with the ipad. */ + pHmacContext->key[ i ] ^= ( char ) 0x36; + } + + returnStatus = pCryptoInterface->hashInit( pCryptoInterface->pHashContext ); + } + + if( returnStatus == 0 ) + { + /* Hash the inner-padded block-sized key. */ + returnStatus = pCryptoInterface->hashUpdate( pCryptoInterface->pHashContext, + pHmacContext->key, + pCryptoInterface->hashBlockLen ); + } + + if( ( returnStatus == 0 ) && ( dataLen > 0U ) ) + { + /* Hash the data. */ + returnStatus = pCryptoInterface->hashUpdate( pCryptoInterface->pHashContext, + pData, + dataLen ); + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ + +static int32_t hmacFinal( HmacContext_t * pHmacContext, + char * pMac, + size_t macLen ) +{ + int32_t returnStatus = -1; + char innerHashDigest[ SIGV4_HASH_MAX_DIGEST_LENGTH ]; + size_t i = 0U; + const SigV4CryptoInterface_t * pCryptoInterface = NULL; + + assert( pHmacContext != NULL ); + assert( pHmacContext->key != NULL ); + assert( pHmacContext->pCryptoInterface != NULL ); + /* Note that we must have a block-sized derived key before calling this function. */ + assert( pHmacContext->pCryptoInterface->hashInit != NULL ); + assert( pHmacContext->pCryptoInterface->hashUpdate != NULL ); + assert( pHmacContext->pCryptoInterface->hashFinal != NULL ); + + pCryptoInterface = pHmacContext->pCryptoInterface; + + /* Write the inner hash. */ + returnStatus = pCryptoInterface->hashFinal( pCryptoInterface->pHashContext, + innerHashDigest, + pCryptoInterface->hashDigestLen ); + + if( returnStatus == 0 ) + { + /* Create the outer-padded key by retrieving the original key from + * the inner-padded key then XOR with opad. XOR is associative, + * so one way to do this is by performing XOR on each byte of the + * inner-padded key with (0x36 ^ 0x5c) = (ipad ^ opad) = 0x6a. */ + for( i = 0U; i < pCryptoInterface->hashBlockLen; i++ ) + { + pHmacContext->key[ i ] ^= ( char ) ( 0x6a ); + } + + returnStatus = pCryptoInterface->hashInit( pCryptoInterface->pHashContext ); + } + + if( returnStatus == 0 ) + { + /* Update hash using the outer-padded key. */ + returnStatus = pCryptoInterface->hashUpdate( pCryptoInterface->pHashContext, + pHmacContext->key, + pCryptoInterface->hashBlockLen ); + } + + if( returnStatus == 0 ) + { + /* Update hash using the inner digest. */ + returnStatus = pCryptoInterface->hashUpdate( pCryptoInterface->pHashContext, + innerHashDigest, + pCryptoInterface->hashDigestLen ); + } + + if( returnStatus == 0 ) + { + /* Write the final HMAC value. */ + returnStatus = pCryptoInterface->hashFinal( pCryptoInterface->pHashContext, + pMac, + macLen ); + } + + /* Reset the HMAC context. */ + pHmacContext->keyLen = 0U; + + return returnStatus; +} + +static SigV4Status_t writeLineToCanonicalRequest( const char * pLine, + size_t lineLen, + CanonicalContext_t * pCanonicalContext ) +{ + SigV4Status_t returnStatus = SigV4Success; + + if( pCanonicalContext->bufRemaining < lineLen + 1U ) + { + returnStatus = SigV4InsufficientMemory; + LOG_INSUFFICIENT_MEMORY_ERROR( "write the credential scope", + lineLen - pCanonicalContext->bufRemaining ); + } + else + { + ( void ) memcpy( pCanonicalContext->pBufCur, + pLine, + lineLen ); + pCanonicalContext->pBufCur += lineLen; + + *pCanonicalContext->pBufCur = LINEFEED_CHAR; + pCanonicalContext->pBufCur += 1U; + + pCanonicalContext->bufRemaining -= lineLen + \ + 1U; + } + + return returnStatus; +} + +static int32_t completeHmac( HmacContext_t * pHmacContext, + const char * pKey, + size_t keyLen, + const char * pData, + size_t dataLen, + char * pOutput, + size_t outputLen, + const SigV4CryptoInterface_t * pCryptoInterface ) +{ + int32_t returnStatus = 0; + + if( outputLen < pCryptoInterface->hashDigestLen ) + { + LogError( ( "Not enough buffer to write the hash digest, bytesExceeded=%lu", + ( unsigned long ) ( pCryptoInterface->hashDigestLen - outputLen ) ) ); + returnStatus = -1; + } + + if( returnStatus == 0 ) + { + returnStatus = hmacKey( pHmacContext, pKey, keyLen ); + } + + if( returnStatus == 0 ) + { + returnStatus = hmacData( pHmacContext, pData, dataLen ); + } + + if( returnStatus == 0 ) + { + returnStatus = hmacFinal( pHmacContext, pOutput, outputLen ); + } + + return returnStatus; +} + +static size_t writeStringToSignPrefix( char * pBufStart, + const char * pAlgorithm, + size_t algorithmLen, + const char * pDateIso8601 ) +{ + /* Need to write all substrings that come before the hash in the string to sign. */ + + /* Write HMAC and hashing algorithm used for SigV4 authentication. */ + ( void ) memcpy( pBufStart, pAlgorithm, algorithmLen ); + pBufStart += algorithmLen; + + *pBufStart = LINEFEED_CHAR; + pBufStart += 1U; + + /* Concatenate entire ISO 8601 date string. */ + ( void ) memcpy( pBufStart, pDateIso8601, SIGV4_ISO_STRING_LEN ); + pBufStart += SIGV4_ISO_STRING_LEN; + + *pBufStart = LINEFEED_CHAR; + + return algorithmLen + 1U + SIGV4_ISO_STRING_LEN + 1U; +} + +static SigV4Status_t writeStringToSign( const SigV4Parameters_t * pParams, + const char * pAlgorithm, + size_t algorithmLen, + CanonicalContext_t * pCanonicalContext ) +{ + SigV4Status_t returnStatus = SigV4Success; + size_t encodedLen = pCanonicalContext->bufRemaining; + char * pBufStart = ( char * ) pCanonicalContext->pBufProcessing; + + returnStatus = completeHashAndHexEncode( pBufStart, + ( size_t ) ( pCanonicalContext->pBufCur - pBufStart ), + pCanonicalContext->pBufCur + 1, + &encodedLen, + pParams->pCryptoInterface ); + + if( returnStatus == SigV4Success ) + { + size_t sizeNeededBeforeHash = algorithmLen + 1U + \ + SIGV4_ISO_STRING_LEN + 1U + \ + sizeNeededForCredentialScope( pParams ) + 1U; + + /* Check if there is enough space for the string to sign. */ + if( sizeNeededBeforeHash + ( pParams->pCryptoInterface->hashDigestLen * 2U ) > + SIGV4_PROCESSING_BUFFER_LENGTH ) + { + returnStatus = SigV4InsufficientMemory; + LOG_INSUFFICIENT_MEMORY_ERROR( "for string to sign", + sizeNeededBeforeHash + ( pParams->pCryptoInterface->hashDigestLen * 2U ) - SIGV4_PROCESSING_BUFFER_LENGTH ); + } + else + { + /* Copy the hash of the canonical request beforehand to its precalculated location + * in the string to sign. */ + ( void ) memmove( pBufStart + sizeNeededBeforeHash, + pCanonicalContext->pBufCur + 1, + encodedLen ); + pCanonicalContext->pBufCur = pBufStart + sizeNeededBeforeHash + encodedLen; + pCanonicalContext->bufRemaining = SIGV4_PROCESSING_BUFFER_LENGTH - encodedLen - sizeNeededBeforeHash; + } + } + + if( returnStatus == SigV4Success ) + { + size_t bytesWritten = 0U; + SigV4String_t credentialScope; + bytesWritten = writeStringToSignPrefix( pBufStart, + pAlgorithm, + algorithmLen, + pParams->pDateIso8601 ); + pBufStart += bytesWritten; + credentialScope.pData = pBufStart; + credentialScope.dataLen = sizeNeededForCredentialScope( pParams ); + /* Concatenate credential scope. */ + ( void ) generateCredentialScope( pParams, &credentialScope ); + pBufStart += credentialScope.dataLen; + /* Concatenate linefeed character. */ + *pBufStart = LINEFEED_CHAR; + } + + return returnStatus; +} + +static SigV4Status_t generateCanonicalRequestUntilHeaders( const SigV4Parameters_t * pParams, + CanonicalContext_t * pCanonicalContext, + char ** pSignedHeaders, + size_t * pSignedHeadersLen ) +{ + SigV4Status_t returnStatus = SigV4Success; + const char * pPath = NULL; + size_t pathLen = 0U; + + if( returnStatus == SigV4Success ) + { + pCanonicalContext->pBufCur = ( char * ) pCanonicalContext->pBufProcessing; + pCanonicalContext->bufRemaining = SIGV4_PROCESSING_BUFFER_LENGTH; + + /* Write the HTTP Request Method to the canonical request. */ + returnStatus = writeLineToCanonicalRequest( pParams->pHttpParameters->pHttpMethod, + pParams->pHttpParameters->httpMethodLen, + pCanonicalContext ); + } + + /* Set defaults for path and algorithm. */ + if( ( pParams->pHttpParameters->pPath == NULL ) || + ( pParams->pHttpParameters->pathLen == 0U ) ) + { + /* If the absolute path is empty, use a forward slash (/). */ + pPath = HTTP_EMPTY_PATH; + pathLen = HTTP_EMPTY_PATH_LEN; + } + else + { + pPath = pParams->pHttpParameters->pPath; + pathLen = pParams->pHttpParameters->pathLen; + } + + if( returnStatus == SigV4Success ) + { + /* Write the URI to the canonical request. */ + if( pParams->pHttpParameters->flags & SIGV4_HTTP_PATH_IS_CANONICAL_FLAG ) + { + /* URI is already canonicalized, so just write it to the buffer as is. */ + returnStatus = writeLineToCanonicalRequest( pPath, + pathLen, + pCanonicalContext ); + } + else if( ( pParams->serviceLen == S3_SERVICE_NAME_LEN ) && + ( strncmp( pParams->pService, S3_SERVICE_NAME, S3_SERVICE_NAME_LEN ) == 0 ) ) + { + /* S3 is the only service in which the URI must only be encoded once. */ + returnStatus = generateCanonicalURI( pPath, pathLen, false, pCanonicalContext ); + } + else + { + returnStatus = generateCanonicalURI( pPath, pathLen, true, pCanonicalContext ); + } + } + + if( returnStatus == SigV4Success ) + { + /* Write the query to the canonical request. */ + if( pParams->pHttpParameters->flags & SIGV4_HTTP_QUERY_IS_CANONICAL_FLAG ) + { + /* HTTP query is already canonicalized, so just write it to the buffer as is. */ + returnStatus = writeLineToCanonicalRequest( pParams->pHttpParameters->pQuery, + pParams->pHttpParameters->queryLen, + pCanonicalContext ); + } + else + { + returnStatus = generateCanonicalQuery( pParams->pHttpParameters->pQuery, pParams->pHttpParameters->queryLen, pCanonicalContext ); + } + } + + if( ( returnStatus == SigV4Success ) && + pParams->pHttpParameters->flags & SIGV4_HTTP_HEADERS_ARE_CANONICAL_FLAG ) + { + /* Headers are already canonicalized, so just write it to the buffer as is. */ + returnStatus = writeLineToCanonicalRequest( pParams->pHttpParameters->pHeaders, + pParams->pHttpParameters->headersLen, + pCanonicalContext ); + } + + if( returnStatus == SigV4Success ) + { + /* Canonicalize original HTTP headers before writing to buffer. */ + returnStatus = generateCanonicalAndSignedHeaders( pParams->pHttpParameters->pHeaders, + pParams->pHttpParameters->headersLen, + pParams->pHttpParameters->flags, + pCanonicalContext, + pSignedHeaders, + pSignedHeadersLen ); + } + + return returnStatus; +} + +static SigV4Status_t generateAuthorizationValuePrefix( const SigV4Parameters_t * pParams, + const char * pAlgorithm, + size_t algorithmLen, + const char * pSignedHeaders, + size_t signedHeadersLen, + char ** pAuthBuf, + size_t * pAuthPrefixLen ) +{ + SigV4Status_t returnStatus = SigV4Success; + SigV4String_t credentialScope; + size_t authPrefixLen = 0U; + + /* Since the signed headers are required to be a part of final Authorization header value, + * we copy the signed headers onto the auth buffer before continuing to generate the signature + * in order to prevent an additional copy and/or usage of extra space. */ + if( returnStatus == SigV4Success ) + { + /* Check if the authorization buffer has enough space to hold the final SigV4 Authorization header value. */ + authPrefixLen = algorithmLen + SPACE_CHAR_LEN + \ + AUTH_CREDENTIAL_PREFIX_LEN + pParams->pCredentials->accessKeyIdLen + \ + CREDENTIAL_SCOPE_SEPARATOR_LEN + sizeNeededForCredentialScope( pParams ) + \ + AUTH_SEPARATOR_LEN + AUTH_SIGNED_HEADERS_PREFIX_LEN + signedHeadersLen + \ + AUTH_SEPARATOR_LEN + AUTH_SIGNATURE_PREFIX_LEN; + + if( *pAuthPrefixLen < authPrefixLen + ( pParams->pCryptoInterface->hashDigestLen * 2U ) ) + { + LogError( ( "Insufficient memory provided to write the Authorization header value, bytesExceeded=%lu", + ( unsigned long ) ( authPrefixLen + ( pParams->pCryptoInterface->hashDigestLen * 2U ) - *pAuthPrefixLen ) ) ); + returnStatus = SigV4InsufficientMemory; + LOG_INSUFFICIENT_MEMORY_ERROR( "string to sign", + sizeNeededBeforeHash + ( pParams->pCryptoInterface->hashDigestLen * 2U ) - SIGV4_PROCESSING_BUFFER_LENGTH ); + } + } + + /* START: Writing of authorization value prefix. */ + /* Write */ + ( void ) memcpy( *pAuthBuf, pAlgorithm, algorithmLen ); + *pAuthBuf += algorithmLen; + **pAuthBuf = SPACE_CHAR; + *pAuthBuf += SPACE_CHAR_LEN; + + /* Write "Credential=/, " */ + ( void ) memcpy( *pAuthBuf, AUTH_CREDENTIAL_PREFIX, AUTH_CREDENTIAL_PREFIX_LEN ); + *pAuthBuf += AUTH_CREDENTIAL_PREFIX_LEN; + ( void ) memcpy( *pAuthBuf, + pParams->pCredentials->pAccessKeyId, + pParams->pCredentials->accessKeyIdLen ); + *pAuthBuf += pParams->pCredentials->accessKeyIdLen; + **pAuthBuf = CREDENTIAL_SCOPE_SEPARATOR; + *pAuthBuf += CREDENTIAL_SCOPE_SEPARATOR_LEN; + credentialScope.pData = *pAuthBuf; + /* #authBufLen is an overestimate but the validation was already done earlier. */ + credentialScope.dataLen = *pAuthPrefixLen; + ( void ) generateCredentialScope( pParams, &credentialScope ); + *pAuthBuf += credentialScope.dataLen; + ( void ) memcpy( *pAuthBuf, AUTH_SEPARATOR, AUTH_SEPARATOR_LEN ); + *pAuthBuf += AUTH_SEPARATOR_LEN; + /* Write "SignedHeaders=, " */ + ( void ) memcpy( *pAuthBuf, AUTH_SIGNED_HEADERS_PREFIX, AUTH_SIGNED_HEADERS_PREFIX_LEN ); + *pAuthBuf += AUTH_SIGNED_HEADERS_PREFIX_LEN; + ( void ) memcpy( *pAuthBuf, pSignedHeaders, signedHeadersLen ); + *pAuthBuf += signedHeadersLen; + + ( void ) memcpy( *pAuthBuf, AUTH_SEPARATOR, AUTH_SEPARATOR_LEN ); + *pAuthBuf += AUTH_SEPARATOR_LEN; + /* Write "Signature=" */ + ( void ) memcpy( *pAuthBuf, AUTH_SIGNATURE_PREFIX, AUTH_SIGNATURE_PREFIX_LEN ); + *pAuthBuf += AUTH_SIGNATURE_PREFIX_LEN; + /* END: Writing of authorization value prefix. */ + + if( returnStatus == SigV4Success ) + { + *pAuthPrefixLen = authPrefixLen; + } + + return returnStatus; +} + +static SigV4Status_t generateSigningKey( const SigV4Parameters_t * pSigV4Params, + HmacContext_t * pHmacContext, + SigV4String_t * pSigningKey, + size_t * pBytesRemaining ) +{ + SigV4Status_t returnStatus = SigV4Success; + int32_t hmacStatus = 0; + char * pSigningKeyStart = NULL; + + assert( pSigV4Params != NULL ); + assert( pHmacContext != NULL ); + assert( pSigningKey != NULL ); + assert( pBytesRemaining != NULL ); + + hmacStatus = hmacKey( pHmacContext, + SIGV4_HMAC_SIGNING_KEY_PREFIX, + SIGV4_HMAC_SIGNING_KEY_PREFIX_LEN ); + + /* To calculate the final signing key, this function needs at least enough + * buffer to hold the length of two digests since one digest is used to + * calculate the other. */ + if( *pBytesRemaining < pSigV4Params->pCryptoInterface->hashDigestLen * 2U ) + { + returnStatus = SigV4InsufficientMemory; + LOG_INSUFFICIENT_MEMORY_ERROR( "generate signing key", + ( pSigV4Params->pCryptoInterface->hashDigestLen * 2U ) - *pBytesRemaining ); + } + + if( hmacStatus == 0 ) + { + hmacStatus = completeHmac( pHmacContext, + pSigV4Params->pCredentials->pSecretAccessKey, + pSigV4Params->pCredentials->secretAccessKeyLen, + pSigV4Params->pDateIso8601, + ISO_DATE_SCOPE_LEN, + pSigningKey->pData, + pSigningKey->dataLen, + pSigV4Params->pCryptoInterface ); + *pBytesRemaining -= pSigV4Params->pCryptoInterface->hashDigestLen; + } + + if( hmacStatus == 0 ) + { + pSigningKeyStart = pSigningKey->pData + pSigV4Params->pCryptoInterface->hashDigestLen + 1U; + hmacStatus = completeHmac( pHmacContext, + pSigningKey->pData, + pSigV4Params->pCryptoInterface->hashDigestLen, + pSigV4Params->pRegion, + pSigV4Params->regionLen, + pSigningKeyStart, + *pBytesRemaining, + pSigV4Params->pCryptoInterface ); + *pBytesRemaining -= pSigV4Params->pCryptoInterface->hashDigestLen; + } + + if( hmacStatus == 0 ) + { + hmacStatus = completeHmac( pHmacContext, + pSigningKeyStart, + pSigV4Params->pCryptoInterface->hashDigestLen, + pSigV4Params->pService, + pSigV4Params->serviceLen, + pSigningKey->pData, + pSigV4Params->pCryptoInterface->hashDigestLen, + pSigV4Params->pCryptoInterface ); + } + + if( hmacStatus == 0 ) + { + hmacStatus = completeHmac( pHmacContext, + pSigningKey->pData, + pSigV4Params->pCryptoInterface->hashDigestLen, + CREDENTIAL_SCOPE_TERMINATOR, + CREDENTIAL_SCOPE_TERMINATOR_LEN, + pSigningKeyStart, + pSigV4Params->pCryptoInterface->hashDigestLen, + pSigV4Params->pCryptoInterface ); + } + + if( hmacStatus == 0 ) + { + pSigningKey->pData = pSigningKeyStart; + pSigningKey->dataLen = pSigV4Params->pCryptoInterface->hashDigestLen; + } + else + { + returnStatus = SigV4HashError; + } + + return returnStatus; +} + +SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, + size_t dateLen, + char * pDateISO8601, + size_t dateISO8601Len ) +{ + SigV4Status_t returnStatus = SigV4InvalidParameter; + SigV4DateTime_t date = { 0 }; + char * pWriteLoc = pDateISO8601; + const char * pFormatStr = NULL; + size_t formatLen = 0U; + + /* Check for NULL parameters. */ + if( pDate == NULL ) + { + LogError( ( "Parameter check failed: pDate is NULL." ) ); + } + else if( pDateISO8601 == NULL ) + { + LogError( ( "Parameter check failed: pDateISO8601 is NULL." ) ); + } + /* Check that the date provided is of the expected length. */ + else if( ( dateLen != SIGV4_EXPECTED_LEN_RFC_3339 ) && + ( dateLen != SIGV4_EXPECTED_LEN_RFC_5322 ) ) + { + LogError( ( "Parameter check failed: dateLen must be either %u or %u, " + "for RFC 3339 and RFC 5322 formats, respectively.", + SIGV4_EXPECTED_LEN_RFC_3339, + SIGV4_EXPECTED_LEN_RFC_5322 ) ); + } + + /* Check that the output buffer provided is large enough for the formatted + * string. */ + else if( dateISO8601Len < SIGV4_ISO_STRING_LEN ) + { + LogError( ( "Parameter check failed: dateISO8601Len must be at least %u.", + SIGV4_ISO_STRING_LEN ) ); + } + else + { + /* Assign format string according to input type received. */ + pFormatStr = ( dateLen == SIGV4_EXPECTED_LEN_RFC_3339 ) ? + ( FORMAT_RFC_3339 ) : ( FORMAT_RFC_5322 ); + + formatLen = ( dateLen == SIGV4_EXPECTED_LEN_RFC_3339 ) ? + ( FORMAT_RFC_3339_LEN ) : ( FORMAT_RFC_5322_LEN ); + + returnStatus = parseDate( pDate, dateLen, pFormatStr, formatLen, &date ); + } + + if( returnStatus == SigV4Success ) + { + returnStatus = validateDateTime( &date ); + } + + if( returnStatus == SigV4Success ) + { + /* Combine date elements into complete ASCII representation, and fill + * buffer with result. */ + intToAscii( date.tm_year, &pWriteLoc, ISO_YEAR_LEN ); + intToAscii( date.tm_mon, &pWriteLoc, ISO_NON_YEAR_LEN ); + intToAscii( date.tm_mday, &pWriteLoc, ISO_NON_YEAR_LEN ); + *pWriteLoc = 'T'; + pWriteLoc++; + intToAscii( date.tm_hour, &pWriteLoc, ISO_NON_YEAR_LEN ); + intToAscii( date.tm_min, &pWriteLoc, ISO_NON_YEAR_LEN ); + intToAscii( date.tm_sec, &pWriteLoc, ISO_NON_YEAR_LEN ); + *pWriteLoc = 'Z'; + + LogDebug( ( "Successfully formatted ISO 8601 date: \"%.*s\"", ( int ) dateISO8601Len, pDateISO8601 ) ); } return returnStatus; } + +SigV4Status_t SigV4_GenerateHTTPAuthorization( const SigV4Parameters_t * pParams, + char * pAuthBuf, + size_t * authBufLen, + char ** pSignature, + size_t * signatureLen ) +{ + SigV4Status_t returnStatus = SigV4Success; + CanonicalContext_t canonicalContext; + const char * pAlgorithm = NULL; + char * pSignedHeaders = NULL; + size_t encodedLen = 0U, algorithmLen = 0U, signedHeadersLen = 0U, authPrefixLen = 0U; + HmacContext_t hmacContext = { 0 }; + SigV4String_t signingKey; + + /*returnStatus = verifySigV4Parameters( pParams );*/ + + authPrefixLen = *authBufLen; + + /* Default arguments. */ + if( ( pParams->pAlgorithm == NULL ) || ( pParams->algorithmLen == 0 ) ) + { + /* The default algorithm is AWS4-HMAC-SHA256. */ + pAlgorithm = SIGV4_AWS4_HMAC_SHA256; + algorithmLen = SIGV4_AWS4_HMAC_SHA256_LENGTH; + } + else + { + pAlgorithm = pParams->pAlgorithm; + algorithmLen = pParams->algorithmLen; + } + + if( returnStatus == SigV4Success ) + { + returnStatus = generateCanonicalRequestUntilHeaders( pParams, &canonicalContext, &pSignedHeaders, &signedHeadersLen ); + } + + /* Write the prefix of the Authorizaton header value. */ + if( returnStatus == SigV4Success ) + { + returnStatus = generateAuthorizationValuePrefix( pParams, + pAlgorithm, algorithmLen, + pSignedHeaders, signedHeadersLen, + &pAuthBuf, &authPrefixLen ); + } + + /* Hash and hex-encode the canonical request to the buffer. */ + if( returnStatus == SigV4Success ) + { + encodedLen = canonicalContext.bufRemaining; + returnStatus = completeHashAndHexEncode( pParams->pHttpParameters->pPayload, + pParams->pHttpParameters->payloadLen, + canonicalContext.pBufCur, + &encodedLen, + pParams->pCryptoInterface ); + } + + /* Write string to sign. */ + if( returnStatus == SigV4Success ) + { + canonicalContext.pBufCur += encodedLen; + canonicalContext.bufRemaining -= encodedLen; + returnStatus = writeStringToSign( pParams, pAlgorithm, algorithmLen, &canonicalContext ); + } + + /* Write the signing key. The is done by computing the following function + * where the + operator means concatenation: + * HMAC(HMAC(HMAC(HMAC("AWS4" + kSecret,pDate),pRegion),pService),"aws4_request") */ + if( returnStatus == SigV4Success ) + { + hmacContext.pCryptoInterface = pParams->pCryptoInterface; + signingKey.pData = canonicalContext.pBufCur; + signingKey.dataLen = canonicalContext.bufRemaining; + returnStatus = generateSigningKey( pParams, + &hmacContext, + &signingKey, + &canonicalContext.bufRemaining ); + } + + /* Use the SigningKey and StringToSign to produce the final signature. + * Note that the StringToSign starts from the beginning of the processing buffer. */ + if( ( returnStatus == SigV4Success ) && + ( completeHmac( &hmacContext, + signingKey.pData, + signingKey.dataLen, + ( char * ) canonicalContext.pBufProcessing, + ( size_t ) ( canonicalContext.pBufCur - ( char * ) canonicalContext.pBufProcessing ), + canonicalContext.pBufCur, + pParams->pCryptoInterface->hashDigestLen, + pParams->pCryptoInterface ) != 0 ) ) + { + returnStatus = SigV4HashError; + } + + /* Hex-encode the final signature beforehand to its precalculated + * location in the buffer provided for the Authorizaton header value. */ + if( returnStatus == SigV4Success ) + { + SigV4String_t originalHmac; + SigV4String_t hexEncodedHmac; + originalHmac.pData = canonicalContext.pBufCur; + originalHmac.dataLen = pParams->pCryptoInterface->hashDigestLen; + hexEncodedHmac.pData = pAuthBuf; + /* #authBufLen is an overestimate but the validation was already done earlier. */ + hexEncodedHmac.dataLen = *authBufLen; + returnStatus = lowercaseHexEncode( &originalHmac, + &hexEncodedHmac ); + *pSignature = hexEncodedHmac.pData; + *signatureLen = hexEncodedHmac.dataLen; + *authBufLen = authPrefixLen + ( pParams->pCryptoInterface->hashDigestLen * 2 ); + } + + return returnStatus; +} From e1d370fe18d9f05550646db68a9ec3a06a1a382b Mon Sep 17 00:00:00 2001 From: Oscar Abrina Date: Fri, 6 Aug 2021 10:48:35 -0400 Subject: [PATCH 03/12] Merge doxygen --- docs/doxygen/pages.dox | 6 ------ lexicon.txt | 22 +++++++++------------- 2 files changed, 9 insertions(+), 19 deletions(-) diff --git a/docs/doxygen/pages.dox b/docs/doxygen/pages.dox index acb0f3d1..6fad7fcf 100644 --- a/docs/doxygen/pages.dox +++ b/docs/doxygen/pages.dox @@ -3,7 +3,6 @@ @anchor sigv4 @brief AWS Iot SigV4 Utility -<<<<<<< HEAD

The AWS IoT SigV4 Library is a standalone utility for generating a signature and authorization header according to the specifications of the AWS Signature Version 4 @@ -11,8 +10,6 @@ signing process. This utility is an optional addition to applications sending di HTTP requests to AWS services requiring SigV4 authentication. The library is written in C and designed to be compliant with ISO C90 and MISRA C. It has proven safe memory use.

-======= ->>>>>>> bf7559c (SigV4_GenerateHTTPAuthorization Implementation) @section sigv4_memory_requirements Memory Requirements @brief Memory requirements of the SigV4 Utility library. @@ -26,7 +23,6 @@ HTTP requests to AWS services requiring SigV4 authentication. The library is wri All functions in the SigV4 library operate only on the buffers provided and use only local variables on the stack.

-<<<<<<< HEAD

Compliance & Coverage

@@ -36,8 +32,6 @@ All functions are written to have minimal complexity. Unit tests and CBMC proofs are written to cover every path of execution and achieve 100% branch coverage.

-======= ->>>>>>> bf7559c (SigV4_GenerateHTTPAuthorization Implementation) */ /** diff --git a/lexicon.txt b/lexicon.txt index 9d5e66ba..bb78ac5f 100644 --- a/lexicon.txt +++ b/lexicon.txt @@ -5,7 +5,7 @@ addtogroup aggregator algorithmlen amz -apr +api ascii auth authbuflen @@ -16,6 +16,7 @@ bufferlen bufremaining canonicalrequest canonicalurilen +cbmc chunked com config @@ -48,6 +49,7 @@ hashupdate headercount headerindex headerlen +headersdatalen headerslen hh hhmmss @@ -162,6 +164,7 @@ servicelen sha signaturelen <<<<<<< HEAD +<<<<<<< HEAD signedheaders ======= <<<<<<< HEAD @@ -171,31 +174,27 @@ signedheaderslen signingkey >>>>>>> bf7559c (SigV4_GenerateHTTPAuthorization Implementation) >>>>>>> d393cea (SigV4_GenerateHTTPAuthorization Implementation) +======= +signedheaders +signedheaderslen +signingkey +>>>>>>> 72971e2 (Merge doxygen) sizeof snprintf ss sscanf -<<<<<<< HEAD standalone strftime -======= -strftime stringtosign ->>>>>>> bf7559c (SigV4_GenerateHTTPAuthorization Implementation) struct structs sts subfolder sublicense thu -<<<<<<< HEAD trimmable trimmedlength tm -======= -tm -trimmedlen ->>>>>>> bf7559c (SigV4_GenerateHTTPAuthorization Implementation) tue txt un @@ -204,10 +203,7 @@ urilen url utc vallen -<<<<<<< HEAD -======= xor xy ->>>>>>> bf7559c (SigV4_GenerateHTTPAuthorization Implementation) yyyy yyyymmdd From 5140fa67e1e914746a2657bff8bebe1ee79de950 Mon Sep 17 00:00:00 2001 From: Oscar Abrina Date: Fri, 6 Aug 2021 11:24:08 -0400 Subject: [PATCH 04/12] Create SigV4ConstString_t type --- lexicon.txt | 1 + source/include/sigv4_internal.h | 4 +-- source/sigv4.c | 60 ++++++++++++++++++++++++++------- 3 files changed, 50 insertions(+), 15 deletions(-) diff --git a/lexicon.txt b/lexicon.txt index bb78ac5f..392184d4 100644 --- a/lexicon.txt +++ b/lexicon.txt @@ -6,6 +6,7 @@ aggregator algorithmlen amz api +apr ascii auth authbuflen diff --git a/source/include/sigv4_internal.h b/source/include/sigv4_internal.h index f48d27db..42454978 100644 --- a/source/include/sigv4_internal.h +++ b/source/include/sigv4_internal.h @@ -148,8 +148,8 @@ typedef struct SigV4String */ typedef struct SigV4ConstString { - const char * pData; /**< SigV4 string data */ - size_t dataLen; /**< Length of pData */ + const char * pData; /**< SigV4 string data */ + size_t dataLen; /**< Length of pData */ } SigV4ConstString_t; /** diff --git a/source/sigv4.c b/source/sigv4.c index 03459c6b..71b667ed 100644 --- a/source/sigv4.c +++ b/source/sigv4.c @@ -46,7 +46,7 @@ * * @return Returns 'true' if @pInput is empty, and 'false' otherwise. */ - static bool emptySigV4String( SigV4String_t * pInput ); + static bool emptySigV4String( SigV4ConstString_t * pInput ); /** * @brief Normalize a URI string according to RFC 3986 and fill destination @@ -305,7 +305,7 @@ static SigV4Status_t generateAuthorizationValuePrefix( const SigV4Parameters_t * * are already canonicalized. * * @param[in] pLine The line to write to the canonical request. - * @param[in] lineLen The length of @p pLine. + * @param[in] lineLen The length of @p pLine * @param[in,out] pCanonicalContext The canonical context where * the line should be written. * @return SigV4InsufficientMemory if the length of the canonical request @@ -315,6 +315,28 @@ static SigV4Status_t writeLineToCanonicalRequest( const char * pLine, size_t lineLen, CanonicalContext_t * pCanonicalContext ); +/** + * @brief Set a query parameter key in the canonical request. + * + * @param[in] currentParameter The index of the query key to set + * @param[in] pKey The pointer to the query key + * @param[in] keyLen The length of @p pKey + * @param[in,out] pCanonicalRequest The canonical request containing the + * query parameter array of keys and values + */ +static void setQueryParameterKey(size_t currentParameter, const char * pKey, size_t keyLen, CanonicalContext_t * pCanonicalRequest); + +/** + * @brief Set a query parameter value in the canonical request. + * + * @param[in] currentParameter The index of the query value to set + * @param[in] pValue The pointer to the query value + * @param[in] valueLen The length of @p pValue + * @param[in,out] pCanonicalRequest The canonical request containing the + * query parameter array of keys and values + */ +static void setQueryParameterValue(size_t currentParameter, const char * pValue, size_t valueLen, CanonicalContext_t * pCanonicalRequest); + /** * @brief Update the HMAC using an input key. * @note This function can be called multiple times before calling @@ -1018,7 +1040,7 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa #if ( SIGV4_USE_CANONICAL_SUPPORT == 1 ) - static bool emptySigV4String( SigV4String_t * pInput ) + static bool emptySigV4String( SigV4ConstString_t * pInput ) { bool returnVal = true; @@ -1619,6 +1641,22 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa return sigV4Status; } +/*-----------------------------------------------------------*/ + +static void setQueryParameterKey(size_t currentParameter, const char * pKey, size_t keyLen, CanonicalContext_t * pCanonicalRequest) +{ + pCanonicalRequest->pQueryLoc[ currentParameter ].key.pData = pKey; + pCanonicalRequest->pQueryLoc[ currentParameter ].key.dataLen = keyLen; +} + +/*-----------------------------------------------------------*/ + +static void setQueryParameterValue(size_t currentParameter, const char * pValue, size_t valueLen, CanonicalContext_t * pCanonicalRequest) +{ + pCanonicalRequest->pQueryLoc[ currentParameter ].value.pData = pValue; + pCanonicalRequest->pQueryLoc[ currentParameter ].value.dataLen = valueLen; +} + /*-----------------------------------------------------------*/ static void setQueryStringFieldsAndValues( const char * pQuery, @@ -1637,8 +1675,7 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa { if( ( pQuery[ i ] == '=' ) && !fieldHasValue ) { - pCanonicalRequest->pQueryLoc[ currentParameter ].key.pData = ( char * ) &pQuery[ startOfFieldOrValue ]; - pCanonicalRequest->pQueryLoc[ currentParameter ].key.dataLen = i - startOfFieldOrValue; + setQueryParameterKey(currentParameter, &pQuery[ startOfFieldOrValue ], i - startOfFieldOrValue, pCanonicalRequest); startOfFieldOrValue = i + 1U; fieldHasValue = 1U; } @@ -1657,19 +1694,16 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa } else if( !fieldHasValue ) { - pCanonicalRequest->pQueryLoc[ currentParameter ].key.pData = ( char * ) &pQuery[ startOfFieldOrValue ]; - pCanonicalRequest->pQueryLoc[ currentParameter ].key.dataLen = i - startOfFieldOrValue; + setQueryParameterKey(currentParameter, &pQuery[ startOfFieldOrValue ], i - startOfFieldOrValue, pCanonicalRequest); /* The previous field did not have a value set for it, so set its value to NULL. */ - pCanonicalRequest->pQueryLoc[ currentParameter ].value.pData = NULL; - pCanonicalRequest->pQueryLoc[ currentParameter ].value.dataLen = 0U; + setQueryParameterValue(currentParameter, NULL, 0U, pCanonicalRequest); startOfFieldOrValue = i + 1U; currentParameter++; } else { /* End of value reached, so store a pointer to the previously set value. */ - pCanonicalRequest->pQueryLoc[ currentParameter ].value.pData = ( char * ) &pQuery[ startOfFieldOrValue ]; - pCanonicalRequest->pQueryLoc[ currentParameter ].value.dataLen = i - startOfFieldOrValue; + setQueryParameterValue(currentParameter, &pQuery[ startOfFieldOrValue ], i - startOfFieldOrValue, pCanonicalRequest); fieldHasValue = 0U; startOfFieldOrValue = i + 1U; currentParameter++; @@ -1690,7 +1724,7 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa } static SigV4Status_t writeValueInCanonicalizedQueryString( char ** pBufCur, - char * pValue, + const char * pValue, size_t valueLen, size_t * pEncodedLen, size_t * pRemainingLen ) @@ -2704,7 +2738,7 @@ SigV4Status_t SigV4_GenerateHTTPAuthorization( const SigV4Parameters_t * pParams HmacContext_t hmacContext = { 0 }; SigV4String_t signingKey; - /*returnStatus = verifySigV4Parameters( pParams );*/ + returnStatus = verifySigV4Parameters( pParams ); authPrefixLen = *authBufLen; From 7cb9491ef13689bc3927db85a9cbb490a1ce16f2 Mon Sep 17 00:00:00 2001 From: Oscar Abrina Date: Fri, 6 Aug 2021 11:35:55 -0400 Subject: [PATCH 05/12] Fix checks --- docs/doxygen/include/size_table.html | 8 ++--- lexicon.txt | 4 +++ source/include/sigv4_internal.h | 4 +-- source/sigv4.c | 44 ++++++++++++++++++---------- 4 files changed, 38 insertions(+), 22 deletions(-) diff --git a/docs/doxygen/include/size_table.html b/docs/doxygen/include/size_table.html index 62e8d2fe..9443f81c 100644 --- a/docs/doxygen/include/size_table.html +++ b/docs/doxygen/include/size_table.html @@ -9,12 +9,12 @@ sigv4.c -
1.5K
-
1.2K
+
4.7K
+
4.1K
Total estimates -
1.5K
-
1.2K
+
4.7K
+
4.1K
diff --git a/lexicon.txt b/lexicon.txt index 392184d4..e4957954 100644 --- a/lexicon.txt +++ b/lexicon.txt @@ -25,6 +25,7 @@ completehashandhexencode const constness copydoc +currentparameter datalen datelen dd @@ -105,6 +106,7 @@ pbufprocessing pbufstart pbytesremaining pcanonicalcontext +pcanonicalrequest pcanonicaluri pcredscope pcryptointerface @@ -146,6 +148,7 @@ psignedheaderslen psigningkey ptestformatfailure puri +pvalue qsort querylen rande @@ -204,6 +207,7 @@ urilen url utc vallen +valuelen xor xy yyyy diff --git a/source/include/sigv4_internal.h b/source/include/sigv4_internal.h index 42454978..f48d27db 100644 --- a/source/include/sigv4_internal.h +++ b/source/include/sigv4_internal.h @@ -148,8 +148,8 @@ typedef struct SigV4String */ typedef struct SigV4ConstString { - const char * pData; /**< SigV4 string data */ - size_t dataLen; /**< Length of pData */ + const char * pData; /**< SigV4 string data */ + size_t dataLen; /**< Length of pData */ } SigV4ConstString_t; /** diff --git a/source/sigv4.c b/source/sigv4.c index 71b667ed..42924f97 100644 --- a/source/sigv4.c +++ b/source/sigv4.c @@ -324,7 +324,10 @@ static SigV4Status_t writeLineToCanonicalRequest( const char * pLine, * @param[in,out] pCanonicalRequest The canonical request containing the * query parameter array of keys and values */ -static void setQueryParameterKey(size_t currentParameter, const char * pKey, size_t keyLen, CanonicalContext_t * pCanonicalRequest); +static void setQueryParameterKey( size_t currentParameter, + const char * pKey, + size_t keyLen, + CanonicalContext_t * pCanonicalRequest ); /** * @brief Set a query parameter value in the canonical request. @@ -335,7 +338,10 @@ static void setQueryParameterKey(size_t currentParameter, const char * pKey, siz * @param[in,out] pCanonicalRequest The canonical request containing the * query parameter array of keys and values */ -static void setQueryParameterValue(size_t currentParameter, const char * pValue, size_t valueLen, CanonicalContext_t * pCanonicalRequest); +static void setQueryParameterValue( size_t currentParameter, + const char * pValue, + size_t valueLen, + CanonicalContext_t * pCanonicalRequest ); /** * @brief Update the HMAC using an input key. @@ -1643,19 +1649,25 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa /*-----------------------------------------------------------*/ -static void setQueryParameterKey(size_t currentParameter, const char * pKey, size_t keyLen, CanonicalContext_t * pCanonicalRequest) -{ - pCanonicalRequest->pQueryLoc[ currentParameter ].key.pData = pKey; - pCanonicalRequest->pQueryLoc[ currentParameter ].key.dataLen = keyLen; -} + static void setQueryParameterKey( size_t currentParameter, + const char * pKey, + size_t keyLen, + CanonicalContext_t * pCanonicalRequest ) + { + pCanonicalRequest->pQueryLoc[ currentParameter ].key.pData = pKey; + pCanonicalRequest->pQueryLoc[ currentParameter ].key.dataLen = keyLen; + } /*-----------------------------------------------------------*/ -static void setQueryParameterValue(size_t currentParameter, const char * pValue, size_t valueLen, CanonicalContext_t * pCanonicalRequest) -{ - pCanonicalRequest->pQueryLoc[ currentParameter ].value.pData = pValue; - pCanonicalRequest->pQueryLoc[ currentParameter ].value.dataLen = valueLen; -} + static void setQueryParameterValue( size_t currentParameter, + const char * pValue, + size_t valueLen, + CanonicalContext_t * pCanonicalRequest ) + { + pCanonicalRequest->pQueryLoc[ currentParameter ].value.pData = pValue; + pCanonicalRequest->pQueryLoc[ currentParameter ].value.dataLen = valueLen; + } /*-----------------------------------------------------------*/ @@ -1675,7 +1687,7 @@ static void setQueryParameterValue(size_t currentParameter, const char * pValue, { if( ( pQuery[ i ] == '=' ) && !fieldHasValue ) { - setQueryParameterKey(currentParameter, &pQuery[ startOfFieldOrValue ], i - startOfFieldOrValue, pCanonicalRequest); + setQueryParameterKey( currentParameter, &pQuery[ startOfFieldOrValue ], i - startOfFieldOrValue, pCanonicalRequest ); startOfFieldOrValue = i + 1U; fieldHasValue = 1U; } @@ -1694,16 +1706,16 @@ static void setQueryParameterValue(size_t currentParameter, const char * pValue, } else if( !fieldHasValue ) { - setQueryParameterKey(currentParameter, &pQuery[ startOfFieldOrValue ], i - startOfFieldOrValue, pCanonicalRequest); + setQueryParameterKey( currentParameter, &pQuery[ startOfFieldOrValue ], i - startOfFieldOrValue, pCanonicalRequest ); /* The previous field did not have a value set for it, so set its value to NULL. */ - setQueryParameterValue(currentParameter, NULL, 0U, pCanonicalRequest); + setQueryParameterValue( currentParameter, NULL, 0U, pCanonicalRequest ); startOfFieldOrValue = i + 1U; currentParameter++; } else { /* End of value reached, so store a pointer to the previously set value. */ - setQueryParameterValue(currentParameter, &pQuery[ startOfFieldOrValue ], i - startOfFieldOrValue, pCanonicalRequest); + setQueryParameterValue( currentParameter, &pQuery[ startOfFieldOrValue ], i - startOfFieldOrValue, pCanonicalRequest ); fieldHasValue = 0U; startOfFieldOrValue = i + 1U; currentParameter++; From 43258d6c2c6a78e947429b7c9d06ac4336acc19e Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Sat, 7 Aug 2021 00:46:48 +0000 Subject: [PATCH 06/12] Hygiene improvements in URI encoding logic --- source/include/sigv4_internal.h | 47 +++++++------- source/sigv4.c | 105 +++++++++++++++++++------------- 2 files changed, 86 insertions(+), 66 deletions(-) diff --git a/source/include/sigv4_internal.h b/source/include/sigv4_internal.h index f48d27db..2d408307 100644 --- a/source/include/sigv4_internal.h +++ b/source/include/sigv4_internal.h @@ -70,41 +70,44 @@ /** * @brief The separator between each component of the credential scope. */ -#define CREDENTIAL_SCOPE_SEPARATOR '/' -#define CREDENTIAL_SCOPE_SEPARATOR_LEN 1U /**< The length of #CREDENTIAL_SCOPE_SEPARATOR. */ +#define CREDENTIAL_SCOPE_SEPARATOR '/' +#define CREDENTIAL_SCOPE_SEPARATOR_LEN 1U /**< The length of #CREDENTIAL_SCOPE_SEPARATOR. */ /** * @brief The last component that terminates the credential scope. */ -#define CREDENTIAL_SCOPE_TERMINATOR "aws4_request" -#define CREDENTIAL_SCOPE_TERMINATOR_LEN ( sizeof( CREDENTIAL_SCOPE_TERMINATOR ) - 1U ) /**< The length of #CREDENTIAL_SCOPE_TERMINATOR. */ +#define CREDENTIAL_SCOPE_TERMINATOR "aws4_request" +#define CREDENTIAL_SCOPE_TERMINATOR_LEN ( sizeof( CREDENTIAL_SCOPE_TERMINATOR ) - 1U ) /**< The length of #CREDENTIAL_SCOPE_TERMINATOR. */ /** * @brief Default value when HttpParameters_t.pPath == NULL. */ -#define HTTP_EMPTY_PATH "/" -#define HTTP_EMPTY_PATH_LEN ( sizeof( HTTP_EMPTY_PATH ) - 1U ) /**< The length of #HTTP_EMPTY_PATH. */ +#define HTTP_EMPTY_PATH "/" +#define HTTP_EMPTY_PATH_LEN ( sizeof( HTTP_EMPTY_PATH ) - 1U ) /**< The length of #HTTP_EMPTY_PATH. */ -#define LINEFEED_CHAR '\n' /**< A linefeed character used to build the canonical request. */ -#define LINEFEED_CHAR_LEN 1U /**< The length of #LINEFEED_CHAR. */ +#define URI_ENCODED_SPECIAL_CHAR_SIZE 3U /**< The size of an encoded URI special character. */ +#define URI_DOUBLE_ENCODED_EQUALS_CHAR_SIZE 5U /**< The size of the double-encoded "=" character. */ -#define SPACE_CHAR ' ' /**< A linefeed character used to build the Authorization header value. */ -#define SPACE_CHAR_LEN 1U /**< The length of #SPACE_CHAR. */ +#define LINEFEED_CHAR '\n' /**< A linefeed character used to build the canonical request. */ +#define LINEFEED_CHAR_LEN 1U /**< The length of #LINEFEED_CHAR. */ -#define S3_SERVICE_NAME "s3" /**< S3 is the only service where the URI must only be encoded once. */ -#define S3_SERVICE_NAME_LEN ( sizeof( S3_SERVICE_NAME ) - 1U ) /**< The length of #S3_SERVICE_NAME. */ +#define SPACE_CHAR ' ' /**< A linefeed character used to build the Authorization header value. */ +#define SPACE_CHAR_LEN 1U /**< The length of #SPACE_CHAR. */ -#define SIGV4_HMAC_SIGNING_KEY_PREFIX "AWS4" /**< HMAC signing key prefix. */ -#define SIGV4_HMAC_SIGNING_KEY_PREFIX_LEN ( sizeof( SIGV4_HMAC_SIGNING_KEY_PREFIX ) - 1U ) /**< The length of #SIGV4_HMAC_SIGNING_KEY_PREFIX. */ +#define S3_SERVICE_NAME "s3" /**< S3 is the only service where the URI must only be encoded once. */ +#define S3_SERVICE_NAME_LEN ( sizeof( S3_SERVICE_NAME ) - 1U ) /**< The length of #S3_SERVICE_NAME. */ -#define AUTH_CREDENTIAL_PREFIX "Credential=" /**< The prefix that goes before the credential value in the Authorization header value. */ -#define AUTH_CREDENTIAL_PREFIX_LEN ( sizeof( AUTH_CREDENTIAL_PREFIX ) - 1U ) /**< The length of #AUTH_CREDENTIAL_PREFIX. */ -#define AUTH_SEPARATOR ", " /**< The separator between each component in the Authorization header value. */ -#define AUTH_SEPARATOR_LEN ( sizeof( AUTH_SEPARATOR ) - 1U ) /**< The length of #AUTH_SEPARATOR. */ -#define AUTH_SIGNED_HEADERS_PREFIX "SignedHeaders=" /**< The prefix that goes before the signed headers in the Authorization header value. */ -#define AUTH_SIGNED_HEADERS_PREFIX_LEN ( sizeof( AUTH_SIGNED_HEADERS_PREFIX ) - 1U ) /**< The length of #AUTH_SIGNED_HEADERS_PREFIX. */ -#define AUTH_SIGNATURE_PREFIX "Signature=" /**< The prefix that goes before the signature in the Authorization header value. */ -#define AUTH_SIGNATURE_PREFIX_LEN ( sizeof( AUTH_SIGNATURE_PREFIX ) - 1U ) /**< The length of #AUTH_SIGNATURE_PREFIX. */ +#define SIGV4_HMAC_SIGNING_KEY_PREFIX "AWS4" /**< HMAC signing key prefix. */ +#define SIGV4_HMAC_SIGNING_KEY_PREFIX_LEN ( sizeof( SIGV4_HMAC_SIGNING_KEY_PREFIX ) - 1U ) /**< The length of #SIGV4_HMAC_SIGNING_KEY_PREFIX. */ + +#define AUTH_CREDENTIAL_PREFIX "Credential=" /**< The prefix that goes before the credential value in the Authorization header value. */ +#define AUTH_CREDENTIAL_PREFIX_LEN ( sizeof( AUTH_CREDENTIAL_PREFIX ) - 1U ) /**< The length of #AUTH_CREDENTIAL_PREFIX. */ +#define AUTH_SEPARATOR ", " /**< The separator between each component in the Authorization header value. */ +#define AUTH_SEPARATOR_LEN ( sizeof( AUTH_SEPARATOR ) - 1U ) /**< The length of #AUTH_SEPARATOR. */ +#define AUTH_SIGNED_HEADERS_PREFIX "SignedHeaders=" /**< The prefix that goes before the signed headers in the Authorization header value. */ +#define AUTH_SIGNED_HEADERS_PREFIX_LEN ( sizeof( AUTH_SIGNED_HEADERS_PREFIX ) - 1U ) /**< The length of #AUTH_SIGNED_HEADERS_PREFIX. */ +#define AUTH_SIGNATURE_PREFIX "Signature=" /**< The prefix that goes before the signature in the Authorization header value. */ +#define AUTH_SIGNATURE_PREFIX_LEN ( sizeof( AUTH_SIGNATURE_PREFIX ) - 1U ) /**< The length of #AUTH_SIGNATURE_PREFIX. */ /** * @brief A helper macro to print insufficient memory errors. diff --git a/source/sigv4.c b/source/sigv4.c index 42924f97..599cb979 100644 --- a/source/sigv4.c +++ b/source/sigv4.c @@ -1155,33 +1155,33 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa return hexChar; } - static size_t writeHexCodeOfChar( char ** pBuffer, + static size_t writeHexCodeOfChar( char * pBuffer, + size_t bufferLen, char code ) { - **pBuffer = '%'; - ++( *pBuffer ); - **pBuffer = toUpperHexChar( code >> 4 ); - ++( *pBuffer ); - **pBuffer = toUpperHexChar( code & 0x0F ); - ++( *pBuffer ); + assert( pBuffer != NULL ); + assert( bufferLen >= URI_ENCODED_SPECIAL_CHAR_SIZE ); - return 3; + *pBuffer = '%'; + *( pBuffer + 1U ) = toUpperHexChar( code >> 4 ); + *( pBuffer + 2U ) = toUpperHexChar( code & 0x0F ); + + return URI_ENCODED_SPECIAL_CHAR_SIZE; } - static size_t writeDoubleEncodedEquals( char ** pBuffer ) + static size_t writeDoubleEncodedEquals( char * pBuffer, + size_t bufferLen ) { - **pBuffer = '%'; - ++( *pBuffer ); - **pBuffer = '2'; - ++( *pBuffer ); - **pBuffer = '5'; - ++( *pBuffer ); - **pBuffer = '3'; - ++( *pBuffer ); - **pBuffer = 'D'; - ++( *pBuffer ); + assert( pBuffer != NULL ); + assert( bufferLen > URI_DOUBLE_ENCODED_EQUALS_CHAR_SIZE ); + + *pBuffer = '%'; + *( pBuffer + 1U ) = '2'; + *( pBuffer + 2U ) = '5'; + *( pBuffer + 3U ) = '3'; + *( pBuffer + 4U ) = 'D'; - return 5; + return URI_DOUBLE_ENCODED_EQUALS_CHAR_SIZE; } static SigV4Status_t encodeURI( const char * pUri, @@ -1192,8 +1192,10 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa bool doubleEncodeEquals ) { const char * pUriLoc = NULL; - char * pBufLoc = NULL; - size_t i = 0U, bytesConsumed = 0U; + char * pBuffer = NULL; + size_t index = 0U, bytesConsumed = 0U; + size_t bufferLen = *canonicalURILen; + char currUriChar; SigV4Status_t returnStatus = SigV4Success; assert( pUri != NULL ); @@ -1202,51 +1204,56 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa assert( *canonicalURILen > 0U ); pUriLoc = pUri; - pBufLoc = pCanonicalURI; + pBuffer = pCanonicalURI; - while( i++ < uriLen && *pUriLoc && returnStatus == SigV4Success ) + for(; index < uriLen; index++ ) { - if( doubleEncodeEquals && ( *pUriLoc == '=' ) ) + currUriChar = *pCanonicalURI[index]; + + if( doubleEncodeEquals && ( currUriChar == '=' ) ) { - if( ( bytesConsumed > SIZE_MAX - 5U ) || ( bytesConsumed + 5U > *canonicalURILen ) ) + if( ( bytesConsumed > ( SIZE_MAX - URI_DOUBLE_ENCODED_EQUALS_CHAR_SIZE ) ) || + ( ( bytesConsumed + URI_DOUBLE_ENCODED_EQUALS_CHAR_SIZE ) > bufferLen ) ) { returnStatus = SigV4InsufficientMemory; LOG_INSUFFICIENT_MEMORY_ERROR( "encode the URI", - bytesConsumed + 5U - *canonicalURILen ); + bytesConsumed + URI_SUZ - bufferLen ); + break; } else { - bytesConsumed += writeDoubleEncodedEquals( &pBufLoc ); + bytesConsumed += writeDoubleEncodedEquals( pBuffer + bytesConsumed, + ( bufferLen - bytesConsumed ) ); } } - else if( isalnum( *pUriLoc ) || ( *pUriLoc == '-' ) || ( *pUriLoc == '_' ) || ( *pUriLoc == '.' ) || ( *pUriLoc == '~' ) || - ( ( *pUriLoc == '/' ) && !encodeSlash ) ) + else if( isalnum( currUriChar ) || ( currUriChar == '-' ) || ( currUriChar == '_' ) || ( currUriChar == '.' ) || ( currUriChar == '~' ) || + ( ( currUriChar == '/' ) && !encodeSlash ) ) { - *pBufLoc = *pUriLoc; - ++pBufLoc; + *( pBuffer + bytesConsumed ) = currUriChar; ++bytesConsumed; } else { - if( ( bytesConsumed > SIZE_MAX - 3U ) || ( bytesConsumed + 3U > *canonicalURILen ) ) + if( ( bytesConsumed > (SIZE_MAX - URI_ENCODED_SPECIAL_CHAR_SIZE) ) || + ( (bytesConsumed + URI_ENCODED_SPECIAL_CHAR_SIZE) > bufferLen ) ) { returnStatus = SigV4InsufficientMemory; LOG_INSUFFICIENT_MEMORY_ERROR( "encode the URI", - bytesConsumed + 3U - *canonicalURILen ); + ( bytesConsumed + URI_ENCODED_SPECIAL_CHAR_SIZE - bufferLen ) ); + break; } else { - bytesConsumed += writeHexCodeOfChar( &pBufLoc, *pUriLoc ); + bytesConsumed += writeHexCodeOfChar( pBuffer + bytesConsumed, + ( bufferLen - bytesConsumed ), currUriChar ); } } - if( bytesConsumed > *canonicalURILen ) + if( bytesConsumed > bufferLen ) { returnStatus = SigV4InsufficientMemory; - LOG_INSUFFICIENT_MEMORY_ERROR( "encode the URI", bytesConsumed - *canonicalURILen ); + LOG_INSUFFICIENT_MEMORY_ERROR( "encode the URI", bytesConsumed - bufferLen ); } - - pUriLoc++; } *canonicalURILen = bytesConsumed; @@ -1445,6 +1452,7 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa assert( canonicalRequest->pBufCur != NULL ); assert( headerCount > 0 ); + /* Store the starting location of the Signed Headers in the Canonical Request buffer. */ *pSignedHeaders = canonicalRequest->pBufCur; for( headerIndex = 0; headerIndex < headerCount; headerIndex++ ) @@ -1463,6 +1471,7 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa } } + /* Store the length of the "Signed Headers" data appended to the Canonical Request. */ *pSignedHeadersLen = ( size_t ) ( canonicalRequest->pBufCur - *pSignedHeaders - 1U ); if( sigV4Status == SigV4Success ) @@ -2251,6 +2260,9 @@ static SigV4Status_t writeLineToCanonicalRequest( const char * pLine, { SigV4Status_t returnStatus = SigV4Success; + assert( ( pLine != NULL ) && ( lineLen > 0 ) ); + assert( ( pCanonicalContext != NULL ) && ( pCanonicalContext->pBufCur != NULL ) ); + if( pCanonicalContext->bufRemaining < lineLen + 1U ) { returnStatus = SigV4InsufficientMemory; @@ -2267,8 +2279,7 @@ static SigV4Status_t writeLineToCanonicalRequest( const char * pLine, *pCanonicalContext->pBufCur = LINEFEED_CHAR; pCanonicalContext->pBufCur += 1U; - pCanonicalContext->bufRemaining -= lineLen + \ - 1U; + pCanonicalContext->bufRemaining -= ( lineLen + 1U ); } return returnStatus; @@ -2443,11 +2454,15 @@ static SigV4Status_t generateCanonicalRequestUntilHeaders( const SigV4Parameters ( strncmp( pParams->pService, S3_SERVICE_NAME, S3_SERVICE_NAME_LEN ) == 0 ) ) { /* S3 is the only service in which the URI must only be encoded once. */ - returnStatus = generateCanonicalURI( pPath, pathLen, false, pCanonicalContext ); + returnStatus = generateCanonicalURI( pPath, pathLen, + false /* Do not encode twice. */, + pCanonicalContext ); } else { - returnStatus = generateCanonicalURI( pPath, pathLen, true, pCanonicalContext ); + returnStatus = generateCanonicalURI( pPath, pathLen, + true /* Encode twice */, + pCanonicalContext ); } } @@ -2463,7 +2478,9 @@ static SigV4Status_t generateCanonicalRequestUntilHeaders( const SigV4Parameters } else { - returnStatus = generateCanonicalQuery( pParams->pHttpParameters->pQuery, pParams->pHttpParameters->queryLen, pCanonicalContext ); + returnStatus = generateCanonicalQuery( pParams->pHttpParameters->pQuery, + pParams->pHttpParameters->queryLen, + pCanonicalContext ); } } From bcd8e72b956a5d3b7afb3c115882dbfcc9a1b369 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Sat, 7 Aug 2021 03:03:38 +0000 Subject: [PATCH 07/12] Hygiene improvements in Authorization Header prefix value logic --- source/sigv4.c | 109 ++++++++++++++++++++++++++++++------------------- 1 file changed, 67 insertions(+), 42 deletions(-) diff --git a/source/sigv4.c b/source/sigv4.c index 599cb979..884486b9 100644 --- a/source/sigv4.c +++ b/source/sigv4.c @@ -276,7 +276,7 @@ static SigV4Status_t generateCanonicalRequestUntilHeaders( const SigV4Parameters size_t * pSignedHeadersLen ); /** - * @brief Generate the prefix of the Authorization header equal to + * @brief Generates the prefix of the Authorization header of the format: * " Credential=/, SignedHeaders=, Signature=" * * @param[in] pParams The application-defined parameters used to @@ -287,7 +287,10 @@ static SigV4Status_t generateCanonicalRequestUntilHeaders( const SigV4Parameters * @param[in] signedHeadersLen The length of @p pSignedHeaders. * @param[in,out] pAuthBuf The authorization buffer where to write the prefix. * Pointer is updated with the next location to write the value of the signature. - * @param[in] pAuthPrefixLen The length of @p pAuthBuf. + * @param[in, out] pAuthPrefixLen On input, it should contain the total length of @p pAuthBuf. + * On output, this will be filled with the length of the Authorization header, if + * operation is successful. + * * @return #SigV4InsufficientMemory if the length of the canonical request output * buffer cannot fit the actual request before the headers, #SigV4Success otherwise. */ @@ -296,7 +299,7 @@ static SigV4Status_t generateAuthorizationValuePrefix( const SigV4Parameters_t * size_t algorithmLen, const char * pSignedHeaders, size_t signedHeadersLen, - char ** pAuthBuf, + char * pAuthBuf, size_t * pAuthPrefixLen ); /** @@ -1184,9 +1187,9 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa return URI_DOUBLE_ENCODED_EQUALS_CHAR_SIZE; } - static SigV4Status_t encodeURI( const char * pUri, + static SigV4Status_t encodeURI( const char * pUri, size_t uriLen, - char * pCanonicalURI, + char * pCanonicalBuffer, size_t * canonicalURILen, bool encodeSlash, bool doubleEncodeEquals ) @@ -1199,16 +1202,16 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa SigV4Status_t returnStatus = SigV4Success; assert( pUri != NULL ); - assert( pCanonicalURI != NULL ); + assert( pCanonicalBuffer != NULL ); assert( canonicalURILen != NULL ); assert( *canonicalURILen > 0U ); pUriLoc = pUri; - pBuffer = pCanonicalURI; + pBuffer = pCanonicalBuffer; for(; index < uriLen; index++ ) { - currUriChar = *pCanonicalURI[index]; + currUriChar = pUri[index]; if( doubleEncodeEquals && ( currUriChar == '=' ) ) { @@ -2507,23 +2510,35 @@ static SigV4Status_t generateCanonicalRequestUntilHeaders( const SigV4Parameters return returnStatus; } + static SigV4Status_t generateAuthorizationValuePrefix( const SigV4Parameters_t * pParams, const char * pAlgorithm, size_t algorithmLen, const char * pSignedHeaders, size_t signedHeadersLen, - char ** pAuthBuf, + char * pAuthBuf, size_t * pAuthPrefixLen ) { SigV4Status_t returnStatus = SigV4Success; SigV4String_t credentialScope; size_t authPrefixLen = 0U; + size_t numOfBytesWritten = 0U; + + assert( pParams != NULL ); + assert( pAlgorithm != NULL ); + assert( algorithmLen > 0 ); + assert( pSignedHeaders != NULL ); + assert( signedHeadersLen > 0 ); + assert( pAuthBuf != NULL ); + assert( ( pAuthPrefixLen != NULL ) && ( *pAuthPrefixLen > 0 ) ); /* Since the signed headers are required to be a part of final Authorization header value, * we copy the signed headers onto the auth buffer before continuing to generate the signature * in order to prevent an additional copy and/or usage of extra space. */ if( returnStatus == SigV4Success ) { + size_t encodedSignatureLen = ( pParams->pCryptoInterface->hashDigestLen * 2U ); + /* Check if the authorization buffer has enough space to hold the final SigV4 Authorization header value. */ authPrefixLen = algorithmLen + SPACE_CHAR_LEN + \ AUTH_CREDENTIAL_PREFIX_LEN + pParams->pCredentials->accessKeyIdLen + \ @@ -2531,60 +2546,70 @@ static SigV4Status_t generateAuthorizationValuePrefix( const SigV4Parameters_t * AUTH_SEPARATOR_LEN + AUTH_SIGNED_HEADERS_PREFIX_LEN + signedHeadersLen + \ AUTH_SEPARATOR_LEN + AUTH_SIGNATURE_PREFIX_LEN; - if( *pAuthPrefixLen < authPrefixLen + ( pParams->pCryptoInterface->hashDigestLen * 2U ) ) + if( *pAuthPrefixLen < ( authPrefixLen + encodedSignatureLen ) ) { LogError( ( "Insufficient memory provided to write the Authorization header value, bytesExceeded=%lu", - ( unsigned long ) ( authPrefixLen + ( pParams->pCryptoInterface->hashDigestLen * 2U ) - *pAuthPrefixLen ) ) ); + ( unsigned long ) ( authPrefixLen + encodedSignatureLen - *pAuthPrefixLen ) ) ); returnStatus = SigV4InsufficientMemory; LOG_INSUFFICIENT_MEMORY_ERROR( "string to sign", - sizeNeededBeforeHash + ( pParams->pCryptoInterface->hashDigestLen * 2U ) - SIGV4_PROCESSING_BUFFER_LENGTH ); + sizeNeededBeforeHash + encodedSignatureLen - SIGV4_PROCESSING_BUFFER_LENGTH ); } } - /* START: Writing of authorization value prefix. */ - /* Write */ - ( void ) memcpy( *pAuthBuf, pAlgorithm, algorithmLen ); - *pAuthBuf += algorithmLen; - **pAuthBuf = SPACE_CHAR; - *pAuthBuf += SPACE_CHAR_LEN; + /* START: Writing of authorization value prefix. */ + /******************* Write *******************************************/ + ( void ) memcpy( pAuthBuf, pAlgorithm, algorithmLen ); + numOfBytesWritten += algorithmLen; - /* Write "Credential=/, " */ - ( void ) memcpy( *pAuthBuf, AUTH_CREDENTIAL_PREFIX, AUTH_CREDENTIAL_PREFIX_LEN ); - *pAuthBuf += AUTH_CREDENTIAL_PREFIX_LEN; - ( void ) memcpy( *pAuthBuf, + /* Add space saparator. */ + pAuthBuf[ numOfBytesWritten++ ] = SPACE_CHAR; + + /**************** Write "Credential=/, " ****************/ + ( void ) memcpy( ( pAuthBuf + numOfBytesWritten ), AUTH_CREDENTIAL_PREFIX, AUTH_CREDENTIAL_PREFIX_LEN ); + numOfBytesWritten += AUTH_CREDENTIAL_PREFIX_LEN; + ( void ) memcpy( ( pAuthBuf + numOfBytesWritten ), pParams->pCredentials->pAccessKeyId, pParams->pCredentials->accessKeyIdLen ); - *pAuthBuf += pParams->pCredentials->accessKeyIdLen; - **pAuthBuf = CREDENTIAL_SCOPE_SEPARATOR; - *pAuthBuf += CREDENTIAL_SCOPE_SEPARATOR_LEN; - credentialScope.pData = *pAuthBuf; + numOfBytesWritten += pParams->pCredentials->accessKeyIdLen; + + pAuthBuf[ numOfBytesWritten++ ] = CREDENTIAL_SCOPE_SEPARATOR; + credentialScope.pData = ( pAuthBuf + numOfBytesWritten ); /* #authBufLen is an overestimate but the validation was already done earlier. */ credentialScope.dataLen = *pAuthPrefixLen; ( void ) generateCredentialScope( pParams, &credentialScope ); - *pAuthBuf += credentialScope.dataLen; - ( void ) memcpy( *pAuthBuf, AUTH_SEPARATOR, AUTH_SEPARATOR_LEN ); - *pAuthBuf += AUTH_SEPARATOR_LEN; - /* Write "SignedHeaders=, " */ - ( void ) memcpy( *pAuthBuf, AUTH_SIGNED_HEADERS_PREFIX, AUTH_SIGNED_HEADERS_PREFIX_LEN ); - *pAuthBuf += AUTH_SIGNED_HEADERS_PREFIX_LEN; - ( void ) memcpy( *pAuthBuf, pSignedHeaders, signedHeadersLen ); - *pAuthBuf += signedHeadersLen; - - ( void ) memcpy( *pAuthBuf, AUTH_SEPARATOR, AUTH_SEPARATOR_LEN ); - *pAuthBuf += AUTH_SEPARATOR_LEN; - /* Write "Signature=" */ - ( void ) memcpy( *pAuthBuf, AUTH_SIGNATURE_PREFIX, AUTH_SIGNATURE_PREFIX_LEN ); - *pAuthBuf += AUTH_SIGNATURE_PREFIX_LEN; + numOfBytesWritten += credentialScope.dataLen; + + /* Add separator before the Signed Headers information. */ + ( void ) memcpy( pAuthBuf + numOfBytesWritten, AUTH_SEPARATOR, AUTH_SEPARATOR_LEN ); + numOfBytesWritten += AUTH_SEPARATOR_LEN; + + + /************************ Write "SignedHeaders=, " *******************************/ + ( void ) memcpy( pAuthBuf + numOfBytesWritten, AUTH_SIGNED_HEADERS_PREFIX, AUTH_SIGNED_HEADERS_PREFIX_LEN ); + numOfBytesWritten += AUTH_SIGNED_HEADERS_PREFIX_LEN; + ( void ) memcpy( pAuthBuf + numOfBytesWritten, pSignedHeaders, signedHeadersLen ); + numOfBytesWritten += signedHeadersLen; + + /* Add separator before the Signature field name. */ + ( void ) memcpy( pAuthBuf + numOfBytesWritten, AUTH_SEPARATOR, AUTH_SEPARATOR_LEN ); + numOfBytesWritten += AUTH_SEPARATOR_LEN; + + /****************************** Write "Signature=" *******************************/ + ( void ) memcpy( pAuthBuf + numOfBytesWritten, AUTH_SIGNATURE_PREFIX, AUTH_SIGNATURE_PREFIX_LEN ); + numOfBytesWritten += AUTH_SIGNATURE_PREFIX_LEN; + /* END: Writing of authorization value prefix. */ if( returnStatus == SigV4Success ) { + assert( authPrefixLen == numOfBytesWritten ); *pAuthPrefixLen = authPrefixLen; } return returnStatus; } + static SigV4Status_t generateSigningKey( const SigV4Parameters_t * pSigV4Params, HmacContext_t * pHmacContext, SigV4String_t * pSigningKey, @@ -2795,7 +2820,7 @@ SigV4Status_t SigV4_GenerateHTTPAuthorization( const SigV4Parameters_t * pParams returnStatus = generateAuthorizationValuePrefix( pParams, pAlgorithm, algorithmLen, pSignedHeaders, signedHeadersLen, - &pAuthBuf, &authPrefixLen ); + pAuthBuf, &authPrefixLen ); } /* Hash and hex-encode the canonical request to the buffer. */ @@ -2854,7 +2879,7 @@ SigV4Status_t SigV4_GenerateHTTPAuthorization( const SigV4Parameters_t * pParams SigV4String_t hexEncodedHmac; originalHmac.pData = canonicalContext.pBufCur; originalHmac.dataLen = pParams->pCryptoInterface->hashDigestLen; - hexEncodedHmac.pData = pAuthBuf; + hexEncodedHmac.pData = pAuthBuf + authPrefixLen; /* #authBufLen is an overestimate but the validation was already done earlier. */ hexEncodedHmac.dataLen = *authBufLen; returnStatus = lowercaseHexEncode( &originalHmac, From 962a2f7fddfb2c01ff4d4e0760caffecb0a6d47b Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Sat, 7 Aug 2021 03:49:59 +0000 Subject: [PATCH 08/12] Minor improvements --- source/sigv4.c | 58 +++++++++++++++++++++++++++++++++----------------- 1 file changed, 38 insertions(+), 20 deletions(-) diff --git a/source/sigv4.c b/source/sigv4.c index 884486b9..edef28e9 100644 --- a/source/sigv4.c +++ b/source/sigv4.c @@ -287,10 +287,10 @@ static SigV4Status_t generateCanonicalRequestUntilHeaders( const SigV4Parameters * @param[in] signedHeadersLen The length of @p pSignedHeaders. * @param[in,out] pAuthBuf The authorization buffer where to write the prefix. * Pointer is updated with the next location to write the value of the signature. - * @param[in, out] pAuthPrefixLen On input, it should contain the total length of @p pAuthBuf. + * @param[in, out] pAuthPrefixLen On input, it should contain the total length of @p pAuthBuf. * On output, this will be filled with the length of the Authorization header, if * operation is successful. - * + * * @return #SigV4InsufficientMemory if the length of the canonical request output * buffer cannot fit the actual request before the headers, #SigV4Success otherwise. */ @@ -505,12 +505,12 @@ static SigV4Status_t generateSigningKey( const SigV4Parameters_t * pSigV4Params, size_t * pBytesRemaining ); /** - * @brief Format the credential scope of the authorization header using the access key ID, - * date, region, and service parameters found in #SigV4Parameters_t and ends with the - * "aws4_request" terminator. + * @brief Format the credential scope for the authorization header. + * Credential scope includes the access key ID, date, region, and service parameters, and + * ends with "aws4_request" terminator. * * @param[in] pSigV4Params The application parameters defining the credential's scope. - * @param[in, out] pCredScope The credential scope in the V4 required format. + * @param[in, out] pCredScope The credential scope in the SigV4 format. * * @return SigV4InsufficientMemory if the length of @p pCredScope was insufficient to * fit the actual credential scope, #SigV4Success otherwise. @@ -952,14 +952,14 @@ static SigV4Status_t lowercaseHexEncode( const SigV4String_t * pInputStr, hex = pHexOutput->pData; - if( pHexOutput->dataLen < pInputStr->dataLen * 2U ) + /* Hex string notification of binary data takes twice the size. */ + if( pHexOutput->dataLen < ( pInputStr->dataLen * 2U ) ) { returnStatus = SigV4InsufficientMemory; LOG_INSUFFICIENT_MEMORY_ERROR( "hex encode", ( pInputStr->dataLen * 2U ) - pHexOutput->dataLen ); } - - if( returnStatus == SigV4Success ) + else { for( i = 0; i < pInputStr->dataLen; i++ ) { @@ -1008,11 +1008,10 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa { returnStatus = SigV4InsufficientMemory; LOG_INSUFFICIENT_MEMORY_ERROR( "write the credential scope", - sizeNeeded - pCredScope->dataLen ); + ( sizeNeeded - pCredScope->dataLen ) ); } - /* Each concatenated component is separated by a '/' character. */ - if( returnStatus == SigV4Success ) + else { /* Concatenate first 8 characters from the provided ISO 8601 string (YYYYMMDD). */ ( void ) memcpy( pBufWrite, pSigV4Params->pDateIso8601, ISO_DATE_SCOPE_LEN ); @@ -1039,6 +1038,7 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa ( void ) memcpy( pBufWrite, CREDENTIAL_SCOPE_TERMINATOR, CREDENTIAL_SCOPE_TERMINATOR_LEN ); pBufWrite += CREDENTIAL_SCOPE_TERMINATOR_LEN; + assert( ( pBufWrite - pCredScope->pData ) == sizeNeeded ); pCredScope->dataLen = sizeNeeded; } @@ -1187,7 +1187,7 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa return URI_DOUBLE_ENCODED_EQUALS_CHAR_SIZE; } - static SigV4Status_t encodeURI( const char * pUri, + static SigV4Status_t encodeURI( const char * pUri, size_t uriLen, char * pCanonicalBuffer, size_t * canonicalURILen, @@ -1209,9 +1209,9 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa pUriLoc = pUri; pBuffer = pCanonicalBuffer; - for(; index < uriLen; index++ ) + for( ; index < uriLen; index++ ) { - currUriChar = pUri[index]; + currUriChar = pUri[ index ]; if( doubleEncodeEquals && ( currUriChar == '=' ) ) { @@ -1237,8 +1237,8 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa } else { - if( ( bytesConsumed > (SIZE_MAX - URI_ENCODED_SPECIAL_CHAR_SIZE) ) || - ( (bytesConsumed + URI_ENCODED_SPECIAL_CHAR_SIZE) > bufferLen ) ) + if( ( bytesConsumed > ( SIZE_MAX - URI_ENCODED_SPECIAL_CHAR_SIZE ) ) || + ( ( bytesConsumed + URI_ENCODED_SPECIAL_CHAR_SIZE ) > bufferLen ) ) { returnStatus = SigV4InsufficientMemory; LOG_INSUFFICIENT_MEMORY_ERROR( "encode the URI", @@ -2006,6 +2006,13 @@ static int32_t completeHash( const char * pInput, { int32_t hashStatus = -1; + assert( pOutput != NULL ); + assert( outputLen > 0 ); + assert( pCryptoInterface != NULL ); + assert( pCryptoInterface->hashInit != NULL ); + assert( pCryptoInterface->hashUpdate != NULL ); + assert( pCryptoInterface->hashFinal != NULL ); + hashStatus = pCryptoInterface->hashInit( pCryptoInterface->pHashContext ); if( hashStatus == 0 ) @@ -2033,22 +2040,25 @@ static SigV4Status_t completeHashAndHexEncode( const char * pInput, { SigV4Status_t returnStatus = SigV4Success; /* Used to store the hash of the request payload. */ - char hashedPayload[ SIGV4_HASH_MAX_DIGEST_LENGTH ]; + char hashBuffer[ SIGV4_HASH_MAX_DIGEST_LENGTH ]; SigV4String_t originalHash; SigV4String_t hexEncodedHash; assert( pOutput != NULL ); assert( pOutputLen != NULL ); assert( pCryptoInterface != NULL ); + assert( pCryptoInterface->hashInit != NULL ); + assert( pCryptoInterface->hashUpdate != NULL ); + assert( pCryptoInterface->hashFinal != NULL ); - originalHash.pData = hashedPayload; + originalHash.pData = hashBuffer; originalHash.dataLen = pCryptoInterface->hashDigestLen; hexEncodedHash.pData = pOutput; hexEncodedHash.dataLen = *pOutputLen; if( completeHash( pInput, inputLen, - hashedPayload, + hashBuffer, pCryptoInterface->hashDigestLen, pCryptoInterface ) != 0 ) { @@ -2329,6 +2339,10 @@ static size_t writeStringToSignPrefix( char * pBufStart, size_t algorithmLen, const char * pDateIso8601 ) { + assert( pBufStart != NULL ); + assert( pAlgorithm != NULL ); + assert( pDateIso8601 != NULL ); + /* Need to write all substrings that come before the hash in the string to sign. */ /* Write HMAC and hashing algorithm used for SigV4 authentication. */ @@ -2355,6 +2369,10 @@ static SigV4Status_t writeStringToSign( const SigV4Parameters_t * pParams, SigV4Status_t returnStatus = SigV4Success; size_t encodedLen = pCanonicalContext->bufRemaining; char * pBufStart = ( char * ) pCanonicalContext->pBufProcessing; + + assert(pParams!= NULL); + assert((pAlgorithm!= NULL) && (algorithmLen > 0)); + assert(pCanonicalContext!= NULL); returnStatus = completeHashAndHexEncode( pBufStart, ( size_t ) ( pCanonicalContext->pBufCur - pBufStart ), From ab889d5b3de20ded7e26be61c21241cacd7ae739 Mon Sep 17 00:00:00 2001 From: Oscar Abrina Date: Mon, 9 Aug 2021 18:46:17 -0400 Subject: [PATCH 09/12] Resolve compiler warning --- source/sigv4.c | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/source/sigv4.c b/source/sigv4.c index edef28e9..640e6639 100644 --- a/source/sigv4.c +++ b/source/sigv4.c @@ -1038,7 +1038,7 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa ( void ) memcpy( pBufWrite, CREDENTIAL_SCOPE_TERMINATOR, CREDENTIAL_SCOPE_TERMINATOR_LEN ); pBufWrite += CREDENTIAL_SCOPE_TERMINATOR_LEN; - assert( ( pBufWrite - pCredScope->pData ) == sizeNeeded ); + assert( ( size_t ) ( pBufWrite - pCredScope->pData ) == sizeNeeded ); pCredScope->dataLen = sizeNeeded; } @@ -2369,10 +2369,10 @@ static SigV4Status_t writeStringToSign( const SigV4Parameters_t * pParams, SigV4Status_t returnStatus = SigV4Success; size_t encodedLen = pCanonicalContext->bufRemaining; char * pBufStart = ( char * ) pCanonicalContext->pBufProcessing; - - assert(pParams!= NULL); - assert((pAlgorithm!= NULL) && (algorithmLen > 0)); - assert(pCanonicalContext!= NULL); + + assert( pParams != NULL ); + assert( ( pAlgorithm != NULL ) && ( algorithmLen > 0 ) ); + assert( pCanonicalContext != NULL ); returnStatus = completeHashAndHexEncode( pBufStart, ( size_t ) ( pCanonicalContext->pBufCur - pBufStart ), From 0a81e655eb12dc3043f0071b42ab8138820dea7c Mon Sep 17 00:00:00 2001 From: Oscar Abrina Date: Mon, 9 Aug 2021 19:08:47 -0400 Subject: [PATCH 10/12] Fix checks --- docs/doxygen/include/size_table.html | 4 +-- lexicon.txt | 13 -------- source/sigv4.c | 50 +++++++++++++--------------- 3 files changed, 25 insertions(+), 42 deletions(-) diff --git a/docs/doxygen/include/size_table.html b/docs/doxygen/include/size_table.html index 9443f81c..c73d0907 100644 --- a/docs/doxygen/include/size_table.html +++ b/docs/doxygen/include/size_table.html @@ -9,12 +9,12 @@ sigv4.c -
4.7K
+
4.8K
4.1K
Total estimates -
4.7K
+
4.8K
4.1K
diff --git a/lexicon.txt b/lexicon.txt index e4957954..80811b42 100644 --- a/lexicon.txt +++ b/lexicon.txt @@ -167,22 +167,9 @@ sep servicelen sha signaturelen -<<<<<<< HEAD -<<<<<<< HEAD -signedheaders -======= -<<<<<<< HEAD -======= -signedheaders -signedheaderslen -signingkey ->>>>>>> bf7559c (SigV4_GenerateHTTPAuthorization Implementation) ->>>>>>> d393cea (SigV4_GenerateHTTPAuthorization Implementation) -======= signedheaders signedheaderslen signingkey ->>>>>>> 72971e2 (Merge doxygen) sizeof snprintf ss diff --git a/source/sigv4.c b/source/sigv4.c index 640e6639..8171c985 100644 --- a/source/sigv4.c +++ b/source/sigv4.c @@ -1181,7 +1181,7 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa *pBuffer = '%'; *( pBuffer + 1U ) = '2'; *( pBuffer + 2U ) = '5'; - *( pBuffer + 3U ) = '3'; + *( pBuffer + URI_ENCODED_SPECIAL_CHAR_SIZE ) = '3'; *( pBuffer + 4U ) = 'D'; return URI_DOUBLE_ENCODED_EQUALS_CHAR_SIZE; @@ -1189,66 +1189,60 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa static SigV4Status_t encodeURI( const char * pUri, size_t uriLen, - char * pCanonicalBuffer, + char * pCanonicalURI, size_t * canonicalURILen, bool encodeSlash, bool doubleEncodeEquals ) { const char * pUriLoc = NULL; - char * pBuffer = NULL; - size_t index = 0U, bytesConsumed = 0U; - size_t bufferLen = *canonicalURILen; - char currUriChar; + char * pBufLoc = NULL; + size_t i = 0U, bytesConsumed = 0U; + size_t bufferLen = 0U; SigV4Status_t returnStatus = SigV4Success; assert( pUri != NULL ); - assert( pCanonicalBuffer != NULL ); + assert( pCanonicalURI != NULL ); assert( canonicalURILen != NULL ); assert( *canonicalURILen > 0U ); pUriLoc = pUri; - pBuffer = pCanonicalBuffer; + pBufLoc = pCanonicalURI; + bufferLen = *canonicalURILen; - for( ; index < uriLen; index++ ) + while( ( i++ < uriLen ) && *pUriLoc && ( returnStatus == SigV4Success ) ) { - currUriChar = pUri[ index ]; - - if( doubleEncodeEquals && ( currUriChar == '=' ) ) + if( doubleEncodeEquals && ( *pUriLoc == '=' ) ) { - if( ( bytesConsumed > ( SIZE_MAX - URI_DOUBLE_ENCODED_EQUALS_CHAR_SIZE ) ) || - ( ( bytesConsumed + URI_DOUBLE_ENCODED_EQUALS_CHAR_SIZE ) > bufferLen ) ) + if( ( bytesConsumed > SIZE_MAX - URI_DOUBLE_ENCODED_EQUALS_CHAR_SIZE ) || + ( bytesConsumed + URI_DOUBLE_ENCODED_EQUALS_CHAR_SIZE > bufferLen ) ) { returnStatus = SigV4InsufficientMemory; LOG_INSUFFICIENT_MEMORY_ERROR( "encode the URI", - bytesConsumed + URI_SUZ - bufferLen ); - break; + ( bytesConsumed + URI_DOUBLE_ENCODED_EQUALS_CHAR_SIZE - bufferLen ) ); } else { - bytesConsumed += writeDoubleEncodedEquals( pBuffer + bytesConsumed, - ( bufferLen - bytesConsumed ) ); + bytesConsumed += writeDoubleEncodedEquals( pBufLoc, bufferLen - bytesConsumed ); } } - else if( isalnum( currUriChar ) || ( currUriChar == '-' ) || ( currUriChar == '_' ) || ( currUriChar == '.' ) || ( currUriChar == '~' ) || - ( ( currUriChar == '/' ) && !encodeSlash ) ) + else if( isalnum( *pUriLoc ) || ( *pUriLoc == '-' ) || ( *pUriLoc == '_' ) || ( *pUriLoc == '.' ) || ( *pUriLoc == '~' ) || + ( ( *pUriLoc == '/' ) && !encodeSlash ) ) { - *( pBuffer + bytesConsumed ) = currUriChar; + *pBufLoc = *pUriLoc; + ++pBufLoc; ++bytesConsumed; } else { - if( ( bytesConsumed > ( SIZE_MAX - URI_ENCODED_SPECIAL_CHAR_SIZE ) ) || - ( ( bytesConsumed + URI_ENCODED_SPECIAL_CHAR_SIZE ) > bufferLen ) ) + if( ( bytesConsumed > SIZE_MAX - URI_ENCODED_SPECIAL_CHAR_SIZE ) || ( bytesConsumed + URI_ENCODED_SPECIAL_CHAR_SIZE > bufferLen ) ) { returnStatus = SigV4InsufficientMemory; LOG_INSUFFICIENT_MEMORY_ERROR( "encode the URI", ( bytesConsumed + URI_ENCODED_SPECIAL_CHAR_SIZE - bufferLen ) ); - break; } else { - bytesConsumed += writeHexCodeOfChar( pBuffer + bytesConsumed, - ( bufferLen - bytesConsumed ), currUriChar ); + bytesConsumed += writeHexCodeOfChar( pBufLoc, bufferLen - bytesConsumed, *pUriLoc ); } } @@ -1257,6 +1251,8 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa returnStatus = SigV4InsufficientMemory; LOG_INSUFFICIENT_MEMORY_ERROR( "encode the URI", bytesConsumed - bufferLen ); } + + pUriLoc++; } *canonicalURILen = bytesConsumed; @@ -2579,7 +2575,7 @@ static SigV4Status_t generateAuthorizationValuePrefix( const SigV4Parameters_t * ( void ) memcpy( pAuthBuf, pAlgorithm, algorithmLen ); numOfBytesWritten += algorithmLen; - /* Add space saparator. */ + /* Add space separator. */ pAuthBuf[ numOfBytesWritten++ ] = SPACE_CHAR; /**************** Write "Credential=/, " ****************/ From 613363d06a6b6028b2c279719472bd52c0b2548f Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Tue, 10 Aug 2021 00:27:08 +0000 Subject: [PATCH 11/12] Address review comments --- lexicon.txt | 15 +++---- source/sigv4.c | 107 ++++++++++++++++++++++++++++++------------------- 2 files changed, 74 insertions(+), 48 deletions(-) diff --git a/lexicon.txt b/lexicon.txt index 80811b42..7b4fa23c 100644 --- a/lexicon.txt +++ b/lexicon.txt @@ -20,8 +20,8 @@ canonicalurilen cbmc chunked com -config completehashandhexencode +config const constness copydoc @@ -32,8 +32,8 @@ dd deconstructed defgroup doubleencodeequals -encodetwice encodeslash +encodetwice endif enums expirationlen @@ -53,6 +53,7 @@ headerindex headerlen headersdatalen headerslen +hexencoded hh hhmmss hmac @@ -68,8 +69,8 @@ ifndef inc ingroup inputlen -ipad iot +ipad iso jan january @@ -155,8 +156,6 @@ rande readloc regionlen rfc -trimmedlen -trimmedlength sdk sec secretaccesskey @@ -183,9 +182,11 @@ sts subfolder sublicense thu +tm trimmable +trimmedlen +trimmedlength trimmedlength -tm tue txt un @@ -198,4 +199,4 @@ valuelen xor xy yyyy -yyyymmdd +yyyymmdd \ No newline at end of file diff --git a/source/sigv4.c b/source/sigv4.c index 8171c985..9987cd27 100644 --- a/source/sigv4.c +++ b/source/sigv4.c @@ -291,8 +291,9 @@ static SigV4Status_t generateCanonicalRequestUntilHeaders( const SigV4Parameters * On output, this will be filled with the length of the Authorization header, if * operation is successful. * - * @return #SigV4InsufficientMemory if the length of the canonical request output - * buffer cannot fit the actual request before the headers, #SigV4Success otherwise. + * @return #SigV4InsufficientMemory if the length of the authorization buffer, @p pAuthBuf + * is insufficient to store the entire authorization header value (i.e. Prefix + HexEncoded Signature); + * otherwise #SigV4Success. */ static SigV4Status_t generateAuthorizationValuePrefix( const SigV4Parameters_t * pParams, const char * pAlgorithm, @@ -391,8 +392,8 @@ static int32_t hmacFinal( HmacContext_t * pHmacContext, size_t macLen ); /** - * @brief Generate the complete HMAC digest given a key and value, then write - * the digest in some output buffer. + * @brief Generates the complete HMAC digest given a key and value, then write + * the digest in the provided output buffer. * * @param[in] pHmacContext The context used for the current HMAC calculation. * @param[in] pKey The key passed as input to the HMAC function. @@ -415,8 +416,8 @@ static int32_t completeHmac( HmacContext_t * pHmacContext, const SigV4CryptoInterface_t * pCryptoInterface ); /** - * @brief Generate the complete hash of an input string, then write - * the digest in some output buffer. + * @brief Generates the complete hash of an input string, then write + * the digest in the provided output buffer. * @note Unlike #completeHashAndHexEncode, this function will not * encode the hash and will simply output the bytes written by the * hash function. @@ -1209,7 +1210,7 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa pBufLoc = pCanonicalURI; bufferLen = *canonicalURILen; - while( ( i++ < uriLen ) && *pUriLoc && ( returnStatus == SigV4Success ) ) + while( ( i++ < uriLen ) && ( returnStatus == SigV4Success ) ) { if( doubleEncodeEquals && ( *pUriLoc == '=' ) ) { @@ -1662,6 +1663,10 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa size_t keyLen, CanonicalContext_t * pCanonicalRequest ) { + assert( pKey != NULL ); + assert( keyLen > 0U ); + assert( ( pCanonicalRequest != NULL ) && ( pCanonicalRequest->pQueryLoc != NULL ) ); + pCanonicalRequest->pQueryLoc[ currentParameter ].key.pData = pKey; pCanonicalRequest->pQueryLoc[ currentParameter ].key.dataLen = keyLen; } @@ -1673,19 +1678,26 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa size_t valueLen, CanonicalContext_t * pCanonicalRequest ) { + assert( ( pCanonicalRequest != NULL ) && ( pCanonicalRequest->pQueryLoc != NULL ) ); + pCanonicalRequest->pQueryLoc[ currentParameter ].value.pData = pValue; pCanonicalRequest->pQueryLoc[ currentParameter ].value.dataLen = valueLen; } /*-----------------------------------------------------------*/ - static void setQueryStringFieldsAndValues( const char * pQuery, - size_t queryLen, - size_t * pNumberOfParameters, - CanonicalContext_t * pCanonicalRequest ) + static SigV4Status_t setQueryStringFieldsAndValues( const char * pQuery, + size_t queryLen, + size_t * pNumberOfParameters, + CanonicalContext_t * pCanonicalRequest ) { size_t currentParameter = 0U, i = 0U, startOfFieldOrValue = 0U; - uint8_t fieldHasValue = 0U; + bool fieldHasValue = false; + SigV4Status_t returnStatus = SigV4Success; + + assert( pNumberOfParameters != NULL ); + assert( pCanonicalRequest != NULL ); + assert( pCanonicalRequest->pQueryLoc != NULL ); /* Note: Constness of the query string is casted out here, taking care not to modify * its contents in any way. */ @@ -1695,9 +1707,10 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa { if( ( pQuery[ i ] == '=' ) && !fieldHasValue ) { + /* Store information about Query Parameter Key in the canonical context. This query parameter has an associated value. */ setQueryParameterKey( currentParameter, &pQuery[ startOfFieldOrValue ], i - startOfFieldOrValue, pCanonicalRequest ); startOfFieldOrValue = i + 1U; - fieldHasValue = 1U; + fieldHasValue = true; } else if( ( i == queryLen - 1U ) || ( ( pQuery[ i ] == '&' ) && ( i != 0U ) ) ) { @@ -1714,9 +1727,12 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa } else if( !fieldHasValue ) { + /* Store information about Query Parameter Key in the canonical context. This query parameter does not have an associated value. */ setQueryParameterKey( currentParameter, &pQuery[ startOfFieldOrValue ], i - startOfFieldOrValue, pCanonicalRequest ); + /* The previous field did not have a value set for it, so set its value to NULL. */ setQueryParameterValue( currentParameter, NULL, 0U, pCanonicalRequest ); + startOfFieldOrValue = i + 1U; currentParameter++; } @@ -1724,7 +1740,7 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa { /* End of value reached, so store a pointer to the previously set value. */ setQueryParameterValue( currentParameter, &pQuery[ startOfFieldOrValue ], i - startOfFieldOrValue, pCanonicalRequest ); - fieldHasValue = 0U; + fieldHasValue = false; startOfFieldOrValue = i + 1U; currentParameter++; } @@ -1736,43 +1752,57 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa if( currentParameter > SIGV4_MAX_QUERY_PAIR_COUNT ) { + returnStatus = SigV4MaxQueryPairCountExceeded; + LogError( ( "Failed to parse query string: Number of query parameters exceeds max threshold defined in config. " + "SIGV4_MAX_QUERY_PAIR_COUNT=%lu", SIGV4_MAX_QUERY_PAIR_COUNT ) ); break; } } *pNumberOfParameters = currentParameter; + + return returnStatus; } - static SigV4Status_t writeValueInCanonicalizedQueryString( char ** pBufCur, + static SigV4Status_t writeValueInCanonicalizedQueryString( char * pBufCur, + size_t bufferLen, const char * pValue, size_t valueLen, - size_t * pEncodedLen, - size_t * pRemainingLen ) + size_t * pEncodedLen ) { SigV4Status_t returnStatus = SigV4Success; + char * pBufCurLoc = pBufCur; + size_t bytesWritten = 0U; - if( *pEncodedLen < 1U ) + assert( pBufCur != NULL ); + assert( pEncodedLen != NULL ); + + /* Check that there is space at least for the equals to character. */ + if( bufferLen < 1U ) { returnStatus = SigV4InsufficientMemory; } else { - **pBufCur = '='; - ++( *pBufCur ); - *pRemainingLen -= 1U; - *pEncodedLen = *pRemainingLen; - returnStatus = encodeURI( pValue, - valueLen, - *pBufCur, - pEncodedLen, - true, - true ); + *pBufCur = '='; + bytesWritten = bufferLen - 1U; + + /* Query parameter values can be empty; + * thus encode value only if it is non-empty. */ + if( pValue != NULL ) + { + returnStatus = encodeURI( pValue, + valueLen, + pBufCur + 1U, + &bytesWritten, + true, + true ); + } } if( returnStatus == SigV4Success ) { - *pBufCur += *pEncodedLen; - *pRemainingLen -= *pEncodedLen; + *pEncodedLen = ( 1U + bytesWritten ); } return returnStatus; @@ -1809,17 +1839,18 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa { pBufLoc += encodedLen; remainingLen -= encodedLen; - encodedLen = remainingLen; /* An empty value corresponds to an empty string. */ if( pCanonicalRequest->pQueryLoc[ i ].value.dataLen > 0U ) { assert( pCanonicalRequest->pQueryLoc[ i ].value.pData != NULL ); - returnStatus = writeValueInCanonicalizedQueryString( &pBufLoc, + returnStatus = writeValueInCanonicalizedQueryString( pBufLoc, + remainingLen, pCanonicalRequest->pQueryLoc[ i ].value.pData, pCanonicalRequest->pQueryLoc[ i ].value.dataLen, - &encodedLen, - &remainingLen ); + &encodedLen ); + pBufLoc += encodedLen; + remainingLen -= encodedLen; } } @@ -1863,13 +1894,7 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa assert( pCanonicalContext != NULL ); assert( pCanonicalContext->pBufCur != NULL ); - setQueryStringFieldsAndValues( pQuery, queryLen, &numberOfParameters, pCanonicalContext ); - - if( numberOfParameters > SIGV4_MAX_QUERY_PAIR_COUNT ) - { - LogError( ( "Number of parameters in the query string has exceeded the maximum of %u.", SIGV4_MAX_QUERY_PAIR_COUNT ) ); - returnStatus = SigV4MaxQueryPairCountExceeded; - } + returnStatus == setQueryStringFieldsAndValues( pQuery, queryLen, &numberOfParameters, pCanonicalContext ); if( returnStatus == SigV4Success ) { From 2380cab027ea670669c6254e9373c39bcf8f7a06 Mon Sep 17 00:00:00 2001 From: Archit Aggarwal Date: Tue, 10 Aug 2021 00:58:36 +0000 Subject: [PATCH 12/12] More comment changes --- source/include/sigv4.h | 5 ++++- source/sigv4.c | 13 +++++++++++++ 2 files changed, 17 insertions(+), 1 deletion(-) diff --git a/source/include/sigv4.h b/source/include/sigv4.h index 2bcbb543..208ef05b 100644 --- a/source/include/sigv4.h +++ b/source/include/sigv4.h @@ -169,7 +169,10 @@ typedef enum SigV4Status /** * @brief The maximum number of query parameters was exceeded while parsing - * the query string input parameter. + * the query string passed to the library. + * The maximum number of supported query parameters can be configured + * with the SIGV4_MAX_QUERY_PAIR_COUNT macro in the library config file + * passed by the application. * * Functions that may return this value: * - #SigV4_GenerateHTTPAuthorization diff --git a/source/sigv4.c b/source/sigv4.c index 9987cd27..1fc3e8b4 100644 --- a/source/sigv4.c +++ b/source/sigv4.c @@ -1159,6 +1159,8 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa return hexChar; } +/*-----------------------------------------------------------*/ + static size_t writeHexCodeOfChar( char * pBuffer, size_t bufferLen, char code ) @@ -1173,6 +1175,8 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa return URI_ENCODED_SPECIAL_CHAR_SIZE; } +/*-----------------------------------------------------------*/ + static size_t writeDoubleEncodedEquals( char * pBuffer, size_t bufferLen ) { @@ -1188,6 +1192,8 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa return URI_DOUBLE_ENCODED_EQUALS_CHAR_SIZE; } +/*-----------------------------------------------------------*/ + static SigV4Status_t encodeURI( const char * pUri, size_t uriLen, char * pCanonicalURI, @@ -1334,6 +1340,7 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa } /*-----------------------------------------------------------*/ + static bool isTrimmableSpace( const char * value, size_t index, size_t valLen, @@ -1764,6 +1771,8 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa return returnStatus; } +/*-----------------------------------------------------------*/ + static SigV4Status_t writeValueInCanonicalizedQueryString( char * pBufCur, size_t bufferLen, const char * pValue, @@ -1808,6 +1817,8 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa return returnStatus; } +/*-----------------------------------------------------------*/ + static SigV4Status_t writeCanonicalQueryParameters( CanonicalContext_t * pCanonicalRequest, size_t numberOfParameters ) { @@ -1884,6 +1895,8 @@ static SigV4Status_t generateCredentialScope( const SigV4Parameters_t * pSigV4Pa return returnStatus; } +/*-----------------------------------------------------------*/ + static SigV4Status_t generateCanonicalQuery( const char * pQuery, size_t queryLen, CanonicalContext_t * pCanonicalContext )