Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support presigned URL signature "UNSIGNED-PAYLOAD" and expose EncodeURI #89

Merged
merged 14 commits into from
Feb 23, 2024
Merged
4 changes: 2 additions & 2 deletions docs/doxygen/include/size_table.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,7 +9,7 @@
</tr>
<tr>
<td>sigv4.c</td>
<td><center>5.2K</center></td>
<td><center>5.3K</center></td>
<td><center>4.4K</center></td>
</tr>
<tr>
Expand All @@ -19,7 +19,7 @@
</tr>
<tr>
<td><b>Total estimates</b></td>
<td><b><center>5.6K</center></b></td>
<td><b><center>5.7K</center></b></td>
<td><b><center>4.7K</center></b></td>
</tr>
</table>
35 changes: 35 additions & 0 deletions source/include/sigv4.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@

/* Standard includes. */
#include <stdint.h>
#include <stdbool.h>
#include <stddef.h>

/* *INDENT-OFF* */
Expand Down Expand Up @@ -125,6 +126,15 @@
*/
#define SIGV4_HTTP_PAYLOAD_IS_HASH 0x8U

/**
* @ingroup sigv4_canonical_flags
* @brief Set this flag to indicate that the HTTP request is
* a presigned URL.
*
* This flag is valid only for #SigV4HttpParameters_t.flags.
*/
#define SIGV4_HTTP_IS_PRESIGNED_URL 0x10U

/**
* @ingroup sigv4_canonical_flags
* @brief Set this flag to indicate that the HTTP request path, query, and
Expand Down Expand Up @@ -565,6 +575,31 @@ SigV4Status_t SigV4_AwsIotDateToIso8601( const char * pDate,
size_t dateISO8601Len );
/* @[declare_sigV4_awsIotDateToIso8601_function] */

#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.
*/
/* @[declare_sigV4_EncodeURI_function] */
SigV4Status_t SigV4_EncodeURI( const char * pUri,
size_t uriLen,
char * pCanonicalURI,
size_t * canonicalURILen,
bool encodeSlash,
bool doubleEncodeEquals );
/* @[declare_sigV4_encodeURI_function] */

#endif /* #if (SIGV4_USE_CANONICAL_SUPPORT == 1) */

/* *INDENT-OFF* */
#ifdef __cplusplus
}
Expand Down
49 changes: 40 additions & 9 deletions source/sigv4.c
Original file line number Diff line number Diff line change
Expand Up @@ -29,7 +29,6 @@

#include <assert.h>
#include <string.h>
#include <stdbool.h>
#include <stdio.h>
#include <stdlib.h>

Expand Down Expand Up @@ -85,11 +84,13 @@
*
* @param[in] pQuery HTTP request query.
* @param[in] queryLen Length of pQuery.
* @param[in] doubleEncodeEqualsInParmsValues whether to double-encode any equals ( = ) characters in parameter values.
* @param[in, out] pCanonicalContext Struct to maintain intermediary buffer
* and state of canonicalization.
*/
static SigV4Status_t generateCanonicalQuery( const char * pQuery,
size_t queryLen,
const bool doubleEncodeEqualsInParmsValues,
CanonicalContext_t * pCanonicalContext );

/**
Expand Down Expand Up @@ -1314,6 +1315,18 @@ static void generateCredentialScope( const SigV4Parameters_t * pSigV4Params,
return ret;
}

/*-----------------------------------------------------------*/

SigV4Status_t SigV4_EncodeURI( const char * pUri,
size_t uriLen,
char * pCanonicalURI,
size_t * canonicalURILen,
bool encodeSlash,
bool doubleEncodeEquals )
{
return encodeURI( pUri, uriLen, pCanonicalURI, canonicalURILen, encodeSlash, doubleEncodeEquals );
}

/*-----------------------------------------------------------*/

static SigV4Status_t encodeURI( const char * pUri,
Expand Down Expand Up @@ -2054,7 +2067,8 @@ static void generateCredentialScope( const SigV4Parameters_t * pSigV4Params,
size_t bufferLen,
const char * pValue,
size_t valueLen,
size_t * pEncodedLen )
size_t * pEncodedLen,
const bool doubleEncodeEqualsInParmsValues )
{
SigV4Status_t returnStatus = SigV4Success;
size_t valueBytesWritten = 0U;
Expand Down Expand Up @@ -2082,8 +2096,8 @@ static void generateCredentialScope( const SigV4Parameters_t * pSigV4Params,
valueLen,
pBufCur + 1U,
&valueBytesWritten,
true,
true );
true /* Encode slash (/) */,
doubleEncodeEqualsInParmsValues );

