diff --git a/docs/doxygen/pages.dox b/docs/doxygen/pages.dox index b73f53de..2dbe8854 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 94699b2e..d284acb0 100644 --- a/lexicon.txt +++ b/lexicon.txt @@ -1,3 +1,4 @@ +<<<<<<< HEAD accesskeyid accesskeylen @@ -9,24 +10,52 @@ api apr ascii authbuflen +======= +accesskeyid +accesskeyidlen +addtodate +addtogroup +aggregator +algorithmlen +amz +apr +ascii +auth +authbuflen +authorizaton +>>>>>>> bf7559c (SigV4_GenerateHTTPAuthorization Implementation) aws br bufferlen bufremaining canonicalrequest canonicalurilen +<<<<<<< HEAD cbmc chunked com config const +======= +chunked +com +config +completehashandhexencode +const +constness +>>>>>>> bf7559c (SigV4_GenerateHTTPAuthorization Implementation) copydoc datalen datelen dd deconstructed defgroup +<<<<<<< HEAD encodeonce +======= +doubleencodeequals +encodetwice +>>>>>>> bf7559c (SigV4_GenerateHTTPAuthorization Implementation) encodeslash endif enums @@ -37,17 +66,31 @@ formatlen github gmt gr +<<<<<<< HEAD +======= +hashblocklen +hashdigestlen +>>>>>>> bf7559c (SigV4_GenerateHTTPAuthorization Implementation) hashfinal hashinit hashupdate headercount headerindex headerlen +<<<<<<< HEAD headersdatalen +======= +>>>>>>> bf7559c (SigV4_GenerateHTTPAuthorization Implementation) headerslen hh hhmmss hmac +<<<<<<< HEAD +======= +hmacdata +hmacfinal +hmackey +>>>>>>> bf7559c (SigV4_GenerateHTTPAuthorization Implementation) html http httpmethodlen @@ -57,13 +100,27 @@ ifndef inc ingroup inputlen +<<<<<<< HEAD +======= +ipad +>>>>>>> bf7559c (SigV4_GenerateHTTPAuthorization Implementation) iot iso jan january +<<<<<<< HEAD +leninput +lentoread +lv +======= +keylen +ksecret leninput lentoread +linelen lv +maclen +>>>>>>> bf7559c (SigV4_GenerateHTTPAuthorization Implementation) mainpage min misra @@ -72,20 +129,41 @@ mon monthsperday noninfringement nullterminate +<<<<<<< HEAD +======= +opad +>>>>>>> bf7559c (SigV4_GenerateHTTPAuthorization Implementation) ored org outputlen paccesskeyid +<<<<<<< HEAD param pathlen pauthbuf +======= +palgorithm +param +pathlen +pauthbuf +pauthprefixlen +>>>>>>> bf7559c (SigV4_GenerateHTTPAuthorization Implementation) payloadlen pbufcur pbuffer pbufloc pbufprocessing +<<<<<<< HEAD +pcanonicaluri +pcredscope +======= +pbufstart +pbytesremaining +pcanonicalcontext pcanonicaluri pcredscope +pcryptointerface +>>>>>>> bf7559c (SigV4_GenerateHTTPAuthorization Implementation) pdata pdate pdateelements @@ -96,13 +174,27 @@ phashcontext pheaders pheadersloc phexoutput +<<<<<<< HEAD phttpmethod pinput pinputstr +======= +phmaccontext +phttpmethod +pinput +pinputstr +pkey +pline +pmac +>>>>>>> bf7559c (SigV4_GenerateHTTPAuthorization Implementation) posix poutput poutputexpected poutputleapexpected +<<<<<<< HEAD +======= +poutputlen +>>>>>>> bf7559c (SigV4_GenerateHTTPAuthorization Implementation) pparams ppath ppayload @@ -114,6 +206,12 @@ psecretaccesskey psecuritytoken pservice psignature +<<<<<<< HEAD +======= +psignedheaders +psignedheaderslen +psigningkey +>>>>>>> bf7559c (SigV4_GenerateHTTPAuthorization Implementation) ptestformatfailure puri qsort @@ -122,6 +220,11 @@ rande readloc regionlen rfc +<<<<<<< HEAD +======= +trimmedlen +trimmedlength +>>>>>>> bf7559c (SigV4_GenerateHTTPAuthorization Implementation) sdk sec secretaccesskey @@ -132,21 +235,37 @@ sep servicelen sha signaturelen +<<<<<<< HEAD +======= +signedheaders +signedheaderslen +signingkey +>>>>>>> bf7559c (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 @@ -155,5 +274,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 09d8c297..a56f3037 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 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. */ + +/* 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 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 SPACE_CHAR ' ' /**< A linefeed character used to build the Authorization header value. */ +#define SPACE_CHAR_LEN 1U /**< The length of #SPACE_CHAR. */ -#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 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 ISO_DATE_SCOPE_LEN 8U /**< Length of date substring used in credential scope. */ +#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 @@ -98,12 +160,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_MAX_BLOCK_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 0c377db9..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 int32_t 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 int32_t 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,131 +1060,284 @@ 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 void generateCanonicalURI( const char * pURI, - size_t uriLen, - bool encodeOnce, - CanonicalContext_t * canonicalRequest ) + static char toUpperHexChar( char value ) { - char * pBufLoc = NULL; - size_t encodedLen, remainingLen = 0U; - - assert( pURI != NULL ); - assert( canonicalRequest != NULL ); - assert( canonicalRequest->pBufCur != NULL ); + char hexChar; - pBufLoc = ( char * ) canonicalRequest->pBufCur; - encodedLen, remainingLen = canonicalRequest->bufRemaining; - encodeURI( pURI, uriLen, pBufLoc, &encodedLen, false, true ); + assert( value < 16 ); - remainingLen -= encodedLen; - - if( !encodeOnce ) + if( value < 10 ) + { + hexChar = '0' + value; + } + else { - encodeURI( pBufLoc, encodedLen, pBufLoc + encodedLen, &remainingLen, false, true ); - memmove( canonicalRequest->pBufCur + encodedLen, canonicalRequest->pBufCur, remainingLen ); + hexChar = ( 'A' + value ) - 10; } - canonicalRequest->pBufCur += remainingLen; - *( canonicalRequest->pBufCur++ ) = '\n'; + return hexChar; + } - canonicalRequest->bufRemaining -= remainingLen + 1; + static size_t writeHexCodeOfChar( char ** pBuffer, + char code ) + { + **pBuffer = '%'; + ++( *pBuffer ); + **pBuffer = toUpperHexChar( code >> 4 ); + ++( *pBuffer ); + **pBuffer = toUpperHexChar( code & 0x0F ); + ++( *pBuffer ); + + return 3; } -/*-----------------------------------------------------------*/ + static size_t writeDoubleEncodedEquals( char ** pBuffer ) + { + **pBuffer = '%'; + ++( *pBuffer ); + **pBuffer = '2'; + ++( *pBuffer ); + **pBuffer = '5'; + ++( *pBuffer ); + **pBuffer = '3'; + ++( *pBuffer ); + **pBuffer = 'D'; + ++( *pBuffer ); + + return 5; + } - static bool isTrimmableSpace( const char * value, - size_t index, - size_t valLen, - size_t trimmedLength ) + static SigV4Status_t encodeURI( const char * pUri, + size_t uriLen, + char * pCanonicalURI, + size_t * canonicalURILen, + bool encodeSlash, + bool doubleEncodeEquals ) { - bool ret = false; + const char * pUriLoc = NULL; + char * pBufLoc = NULL; + size_t i = 0U, bytesConsumed = 0U; + SigV4Status_t returnStatus = SigV4Success; - assert( ( value != NULL ) && ( index < valLen ) ); + assert( pUri != NULL ); + assert( pCanonicalURI != NULL ); + assert( canonicalURILen != NULL ); + assert( *canonicalURILen > 0U ); - /* Only trim spaces. */ - if( isspace( value[ index ] ) ) + pUriLoc = pUri; + pBufLoc = pCanonicalURI; + + while( i++ < uriLen && *pUriLoc && returnStatus == SigV4Success ) { - /* The last character is a trailing space. */ - if( ( index + 1 ) == valLen ) + if( doubleEncodeEquals && ( *pUriLoc == '=' ) ) { - ret = true; + if( ( bytesConsumed > SIZE_MAX - 5U ) || ( bytesConsumed + 5U > *canonicalURILen ) ) + { + returnStatus = SigV4InsufficientMemory; + LOG_INSUFFICIENT_MEMORY_ERROR( "encode the URI", + bytesConsumed + 5U - *canonicalURILen ); + } + else + { + bytesConsumed += writeDoubleEncodedEquals( &pBufLoc ); + } } - /* Trim if the next character is also a space. */ - else if( isspace( value[ index + 1 ] ) ) + else if( isalnum( *pUriLoc ) || ( *pUriLoc == '-' ) || ( *pUriLoc == '_' ) || ( *pUriLoc == '.' ) || ( *pUriLoc == '~' ) || + ( ( *pUriLoc == '/' ) && !encodeSlash ) ) { - ret = true; + *pBufLoc = *pUriLoc; + ++pBufLoc; + ++bytesConsumed; } - /* It is a leading space if no characters have been written yet. */ - else if( trimmedLength == 0U ) + else { - ret = true; - } - } - + 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; } /*-----------------------------------------------------------*/ - 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 ) { SigV4Status_t status = SigV4Success; size_t index = 0; @@ -1023,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; @@ -1033,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 ); @@ -1049,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'; } @@ -1100,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; @@ -1172,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; @@ -1194,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--; } @@ -1217,74 +1609,238 @@ 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 generateCanonicalQuery( const char * pQuery, - size_t queryLen, - CanonicalContext_t * canonicalRequest ) +/*-----------------------------------------------------------*/ + + static void setQueryStringFieldsAndValues( const char * pQuery, + size_t queryLen, + size_t * pNumberOfParameters, + CanonicalContext_t * pCanonicalRequest ) { - size_t index, remainingLen, i = 0U; - char * pBufLoc, * tokenQueries, * tokenParams = NULL; + size_t currentParameter = 0U, i = 0U, startOfFieldOrValue = 0U; + uint8_t fieldHasValue = 0U; - assert( pQuery != NULL ); - assert( queryLen > 0U ); - assert( canonicalRequest != NULL ); - assert( canonicalRequest->pBufCur != NULL ); + /* 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; + } + } - remainingLen = canonicalRequest->bufRemaining; - pBufLoc = ( char * ) canonicalRequest->pBufCur; + *pNumberOfParameters = currentParameter; + } - tokenQueries = strtok( ( char * ) pQuery, "&" ); + static SigV4Status_t writeValueInCanonicalizedQueryString( char ** pBufCur, + char * pValue, + size_t valueLen, + size_t * pEncodedLen, + size_t * pRemainingLen ) + { + SigV4Status_t returnStatus = SigV4Success; - while( tokenQueries != NULL ) + if( *pEncodedLen < 1U ) + { + returnStatus = SigV4InsufficientMemory; + } + else { - canonicalRequest->pQueryLoc[ index ] = &tokenQueries[ 0 ]; - tokenQueries = strtok( NULL, "&" ); + **pBufCur = '='; + ++( *pBufCur ); + *pRemainingLen -= 1U; + *pEncodedLen = *pRemainingLen; + returnStatus = encodeURI( pValue, + valueLen, + *pBufCur, + pEncodedLen, + true, + true ); + } - index++; + if( returnStatus == SigV4Success ) + { + *pBufCur += *pEncodedLen; + *pRemainingLen -= *pEncodedLen; } - qsort( canonicalRequest->pQueryLoc, index, sizeof( char * ), cmpKeyValue ); + return returnStatus; + } - for( i = 0U; i < index; i++ ) - { - tokenParams = strtok( canonicalRequest->pQueryLoc[ i ], "=" ); + 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; - if( tokenParams != NULL ) + 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 ) { - encodeURI( tokenParams, strlen( tokenParams ), pBufLoc, &remainingLen, true, false ); - pBufLoc += remainingLen; - *pBufLoc = '='; /* Overwrite null character. */ + pBufLoc += encodedLen; + remainingLen -= encodedLen; + encodedLen = remainingLen; - canonicalRequest->bufRemaining -= remainingLen; - remainingLen = canonicalRequest->bufRemaining; + /* 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 ); + } } - tokenParams = strtok( NULL, "=" ); - - if( tokenParams != NULL ) + if( ( remainingLen < 1U ) && ( numberOfParameters != i + 1 ) ) { - encodeURI( tokenParams, strlen( tokenParams ), pBufLoc, &remainingLen, true, false ); - pBufLoc += remainingLen; - - canonicalRequest->bufRemaining -= remainingLen; - remainingLen = canonicalRequest->bufRemaining; + 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( index != i + 1 ) + if( returnStatus != SigV4Success ) + { + break; + } + else { - *( pBufLoc++ ) = '&'; - *( pBufLoc++ ) = '\0'; - *( pBufLoc++ ) = '\n'; - canonicalRequest->bufRemaining -= 3; + pCanonicalRequest->pBufCur = pBufLoc; + pCanonicalRequest->bufRemaining = remainingLen; } } - canonicalRequest->pBufCur = pBufLoc; + 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 ) */ @@ -1351,24 +1907,26 @@ static SigV4Status_t verifySigV4Parameters( const SigV4Parameters_t * pParams ) LogError( ( "Parameter check failed: pParams->pCryptoInterface->pHashContext is NULL." ) ); returnStatus = SigV4InvalidParameter; } - else if( pParams->pHttpParameters == NULL ) + else if( pParams->pCryptoInterface->hashBlockLen > SIGV4_HASH_MAX_BLOCK_LENGTH ) { - LogError( ( "Parameter check failed: pParams->pHttpParameters is NULL." ) ); + 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->pHttpParameters->pHttpMethod == NULL ) + else if( pParams->pCryptoInterface->hashDigestLen > SIGV4_HASH_MAX_DIGEST_LENGTH ) { - LogError( ( "Parameter check failed: pParams->pHttpParameters->pHttpMethod is NULL." ) ); + 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->pPath == NULL ) + else if( pParams->pHttpParameters == NULL ) { - LogError( ( "Parameter check failed: pParams->pHttpParameters->pPath is NULL." ) ); + LogError( ( "Parameter check failed: pParams->pHttpParameters is NULL." ) ); returnStatus = SigV4InvalidParameter; } - else if( pParams->pHttpParameters->pQuery == NULL ) + else if( pParams->pHttpParameters->pHttpMethod == NULL ) { - LogError( ( "Parameter check failed: pParams->pHttpParameters->pQuery is NULL." ) ); + LogError( ( "Parameter check failed: pParams->pHttpParameters->pHttpMethod is NULL." ) ); returnStatus = SigV4InvalidParameter; } else if( pParams->pHttpParameters->pHeaders == NULL ) @@ -1376,10 +1934,196 @@ static SigV4Status_t verifySigV4Parameters( const SigV4Parameters_t * pParams ) LogError( ( "Parameter check failed: pParams->pHttpParameters->pHeaders is NULL." ) ); returnStatus = SigV4InvalidParameter; } - else if( pParams->pHttpParameters->pPayload == NULL ) + + 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 ) { - LogError( ( "Parameter check failed: pParams->pHttpParameters->pPayload is NULL." ) ); - returnStatus = SigV4InvalidParameter; + 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; @@ -1387,6 +2131,489 @@ static SigV4Status_t verifySigV4Parameters( const SigV4Parameters_t * pParams ) /*-----------------------------------------------------------*/ +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, @@ -1462,3 +2689,117 @@ 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 = 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; +}