diff --git a/.gitmodules b/.gitmodules index 4cd8b20a..0019fecc 100644 --- a/.gitmodules +++ b/.gitmodules @@ -3,7 +3,7 @@ url = https://github.com/ThrowTheSwitch/CMock.git [submodule "test/cbmc/aws-templates-for-cbmc-proofs"] path = test/cbmc/aws-templates-for-cbmc-proofs - url = git@github.com:awslabs/aws-templates-for-cbmc-proofs.git + url = https://github.com/awslabs/aws-templates-for-cbmc-proofs.git [submodule "test/cbmc/litani"] path = test/cbmc/litani url = https://github.com/awslabs/aws-build-accumulator diff --git a/README.md b/README.md index f1bc1f90..52bdc1f2 100644 --- a/README.md +++ b/README.md @@ -1,7 +1,13 @@ -# AWS IoT SigV4 Client library +# AWS IoT SigV4 Utility Library + +The AWS IoT SigV4 Library is a standalone utility for generating a signature and authorization header according to the specifications of the [Signature Version 4](https://docs.aws.amazon.com/general/latest/gr/signature-version-4.html) signing process. This utility is an optional addition to applications sending direct HTTP requests to AWS services requiring SigV4 authentication. ## Building the SigV4 Library +The [source](https://github.com/aws/SigV4-for-AWS-IoT-embedded-sdk/tree/main/source) directory contains all of the source files required to build the SigV4 Library. The [source/include](https://github.com/aws/SigV4-for-AWS-IoT-embedded-sdk/tree/main/source/include) folder should be added to the compiler's include path. + +To use CMake, please refer to the [sigV4FilePaths.cmake](https://github.com/aws/SigV4-for-AWS-IoT-embedded-sdk/blob/main/sigv4FilePaths.cmake) file, which contains the relevant information regarding source files and header include paths required to build this library. + ## Building Unit Tests ### Platform Prerequisites diff --git a/source/sigv4.c b/source/sigv4.c index 8f108c2d..c653ebe2 100644 --- a/source/sigv4.c +++ b/source/sigv4.c @@ -27,12 +27,81 @@ #include #include +#include #include "sigv4.h" #include "sigv4_internal.h" /*-----------------------------------------------------------*/ +#if ( SIGV4_USE_CANONICAL_SUPPORT == 1 ) + +/** + * @brief Normalize a URI string according to RFC 3986 and fill destination + * buffer with the formatted string. + * + * @param[in] pURI The URI string to encode. + * @param[in] uriLen Length of pURI. + * @param[out] pCanonicalURI The resulting canonicalized URI. + * @param[in, out] canonicalURILen input: the length of pCanonicalURI, + * output: the length of the generated canonical URI. + * @param[in] encodeSlash Option to indicate if slashes should be encoded. + * @param[in] nullTerminate Option to indicate if a null character should be + * added to the end of the canonical URI. + */ + static void encodeURI( const char * pURI, + size_t uriLen, + char * pCanonicalURI, + size_t * canonicalURILen, + bool encodeSlash, + bool nullTerminate ); + +/** + * @brief Canonicalize the full URI path. The input URI starts after the + * HTTP host and ends at the question mark character ("?") that begins the + * query string parameters (if any). Example: folder/subfolder/item.txt" + * + * @param[in] pUri HTTP request URI, also known that the request absolute + * path. + * @param[in] uriLen Length of pURI. + * @param[in] encodeOnce Service-dependent option to indicate whether + * encoding should be done once or twice. For example, S3 requires that the + * URI is encoded only once, while other services encode twice. + * @param[in, out] canonicalRequest Struct to maintain intermediary buffer + * and state of canonicalization. + */ + static void generateCanonicalURI( const char * pURI, + size_t uriLen, + bool encodeOnce, + canonicalContext_t * canonicalRequest ); + +/** + * @brief Canonicalize the query string HTTP URL, beginning (but not + * including) at the "?" character. Does not include "/". + * + * @param[in] pQuery HTTP request query. + * @param[in] queryLen Length of pQuery. + * @param[in, out] canonicalRequest Struct to maintain intermediary buffer + * and state of canonicalization. + */ + static void generateCanonicalQuery( const char * pQuery, + size_t queryLen, + canonicalContext_t * canonicalRequest ); + +/** + * @brief Canonicalize the HTTP request headers. + * + * @param[in] pHeaders The raw HTTP headers. + * @param[in] headerLen Length of pHeaders. + * @param[in, out] canonicalRequest Struct to maintain intermediary buffer + * and state of canonicalization. + */ + static void generateCanonicalHeaders( const char * pHeaders, + size_t headerLen, + canonicalContext_t * canonicalRequest ); + +#endif /* #if (SIGV4_USE_CANONICAL_SUPPORT == 1) */ + /** * @brief Converts an integer value to its ASCII representation, and stores the * result in the provided buffer. @@ -98,7 +167,6 @@ static SigV4Status_t scanValue( const char * pDate, size_t lenToRead, SigV4DateTime_t * pDateElements ); - /** * @brief Parses date according to format string parameter, and populates date * representation struct SigV4DateTime_t with its elements. @@ -122,6 +190,15 @@ static SigV4Status_t parseDate( const char * pDate, 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, #SigV4InvalidParameters otherwise. + */ +static SigV4Status_t verifySigV4Parameters( const SigV4Parameters_t * pParams ); + /*-----------------------------------------------------------*/ static void intToAscii( int32_t value, @@ -147,6 +224,7 @@ static void intToAscii( int32_t value, } /*-----------------------------------------------------------*/ + static SigV4Status_t checkLeap( const SigV4DateTime_t * pDateElements ) { SigV4Status_t returnStatus = SigV4ISOFormattingError; @@ -437,6 +515,276 @@ static SigV4Status_t parseDate( const char * pDate, /*-----------------------------------------------------------*/ +/* Converts a hex character to its integer value */ +static char hexToInt( char pHex ) +{ + uint8_t byte = pHex; + + if( ( byte >= '0' ) && ( byte <= '9' ) ) + { + byte = byte - '0'; + } + else if( ( byte >= 'a' ) && ( byte <= 'f' ) ) + { + byte = byte - 'a' + 10; + } + else if( ( byte >= 'A' ) && ( byte <= 'F' ) ) + { + byte = byte - 'A' + 10; + } + + return byte & 0xF; +} + +/* Converts an integer value to its hex character */ +static char intToHex( char pInt ) +{ + static char hex[] = "0123456789abcdef"; + + return hex[ pInt & 15 ]; +} + +/*-----------------------------------------------------------*/ + +#if ( SIGV4_USE_CANONICAL_SUPPORT == 1 ) + + static void encodeURI( const char * pURI, + size_t uriLen, + char * pCanonicalURI, + size_t * canonicalURILen, + bool encodeSlash, + bool nullTerminate ) + { + const char * pURILoc = pURI; + char * pBufLoc = pCanonicalURI; + size_t index = 0U; + + assert( pURI != NULL ); + assert( pCanonicalURI != NULL ); + assert( canonicalURILen != NULL ); + assert( *canonicalURILen > 0U ); + + while( index < uriLen && *pURILoc ) + { + if( isalnum( *pURILoc ) || ( *pURILoc == '-' ) || ( *pURILoc == '_' ) || ( *pURILoc == '.' ) || ( *pURILoc == '~' ) ) + { + *pBufLoc++ = *pURILoc; + index++; + } + else if( ( *pURILoc == '/' ) && !encodeSlash ) + { + *pBufLoc++ = *pURILoc; + index++; + } + else + { + *pBufLoc++ = '%'; + *pBufLoc++ = intToHex( *pURILoc >> 4 ); + *pBufLoc++ = intToHex( *pURILoc & 15 ); + + index += 3; + } + + pURILoc++; + } + + if( nullTerminate ) + { + *pBufLoc++ = '\0'; + index++; + } + + *canonicalURILen = index; + } + + static void generateCanonicalURI( const char * pURI, + size_t uriLen, + bool encodeOnce, + canonicalContext_t * canonicalRequest ) + { + size_t encodedLen, remainingLen = canonicalRequest->bufRemaining; + char * pBufLoc = canonicalRequest->pBufCur; + + assert( pURI != NULL ); + assert( canonicalRequest != NULL ); + + encodeURI( pURI, uriLen, pBufLoc, &encodedLen, false, true ); + + remainingLen -= encodedLen; + + if( !encodeOnce ) + { + encodeURI( pBufLoc, encodedLen, pBufLoc + encodedLen, &remainingLen, false, true ); + memmove( canonicalRequest->pBufCur + encodedLen, canonicalRequest->pBufCur, remainingLen ); + } + + canonicalRequest->pBufCur += remainingLen; + *( canonicalRequest->pBufCur++ ) = '\n'; + + canonicalRequest->bufRemaining -= remainingLen + 1; + } + + static void generateCanonicalQuery( const char * pQuery, + size_t queryLen, + canonicalContext_t * canonicalRequest ) + { + size_t index = 0U; + size_t i = 0U; + size_t remainingLen = canonicalRequest->bufRemaining; + char * pBufLoc = canonicalRequest->pBufCur; + char * tokenQueries, * tokenParams; + + assert( pQuery != NULL ); + assert( canonicalRequest != NULL ); + + tokenQueries = strtok( ( char * ) pQuery, "&" ); + + while( tokenQueries != NULL ) + { + canonicalRequest->pQueryLoc[ index ] = &tokenQueries[ 0 ]; + tokenQueries = strtok( NULL, "&" ); + + index++; + } + + qsort( canonicalRequest->pQueryLoc, index, sizeof( char * ), qSortCompare ); + + for( i = 0U; i < index; i++ ) + { + tokenParams = strtok( canonicalRequest->pQueryLoc[ i ], "=" ); + + if( tokenParams != NULL ) + { + encodeURI( tokenParams, strlen( tokenParams ), pBufLoc, &remainingLen, true, false ); + pBufLoc += remainingLen; + *pBufLoc = '='; /* Overwrite null character. */ + + canonicalRequest->bufRemaining -= remainingLen; + remainingLen = canonicalRequest->bufRemaining; + } + + tokenParams = strtok( NULL, "=" ); + + if( tokenParams != NULL ) + { + encodeURI( tokenParams, strlen( tokenParams ), pBufLoc, &remainingLen, true, false ); + pBufLoc += remainingLen; + + canonicalRequest->bufRemaining -= remainingLen; + remainingLen = canonicalRequest->bufRemaining; + } + + if( index != i + 1 ) + { + *pBufLoc++ = '&'; + *pBufLoc++ = '\0'; + *pBufLoc++ = '\n'; + canonicalRequest->bufRemaining -= 3; + } + } + + canonicalRequest->pBufCur = pBufLoc; + } + +#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 ) + { + LogError( ( "Parameter check failed: pParams->pHttpParameters is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + else if( pParams->pHttpParameters->pHttpMethod == NULL ) + { + LogError( ( "Parameter check failed: pParams->pHttpParameters->pHttpMethod is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + else if( pParams->pHttpParameters->pPath == NULL ) + { + LogError( ( "Parameter check failed: pParams->pHttpParameters->pPath is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + else if( pParams->pHttpParameters->pQuery == NULL ) + { + LogError( ( "Parameter check failed: pParams->pHttpParameters->pQuery is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + else if( pParams->pHttpParameters->pHeaders == NULL ) + { + LogError( ( "Parameter check failed: pParams->pHttpParameters->pHeaders is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + else if( pParams->pHttpParameters->pPayload == NULL ) + { + LogError( ( "Parameter check failed: pParams->pHttpParameters->pPayload is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + + return returnStatus; +} + +/*-----------------------------------------------------------*/ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, size_t dateLen, @@ -513,3 +861,54 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate, return returnStatus; } + +SigV4Status_t SigV4_GenerateHTTPAuthorization( const SigV4Parameters_t * pParams, + char * pAuthBuf, + size_t * authBufLen, + char ** pSignature, + size_t * signatureLen ) +{ + SigV4Status_t returnStatus = verifySigV4Parameters( pParams ); + + if( returnStatus == SigV4InvalidParameter ) + { + LogError( ( "Parameter check failed: pParams is invalid, contains NULL member(s)." ) ) + } + else if( pAuthBuf == NULL ) + { + LogError( ( "Parameter check failed: pAuthBuf is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + else if( authBufLen == NULL ) + { + LogError( ( "Parameter check failed: authBufLen is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + else if( *authBufLen == 0U ) + { + LogError( ( "Parameter check failed: authBufLen[0] must be greater than 0." ) ); + returnStatus = SigV4InvalidParameter; + } + else if( pSignature == NULL ) + { + LogError( ( "Parameter check failed: pSignature is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + else if( signatureLen == NULL ) + { + LogError( ( "Parameter check failed: signatureLen is NULL." ) ); + returnStatus = SigV4InvalidParameter; + } + + if( returnStatus == SigV4Success ) + { + canonicalContext_t encodingContext = { 0 }; + + generateCanonicalURI( pParams->pPath, + pathLen, + true, + &encodingContext ); + } + + return returnStatus; +} diff --git a/test/cbmc/aws-templates-for-cbmc-proofs b/test/cbmc/aws-templates-for-cbmc-proofs index 27fdd8ee..8f2dafc1 160000 --- a/test/cbmc/aws-templates-for-cbmc-proofs +++ b/test/cbmc/aws-templates-for-cbmc-proofs @@ -1 +1 @@ -Subproject commit 27fdd8ee01705d917bcf0a3c37ff687779a87bda +Subproject commit 8f2dafc1db539ca97b6f4b8533f069e176c11b1c