if( returnStatus == SigV4Success )
{
Expand All @@ -2098,7 +2112,8 @@ static void generateCredentialScope( const SigV4Parameters_t * pSigV4Params,
/*-----------------------------------------------------------*/

static SigV4Status_t writeCanonicalQueryParameters( CanonicalContext_t * pCanonicalRequest,
size_t numberOfParameters )
size_t numberOfParameters,
const bool doubleEncodeEqualsInParmsValues )
{
SigV4Status_t returnStatus = SigV4Success;
char * pBufLoc = NULL;
Expand All @@ -2122,7 +2137,7 @@ static void generateCredentialScope( const SigV4Parameters_t * pSigV4Params,
pBufLoc,
&encodedLen,
true /* Encode slash (/) */,
false /* Do not encode '='. */ );
false /* Do not double encode '='. */ );

if( returnStatus == SigV4Success )
{
Expand All @@ -2134,7 +2149,8 @@ static void generateCredentialScope( const SigV4Parameters_t * pSigV4Params,
remainingLen,
pCanonicalRequest->pQueryLoc[ paramsIndex ].value.pData,
pCanonicalRequest->pQueryLoc[ paramsIndex ].value.dataLen,
&encodedLen );
&encodedLen,
doubleEncodeEqualsInParmsValues );
pBufLoc += encodedLen;
remainingLen -= encodedLen;
}
Expand Down Expand Up @@ -2176,6 +2192,7 @@ static void generateCredentialScope( const SigV4Parameters_t * pSigV4Params,

static SigV4Status_t generateCanonicalQuery( const char * pQuery,
size_t queryLen,
const bool doubleEncodeEqualsInParmsValues,
CanonicalContext_t * pCanonicalContext )
{
SigV4Status_t returnStatus = SigV4Success;
Expand All @@ -2199,9 +2216,8 @@ static void generateCredentialScope( const SigV4Parameters_t * pSigV4Params,
* - 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 );
returnStatus = writeCanonicalQueryParameters( pCanonicalContext, numberOfParameters, doubleEncodeEqualsInParmsValues );
}

if( returnStatus == SigV4Success )
Expand Down Expand Up @@ -2799,6 +2815,13 @@ static SigV4Status_t generateCanonicalRequestUntilHeaders( const SigV4Parameters
SigV4Status_t returnStatus = SigV4Success;
const char * pPath = NULL;
size_t pathLen = 0U;
bool doubleEncodeEqualsInParmsValues = true;

/* In presigned URL we do not want to double-encode any equals ( = ) characters in parameter values */
if( FLAG_IS_SET( pParams->pHttpParameters->flags, SIGV4_HTTP_IS_PRESIGNED_URL ) )
{
doubleEncodeEqualsInParmsValues = false;
}

/* Set defaults for path and algorithm. */
if( ( pParams->pHttpParameters->pPath == NULL ) ||
Expand Down Expand Up @@ -2863,6 +2886,7 @@ static SigV4Status_t generateCanonicalRequestUntilHeaders( const SigV4Parameters
{
returnStatus = generateCanonicalQuery( pParams->pHttpParameters->pQuery,
pParams->pHttpParameters->queryLen,
doubleEncodeEqualsInParmsValues,
pCanonicalContext );
}
}
Expand Down Expand Up @@ -3090,6 +3114,13 @@ static SigV4Status_t writePayloadHashToCanonicalRequest( const SigV4Parameters_t
/* Remove new line at the end of the payload. */
pCanonicalContext->pBufCur--;
}
else if( FLAG_IS_SET( pParams->pHttpParameters->flags, SIGV4_HTTP_IS_PRESIGNED_URL ) )
{
/* Copy the UNSIGNED-PAYLOAD data in the headers data list. */
returnStatus = copyHeaderStringToCanonicalBuffer( "UNSIGNED-PAYLOAD", strlen( "UNSIGNED-PAYLOAD" ), pParams->pHttpParameters->flags, '\n', pCanonicalContext );
/* Remove new line at the end of the payload. */
pCanonicalContext->pBufCur--;
}
else
{
encodedLen = pCanonicalContext->bufRemaining;
Expand Down
1 change: 1 addition & 0 deletions test/cbmc/include/sigv4_stubs.h
Original file line number Diff line number Diff line change
Expand Up @@ -59,6 +59,7 @@ SigV4Status_t encodeURI( const char * pUri,

SigV4Status_t generateCanonicalQuery( const char * pQuery,
size_t queryLen,
const bool doubleEncodeEqualsInParmsValues,
CanonicalContext_t * pCanonicalContext );

SigV4Status_t generateCanonicalAndSignedHeaders( const char * pHeaders,
Expand Down
2 changes: 2 additions & 0 deletions test/cbmc/proofs/SigV4_GenerateHTTPAuthorization/Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@ MAX_HEADERS_LEN=6
MAX_URI_LEN=3
MAX_REQUEST_LEN=16
S3_SERVICE_LEN=2
UNSIGNED_PAYLOAD_LEN=17
MAX_REGION_LEN=30
MAX_SERVICE_LEN=30
MAX_ALGORITHM_LEN=30
Expand Down Expand Up @@ -50,6 +51,7 @@ UNWINDSET += lowercaseHexEncode.0:$(MAX_HASH_DIGEST_LEN)
UNWINDSET += hmacIntermediate.0:$(MAX_HASH_BLOCK_LEN)
UNWINDSET += hmacFinal.0:$(MAX_HASH_BLOCK_LEN)
UNWINDSET += strncmp.0:$(S3_SERVICE_LEN)
UNWINDSET += strlen.0:$(UNSIGNED_PAYLOAD_LEN)

PROOF_SOURCES += $(PROOFDIR)/$(HARNESS_FILE).c
PROOF_SOURCES += $(SRCDIR)/test/cbmc/stubs/hash_stubs.c
Expand Down
1 change: 1 addition & 0 deletions test/cbmc/stubs/sigv4_stubs.c
Original file line number Diff line number Diff line change
Expand Up @@ -174,6 +174,7 @@ SigV4Status_t encodeURI( const char * pUri,

SigV4Status_t generateCanonicalQuery( const char * pQuery,
size_t queryLen,
const bool doubleEncodeEqualsInParmsValues,
CanonicalContext_t * pCanonicalContext )
{
SigV4Status_t returnStatus = SigV4InsufficientMemory;
Expand Down
11 changes: 11 additions & 0 deletions test/unit-test/sigv4_utest.c
Original file line number Diff line number Diff line change
Expand Up @@ -710,6 +710,17 @@ void test_SigV4_GenerateHTTPAuthorization_Happy_Paths()
TEST_ASSERT_EQUAL( SigV4Success, returnStatus );
TEST_ASSERT_EQUAL( SIGV4_HASH_MAX_DIGEST_LENGTH * 2U, signatureLen );
TEST_ASSERT_EQUAL_MEMORY( pExpectedSignature, signature, signatureLen );

/* Coverage for NON double-encoded equals in query string value for presigned URL. */
resetInputParams();
params.pHttpParameters->pQuery = QUERY_VALUE_HAS_EQUALS;
params.pHttpParameters->queryLen = STR_LIT_LEN( QUERY_VALUE_HAS_EQUALS );
params.pHttpParameters->flags = SIGV4_HTTP_IS_PRESIGNED_URL;
pExpectedSignature = "6759e09cf532c4f9b5190873cb4c43305180f5d4d3418d65b6c0affce827dbc4";
returnStatus = SigV4_GenerateHTTPAuthorization( &params, authBuf, &authBufLen, &signature, &signatureLen );
TEST_ASSERT_EQUAL( SigV4Success, returnStatus );
TEST_ASSERT_EQUAL( SIGV4_HASH_MAX_DIGEST_LENGTH * 2U, signatureLen );
TEST_ASSERT_EQUAL_MEMORY( pExpectedSignature, signature, signatureLen );
}

/* Test the API for handling corner cases of sorting the Query Parameters (when generating Canonical Query) */
Expand Down
Loading