3294 lines
134 KiB
C
3294 lines
134 KiB
C
/*
|
|
* SigV4 Library v1.2.0
|
|
* Copyright (C) 2021 Amazon.com, Inc. or its affiliates. All Rights Reserved.
|
|
*
|
|
* SPDX-License-Identifier: MIT
|
|
*
|
|
* Permission is hereby granted, free of charge, to any person obtaining a copy of
|
|
* this software and associated documentation files (the "Software"), to deal in
|
|
* the Software without restriction, including without limitation the rights to
|
|
* use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of
|
|
* the Software, and to permit persons to whom the Software is furnished to do so,
|
|
* subject to the following conditions:
|
|
*
|
|
* The above copyright notice and this permission notice shall be included in all
|
|
* copies or substantial portions of the Software.
|
|
*
|
|
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS
|
|
* FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR
|
|
* COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER
|
|
* IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
|
|
* CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
*/
|
|
|
|
/**
|
|
* @file sigv4.c
|
|
* @brief Implements the user-facing functions in sigv4.h
|
|
*/
|
|
|
|
#include <assert.h>
|
|
#include <string.h>
|
|
#include <stdbool.h>
|
|
#include <stdio.h>
|
|
#include <stdlib.h>
|
|
|
|
#include "sigv4.h"
|
|
#include "sigv4_internal.h"
|
|
#include "sigv4_quicksort.h"
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
#if ( SIGV4_USE_CANONICAL_SUPPORT == 1 )
|
|
|
|
/**
|
|
* @brief Normalize a URI string according to RFC 3986 and fill destination
|
|
* buffer with the formatted string.
|
|
*
|
|
* @param[in] pUri The URI string to encode.
|
|
* @param[in] uriLen Length of pUri.
|
|
* @param[out] pCanonicalURI The resulting canonicalized URI.
|
|
* @param[in, out] canonicalURILen input: the length of pCanonicalURI,
|
|
* output: the length of the generated canonical URI.
|
|
* @param[in] encodeSlash Option to indicate if slashes should be encoded.
|
|
* @param[in] doubleEncodeEquals Option to indicate if equals should be double-encoded.
|
|
*/
|
|
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
|
|
* HTTP host and ends at the question mark character ("?") that begins the
|
|
* query string parameters (if any). Example: folder/subfolder/item.txt"
|
|
*
|
|
* @param[in] pUri HTTP request URI, also known that the request absolute
|
|
* path.
|
|
* @param[in] uriLen Length of pUri.
|
|
* @param[in] 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] pCanonicalRequest Struct to maintain intermediary buffer
|
|
* and state of canonicalization.
|
|
*/
|
|
static SigV4Status_t generateCanonicalURI( const char * pUri,
|
|
size_t uriLen,
|
|
bool encodeTwice,
|
|
CanonicalContext_t * pCanonicalRequest );
|
|
|
|
/**
|
|
* @brief Canonicalize the query string HTTP URL, beginning (but not
|
|
* including) at the "?" character. Does not include "/".
|
|
*
|
|
* @param[in] pQuery HTTP request query.
|
|
* @param[in] queryLen Length of pQuery.
|
|
* @param[in, out] pCanonicalContext Struct to maintain intermediary buffer
|
|
* and state of canonicalization.
|
|
*/
|
|
static SigV4Status_t generateCanonicalQuery( const char * pQuery,
|
|
size_t queryLen,
|
|
CanonicalContext_t * pCanonicalContext );
|
|
|
|
/**
|
|
* @brief Determine if a character can be written without needing URI encoding when generating Canonical Request.
|
|
*
|
|
* @param[in] c The character to evaluate.
|
|
* @param[in] encodeSlash Whether slashes may be encoded.
|
|
*
|
|
* @return `true` if the character does not need encoding, `false` if it does.
|
|
*/
|
|
static bool isAllowedChar( char c,
|
|
bool encodeSlash );
|
|
|
|
/**
|
|
* @brief Compare two SigV4 data structures lexicographically, without case-sensitivity.
|
|
*
|
|
* @param[in] pFirstVal SigV4 key value data structure to sort.
|
|
* @param[in] pSecondVal SigV4 key value data structure to sort.
|
|
*
|
|
* @return Returns a value less than 0 if @pFirstVal < @pSecondVal, or
|
|
* a value greater than 0 if @pSecondVal < @pFirstVal. 0 is never returned in
|
|
* order to provide stability to quickSort() calls.
|
|
*/
|
|
static int32_t cmpHeaderField( const void * pFirstVal,
|
|
const void * pSecondVal );
|
|
|
|
#endif /* #if (SIGV4_USE_CANONICAL_SUPPORT == 1) */
|
|
|
|
/**
|
|
* @brief Converts an integer value to its ASCII representation, and stores the
|
|
* result in the provided buffer.
|
|
*
|
|
* @param[in] value The value to convert to ASCII.
|
|
* @param[in, out] pBuffer The starting location of the buffer on input, and the
|
|
* ending location on output.
|
|
* @param[in] bufferLen Width of value to write (padded with leading 0s if
|
|
* necessary).
|
|
*/
|
|
static void intToAscii( int32_t value,
|
|
char ** pBuffer,
|
|
size_t bufferLen );
|
|
|
|
/**
|
|
* @brief Extract all header key-value pairs from the passed headers data and add them
|
|
* to the canonical request.
|
|
*
|
|
* @param[in] pHeaders HTTP headers to canonicalize.
|
|
* @param[in] headersLen Length of HTTP headers to canonicalize.
|
|
* @param[in] flags Flag to indicate if headers are already
|
|
* in the canonical form.
|
|
* @param[out] canonicalRequest Struct to maintain intermediary buffer
|
|
* and state of canonicalization.
|
|
* @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.
|
|
* #SigV4InsufficientMemory if canonical request buffer cannot accommodate the header.
|
|
* #SigV4InvalidParameter if HTTP headers are invalid.
|
|
* #SigV4MaxHeaderPairCountExceeded if number of headers that needs to be canonicalized
|
|
* exceed the SIGV4_MAX_HTTP_HEADER_COUNT macro defined in the config file.
|
|
*/
|
|
static SigV4Status_t 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.
|
|
*
|
|
* @param[in] headerCount Number of headers which needs to be appended.
|
|
* @param[in] flags Flag to indicate if headers are already
|
|
* in the canonical form.
|
|
* @param[in,out] canonicalRequest Struct to maintain intermediary buffer
|
|
* and state of canonicalization.
|
|
* @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,
|
|
char ** pSignedHeaders,
|
|
size_t * pSignedHeadersLen );
|
|
|
|
/**
|
|
* @brief Canonicalize headers and append it to the Canonical Request buffer.
|
|
*
|
|
* @param[in] headerCount Number of headers which needs to be appended.
|
|
* @param[in] flags Flag to indicate if headers are already
|
|
* in the canonical form.
|
|
* @param[in,out] canonicalRequest Struct to maintain intermediary buffer
|
|
* and state of canonicalization.
|
|
*
|
|
* @return Following statuses will be returned by the function:
|
|
* #SigV4Success if headers are successfully added to the canonical request.
|
|
* #SigV4InsufficientMemory if canonical request buffer cannot accommodate the header.
|
|
*/
|
|
static SigV4Status_t appendCanonicalizedHeaders( size_t headerCount,
|
|
uint32_t flags,
|
|
CanonicalContext_t * canonicalRequest );
|
|
|
|
/**
|
|
* @brief Store the location of HTTP request hashed payload in the HTTP request.
|
|
*
|
|
* @param[in] headerIndex Index of request Header in the list of parsed headers.
|
|
* @param[in] pAmzSHA256Header Literal for x-amz-content-sha256 header in HTTP request.
|
|
* @param[in] amzSHA256HeaderLen Length of @p pAmzSHA256Header.
|
|
* @param[in,out] pCanonicalRequest Struct to maintain intermediary buffer
|
|
* and state of canonicalization.
|
|
*/
|
|
static void storeHashedPayloadLocation( size_t headerIndex,
|
|
const char * pAmzSHA256Header,
|
|
size_t amzSHA256HeaderLen,
|
|
CanonicalContext_t * pCanonicalRequest );
|
|
|
|
/**
|
|
* @brief Parse each header key and value pair from HTTP headers.
|
|
*
|
|
* @param[in] pHeaders HTTP headers to parse.
|
|
* @param[in] headersDataLen Length of HTTP headers to parse.
|
|
* @param[in] flags Flag to indicate if headers are already
|
|
* in the canonical form.
|
|
* @param[out] headerCount Count of key-value pairs parsed from pData.
|
|
* @param[out] canonicalRequest Struct to maintain intermediary buffer
|
|
* and state of canonicalization.
|
|
*
|
|
* @return Following statuses will be returned by the function:
|
|
* #SigV4Success if header key or value is successfully added to the canonical request.
|
|
* #SigV4InsufficientMemory if canonical request buffer cannot accommodate the header.
|
|
* #SigV4MaxHeaderPairCountExceeded if number of key-value entries in the headers data
|
|
* exceeds the SIGV4_MAX_HTTP_HEADER_COUNT macro defined in the config file.
|
|
*/
|
|
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.
|
|
*
|
|
* @param[in] pData Header Key or value to be copied to the canonical request.
|
|
* @param[in] dataLen Length of Header Key or value.
|
|
* @param[in] flags Flag to indicate if headers are already
|
|
* in the canonical form.
|
|
* @param[in] separator Character separating the multiple key-value pairs or key and values.
|
|
* @param[in,out] canonicalRequest Struct to maintain intermediary buffer
|
|
* and state of canonicalization.
|
|
*
|
|
* @return Following statuses will be returned by the function:
|
|
* #SigV4Success if the headers are successfully added to the canonical request.
|
|
* #SigV4InsufficientMemory if canonical request buffer cannot accommodate the header.
|
|
*/
|
|
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
|
|
* that can be trimmed when creating "Canonical Headers".
|
|
* All leading and trailing spaces in the header strings need to be trimmed. Also, sequential spaces
|
|
* in the header value need to be trimmed to a single space.
|
|
*
|
|
* Example of modifying header field for Canonical Headers:
|
|
* Actual header pair: | Modifier header pair
|
|
* My-Header2: "a b c" \n | my-header2:"a b c"\n
|
|
*
|
|
* @param[in] value Header value or key string to be trimmed.
|
|
* @param[in] index Index of current character.
|
|
* @param[in] valLen Length of the string.
|
|
* @param[in] trimmedLength Current length of trimmed string.
|
|
*
|
|
* @return `true` if the character needs to be trimmed, else `false`.
|
|
*/
|
|
static bool isTrimmableSpace( const char * value,
|
|
size_t index,
|
|
size_t valLen,
|
|
size_t trimmedLength );
|
|
|
|
/**
|
|
* @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 );
|
|
|
|
/**
|
|
* @brief Generates the prefix of the Authorization header of the format:
|
|
* "<algorithm> Credential=<access key ID>/<credential scope>, SignedHeaders=<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, out] pAuthPrefixLen On input, it should contain the total length of @p pAuthBuf.
|
|
* On output, this will be filled with the length of the Authorization header, if
|
|
* operation is successful.
|
|
*
|
|
* @return #SigV4InsufficientMemory if the length of the authorization buffer, @p pAuthBuf
|
|
* is insufficient to store the entire authorization header value (i.e. Prefix + HexEncoded Signature);
|
|
* otherwise #SigV4Success.
|
|
*/
|
|
static SigV4Status_t generateAuthorizationValuePrefix( const SigV4Parameters_t * pParams,
|
|
const char * pAlgorithm,
|
|
size_t algorithmLen,
|
|
const char * pSignedHeaders,
|
|
size_t signedHeadersLen,
|
|
char * pAuthBuf,
|
|
size_t * pAuthPrefixLen );
|
|
|
|
/**
|
|
* @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 );
|
|
|
|
/**
|
|
* @brief Set a query parameter key in the canonical request.
|
|
*
|
|
* @param[in] currentParameter The index of the query key to set
|
|
* @param[in] pKey The pointer to the query key
|
|
* @param[in] keyLen The length of @p pKey
|
|
* @param[in,out] pCanonicalRequest The canonical request containing the
|
|
* query parameter array of keys and values
|
|
*/
|
|
static void setQueryParameterKey( size_t currentParameter,
|
|
const char * pKey,
|
|
size_t keyLen,
|
|
CanonicalContext_t * pCanonicalRequest );
|
|
|
|
/**
|
|
* @brief Set a query parameter value in the canonical request.
|
|
*
|
|
* @param[in] currentParameter The index of the query value to set
|
|
* @param[in] pValue The pointer to the query value
|
|
* @param[in] valueLen The length of @p pValue
|
|
* @param[in,out] pCanonicalRequest The canonical request containing the
|
|
* query parameter array of keys and values
|
|
*/
|
|
static void setQueryParameterValue( size_t currentParameter,
|
|
const char * pValue,
|
|
size_t valueLen,
|
|
CanonicalContext_t * pCanonicalRequest );
|
|
|
|
/**
|
|
* @brief Convert the character to lowercase.
|
|
*
|
|
* @param[in] inputChar character to be lowercased.
|
|
*
|
|
* @return Input character converted to lowercase.
|
|
*/
|
|
static char lowercaseCharacter( char inputChar );
|
|
|
|
/**
|
|
* @brief Write the HTTP request payload hash to the canonical request.
|
|
*
|
|
* @param[in] pParams The application-defined parameters used for writing
|
|
* the payload hash.
|
|
* @param[out] pCanonicalContext The canonical context where the payload
|
|
* hash should be written.
|
|
*
|
|
* @return SigV4InsufficientMemory if the length of the canonical request
|
|
* buffer cannot write the desired line, #SigV4Success otherwise.
|
|
*/
|
|
static SigV4Status_t writePayloadHashToCanonicalRequest( const SigV4Parameters_t * pParams,
|
|
CanonicalContext_t * pCanonicalContext );
|
|
|
|
/**
|
|
* @brief Generates the key for the HMAC operation.
|
|
*
|
|
* @note This function can be called multiple times before calling
|
|
* #hmacIntermediate. Appending multiple substrings, then calling #hmacAddKey
|
|
* on the appended string is also equivalent to calling #hmacAddKey on
|
|
* each individual substring.
|
|
* @note This function accepts a const char * so that string literals
|
|
* can be passed in.
|
|
*
|
|
* @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.
|
|
* @param[in] isKeyPrefix Flag to indicate whether the passed key is
|
|
* prefix of a complete key for an HMAC operation. If this is a prefix,
|
|
* then it will be stored in cache for use with remaining part of the
|
|
* key that will be provided in a subsequent call to @ref hmacAddKey.
|
|
*
|
|
* @return Zero on success, all other return values are failures.
|
|
*/
|
|
static int32_t hmacAddKey( HmacContext_t * pHmacContext,
|
|
const char * pKey,
|
|
size_t keyLen,
|
|
bool isKeyPrefix );
|
|
|
|
/**
|
|
* @brief Generates the intermediate hash output in the HMAC signing process.
|
|
* This represents the H( K' ^ i_pad || message ) part of the HMAC algorithm.
|
|
*
|
|
* @note This MUST be ONLY called after #hmacAddKey; otherwise results in
|
|
* undefined behavior. Likewise, one SHOULD NOT call #hmacAddKey after
|
|
* calling #hmacIntermediate. One must call #hmacFinal first before calling
|
|
* #hmacAddKey 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 hmacIntermediate( HmacContext_t * pHmacContext,
|
|
const char * pData,
|
|
size_t dataLen );
|
|
|
|
/**
|
|
* @brief Generates the end output of the HMAC algorithm.
|
|
*
|
|
* This represents the second hash operation in the HMAC algorithm:
|
|
* H( K' ^ o_pad || Intermediate Hash Output )
|
|
* where the Intermediate Hash Output is generated from the call
|
|
* to @ref hmacIntermediate.
|
|
*
|
|
* @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 );
|
|
|
|
/**
|
|
* @brief Generates the complete HMAC digest given a key and value, then write
|
|
* the digest in the provided output buffer.
|
|
*
|
|
* @param[in] pHmacContext The context used for the current HMAC calculation.
|
|
* @param[in] pKey The key passed as input to the HMAC function.
|
|
* @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.
|
|
* @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 );
|
|
|
|
/**
|
|
* @brief Generates the complete hash of an input string, then write
|
|
* the digest in the provided output buffer.
|
|
* @note Unlike #completeHashAndHexEncode, this function will not
|
|
* encode the hash and will simply output the bytes written by the
|
|
* hash function.
|
|
*
|
|
* @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 uint8_t * pInput,
|
|
size_t inputLen,
|
|
uint8_t * pOutput,
|
|
size_t outputLen,
|
|
const SigV4CryptoInterface_t * pCryptoInterface );
|
|
|
|
/**
|
|
* @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 );
|
|
|
|
/**
|
|
* @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 );
|
|
|
|
/**
|
|
* @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 );
|
|
|
|
/**
|
|
* @brief Format the credential scope for the authorization header.
|
|
* Credential scope includes the access key ID, date, region, and service parameters, and
|
|
* ends with "aws4_request" terminator.
|
|
*
|
|
* @param[in] pSigV4Params The application parameters defining the credential's scope.
|
|
* @param[in, out] pCredScope The credential scope in the SigV4 format.
|
|
*/
|
|
static void generateCredentialScope( const 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 input parameters to the SigV4_GenerateHTTPAuthorization API.
|
|
*
|
|
* @param[in] pParams Complete SigV4 configurations passed by application.
|
|
* @param[in] pAuthBuf The user-supplied buffer for filling Authorization Header.
|
|
* @param[in] authBufLen The user-supplied size value of @p pAuthBuf buffer.
|
|
* @param[in] pSignature The user-supplied pointer memory to store starting location of
|
|
* Signature in Authorization Buffer.
|
|
* @param[in] signatureLen The user-supplied pointer to store length of Signature.
|
|
*
|
|
* @return #SigV4Success if successful, #SigV4InvalidParameter otherwise.
|
|
*/
|
|
static SigV4Status_t verifyParamsToGenerateAuthHeaderApi( const SigV4Parameters_t * pParams,
|
|
const char * pAuthBuf,
|
|
const size_t * authBufLen,
|
|
char * const * pSignature,
|
|
const size_t * signatureLen );
|
|
|
|
/**
|
|
* @brief Assign default arguments based on parameters set in @p pParams.
|
|
*
|
|
* @param[in] pParams Complete SigV4 configurations passed by application.
|
|
* @param[out] pAlgorithm The algorithm used for SigV4 authentication.
|
|
* @param[out] algorithmLen The length of @p pAlgorithm.
|
|
*/
|
|
static void assignDefaultArguments( const SigV4Parameters_t * pParams,
|
|
const char ** pAlgorithm,
|
|
size_t * algorithmLen );
|
|
|
|
/**
|
|
* @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 );
|
|
|
|
/**
|
|
* @brief Copy a string into a char * buffer.
|
|
* @note This function can be used to copy a string literal without
|
|
* MISRA warnings.
|
|
*
|
|
* @note This function assumes the destination buffer is large enough to hold
|
|
* the string to copy, so will always write @p length bytes.
|
|
*
|
|
* @param[in] destination The buffer to write.
|
|
* @param[in] source String to copy.
|
|
* @param[in] length Number of characters to copy.
|
|
*
|
|
* @return @p length The number of characters written from @p source into
|
|
* @p destination.
|
|
*/
|
|
static size_t copyString( char * destination,
|
|
const char * source,
|
|
size_t length );
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
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 ) );
|
|
}
|
|
}
|
|
|
|
/* SigV4DateTime_t values are asserted to be non-negative before they are
|
|
* assigned in function addToDate(). Therefore, we only verify logical upper
|
|
* bounds for the following values. */
|
|
if( pDateElements->tm_hour > 23 )
|
|
{
|
|
LogError( ( "Invalid 'hour' value parsed from date string. "
|
|
"Expected an integer between 0 and 23, received: %ld",
|
|
( long int ) pDateElements->tm_hour ) );
|
|
returnStatus = SigV4ISOFormattingError;
|
|
}
|
|
|
|
if( pDateElements->tm_min > 59 )
|
|
{
|
|
LogError( ( "Invalid 'minute' value parsed from date string. "
|
|
"Expected an integer between 0 and 59, received: %ld",
|
|
( long int ) pDateElements->tm_min ) );
|
|
returnStatus = SigV4ISOFormattingError;
|
|
}
|
|
|
|
/* An upper limit of 60 accounts for the occasional leap second UTC
|
|
* adjustment. */
|
|
if( pDateElements->tm_sec > 60 )
|
|
{
|
|
LogError( ( "Invalid 'second' value parsed from date string. "
|
|
"Expected an integer between 0 and 60, received: %ld",
|
|
( long int ) pDateElements->tm_sec ) );
|
|
returnStatus = SigV4ISOFormattingError;
|
|
}
|
|
|
|
return returnStatus;
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
static void addToDate( const char formatChar,
|
|
int32_t result,
|
|
SigV4DateTime_t * pDateElements )
|
|
{
|
|
assert( pDateElements != NULL );
|
|
assert( result >= 0 );
|
|
|
|
switch( formatChar )
|
|
{
|
|
case 'Y':
|
|
pDateElements->tm_year = result;
|
|
break;
|
|
|
|
case 'M':
|
|
pDateElements->tm_mon = result;
|
|
break;
|
|
|
|
case 'D':
|
|
pDateElements->tm_mday = result;
|
|
break;
|
|
|
|
case 'h':
|
|
pDateElements->tm_hour = result;
|
|
break;
|
|
|
|
case 'm':
|
|
pDateElements->tm_min = result;
|
|
break;
|
|
|
|
case 's':
|
|
pDateElements->tm_sec = result;
|
|
break;
|
|
|
|
default:
|
|
|
|
/* Do not assign values for skipped characters ('*'), or
|
|
* unrecognized format specifiers. */
|
|
break;
|
|
}
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
static SigV4Status_t scanValue( const char * pDate,
|
|
const char formatChar,
|
|
size_t readLoc,
|
|
size_t lenToRead,
|
|
SigV4DateTime_t * pDateElements )
|
|
{
|
|
SigV4Status_t returnStatus = SigV4InvalidParameter;
|
|
const char * const pMonthNames[] = MONTH_NAMES;
|
|
const char * pLoc = pDate + readLoc;
|
|
size_t remainingLenToRead = lenToRead;
|
|
int32_t result = 0;
|
|
|
|
assert( pDate != NULL );
|
|
assert( pDateElements != NULL );
|
|
|
|
if( formatChar == '*' )
|
|
{
|
|
remainingLenToRead = 0U;
|
|
}
|
|
|
|
/* Determine if month value is non-numeric. */
|
|
if( ( formatChar == 'M' ) && ( remainingLenToRead == MONTH_ASCII_LEN ) )
|
|
{
|
|
while( result++ < 12 )
|
|
{
|
|
/* Search month array for parsed string. */
|
|
if( strncmp( pMonthNames[ result - 1 ], pLoc, MONTH_ASCII_LEN ) == 0 )
|
|
{
|
|
returnStatus = SigV4Success;
|
|
break;
|
|
}
|
|
}
|
|
|
|
if( returnStatus != SigV4Success )
|
|
{
|
|
LogError( ( "Unable to match string '%.3s' to a month value.",
|
|
pLoc ) );
|
|
returnStatus = SigV4ISOFormattingError;
|
|
}
|
|
|
|
remainingLenToRead = 0U;
|
|
}
|
|
|
|
/* Interpret integer value of numeric representation. */
|
|
while( ( remainingLenToRead > 0U ) && ( *pLoc >= '0' ) && ( *pLoc <= '9' ) )
|
|
{
|
|
result = ( result * 10 ) + ( int32_t ) ( *pLoc - '0' );
|
|
remainingLenToRead--;
|
|
pLoc += 1;
|
|
}
|
|
|
|
if( remainingLenToRead != 0U )
|
|
{
|
|
LogError( ( "Parsing Error: Expected numerical string of type '%%%d%c', "
|
|
"but received '%.*s'.",
|
|
( int ) lenToRead,
|
|
formatChar,
|
|
( int ) lenToRead,
|
|
pLoc ) );
|
|
returnStatus = SigV4ISOFormattingError;
|
|
}
|
|
|
|
if( returnStatus != SigV4ISOFormattingError )
|
|
{
|
|
addToDate( formatChar,
|
|
result,
|
|
pDateElements );
|
|
}
|
|
|
|
return returnStatus;
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
static SigV4Status_t parseDate( const char * pDate,
|
|
size_t dateLen,
|
|
const char * pFormat,
|
|
size_t formatLen,
|
|
SigV4DateTime_t * pDateElements )
|
|
{
|
|
SigV4Status_t returnStatus = SigV4InvalidParameter;
|
|
size_t readLoc = 0U, formatIndex = 0U;
|
|
uint8_t lenToRead = 0U;
|
|
|
|
assert( pDate != NULL );
|
|
assert( pFormat != NULL );
|
|
assert( pDateElements != NULL );
|
|
( void ) dateLen;
|
|
|
|
/* Loop through the format string. */
|
|
while( ( formatIndex < formatLen ) && ( returnStatus != SigV4ISOFormattingError ) )
|
|
{
|
|
if( pFormat[ formatIndex ] == '%' )
|
|
{
|
|
/* '%' must be followed by a length and type specification. */
|
|
assert( formatIndex < formatLen - 2 );
|
|
formatIndex++;
|
|
|
|
/* Numerical value of length specifier character. */
|
|
lenToRead = ( ( uint8_t ) pFormat[ formatIndex ] - ( uint8_t ) '0' );
|
|
formatIndex++;
|
|
|
|
/* Ensure read is within buffer bounds. */
|
|
assert( readLoc + lenToRead - 1 < dateLen );
|
|
returnStatus = scanValue( pDate,
|
|
pFormat[ formatIndex ],
|
|
readLoc,
|
|
lenToRead,
|
|
pDateElements );
|
|
|
|
readLoc += lenToRead;
|
|
}
|
|
else if( pDate[ readLoc ] != pFormat[ formatIndex ] )
|
|
{
|
|
LogError( ( "Parsing error: Expected character '%c', "
|
|
"but received '%c'.",
|
|
pFormat[ formatIndex ], pDate[ readLoc ] ) );
|
|
returnStatus = SigV4ISOFormattingError;
|
|
}
|
|
else
|
|
{
|
|
readLoc++;
|
|
LogDebug( ( "Successfully matched character '%c' found in format string.",
|
|
pDate[ readLoc - 1 ] ) );
|
|
}
|
|
|
|
formatIndex++;
|
|
}
|
|
|
|
if( ( returnStatus != SigV4ISOFormattingError ) )
|
|
{
|
|
returnStatus = SigV4Success;
|
|
}
|
|
else
|
|
{
|
|
LogError( ( "Parsing Error: Date did not match expected string format." ) );
|
|
returnStatus = SigV4ISOFormattingError;
|
|
}
|
|
|
|
return returnStatus;
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
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;
|
|
const uint8_t * bytes;
|
|
|
|
assert( pInputStr != NULL );
|
|
assert( pHexOutput != NULL );
|
|
assert( pInputStr->pData != NULL );
|
|
assert( pHexOutput->pData != NULL );
|
|
|
|
hex = pHexOutput->pData;
|
|
bytes = ( const uint8_t * ) pInputStr->pData;
|
|
|
|
/* Hex string notification of binary data takes twice the size. */
|
|
if( pHexOutput->dataLen < ( pInputStr->dataLen * 2U ) )
|
|
{
|
|
returnStatus = SigV4InsufficientMemory;
|
|
LOG_INSUFFICIENT_MEMORY_ERROR( "hex encode",
|
|
( pInputStr->dataLen * 2U ) - pHexOutput->dataLen );
|
|
}
|
|
else
|
|
{
|
|
for( i = 0; i < pInputStr->dataLen; i++ )
|
|
{
|
|
*hex = digitArr[ ( bytes[ i ] & 0xF0U ) >> 4 ];
|
|
hex++;
|
|
*hex = digitArr[ ( bytes[ i ] & 0x0FU ) ];
|
|
hex++;
|
|
}
|
|
|
|
pHexOutput->dataLen = pInputStr->dataLen * 2U;
|
|
}
|
|
|
|
return returnStatus;
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
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 size_t copyString( char * destination,
|
|
const char * source,
|
|
size_t length )
|
|
{
|
|
( void ) memcpy( destination, source, length );
|
|
return length;
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
static void generateCredentialScope( const SigV4Parameters_t * pSigV4Params,
|
|
SigV4String_t * pCredScope )
|
|
{
|
|
char * pBufWrite = NULL;
|
|
size_t credScopeLen = sizeNeededForCredentialScope( pSigV4Params );
|
|
|
|
assert( pSigV4Params != NULL );
|
|
assert( pSigV4Params->pCredentials != NULL );
|
|
assert( pSigV4Params->pRegion != NULL );
|
|
assert( pSigV4Params->pService != NULL );
|
|
assert( pCredScope != NULL );
|
|
assert( pCredScope->pData != NULL );
|
|
assert( pCredScope->dataLen >= credScopeLen );
|
|
|
|
pBufWrite = pCredScope->pData;
|
|
|
|
/* Each concatenated component is separated by a '/' character. */
|
|
/* 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;
|
|
|
|
*pBufWrite = CREDENTIAL_SCOPE_SEPARATOR;
|
|
pBufWrite += CREDENTIAL_SCOPE_SEPARATOR_LEN;
|
|
|
|
/* Concatenate AWS region. */
|
|
( void ) memcpy( pBufWrite, pSigV4Params->pRegion, pSigV4Params->regionLen );
|
|
pBufWrite += pSigV4Params->regionLen;
|
|
|
|
*pBufWrite = CREDENTIAL_SCOPE_SEPARATOR;
|
|
pBufWrite += CREDENTIAL_SCOPE_SEPARATOR_LEN;
|
|
|
|
/* Concatenate AWS service. */
|
|
( void ) memcpy( pBufWrite, pSigV4Params->pService, pSigV4Params->serviceLen );
|
|
pBufWrite += pSigV4Params->serviceLen;
|
|
|
|
*pBufWrite = CREDENTIAL_SCOPE_SEPARATOR;
|
|
pBufWrite += CREDENTIAL_SCOPE_SEPARATOR_LEN;
|
|
|
|
/* Concatenate terminator. */
|
|
pBufWrite += copyString( pBufWrite, CREDENTIAL_SCOPE_TERMINATOR, CREDENTIAL_SCOPE_TERMINATOR_LEN );
|
|
|
|
/* Verify that the number of bytes written match the sizeNeededForCredentialScope()
|
|
* utility function for calculating size of credential scope. */
|
|
assert( ( size_t ) ( pBufWrite - pCredScope->pData ) == credScopeLen );
|
|
|
|
pCredScope->dataLen = credScopeLen;
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
#if ( SIGV4_USE_CANONICAL_SUPPORT == 1 )
|
|
|
|
static int32_t cmpHeaderField( const void * pFirstVal,
|
|
const void * pSecondVal )
|
|
{
|
|
const SigV4KeyValuePair_t * pFirst, * pSecond = NULL;
|
|
size_t lenSmall = 0U;
|
|
|
|
assert( pFirstVal != NULL );
|
|
assert( pSecondVal != NULL );
|
|
|
|
pFirst = ( const SigV4KeyValuePair_t * ) pFirstVal;
|
|
pSecond = ( const SigV4KeyValuePair_t * ) pSecondVal;
|
|
|
|
assert( ( pFirst->key.pData != NULL ) && ( pFirst->key.dataLen != 0U ) );
|
|
assert( ( pSecond->key.pData != NULL ) && ( pSecond->key.dataLen != 0U ) );
|
|
|
|
if( pFirst->key.dataLen <= pSecond->key.dataLen )
|
|
{
|
|
lenSmall = pFirst->key.dataLen;
|
|
}
|
|
else
|
|
{
|
|
lenSmall = pSecond->key.dataLen;
|
|
}
|
|
|
|
return strncmp( pFirst->key.pData,
|
|
pSecond->key.pData,
|
|
lenSmall );
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
static int32_t cmpQueryFieldValue( const void * pFirstVal,
|
|
const void * pSecondVal )
|
|
{
|
|
const SigV4KeyValuePair_t * pFirst, * pSecond = NULL;
|
|
size_t lenSmall = 0U;
|
|
int32_t compResult = -1;
|
|
|
|
assert( pFirstVal != NULL );
|
|
assert( pSecondVal != NULL );
|
|
|
|
pFirst = ( const SigV4KeyValuePair_t * ) pFirstVal;
|
|
pSecond = ( const SigV4KeyValuePair_t * ) pSecondVal;
|
|
|
|
assert( ( pFirst->key.pData != NULL ) && ( pFirst->key.dataLen != 0U ) );
|
|
assert( ( pSecond->key.pData != NULL ) && ( pSecond->key.dataLen != 0U ) );
|
|
|
|
lenSmall = ( pFirst->key.dataLen < pSecond->key.dataLen ) ? pFirst->key.dataLen : pSecond->key.dataLen;
|
|
compResult = ( int32_t ) strncmp( pFirst->key.pData,
|
|
pSecond->key.pData,
|
|
lenSmall );
|
|
|
|
if( compResult == 0 )
|
|
{
|
|
if( pFirst->key.dataLen == pSecond->key.dataLen )
|
|
{
|
|
/* 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( pFirst->value.pData,
|
|
pSecond->value.pData,
|
|
lenSmall );
|
|
}
|
|
else
|
|
{
|
|
/* Fields share a common prefix, so the shorter one should come first. */
|
|
compResult = ( pFirst->key.dataLen < pSecond->key.dataLen ) ? -1 : 1;
|
|
}
|
|
}
|
|
|
|
if( ( compResult == 0 ) && ( pFirst->value.dataLen != pSecond->value.dataLen ) )
|
|
{
|
|
/* Values share a common prefix, so the shorter one should come first. */
|
|
compResult = ( pFirst->value.dataLen < pSecond->value.dataLen ) ? -1 : 1;
|
|
}
|
|
|
|
return compResult;
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
static char toUpperHexChar( uint8_t value )
|
|
{
|
|
static const char upperHexArr[] = "0123456789ABCDEF";
|
|
|
|
assert( value < 16U );
|
|
|
|
return upperHexArr[ value ];
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
static size_t writeHexCodeOfChar( char * pBuffer,
|
|
size_t bufferLen,
|
|
char code )
|
|
{
|
|
assert( pBuffer != NULL );
|
|
assert( bufferLen >= URI_ENCODED_SPECIAL_CHAR_SIZE );
|
|
|
|
/* Suppress unused warning in when asserts are disabled. */
|
|
( void ) bufferLen;
|
|
|
|
*pBuffer = '%';
|
|
*( pBuffer + 1U ) = toUpperHexChar( ( ( uint8_t ) code ) >> 4U );
|
|
*( pBuffer + 2U ) = toUpperHexChar( ( ( uint8_t ) code ) & 0x0FU );
|
|
|
|
return URI_ENCODED_SPECIAL_CHAR_SIZE;
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
static size_t writeDoubleEncodedEquals( char * pBuffer,
|
|
size_t bufferLen )
|
|
{
|
|
assert( pBuffer != NULL );
|
|
assert( bufferLen > URI_DOUBLE_ENCODED_EQUALS_CHAR_SIZE );
|
|
|
|
/* Suppress unused warning in when asserts are disabled. */
|
|
( void ) bufferLen;
|
|
|
|
*pBuffer = '%';
|
|
*( pBuffer + 1U ) = '2';
|
|
*( pBuffer + 2U ) = '5';
|
|
*( pBuffer + 3U ) = '3';
|
|
*( pBuffer + 4U ) = 'D';
|
|
|
|
return URI_DOUBLE_ENCODED_EQUALS_CHAR_SIZE;
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
static bool isAllowedChar( char c,
|
|
bool encodeSlash )
|
|
{
|
|
bool ret = false;
|
|
|
|
/* Lowercase. */
|
|
if( ( c >= 'a' ) && ( c <= 'z' ) )
|
|
{
|
|
ret = true;
|
|
}
|
|
/* Uppercase. */
|
|
else if( ( c >= 'A' ) && ( c <= 'Z' ) )
|
|
{
|
|
ret = true;
|
|
}
|
|
/* Numeric. */
|
|
else if( ( c >= '0' ) && ( c <= '9' ) )
|
|
{
|
|
ret = true;
|
|
}
|
|
/* Other characters. */
|
|
else if( ( c == '-' ) || ( c == '_' ) || ( c == '.' ) || ( c == '~' ) )
|
|
{
|
|
ret = true;
|
|
}
|
|
else if( c == '/' )
|
|
{
|
|
ret = !encodeSlash;
|
|
}
|
|
else
|
|
{
|
|
ret = false;
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
static SigV4Status_t encodeURI( const char * pUri,
|
|
size_t uriLen,
|
|
char * pCanonicalURI,
|
|
size_t * canonicalURILen,
|
|
bool encodeSlash,
|
|
bool doubleEncodeEquals )
|
|
{
|
|
size_t uriIndex = 0U, bytesConsumed = 0U;
|
|
size_t bufferLen = 0U;
|
|
SigV4Status_t returnStatus = SigV4Success;
|
|
|
|
assert( pUri != NULL );
|
|
assert( pCanonicalURI != NULL );
|
|
assert( canonicalURILen != NULL );
|
|
|
|
bufferLen = *canonicalURILen;
|
|
|
|
while( ( uriIndex < uriLen ) && ( returnStatus == SigV4Success ) )
|
|
{
|
|
if( doubleEncodeEquals && ( pUri[ uriIndex ] == '=' ) )
|
|
{
|
|
if( ( bufferLen - bytesConsumed ) < URI_DOUBLE_ENCODED_EQUALS_CHAR_SIZE )
|
|
{
|
|
returnStatus = SigV4InsufficientMemory;
|
|
LOG_INSUFFICIENT_MEMORY_ERROR( "double encode '=' character in canonical query",
|
|
( bytesConsumed + URI_DOUBLE_ENCODED_EQUALS_CHAR_SIZE - bufferLen ) );
|
|
}
|
|
else
|
|
{
|
|
bytesConsumed += writeDoubleEncodedEquals( pCanonicalURI + bytesConsumed, bufferLen - bytesConsumed );
|
|
}
|
|
}
|
|
else if( isAllowedChar( pUri[ uriIndex ], encodeSlash ) )
|
|
{
|
|
/* If the output buffer has space, add the character as-is in URI encoding as it
|
|
* is neither a special character nor an '=' character requiring double encoding. */
|
|
if( bytesConsumed < bufferLen )
|
|
{
|
|
pCanonicalURI[ bytesConsumed ] = pUri[ uriIndex ];
|
|
++bytesConsumed;
|
|
}
|
|
else
|
|
{
|
|
returnStatus = SigV4InsufficientMemory;
|
|
LogError( ( "Failed to encode URI in buffer due to insufficient memory" ) );
|
|
}
|
|
}
|
|
else if( pUri[ uriIndex ] == '\0' )
|
|
{
|
|
/* The URI path beyond the NULL terminator is not encoded. */
|
|
uriIndex = uriLen;
|
|
}
|
|
else
|
|
{
|
|
if( ( bufferLen - bytesConsumed ) < URI_ENCODED_SPECIAL_CHAR_SIZE )
|
|
{
|
|
returnStatus = SigV4InsufficientMemory;
|
|
LOG_INSUFFICIENT_MEMORY_ERROR( "encode special character in canonical URI",
|
|
( bytesConsumed + URI_ENCODED_SPECIAL_CHAR_SIZE - bufferLen ) );
|
|
}
|
|
else
|
|
{
|
|
bytesConsumed += writeHexCodeOfChar( pCanonicalURI + bytesConsumed, bufferLen - bytesConsumed, pUri[ uriIndex - 1U ] );
|
|
}
|
|
}
|
|
|
|
uriIndex++;
|
|
}
|
|
|
|
if( returnStatus == SigV4Success )
|
|
{
|
|
/* Set the output parameter of the number of URI encoded bytes written
|
|
* to the buffer. */
|
|
*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 newline character after canonical URI", 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( isWhitespace( value[ index ] ) )
|
|
{
|
|
/* The last character is a trailing space. */
|
|
if( ( index + 1U ) == valLen )
|
|
{
|
|
ret = true;
|
|
}
|
|
/* Trim if the next character is also a space. */
|
|
else if( isWhitespace( value[ index + 1U ] ) )
|
|
{
|
|
ret = true;
|
|
}
|
|
/* It is a leading space if no characters have been written yet. */
|
|
else if( trimmedLength == 0U )
|
|
{
|
|
ret = true;
|
|
}
|
|
else
|
|
{
|
|
/* Empty else. */
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
static char lowercaseCharacter( char inputChar )
|
|
{
|
|
char outputChar;
|
|
|
|
/* Get the offset from a capital to lowercase character */
|
|
int8_t offset = 'a' - 'A';
|
|
|
|
if( ( inputChar >= 'A' ) && ( inputChar <= 'Z' ) )
|
|
{
|
|
outputChar = inputChar + offset;
|
|
}
|
|
else
|
|
{
|
|
outputChar = inputChar;
|
|
}
|
|
|
|
return outputChar;
|
|
}
|
|
|
|
static SigV4Status_t copyHeaderStringToCanonicalBuffer( const char * pData,
|
|
size_t dataLen,
|
|
uint32_t flags,
|
|
char separator,
|
|
CanonicalContext_t * canonicalRequest )
|
|
{
|
|
SigV4Status_t status = SigV4Success;
|
|
size_t index = 0;
|
|
size_t numOfBytesCopied = 0;
|
|
size_t buffRemaining;
|
|
char * pCurrBufLoc;
|
|
|
|
assert( ( pData != NULL ) && ( dataLen > 0 ) );
|
|
assert( canonicalRequest != NULL );
|
|
assert( canonicalRequest->pBufCur != NULL );
|
|
|
|
buffRemaining = canonicalRequest->bufRemaining;
|
|
pCurrBufLoc = canonicalRequest->pBufCur;
|
|
|
|
for( index = 0; index < dataLen; index++ )
|
|
{
|
|
/* If the header field is not in canonical form already, we need to check
|
|
* whether this character represents a trimmable space. */
|
|
if( !FLAG_IS_SET( flags, SIGV4_HTTP_HEADERS_ARE_CANONICAL_FLAG ) &&
|
|
isTrimmableSpace( pData, index, dataLen, numOfBytesCopied ) )
|
|
{
|
|
/* Cannot copy trimmable space into canonical request buffer. */
|
|
}
|
|
/* Remaining buffer space should at least accommodate the character to copy and the trailing separator character. */
|
|
else if( buffRemaining <= 1U )
|
|
{
|
|
status = SigV4InsufficientMemory;
|
|
break;
|
|
}
|
|
else
|
|
{
|
|
/* Lowercase header key only. '\n' character marks the end of the value and header value
|
|
* does not need to be lowercased. */
|
|
if( separator == '\n' )
|
|
{
|
|
*pCurrBufLoc = ( pData[ index ] );
|
|
}
|
|
else
|
|
{
|
|
*pCurrBufLoc = lowercaseCharacter( pData[ index ] );
|
|
}
|
|
|
|
pCurrBufLoc++;
|
|
numOfBytesCopied++;
|
|
buffRemaining--;
|
|
}
|
|
}
|
|
|
|
/* Check that data to be copied does not contain all spaces only. */
|
|
if( ( status == SigV4Success ) && ( numOfBytesCopied == 0U ) )
|
|
{
|
|
status = SigV4InvalidParameter;
|
|
}
|
|
|
|
/* Add the ending separating character passed to the function.
|
|
* Note: Space for the separator character is accounted for while copying
|
|
* header field data to canonical request buffer. */
|
|
if( status == SigV4Success )
|
|
{
|
|
assert( buffRemaining >= 1 );
|
|
*pCurrBufLoc = separator;
|
|
pCurrBufLoc++;
|
|
canonicalRequest->pBufCur = pCurrBufLoc;
|
|
canonicalRequest->bufRemaining = ( buffRemaining - 1U );
|
|
}
|
|
|
|
return status;
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
static SigV4Status_t appendSignedHeaders( size_t headerCount,
|
|
uint32_t flags,
|
|
CanonicalContext_t * canonicalRequest,
|
|
char ** pSignedHeaders,
|
|
size_t * pSignedHeadersLen )
|
|
{
|
|
size_t headerIndex = 0, keyLen = 0;
|
|
SigV4Status_t sigV4Status = SigV4Success;
|
|
const char * headerKey;
|
|
ptrdiff_t signedHeadersLen = 0;
|
|
|
|
assert( canonicalRequest != NULL );
|
|
assert( canonicalRequest->pBufCur != NULL );
|
|
assert( headerCount > 0 );
|
|
|
|
/* Store the starting location of the Signed Headers in the Canonical Request buffer. */
|
|
*pSignedHeaders = canonicalRequest->pBufCur;
|
|
|
|
for( headerIndex = 0; headerIndex < headerCount; headerIndex++ )
|
|
{
|
|
assert( ( canonicalRequest->pHeadersLoc[ headerIndex ].key.pData ) != NULL );
|
|
keyLen = canonicalRequest->pHeadersLoc[ headerIndex ].key.dataLen;
|
|
|
|
headerKey = canonicalRequest->pHeadersLoc[ headerIndex ].key.pData;
|
|
|
|
/* ';' is used to separate signed multiple headers in the canonical request. */
|
|
sigV4Status = copyHeaderStringToCanonicalBuffer( headerKey, keyLen, flags, ';', canonicalRequest );
|
|
|
|
if( sigV4Status != SigV4Success )
|
|
{
|
|
LogError( ( "Unable to write Signed Headers for Canonical Request: Insufficient memory configured in \"SIGV4_PROCESSING_BUFFER_LENGTH\"" ) );
|
|
break;
|
|
}
|
|
}
|
|
|
|
/* Store the length of the "Signed Headers" data appended to the Canonical Request. */
|
|
signedHeadersLen = canonicalRequest->pBufCur - *pSignedHeaders - 1;
|
|
*pSignedHeadersLen = ( size_t ) signedHeadersLen;
|
|
|
|
if( sigV4Status == SigV4Success )
|
|
{
|
|
/* Replacing the last ';' with '\n' as last header should not have ';'. */
|
|
*( canonicalRequest->pBufCur - 1 ) = '\n';
|
|
}
|
|
|
|
return sigV4Status;
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
static void storeHashedPayloadLocation( size_t headerIndex,
|
|
const char * pAmzSHA256Header,
|
|
size_t amzSHA256HeaderLen,
|
|
CanonicalContext_t * pCanonicalRequest )
|
|
{
|
|
assert( pCanonicalRequest != NULL );
|
|
|
|
const uint8_t * pHeaderData = ( const uint8_t * ) pCanonicalRequest->pHeadersLoc[ headerIndex ].key.pData;
|
|
size_t headerLen = pCanonicalRequest->pHeadersLoc[ headerIndex ].key.dataLen;
|
|
const uint8_t * pHeaderLiteral = ( const uint8_t * ) pAmzSHA256Header;
|
|
|
|
if( ( headerLen == amzSHA256HeaderLen ) && ( memcmp( pHeaderData, pHeaderLiteral, headerLen ) == 0 ) )
|
|
{
|
|
pCanonicalRequest->pHashPayloadLoc = pCanonicalRequest->pHeadersLoc[ headerIndex ].value.pData;
|
|
pCanonicalRequest->hashPayloadLen = pCanonicalRequest->pHeadersLoc[ headerIndex ].value.dataLen;
|
|
}
|
|
}
|
|
|
|
static SigV4Status_t appendCanonicalizedHeaders( size_t headerCount,
|
|
uint32_t flags,
|
|
CanonicalContext_t * canonicalRequest )
|
|
{
|
|
size_t headerIndex = 0, keyLen = 0, valLen = 0;
|
|
const char * value;
|
|
const char * headerKey;
|
|
SigV4Status_t sigV4Status = SigV4Success;
|
|
|
|
assert( canonicalRequest != NULL );
|
|
assert( canonicalRequest->pBufCur != NULL );
|
|
assert( headerCount > 0 );
|
|
|
|
for( headerIndex = 0; headerIndex < headerCount; headerIndex++ )
|
|
{
|
|
assert( canonicalRequest->pHeadersLoc[ headerIndex ].key.pData != NULL );
|
|
keyLen = canonicalRequest->pHeadersLoc[ headerIndex ].key.dataLen;
|
|
valLen = canonicalRequest->pHeadersLoc[ headerIndex ].value.dataLen;
|
|
headerKey = canonicalRequest->pHeadersLoc[ headerIndex ].key.pData;
|
|
/* ':' is used to separate header key and header value in the canonical request. */
|
|
sigV4Status = copyHeaderStringToCanonicalBuffer( headerKey, keyLen, flags, ':', canonicalRequest );
|
|
|
|
if( sigV4Status == SigV4Success )
|
|
{
|
|
value = canonicalRequest->pHeadersLoc[ headerIndex ].value.pData;
|
|
/* '\n' is used to separate each key-value pair in the canonical request. */
|
|
sigV4Status = copyHeaderStringToCanonicalBuffer( value, valLen, flags, '\n', canonicalRequest );
|
|
}
|
|
|
|
if( sigV4Status != SigV4Success )
|
|
{
|
|
break;
|
|
}
|
|
}
|
|
|
|
return sigV4Status;
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
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;
|
|
const char * pCurrLoc;
|
|
bool keyFlag = true;
|
|
SigV4Status_t sigV4Status = SigV4Success;
|
|
ptrdiff_t dataLen = 0;
|
|
|
|
assert( pHeaders != NULL );
|
|
assert( headersDataLen > 0 );
|
|
assert( canonicalRequest != NULL );
|
|
assert( headerCount != NULL );
|
|
|
|
noOfHeaders = *headerCount;
|
|
pKeyOrValStartLoc = pHeaders;
|
|
pCurrLoc = pHeaders;
|
|
|
|
for( index = 0; index < headersDataLen; index++ )
|
|
{
|
|
if( noOfHeaders == SIGV4_MAX_HTTP_HEADER_COUNT )
|
|
{
|
|
sigV4Status = SigV4MaxHeaderPairCountExceeded;
|
|
break;
|
|
}
|
|
/* Look for key part of an header field entry. */
|
|
else if( ( keyFlag ) && ( pHeaders[ index ] == ':' ) )
|
|
{
|
|
dataLen = pCurrLoc - pKeyOrValStartLoc;
|
|
canonicalRequest->pHeadersLoc[ noOfHeaders ].key.pData = pKeyOrValStartLoc;
|
|
canonicalRequest->pHeadersLoc[ noOfHeaders ].key.dataLen = ( size_t ) dataLen;
|
|
pKeyOrValStartLoc = pCurrLoc + 1U;
|
|
keyFlag = false;
|
|
}
|
|
/* Look for header value part of a header field entry for both canonicalized and non-canonicalized forms. */
|
|
/* Non-canonicalized headers will have header values ending with "\r\n". */
|
|
else if( ( !keyFlag ) && !FLAG_IS_SET( flags, SIGV4_HTTP_HEADERS_ARE_CANONICAL_FLAG ) && ( ( index + 1U ) < headersDataLen ) &&
|
|
( 0 == strncmp( pCurrLoc, HTTP_REQUEST_LINE_ENDING, HTTP_REQUEST_LINE_ENDING_LEN ) ) )
|
|
{
|
|
dataLen = pCurrLoc - pKeyOrValStartLoc;
|
|
canonicalRequest->pHeadersLoc[ noOfHeaders ].value.pData = pKeyOrValStartLoc;
|
|
canonicalRequest->pHeadersLoc[ noOfHeaders ].value.dataLen = ( size_t ) dataLen;
|
|
|
|
/* Storing location of hashed request payload */
|
|
storeHashedPayloadLocation( noOfHeaders, SIGV4_HTTP_X_AMZ_CONTENT_SHA256_HEADER, SIGV4_HTTP_X_AMZ_CONTENT_SHA256_HEADER_LENGTH, canonicalRequest );
|
|
|
|
/* Set starting location of the next header key string after the "\r\n". */
|
|
pKeyOrValStartLoc = pCurrLoc + 2U;
|
|
keyFlag = true;
|
|
noOfHeaders++;
|
|
}
|
|
/* Canonicalized headers will have header values ending just with "\n". */
|
|
else if( ( !keyFlag ) && FLAG_IS_SET( flags, SIGV4_HTTP_HEADERS_ARE_CANONICAL_FLAG ) && ( pHeaders[ index ] == '\n' ) )
|
|
{
|
|
dataLen = pCurrLoc - pKeyOrValStartLoc;
|
|
canonicalRequest->pHeadersLoc[ noOfHeaders ].value.pData = pKeyOrValStartLoc;
|
|
canonicalRequest->pHeadersLoc[ noOfHeaders ].value.dataLen = ( size_t ) dataLen;
|
|
|
|
/* Storing location of hashed request payload */
|
|
storeHashedPayloadLocation( noOfHeaders, SIGV4_HTTP_X_AMZ_CONTENT_SHA256_HEADER, SIGV4_HTTP_X_AMZ_CONTENT_SHA256_HEADER_LENGTH, canonicalRequest );
|
|
|
|
/* Set starting location of the next header key string after the "\n". */
|
|
pKeyOrValStartLoc = pCurrLoc + 1U;
|
|
keyFlag = true;
|
|
noOfHeaders++;
|
|
}
|
|
else
|
|
{
|
|
/* Empty else. */
|
|
}
|
|
|
|
pCurrLoc++;
|
|
}
|
|
|
|
/* Ensure each key has its corresponding value. */
|
|
assert( keyFlag == true );
|
|
|
|
/* If no header was found OR header value was not found for a header key,
|
|
* that represents incorrect HTTP headers data passed by the application. */
|
|
if( ( noOfHeaders == 0U ) || ( keyFlag == false ) )
|
|
{
|
|
sigV4Status = SigV4InvalidHttpHeaders;
|
|
}
|
|
else
|
|
{
|
|
*headerCount = noOfHeaders;
|
|
}
|
|
|
|
return sigV4Status;
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
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;
|
|
|
|
assert( pHeaders != NULL );
|
|
assert( canonicalRequest != NULL );
|
|
assert( canonicalRequest->pBufCur != NULL );
|
|
assert( pSignedHeaders != NULL );
|
|
assert( pSignedHeadersLen != NULL );
|
|
|
|
/* Parsing header string to extract key and value. */
|
|
sigV4Status = parseHeaderKeyValueEntries( pHeaders,
|
|
headersLen,
|
|
flags,
|
|
&noOfHeaders,
|
|
canonicalRequest );
|
|
|
|
if( sigV4Status == SigV4Success )
|
|
{
|
|
if( FLAG_IS_SET( flags, SIGV4_HTTP_HEADERS_ARE_CANONICAL_FLAG ) )
|
|
{
|
|
/* Headers are already canonicalized, so just write it to the buffer as is. */
|
|
sigV4Status = writeLineToCanonicalRequest( pHeaders,
|
|
headersLen,
|
|
canonicalRequest );
|
|
}
|
|
else
|
|
{
|
|
/* Sorting headers based on keys. */
|
|
quickSort( 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 ) && !FLAG_IS_SET( flags, SIGV4_HTTP_HEADERS_ARE_CANONICAL_FLAG ) )
|
|
{
|
|
if( canonicalRequest->bufRemaining < 1U )
|
|
{
|
|
sigV4Status = SigV4InsufficientMemory;
|
|
LOG_INSUFFICIENT_MEMORY_ERROR( "write the newline character after canonical headers", 1U );
|
|
}
|
|
else
|
|
{
|
|
*canonicalRequest->pBufCur = LINEFEED_CHAR;
|
|
canonicalRequest->pBufCur++;
|
|
canonicalRequest->bufRemaining--;
|
|
}
|
|
}
|
|
|
|
if( sigV4Status == SigV4Success )
|
|
{
|
|
sigV4Status = appendSignedHeaders( noOfHeaders,
|
|
flags,
|
|
canonicalRequest,
|
|
pSignedHeaders,
|
|
pSignedHeadersLen );
|
|
}
|
|
|
|
return sigV4Status;
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
static void setQueryParameterKey( size_t currentParameter,
|
|
const char * pKey,
|
|
size_t keyLen,
|
|
CanonicalContext_t * pCanonicalRequest )
|
|
{
|
|
assert( pKey != NULL );
|
|
assert( keyLen > 0U );
|
|
assert( ( pCanonicalRequest != NULL ) && ( pCanonicalRequest->pQueryLoc != NULL ) );
|
|
|
|
pCanonicalRequest->pQueryLoc[ currentParameter ].key.pData = pKey;
|
|
pCanonicalRequest->pQueryLoc[ currentParameter ].key.dataLen = keyLen;
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
static void setQueryParameterValue( size_t currentParameter,
|
|
const char * pValue,
|
|
size_t valueLen,
|
|
CanonicalContext_t * pCanonicalRequest )
|
|
{
|
|
assert( ( pCanonicalRequest != NULL ) && ( pCanonicalRequest->pQueryLoc != NULL ) );
|
|
|
|
pCanonicalRequest->pQueryLoc[ currentParameter ].value.pData = pValue;
|
|
pCanonicalRequest->pQueryLoc[ currentParameter ].value.dataLen = valueLen;
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
static void processAmpersandInQueryString( bool fieldHasValue,
|
|
size_t currQueryIndex,
|
|
size_t * pStartOfFieldOrValue,
|
|
size_t * pCurrParamCount,
|
|
const char * pQuery,
|
|
CanonicalContext_t * pCanonicalRequest )
|
|
{
|
|
bool previousParamIsValid = true;
|
|
|
|
assert( pCurrParamCount != NULL );
|
|
assert( pStartOfFieldOrValue != NULL );
|
|
assert( pQuery != NULL );
|
|
assert( pCanonicalRequest != NULL );
|
|
|
|
/* Process scenarios when a value for the previous parameter entry has been detected. */
|
|
if( fieldHasValue )
|
|
{
|
|
/* Case when parameter has empty value even though the query parameter entry has the form
|
|
* "<param>=". */
|
|
if( ( currQueryIndex - *pStartOfFieldOrValue ) == 0U )
|
|
{
|
|
/* Store the previous parameter's empty value information. Use NULL to represent empty value. */
|
|
setQueryParameterValue( *pCurrParamCount, NULL, 0U, pCanonicalRequest );
|
|
}
|
|
|
|
/* This represents the case when the previous parameter has a value associated with it. */
|
|
else
|
|
{
|
|
/* End of value reached, so store a pointer to the previously set value. */
|
|
setQueryParameterValue( *pCurrParamCount, &pQuery[ *pStartOfFieldOrValue ], currQueryIndex - *pStartOfFieldOrValue, pCanonicalRequest );
|
|
}
|
|
}
|
|
/* A parameter value has not been found for the previous parameter. */
|
|
else
|
|
{
|
|
/* Ignore unnecessary '&' that represent empty parameter names.
|
|
* Trimmable '&'s can be leading, trailing or repeated as separators between parameter
|
|
* pairs.
|
|
* For example, this function will prune "&p1=v1&&p2=v2&" to "p1=v1&p2=v2". */
|
|
if( ( currQueryIndex - *pStartOfFieldOrValue ) == 0U )
|
|
{
|
|
/* Do nothing as previous parameter name is empty. */
|
|
previousParamIsValid = false;
|
|
}
|
|
|
|
/* Case when a new query parameter pair has begun without the previous parameter having an associated value,
|
|
* i.e. of the format "<param1>&<param2>=<value>" where "<param1>".
|
|
* In thus case, as the previous parameter does not have a value, we will store an empty value for it. */
|
|
else
|
|
{
|
|
/* Store information about previous query parameter name. The query parameter has no associated value. */
|
|
setQueryParameterKey( *pCurrParamCount, &pQuery[ *pStartOfFieldOrValue ], currQueryIndex - *pStartOfFieldOrValue, pCanonicalRequest );
|
|
|
|
/* Store the previous parameter's empty value information. Use NULL to represent empty value. */
|
|
setQueryParameterValue( *pCurrParamCount, NULL, 0U, pCanonicalRequest );
|
|
}
|
|
}
|
|
|
|
/* Increment the parameter count if the previous parameter is not empty
|
|
* An empty previous parameter is caused in query strings where the '&' are unnecessary. */
|
|
if( previousParamIsValid == true )
|
|
{
|
|
/* As the previous parameter is valid, increment the total parameters
|
|
* parsed so far from the query string. */
|
|
( *pCurrParamCount )++;
|
|
}
|
|
|
|
/* As '&' is always treated as a separator, update the starting index to represent the next
|
|
* query parameter. */
|
|
*pStartOfFieldOrValue = currQueryIndex + 1U;
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
static SigV4Status_t parseQueryString( const char * pQuery,
|
|
size_t queryLen,
|
|
size_t * pNumberOfParameters,
|
|
CanonicalContext_t * pCanonicalRequest )
|
|
{
|
|
size_t currentParameter = 0U, i = 0U, startOfFieldOrValue = 0U;
|
|
bool fieldHasValue = false;
|
|
SigV4Status_t returnStatus = SigV4Success;
|
|
|
|
assert( pNumberOfParameters != NULL );
|
|
assert( pCanonicalRequest != NULL );
|
|
assert( pCanonicalRequest->pQueryLoc != NULL );
|
|
|
|
/* Note: Constness of the query string is casted out here, taking care not to modify
|
|
* its contents in any way. */
|
|
|
|
/* Set cursors to each field and value in the query string.
|
|
* Note: We iterate the index to queryLen (instead of queryLen - 1) for the
|
|
* special case of handling the last index as a terminating value of a query
|
|
* parameter. */
|
|
for( i = 0U; i <= queryLen; i++ )
|
|
{
|
|
/* This test is at the beginning of the loop to ensure that
|
|
* `pCanonicalRequest->pQueryLoc`is only accessed with a valid index.
|
|
* The final iteration may result in `currentParameter` holding
|
|
* SIGV4_MAX_QUERY_PAIR_COUNT, in order to set the number of parameters. */
|
|
if( currentParameter >= SIGV4_MAX_QUERY_PAIR_COUNT )
|
|
{
|
|
returnStatus = SigV4MaxQueryPairCountExceeded;
|
|
LogError( ( "Failed to parse query string: Number of query parameters exceeds max threshold defined in config. "
|
|
"SIGV4_MAX_QUERY_PAIR_COUNT=%lu", ( unsigned long ) SIGV4_MAX_QUERY_PAIR_COUNT ) );
|
|
break;
|
|
}
|
|
|
|
/* Encountering an '&' indicates the start of a new key, and the end of the
|
|
* old value (or key if the field has no value). The last value will not be
|
|
* followed by anything, so we iterate to one beyond the end of the string.
|
|
* Short circuit evaluation ensures the string is not dereferenced for that case. */
|
|
if( ( i == queryLen ) || ( pQuery[ i ] == '&' ) )
|
|
{
|
|
processAmpersandInQueryString( fieldHasValue, i, &startOfFieldOrValue,
|
|
¤tParameter, pQuery, pCanonicalRequest );
|
|
|
|
/* As '&' represents a new parameter, reset the flag about parameter value state.*/
|
|
fieldHasValue = false;
|
|
}
|
|
else if( ( pQuery[ i ] == '=' ) && !fieldHasValue )
|
|
{
|
|
/* Store information about Query Parameter Key in the canonical context. This query parameter has an associated value. */
|
|
setQueryParameterKey( currentParameter, &pQuery[ startOfFieldOrValue ], i - startOfFieldOrValue, pCanonicalRequest );
|
|
|
|
/* Set the starting index for the query parameter's value. */
|
|
startOfFieldOrValue = i + 1U;
|
|
|
|
/* Set the flag to indicate that the current parameter's value has been found. */
|
|
fieldHasValue = true;
|
|
}
|
|
else
|
|
{
|
|
/* Empty else. */
|
|
}
|
|
}
|
|
|
|
*pNumberOfParameters = currentParameter;
|
|
|
|
return returnStatus;
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
static SigV4Status_t writeValueInCanonicalizedQueryString( char * pBufCur,
|
|
size_t bufferLen,
|
|
const char * pValue,
|
|
size_t valueLen,
|
|
size_t * pEncodedLen )
|
|
{
|
|
SigV4Status_t returnStatus = SigV4Success;
|
|
size_t valueBytesWritten = 0U;
|
|
|
|
assert( pBufCur != NULL );
|
|
assert( pEncodedLen != NULL );
|
|
assert( ( pValue == NULL ) || ( valueLen != 0U ) );
|
|
|
|
/* Check that there is space at least for the equals to character. */
|
|
if( bufferLen < 1U )
|
|
{
|
|
LOG_INSUFFICIENT_MEMORY_ERROR( "write '=' query parameter separator", 1U );
|
|
returnStatus = SigV4InsufficientMemory;
|
|
}
|
|
else
|
|
{
|
|
*pBufCur = '=';
|
|
*pEncodedLen = 1U;
|
|
valueBytesWritten = bufferLen - 1U;
|
|
|
|
/* Encode parameter value if non-empty. Query parameters can have empty values. */
|
|
if( valueLen > 0U )
|
|
{
|
|
returnStatus = encodeURI( pValue,
|
|
valueLen,
|
|
pBufCur + 1U,
|
|
&valueBytesWritten,
|
|
true,
|
|
true );
|
|
|
|
if( returnStatus == SigV4Success )
|
|
{
|
|
*pEncodedLen += valueBytesWritten;
|
|
}
|
|
}
|
|
}
|
|
|
|
return returnStatus;
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
static SigV4Status_t writeCanonicalQueryParameters( CanonicalContext_t * pCanonicalRequest,
|
|
size_t numberOfParameters )
|
|
{
|
|
SigV4Status_t returnStatus = SigV4Success;
|
|
char * pBufLoc = NULL;
|
|
size_t encodedLen = 0U, remainingLen = 0U, paramsIndex = 0U;
|
|
|
|
assert( pCanonicalRequest != NULL );
|
|
assert( pCanonicalRequest->pBufCur != NULL );
|
|
assert( pCanonicalRequest->pQueryLoc != NULL );
|
|
|
|
pBufLoc = pCanonicalRequest->pBufCur;
|
|
remainingLen = pCanonicalRequest->bufRemaining;
|
|
|
|
for( paramsIndex = 0U; paramsIndex < numberOfParameters; paramsIndex++ )
|
|
{
|
|
assert( pCanonicalRequest->pQueryLoc[ paramsIndex ].key.pData != NULL );
|
|
assert( pCanonicalRequest->pQueryLoc[ paramsIndex ].key.dataLen > 0U );
|
|
|
|
encodedLen = remainingLen;
|
|
returnStatus = encodeURI( pCanonicalRequest->pQueryLoc[ paramsIndex ].key.pData,
|
|
pCanonicalRequest->pQueryLoc[ paramsIndex ].key.dataLen,
|
|
pBufLoc,
|
|
&encodedLen,
|
|
true /* Encode slash (/) */,
|
|
false /* Do not encode '='. */ );
|
|
|
|
if( returnStatus == SigV4Success )
|
|
{
|
|
pBufLoc += encodedLen;
|
|
remainingLen -= encodedLen;
|
|
|
|
assert( pCanonicalRequest->pQueryLoc[ paramsIndex ].value.pData != NULL );
|
|
returnStatus = writeValueInCanonicalizedQueryString( pBufLoc,
|
|
remainingLen,
|
|
pCanonicalRequest->pQueryLoc[ paramsIndex ].value.pData,
|
|
pCanonicalRequest->pQueryLoc[ paramsIndex ].value.dataLen,
|
|
&encodedLen );
|
|
pBufLoc += encodedLen;
|
|
remainingLen -= encodedLen;
|
|
}
|
|
|
|
/* If there is a succeeding query parameter, add the '&' separator in the canonical query. */
|
|
if( ( returnStatus == SigV4Success ) && ( ( paramsIndex + 1U ) != numberOfParameters ) )
|
|
{
|
|
/* Before adding the '&' for the next query parameter, check that there is
|
|
* space in the buffer. */
|
|
if( remainingLen > 0U )
|
|
{
|
|
*pBufLoc = '&';
|
|
++pBufLoc;
|
|
remainingLen--;
|
|
}
|
|
|
|
/* There is at least another query parameter entry to encode
|
|
* but no space in the buffer. */
|
|
else
|
|
{
|
|
returnStatus = SigV4InsufficientMemory;
|
|
LOG_INSUFFICIENT_MEMORY_ERROR( "write query parameter separator, '&'", 1U );
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Update the context state if canonical query generation was successful. */
|
|
if( returnStatus == SigV4Success )
|
|
{
|
|
pCanonicalRequest->pBufCur = pBufLoc;
|
|
pCanonicalRequest->bufRemaining = remainingLen;
|
|
}
|
|
|
|
return returnStatus;
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
static SigV4Status_t generateCanonicalQuery( const char * pQuery,
|
|
size_t queryLen,
|
|
CanonicalContext_t * pCanonicalContext )
|
|
{
|
|
SigV4Status_t returnStatus = SigV4Success;
|
|
size_t numberOfParameters = 0U;
|
|
|
|
assert( pCanonicalContext != NULL );
|
|
assert( pCanonicalContext->pBufCur != NULL );
|
|
|
|
if( pQuery != NULL )
|
|
{
|
|
returnStatus = parseQueryString( pQuery, queryLen, &numberOfParameters, pCanonicalContext );
|
|
}
|
|
|
|
if( ( returnStatus == SigV4Success ) && ( numberOfParameters > 0U ) )
|
|
{
|
|
/* Sort the parameter names by character code point in ascending order.
|
|
* Parameters with duplicate names should be sorted by value. */
|
|
quickSort( 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 )
|
|
{
|
|
if( pCanonicalContext->bufRemaining > 0U )
|
|
{
|
|
/* Append a linefeed at the end. */
|
|
*pCanonicalContext->pBufCur = LINEFEED_CHAR;
|
|
pCanonicalContext->pBufCur += 1U;
|
|
pCanonicalContext->bufRemaining -= 1U;
|
|
}
|
|
else
|
|
{
|
|
returnStatus = SigV4InsufficientMemory;
|
|
LOG_INSUFFICIENT_MEMORY_ERROR( "write newline character after canonical query", 1U );
|
|
}
|
|
}
|
|
|
|
return returnStatus;
|
|
}
|
|
|
|
#endif /* #if ( SIGV4_USE_CANONICAL_SUPPORT == 1 ) */
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
static SigV4Status_t verifyParamsToGenerateAuthHeaderApi( const SigV4Parameters_t * pParams,
|
|
const char * pAuthBuf,
|
|
const size_t * authBufLen,
|
|
char * const * pSignature,
|
|
const size_t * signatureLen )
|
|
{
|
|
SigV4Status_t returnStatus = SigV4Success;
|
|
|
|
/* Check for NULL members of struct pParams */
|
|
if( ( pParams == NULL ) || ( pAuthBuf == NULL ) || ( authBufLen == NULL ) ||
|
|
( pSignature == NULL ) || ( signatureLen == NULL ) )
|
|
{
|
|
LogError( ( "Parameter check failed: At least one of the input parameters is NULL. "
|
|
"Input parameters cannot be NULL" ) );
|
|
returnStatus = SigV4InvalidParameter;
|
|
}
|
|
else if( pParams->pCredentials == NULL )
|
|
{
|
|
LogError( ( "Parameter check failed: pParams->pCredentials is NULL." ) );
|
|
returnStatus = SigV4InvalidParameter;
|
|
}
|
|
else if( ( pParams->pCredentials->pAccessKeyId == NULL ) || ( pParams->pCredentials->accessKeyIdLen == 0U ) )
|
|
{
|
|
LogError( ( "Parameter check failed: Access Key ID data is empty." ) );
|
|
returnStatus = SigV4InvalidParameter;
|
|
}
|
|
else if( ( pParams->pCredentials->pSecretAccessKey == NULL ) || ( pParams->pCredentials->secretAccessKeyLen == 0U ) )
|
|
{
|
|
LogError( ( "Parameter check failed: Secret Access Key data is empty." ) );
|
|
returnStatus = SigV4InvalidParameter;
|
|
}
|
|
else if( pParams->pDateIso8601 == NULL )
|
|
{
|
|
LogError( ( "Parameter check failed: pParams->DateIso8601 data is NULL." ) );
|
|
returnStatus = SigV4InvalidParameter;
|
|
}
|
|
else if( ( pParams->pRegion == NULL ) || ( pParams->regionLen == 0U ) )
|
|
{
|
|
LogError( ( "Parameter check failed: Region data is empty." ) );
|
|
returnStatus = SigV4InvalidParameter;
|
|
}
|
|
else if( ( pParams->pService == NULL ) || ( pParams->serviceLen == 0U ) )
|
|
{
|
|
LogError( ( "Parameter check failed: Service data is empty." ) );
|
|
returnStatus = SigV4InvalidParameter;
|
|
}
|
|
else if( ( pParams->pAlgorithm != NULL ) && ( pParams->algorithmLen == 0U ) )
|
|
{
|
|
LogError( ( "Parameter check failed: Algorithm is specified but length (pParams->algorithmLen) passed is 0U." ) );
|
|
returnStatus = SigV4InvalidParameter;
|
|
}
|
|
else if( pParams->pCryptoInterface == NULL )
|
|
{
|
|
LogError( ( "Parameter check failed: pParams->pCryptoInterface is NULL." ) );
|
|
returnStatus = SigV4InvalidParameter;
|
|
}
|
|
else if( ( pParams->pCryptoInterface->hashInit == NULL ) || ( pParams->pCryptoInterface->hashUpdate == NULL ) ||
|
|
( pParams->pCryptoInterface->hashFinal == NULL ) )
|
|
{
|
|
LogError( ( "Parameter check failed: At least one of hashInit, hashUpdate, hashFinal function pointer members is NULL." ) );
|
|
returnStatus = SigV4InvalidParameter;
|
|
}
|
|
else if( pParams->pCryptoInterface->hashBlockLen > SIGV4_HASH_MAX_BLOCK_LENGTH )
|
|
{
|
|
LogError( ( "Parameter check failed: pParams->pCryptoInterface->hashBlockLen is greater than `SIGV4_HASH_MAX_BLOCK_LENGTH`, "
|
|
"which can be configured in sigv4_config.h." ) );
|
|
returnStatus = SigV4InvalidParameter;
|
|
}
|
|
else if( pParams->pCryptoInterface->hashDigestLen > SIGV4_HASH_MAX_DIGEST_LENGTH )
|
|
{
|
|
LogError( ( "Parameter check failed: pParams->pCryptoInterface->hashDigestLen is greater than `SIGV4_HASH_MAX_DIGEST_LENGTH`, "
|
|
"which can be configured in sigv4_config.h." ) );
|
|
returnStatus = SigV4InvalidParameter;
|
|
}
|
|
else if( pParams->pHttpParameters == NULL )
|
|
{
|
|
LogError( ( "Parameter check failed: pParams->pHttpParameters is NULL." ) );
|
|
returnStatus = SigV4InvalidParameter;
|
|
}
|
|
else if( ( pParams->pHttpParameters->pHttpMethod == NULL ) || ( pParams->pHttpParameters->httpMethodLen == 0U ) )
|
|
{
|
|
LogError( ( "Parameter check failed: HTTP Method data is either NULL or zero bytes in length." ) );
|
|
returnStatus = SigV4InvalidParameter;
|
|
}
|
|
else if( ( pParams->pHttpParameters->pHeaders == NULL ) || ( pParams->pHttpParameters->headersLen == 0U ) )
|
|
{
|
|
LogError( ( "Parameter check failed: HTTP URI path information is either NULL or zero bytes in length." ) );
|
|
returnStatus = SigV4InvalidParameter;
|
|
}
|
|
else
|
|
{
|
|
/* Empty else block for MISRA C:2012 compliance. */
|
|
}
|
|
|
|
return returnStatus;
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
static void assignDefaultArguments( const SigV4Parameters_t * pParams,
|
|
const char ** pAlgorithm,
|
|
size_t * algorithmLen )
|
|
{
|
|
if( pParams->pAlgorithm == NULL )
|
|
{
|
|
/* 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;
|
|
}
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
static int32_t completeHash( const uint8_t * pInput,
|
|
size_t inputLen,
|
|
uint8_t * pOutput,
|
|
size_t outputLen,
|
|
const SigV4CryptoInterface_t * pCryptoInterface )
|
|
{
|
|
int32_t hashStatus = -1;
|
|
|
|
assert( pOutput != NULL );
|
|
assert( outputLen > 0 );
|
|
assert( pCryptoInterface != NULL );
|
|
assert( pCryptoInterface->hashInit != NULL );
|
|
assert( pCryptoInterface->hashUpdate != NULL );
|
|
assert( pCryptoInterface->hashFinal != NULL );
|
|
|
|
hashStatus = pCryptoInterface->hashInit( pCryptoInterface->pHashContext );
|
|
|
|
if( hashStatus == 0 )
|
|
{
|
|
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. */
|
|
uint8_t hashBuffer[ SIGV4_HASH_MAX_DIGEST_LENGTH ];
|
|
SigV4String_t originalHash;
|
|
SigV4String_t hexEncodedHash;
|
|
|
|
assert( pOutput != NULL );
|
|
assert( pOutputLen != NULL );
|
|
assert( pCryptoInterface != NULL );
|
|
assert( pCryptoInterface->hashInit != NULL );
|
|
assert( pCryptoInterface->hashUpdate != NULL );
|
|
assert( pCryptoInterface->hashFinal != NULL );
|
|
|
|
originalHash.pData = ( char * ) hashBuffer;
|
|
originalHash.dataLen = pCryptoInterface->hashDigestLen;
|
|
hexEncodedHash.pData = pOutput;
|
|
hexEncodedHash.dataLen = *pOutputLen;
|
|
|
|
if( completeHash( ( const uint8_t * ) pInput,
|
|
inputLen,
|
|
hashBuffer,
|
|
pCryptoInterface->hashDigestLen,
|
|
pCryptoInterface ) != 0 )
|
|
{
|
|
returnStatus = SigV4HashError;
|
|
}
|
|
|
|
if( returnStatus == SigV4Success )
|
|
{
|
|
/* Hex-encode the request payload. */
|
|
returnStatus = lowercaseHexEncode( &originalHash,
|
|
&hexEncodedHash );
|
|
}
|
|
|
|
if( returnStatus == SigV4Success )
|
|
{
|
|
assert( hexEncodedHash.dataLen == pCryptoInterface->hashDigestLen * 2U );
|
|
*pOutputLen = hexEncodedHash.dataLen;
|
|
}
|
|
|
|
return returnStatus;
|
|
}
|
|
|
|
static int32_t hmacAddKey( HmacContext_t * pHmacContext,
|
|
const char * pKey,
|
|
size_t keyLen,
|
|
bool isKeyPrefix )
|
|
{
|
|
int32_t returnStatus = 0;
|
|
const SigV4CryptoInterface_t * pCryptoInterface = NULL;
|
|
const uint8_t * pUnsignedKey = ( const uint8_t * ) pKey;
|
|
|
|
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, pUnsignedKey, keyLen );
|
|
pHmacContext->keyLen += keyLen;
|
|
}
|
|
else
|
|
{
|
|
returnStatus = pCryptoInterface->hashInit( pCryptoInterface->pHashContext );
|
|
|
|
/* Hash part of the key that is cached in the HMAC context. */
|
|
if( returnStatus == 0 )
|
|
{
|
|
returnStatus = pCryptoInterface->hashUpdate( pCryptoInterface->pHashContext,
|
|
pHmacContext->key,
|
|
pHmacContext->keyLen );
|
|
}
|
|
|
|
/* Hash down the remaining part of the key in order to create a block-sized derived key. */
|
|
if( returnStatus == 0 )
|
|
{
|
|
returnStatus = pCryptoInterface->hashUpdate( pCryptoInterface->pHashContext,
|
|
pUnsignedKey,
|
|
keyLen );
|
|
}
|
|
|
|
if( returnStatus == 0 )
|
|
{
|
|
/* Store the final block-sized derived key. */
|
|
returnStatus = pCryptoInterface->hashFinal( pCryptoInterface->pHashContext,
|
|
pHmacContext->key,
|
|
pCryptoInterface->hashBlockLen );
|
|
pHmacContext->keyLen = pCryptoInterface->hashDigestLen;
|
|
}
|
|
}
|
|
|
|
/* If the complete key has been obtained and it is less than the hash block size, then append
|
|
* padding with zero valued bytes to make it block-sized. */
|
|
if( !isKeyPrefix && ( returnStatus == 0 ) && ( pHmacContext->keyLen < pCryptoInterface->hashBlockLen ) )
|
|
{
|
|
/* 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 );
|
|
|
|
pHmacContext->keyLen = pCryptoInterface->hashBlockLen;
|
|
}
|
|
|
|
return returnStatus;
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
static int32_t hmacIntermediate( HmacContext_t * pHmacContext,
|
|
const char * pData,
|
|
size_t dataLen )
|
|
{
|
|
int32_t returnStatus = 0;
|
|
size_t i = 0U;
|
|
const SigV4CryptoInterface_t * pCryptoInterface = pHmacContext->pCryptoInterface;
|
|
|
|
assert( pHmacContext != NULL );
|
|
assert( dataLen > 0U );
|
|
assert( pHmacContext->key != NULL );
|
|
assert( pHmacContext->pCryptoInterface != NULL );
|
|
assert( pHmacContext->pCryptoInterface->hashInit != NULL );
|
|
assert( pHmacContext->pCryptoInterface->hashUpdate != NULL );
|
|
assert( pHmacContext->pCryptoInterface->hashFinal != NULL );
|
|
assert( pHmacContext->keyLen == pCryptoInterface->hashBlockLen );
|
|
|
|
/* Derive the inner HMAC key by XORing the key with inner pad byte. */
|
|
for( i = 0U; i < pCryptoInterface->hashBlockLen; i++ )
|
|
{
|
|
/* XOR the key with the ipad. */
|
|
pHmacContext->key[ i ] ^= HMAC_INNER_PAD_BYTE;
|
|
}
|
|
|
|
/* Initialize the Hash Crypto interface for performing new hash operation
|
|
* of H(Inner Key || Data) in the HMAC algorithm. */
|
|
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 )
|
|
{
|
|
/* Hash the data. */
|
|
returnStatus = pCryptoInterface->hashUpdate( pCryptoInterface->pHashContext,
|
|
( const uint8_t * ) pData,
|
|
dataLen );
|
|
}
|
|
|
|
return returnStatus;
|
|
}
|
|
|
|
/*-----------------------------------------------------------*/
|
|
|
|
static int32_t hmacFinal( HmacContext_t * pHmacContext,
|
|
char * pMac,
|
|
size_t macLen )
|
|
{
|
|
int32_t returnStatus = -1;
|
|
uint8_t 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 (by XORing with ipad byte) and then XOR with opad
|
|
* to generate the outer-padded key. As XOR is associative, one way to do
|
|
* this is by performing XOR on each byte of the inner-padded key (ipad ^ opad). */
|
|
for( i = 0U; i < pCryptoInterface->hashBlockLen; i++ )
|
|
{
|
|
pHmacContext->key[ i ] ^= HMAX_IPAD_XOR_OPAD_BYTE;
|
|
}
|
|
|
|
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,
|
|
( uint8_t * ) 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;
|
|
|
|
assert( pLine != NULL );
|
|
assert( ( pCanonicalContext != NULL ) && ( pCanonicalContext->pBufCur != NULL ) );
|
|
|
|
/* Make sure that there is space for the Method and the newline character.*/
|
|
if( pCanonicalContext->bufRemaining < ( lineLen + 1U ) )
|
|
{
|
|
returnStatus = SigV4InsufficientMemory;
|
|
}
|
|
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 )
|
|
{
|
|
int32_t returnStatus = 0;
|
|
|
|
assert( pHmacContext != NULL );
|
|
assert( pKey != NULL );
|
|
assert( keyLen > 0U );
|
|
assert( pData != NULL );
|
|
assert( dataLen > 0U );
|
|
assert( pOutput != NULL );
|
|
assert( outputLen >= pHmacContext->pCryptoInterface->hashDigestLen );
|
|
|
|
returnStatus = hmacAddKey( pHmacContext,
|
|
pKey,
|
|
keyLen,
|
|
false /* Not a key prefix. */ );
|
|
|
|
if( returnStatus == 0 )
|
|
{
|
|
returnStatus = hmacIntermediate( 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 )
|
|
{
|
|
char * pBuffer = pBufStart;
|
|
|
|
assert( pBufStart != NULL );
|
|
assert( pAlgorithm != NULL );
|
|
assert( pDateIso8601 != NULL );
|
|
|
|
/* Need to write all substrings that come before the hash in the string to sign. */
|
|
|
|
/* Write HMAC and hashing algorithm used for SigV4 authentication. */
|
|
( void ) memcpy( pBuffer, pAlgorithm, algorithmLen );
|
|
pBuffer += algorithmLen;
|
|
|
|
*pBuffer = LINEFEED_CHAR;
|
|
pBuffer += 1U;
|
|
|
|
/* Concatenate entire ISO 8601 date string. */
|
|
( void ) memcpy( pBuffer, pDateIso8601, SIGV4_ISO_STRING_LEN );
|
|
pBuffer += SIGV4_ISO_STRING_LEN;
|
|
|
|
*pBuffer = 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;
|
|
char * pBufStart = ( char * ) pCanonicalContext->pBufProcessing;
|
|
ptrdiff_t bufferLen = pCanonicalContext->pBufCur - pBufStart;
|
|
/* An overestimate but sufficient memory is checked before proceeding. */
|
|
size_t encodedLen = SIGV4_PROCESSING_BUFFER_LENGTH;
|
|
|
|
/* The string to sign is composed of (+ means string concatenation):
|
|
* Algorithm + \n +
|
|
* RequestDateTime + \n +
|
|
* CredentialScope + \n +
|
|
* HashedCanonicalRequest
|
|
*
|
|
* The processing buffer is verified beforehand that it has enough
|
|
* space to hold this string. */
|
|
size_t sizeNeededBeforeHash = algorithmLen + 1U + \
|
|
SIGV4_ISO_STRING_LEN + 1U;
|
|
|
|
assert( pParams != NULL );
|
|
assert( ( pAlgorithm != NULL ) && ( algorithmLen > 0 ) );
|
|
assert( pCanonicalContext != NULL );
|
|
|
|
sizeNeededBeforeHash += 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
|
|
{
|
|
/* Hash the canonical request to its precalculated location in the string to sign. */
|
|
returnStatus = completeHashAndHexEncode( pBufStart,
|
|
( size_t ) bufferLen,
|
|
pBufStart + sizeNeededBeforeHash,
|
|
&encodedLen,
|
|
pParams->pCryptoInterface );
|
|
}
|
|
|
|
if( returnStatus == SigV4Success )
|
|
{
|
|
size_t bytesWritten = 0U;
|
|
SigV4String_t credentialScope;
|
|
|
|
pCanonicalContext->pBufCur = pBufStart + sizeNeededBeforeHash + encodedLen;
|
|
pCanonicalContext->bufRemaining = SIGV4_PROCESSING_BUFFER_LENGTH - encodedLen - sizeNeededBeforeHash;
|
|
|
|
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;
|
|
}
|
|
|
|
if( returnStatus == SigV4Success )
|
|
{
|
|
LogDebug( ( "Generated String To Sign Key: %.*s",
|
|
( unsigned int ) ( pCanonicalContext->pBufCur - pBufStart ),
|
|
pBufStart ) );
|
|
}
|
|
|
|
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;
|
|
|
|
/* 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;
|
|
}
|
|
|
|
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 );
|
|
|
|
if( returnStatus == SigV4Success )
|
|
{
|
|
/* Write the URI to the canonical request. */
|
|
if( FLAG_IS_SET( 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 /* Do not encode twice. */,
|
|
pCanonicalContext );
|
|
}
|
|
else
|
|
{
|
|
returnStatus = generateCanonicalURI( pPath, pathLen,
|
|
true /* Encode twice */,
|
|
pCanonicalContext );
|
|
}
|
|
}
|
|
|
|
if( returnStatus == SigV4Success )
|
|
{
|
|
/* Write the query to the canonical request. */
|
|
if( FLAG_IS_SET( pParams->pHttpParameters->flags, SIGV4_HTTP_QUERY_IS_CANONICAL_FLAG ) &&
|
|
( pParams->pHttpParameters->pQuery != NULL ) )
|
|
{
|
|
/* 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 )
|
|
{
|
|
/* 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;
|
|
size_t numOfBytesWritten = 0U;
|
|
|
|
assert( pParams != NULL );
|
|
assert( pAlgorithm != NULL );
|
|
assert( algorithmLen > 0 );
|
|
assert( pSignedHeaders != NULL );
|
|
assert( signedHeadersLen > 0 );
|
|
assert( pAuthBuf != NULL );
|
|
assert( pAuthPrefixLen != NULL );
|
|
|
|
/* 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. */
|
|
size_t encodedSignatureLen = ( pParams->pCryptoInterface->hashDigestLen * 2U );
|
|
|
|
/* Check if the authorization buffer has enough space to hold the final SigV4 Authorization header value. */
|
|
authPrefixLen = algorithmLen + SPACE_CHAR_LEN + \
|
|
AUTH_CREDENTIAL_PREFIX_LEN + pParams->pCredentials->accessKeyIdLen + \
|
|
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 + encodedSignatureLen ) )
|
|
{
|
|
LogError( ( "Insufficient memory provided to write the Authorization header value, bytesExceeded=%lu",
|
|
( unsigned long ) ( authPrefixLen + encodedSignatureLen - *pAuthPrefixLen ) ) );
|
|
returnStatus = SigV4InsufficientMemory;
|
|
}
|
|
else
|
|
{
|
|
/* START: Writing of authorization value prefix. */
|
|
/******************* Write <algorithm> *******************************************/
|
|
( void ) memcpy( pAuthBuf, pAlgorithm, algorithmLen );
|
|
numOfBytesWritten += algorithmLen;
|
|
|
|
/* Add space separator. */
|
|
pAuthBuf[ numOfBytesWritten ] = SPACE_CHAR;
|
|
numOfBytesWritten += SPACE_CHAR_LEN;
|
|
|
|
/**************** Write "Credential=<access key ID>/<credential scope>, " ****************/
|
|
numOfBytesWritten += copyString( ( pAuthBuf + numOfBytesWritten ), AUTH_CREDENTIAL_PREFIX, AUTH_CREDENTIAL_PREFIX_LEN );
|
|
( void ) memcpy( ( pAuthBuf + numOfBytesWritten ),
|
|
pParams->pCredentials->pAccessKeyId,
|
|
pParams->pCredentials->accessKeyIdLen );
|
|
numOfBytesWritten += pParams->pCredentials->accessKeyIdLen;
|
|
|
|
pAuthBuf[ numOfBytesWritten ] = CREDENTIAL_SCOPE_SEPARATOR;
|
|
numOfBytesWritten += CREDENTIAL_SCOPE_SEPARATOR_LEN;
|
|
credentialScope.pData = ( pAuthBuf + numOfBytesWritten );
|
|
/* #authBufLen is an overestimate but the validation was already done earlier. */
|
|
credentialScope.dataLen = *pAuthPrefixLen;
|
|
( void ) generateCredentialScope( pParams, &credentialScope );
|
|
numOfBytesWritten += credentialScope.dataLen;
|
|
|
|
/* Add separator before the Signed Headers information. */
|
|
numOfBytesWritten += copyString( pAuthBuf + numOfBytesWritten, AUTH_SEPARATOR, AUTH_SEPARATOR_LEN );
|
|
|
|
|
|
/************************ Write "SignedHeaders=<signedHeaders>, " *******************************/
|
|
numOfBytesWritten += copyString( pAuthBuf + numOfBytesWritten, AUTH_SIGNED_HEADERS_PREFIX, AUTH_SIGNED_HEADERS_PREFIX_LEN );
|
|
( void ) memcpy( pAuthBuf + numOfBytesWritten, pSignedHeaders, signedHeadersLen );
|
|
numOfBytesWritten += signedHeadersLen;
|
|
|
|
/* Add separator before the Signature field name. */
|
|
numOfBytesWritten += copyString( pAuthBuf + numOfBytesWritten, AUTH_SEPARATOR, AUTH_SEPARATOR_LEN );
|
|
|
|
/****************************** Write "Signature=<signature>" *******************************/
|
|
numOfBytesWritten += copyString( pAuthBuf + numOfBytesWritten, AUTH_SIGNATURE_PREFIX, AUTH_SIGNATURE_PREFIX_LEN );
|
|
|
|
/* END: Writing of authorization value prefix. */
|
|
|
|
/* Avoid warnings from last write if asserts are disabled. */
|
|
( void ) numOfBytesWritten;
|
|
assert( authPrefixLen == numOfBytesWritten );
|
|
*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 );
|
|
|
|
/* 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( returnStatus != SigV4InsufficientMemory )
|
|
{
|
|
/* Fill the key prefix, "AWS4" in the HMAC context cache.
|
|
* The prefix is part of the key for the first round of HMAC operation:
|
|
* HMAC("AWS4" + SecretKey, Date) */
|
|
hmacStatus = hmacAddKey( pHmacContext,
|
|
SIGV4_HMAC_SIGNING_KEY_PREFIX,
|
|
SIGV4_HMAC_SIGNING_KEY_PREFIX_LEN,
|
|
true /* Is key prefix. */ );
|
|
|
|
/* The above call should always succeed as it only populates the HMAC key cache. */
|
|
assert( hmacStatus == 0 );
|
|
}
|
|
|
|
if( ( returnStatus != SigV4InsufficientMemory ) && ( hmacStatus == 0 ) )
|
|
{
|
|
hmacStatus = completeHmac( pHmacContext,
|
|
pSigV4Params->pCredentials->pSecretAccessKey,
|
|
pSigV4Params->pCredentials->secretAccessKeyLen,
|
|
pSigV4Params->pDateIso8601,
|
|
ISO_DATE_SCOPE_LEN,
|
|
pSigningKey->pData,
|
|
pSigningKey->dataLen );
|
|
*pBytesRemaining -= pSigV4Params->pCryptoInterface->hashDigestLen;
|
|
}
|
|
|
|
if( ( returnStatus != SigV4InsufficientMemory ) && ( hmacStatus == 0 ) )
|
|
{
|
|
pSigningKeyStart = pSigningKey->pData + pSigV4Params->pCryptoInterface->hashDigestLen + 1U;
|
|
hmacStatus = completeHmac( pHmacContext,
|
|
pSigningKey->pData,
|
|
pSigV4Params->pCryptoInterface->hashDigestLen,
|
|
pSigV4Params->pRegion,
|
|
pSigV4Params->regionLen,
|
|
pSigningKeyStart,
|
|
*pBytesRemaining );
|
|
*pBytesRemaining -= pSigV4Params->pCryptoInterface->hashDigestLen;
|
|
}
|
|
|
|
if( ( returnStatus != SigV4InsufficientMemory ) && ( hmacStatus == 0 ) )
|
|
{
|
|
hmacStatus = completeHmac( pHmacContext,
|
|
pSigningKeyStart,
|
|
pSigV4Params->pCryptoInterface->hashDigestLen,
|
|
pSigV4Params->pService,
|
|
pSigV4Params->serviceLen,
|
|
pSigningKey->pData,
|
|
pSigV4Params->pCryptoInterface->hashDigestLen );
|
|
}
|
|
|
|
if( ( returnStatus != SigV4InsufficientMemory ) && ( hmacStatus == 0 ) )
|
|
{
|
|
hmacStatus = completeHmac( pHmacContext,
|
|
pSigningKey->pData,
|
|
pSigV4Params->pCryptoInterface->hashDigestLen,
|
|
CREDENTIAL_SCOPE_TERMINATOR,
|
|
CREDENTIAL_SCOPE_TERMINATOR_LEN,
|
|
pSigningKeyStart,
|
|
pSigV4Params->pCryptoInterface->hashDigestLen );
|
|
}
|
|
|
|
if( ( returnStatus != SigV4InsufficientMemory ) && ( hmacStatus == 0 ) )
|
|
{
|
|
pSigningKey->pData = pSigningKeyStart;
|
|
pSigningKey->dataLen = pSigV4Params->pCryptoInterface->hashDigestLen;
|
|
}
|
|
|
|
/* If there was a hashing error in HMAC operations, set the appropriate error code. */
|
|
if( hmacStatus != 0 )
|
|
{
|
|
returnStatus = SigV4HashError;
|
|
}
|
|
|
|
return returnStatus;
|
|
}
|
|
|
|
static SigV4Status_t writePayloadHashToCanonicalRequest( const SigV4Parameters_t * pParams,
|
|
CanonicalContext_t * pCanonicalContext )
|
|
{
|
|
size_t encodedLen = 0U;
|
|
SigV4Status_t returnStatus = SigV4Success;
|
|
|
|
assert( pParams != NULL );
|
|
assert( pCanonicalContext != NULL );
|
|
|
|
if( FLAG_IS_SET( pParams->pHttpParameters->flags, SIGV4_HTTP_PAYLOAD_IS_HASH ) )
|
|
{
|
|
/* Copy the hashed payload data supplied by the user in the headers data list. */
|
|
returnStatus = copyHeaderStringToCanonicalBuffer( pCanonicalContext->pHashPayloadLoc, pCanonicalContext->hashPayloadLen, pParams->pHttpParameters->flags, '\n', pCanonicalContext );
|
|
/* Remove new line at the end of the payload. */
|
|
pCanonicalContext->pBufCur--;
|
|
}
|
|
else
|
|
{
|
|
encodedLen = pCanonicalContext->bufRemaining;
|
|
/* Calculate hash of the request payload. */
|
|
returnStatus = completeHashAndHexEncode( pParams->pHttpParameters->pPayload,
|
|
pParams->pHttpParameters->payloadLen,
|
|
pCanonicalContext->pBufCur,
|
|
&encodedLen,
|
|
pParams->pCryptoInterface );
|
|
pCanonicalContext->pBufCur += encodedLen;
|
|
pCanonicalContext->bufRemaining -= encodedLen;
|
|
}
|
|
|
|
return returnStatus;
|
|
}
|
|
|
|
|
|
SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate,
|
|
size_t dateLen,
|
|
char * pDateISO8601,
|
|
size_t dateISO8601Len )
|
|
{
|
|
SigV4Status_t returnStatus = SigV4InvalidParameter;
|
|
SigV4DateTime_t date = { 0 };
|
|
char * pWriteLoc = pDateISO8601;
|
|
const char * pFormatStr = NULL;
|
|
size_t formatLen = 0U;
|
|
|
|
/* Check for NULL parameters. */
|
|
if( pDate == NULL )
|
|
{
|
|
LogError( ( "Parameter check failed: pDate is NULL." ) );
|
|
}
|
|
else if( pDateISO8601 == NULL )
|
|
{
|
|
LogError( ( "Parameter check failed: pDateISO8601 is NULL." ) );
|
|
}
|
|
/* Check that the date provided is of the expected length. */
|
|
else if( ( dateLen != SIGV4_EXPECTED_LEN_RFC_3339 ) &&
|
|
( dateLen != SIGV4_EXPECTED_LEN_RFC_5322 ) )
|
|
{
|
|
LogError( ( "Parameter check failed: dateLen must be either %u or %u, "
|
|
"for RFC 3339 and RFC 5322 formats, respectively.",
|
|
SIGV4_EXPECTED_LEN_RFC_3339,
|
|
SIGV4_EXPECTED_LEN_RFC_5322 ) );
|
|
}
|
|
|
|
/* Check that the output buffer provided is large enough for the formatted
|
|
* string. */
|
|
else if( dateISO8601Len < SIGV4_ISO_STRING_LEN )
|
|
{
|
|
LogError( ( "Parameter check failed: dateISO8601Len must be at least %u.",
|
|
SIGV4_ISO_STRING_LEN ) );
|
|
}
|
|
else
|
|
{
|
|
/* Assign format string according to input type received. */
|
|
pFormatStr = ( dateLen == SIGV4_EXPECTED_LEN_RFC_3339 ) ?
|
|
( FORMAT_RFC_3339 ) : ( FORMAT_RFC_5322 );
|
|
|
|
formatLen = ( dateLen == SIGV4_EXPECTED_LEN_RFC_3339 ) ?
|
|
( FORMAT_RFC_3339_LEN ) : ( FORMAT_RFC_5322_LEN );
|
|
|
|
returnStatus = parseDate( pDate, dateLen, pFormatStr, formatLen, &date );
|
|
}
|
|
|
|
if( returnStatus == SigV4Success )
|
|
{
|
|
returnStatus = validateDateTime( &date );
|
|
}
|
|
|
|
if( returnStatus == SigV4Success )
|
|
{
|
|
/* Combine date elements into complete ASCII representation, and fill
|
|
* buffer with result. */
|
|
intToAscii( date.tm_year, &pWriteLoc, ISO_YEAR_LEN );
|
|
intToAscii( date.tm_mon, &pWriteLoc, ISO_NON_YEAR_LEN );
|
|
intToAscii( date.tm_mday, &pWriteLoc, ISO_NON_YEAR_LEN );
|
|
*pWriteLoc = 'T';
|
|
pWriteLoc++;
|
|
intToAscii( date.tm_hour, &pWriteLoc, ISO_NON_YEAR_LEN );
|
|
intToAscii( date.tm_min, &pWriteLoc, ISO_NON_YEAR_LEN );
|
|
intToAscii( date.tm_sec, &pWriteLoc, ISO_NON_YEAR_LEN );
|
|
*pWriteLoc = 'Z';
|
|
|
|
LogDebug( ( "Successfully formatted ISO 8601 date: \"%.*s\"",
|
|
( int ) dateISO8601Len,
|
|
pDateISO8601 ) );
|
|
}
|
|
|
|
return returnStatus;
|
|
}
|
|
|
|
SigV4Status_t SigV4_GenerateHTTPAuthorization( const SigV4Parameters_t * pParams,
|
|
char * pAuthBuf,
|
|
size_t * authBufLen,
|
|
char ** pSignature,
|
|
size_t * signatureLen )
|
|
{
|
|
SigV4Status_t returnStatus = SigV4Success;
|
|
CanonicalContext_t canonicalContext;
|
|
const char * pAlgorithm = NULL;
|
|
char * pSignedHeaders = NULL;
|
|
size_t algorithmLen = 0U, signedHeadersLen = 0U, authPrefixLen = 0U;
|
|
HmacContext_t hmacContext = { 0 };
|
|
|
|
SigV4String_t signingKey;
|
|
ptrdiff_t bufferLen;
|
|
|
|
returnStatus = verifyParamsToGenerateAuthHeaderApi( pParams,
|
|
pAuthBuf, authBufLen,
|
|
pSignature, signatureLen );
|
|
|
|
if( returnStatus == SigV4Success )
|
|
{
|
|
assignDefaultArguments( pParams, &pAlgorithm, &algorithmLen );
|
|
}
|
|
|
|
if( returnStatus == SigV4Success )
|
|
{
|
|
returnStatus = generateCanonicalRequestUntilHeaders( pParams, &canonicalContext,
|
|
&pSignedHeaders,
|
|
&signedHeadersLen );
|
|
}
|
|
|
|
/* Hash and hex-encode the canonical request to the buffer. */
|
|
if( returnStatus == SigV4Success )
|
|
{
|
|
/* Write HTTP request payload hash to the canonical request. */
|
|
returnStatus = writePayloadHashToCanonicalRequest( pParams, &canonicalContext );
|
|
}
|
|
|
|
/* Write the prefix of the Authorizaton header value. */
|
|
if( returnStatus == SigV4Success )
|
|
{
|
|
LogDebug( ( "Generated Canonical Request: %.*s",
|
|
( unsigned int ) ( ( uint8_t * ) canonicalContext.pBufCur - canonicalContext.pBufProcessing ),
|
|
canonicalContext.pBufProcessing ) );
|
|
|
|
authPrefixLen = *authBufLen;
|
|
returnStatus = generateAuthorizationValuePrefix( pParams,
|
|
pAlgorithm, algorithmLen,
|
|
pSignedHeaders, signedHeadersLen,
|
|
pAuthBuf, &authPrefixLen );
|
|
}
|
|
|
|
/* Write string to sign. */
|
|
if( returnStatus == SigV4Success )
|
|
{
|
|
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 )
|
|
{
|
|
bufferLen = canonicalContext.pBufCur - ( char * ) canonicalContext.pBufProcessing;
|
|
returnStatus = ( completeHmac( &hmacContext,
|
|
signingKey.pData,
|
|
signingKey.dataLen,
|
|
( char * ) canonicalContext.pBufProcessing,
|
|
( size_t ) bufferLen,
|
|
canonicalContext.pBufCur,
|
|
pParams->pCryptoInterface->hashDigestLen ) != 0 )
|
|
? SigV4HashError : SigV4Success;
|
|
}
|
|
|
|
/* 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 + authPrefixLen;
|
|
/* #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 << 1U );
|
|
}
|
|
|
|
return returnStatus;
|
|
}
|