From 41faf7c40fe6935ad9c041582355aa78062115b6 Mon Sep 17 00:00:00 2001 From: Nandor Kracser Date: Tue, 4 Aug 2026 14:57:10 +0200 Subject: [PATCH 1/2] fix: keep the signer's working buffers off the caller's stack aws_sigv4_sign() built the canonical request, the string to sign, and the header and query-parameter arrays in automatic storage -- ~8.4 KB of it on the aws_sigv4_sign -> get_canonical_request -> get_canonical_headers path alone. That is more than half of a Linux kernel task stack (16 KB), and the riptides driver calls the signer several frames deep inside sendmsg, so signing an S3 request panicked the kernel: Insufficient stack space to handle exception! ... sort_r+0x8/0x220 get_canonical_headers+0x214/0x4e0 [riptides_pro] get_canonical_request+0x1bc/0x3c8 [riptides_pro] aws_sigv4_sign+0x364/0x488 [riptides_pro] sign_aws_headers.constprop.0+0x758/0x1060 [riptides_pro] ... Kernel panic - not syncing: kernel stack overflow Growing AWS_SIGV4_CANONICAL_REQUEST_BUF_LEN to 4096 and the header limit to 24 in e204ea6 is what tipped it over. Those changes were correct; the problem is that they enlarged frames that had no business being this big in the first place. The buffers now live in a caller-owned aws_sigv4_scratch_t, passed in via aws_sigv4_params_t.scratch. The caller decides where the ~8 KB comes from (the driver heap-allocates it per request) and the library stays allocation-free, which is what makes it usable from a kernel or an embedded caller at all. Measured frame sizes, clang -O2 aarch64: aws_sigv4_sign 5552 -> 416 get_canonical_request 1760 -> 160 get_canonical_headers 1088 -> 240 get_signed_headers 544 -> 112 Two related fixes in parse_query_params(): it never bounded itself against AWS_SIGV4_MAX_NUM_QUERY_COMPONENTS, so a query string with more than 50 components wrote past the array -- it now fails the signing instead. And a component with no '=' left key/value fields unassigned, which used to read back whatever the buffer happened to hold; every component is now initialised explicitly, so behaviour no longer depends on the caller zeroing the scratch. The existing signature vectors are unchanged: the signatures are byte-identical. Added coverage for the missing-scratch and too-many-query-components paths. --- example.c | 5 ++++ sigv4.c | 59 +++++++++++++++++++++++++++--------------- sigv4.h | 24 +++++++++++++++++ test.c | 77 +++++++++++++++++++++++++++++++++++++++++++++++++++++++ 4 files changed, 144 insertions(+), 21 deletions(-) diff --git a/example.c b/example.c index 63029ed..9de5827 100644 --- a/example.c +++ b/example.c @@ -4,6 +4,10 @@ #include "sigv4.h" +/* the signer works out of caller-owned buffers; ~8 KB, so keep it out of + automatic storage -- on a kernel stack it would not fit at all */ +static aws_sigv4_scratch_t scratch; + int HMAC_SHA256(const unsigned char *data, size_t data_len, const unsigned char *key, size_t key_len, unsigned char *out, size_t *out_len) @@ -29,6 +33,7 @@ int main() .hmac_sha256 = HMAC_SHA256, .sha256 = (void *)SHA256, .sort = qsort, + .scratch = &scratch, }; char auth_buf[AWS_SIGV4_AUTH_HEADER_MAX_LEN] = {0}; diff --git a/sigv4.c b/sigv4.c index f49ace9..bf2fc0d 100644 --- a/sigv4.c +++ b/sigv4.c @@ -4,13 +4,8 @@ #define AWS_SIGV4_SIGNING_ALGORITHM "AWS4-HMAC-SHA256" #define SHA256_DIGEST_SIZE 32 #define AWS_SIGV4_HEX_SHA256_LENGTH SHA256_DIGEST_SIZE * 2 -#define AWS_SIGV4_CANONICAL_REQUEST_BUF_LEN 4096 // Increased for large session tokens -#define AWS_SIGV4_STRING_TO_SIGN_BUF_LEN 1024 #define AWS_SIGV4_KEY_BUF_LEN 64 -#define AWS_SIGV4_MAX_NUM_QUERY_COMPONENTS 50 #define HMAC_MAX_MD_CBLOCK 128 -/* host, x-amz-date and x-amz-content-sha256 are appended to the caller's headers */ -#define AWS_SIGV4_MAX_NUM_CANONICAL_HEADERS (AWS_SIGV4_MAX_NUM_HEADERS + 3) /* writable space left in an output buffer ending at last (exclusive) */ static unsigned int space_left(unsigned char *str, unsigned char *last) @@ -148,18 +143,23 @@ static unsigned char *construct_query_str(unsigned char *dst_cstr, return dst_cstr; } -static void parse_query_params(aws_sigv4_str_t *query_str, - aws_sigv4_kv_t *query_params, - size_t *arr_len) +/* parses at most AWS_SIGV4_MAX_NUM_QUERY_COMPONENTS components into query_params and + returns the number parsed; a longer query string is rejected by the caller rather + than silently signed without its trailing components */ +static int parse_query_params(aws_sigv4_str_t *query_str, + aws_sigv4_kv_t *query_params, + size_t *arr_len) { + *arr_len = 0; if (aws_sigv4_empty_str(query_str) || query_params == NULL) { - arr_len = 0; - return; + return AWS_SIGV4_OK; } size_t idx = 0; unsigned char *c_ptr = query_str->data; - query_params[0].key.data = c_ptr; + /* a component without '=' never reaches the assignments below, so start every one + of them empty rather than reading whatever the scratch buffer happened to hold */ + query_params[0] = (aws_sigv4_kv_t){.key = {.data = c_ptr}}; /* here we assume query string are well-formed */ while (c_ptr != query_str->data + query_str->len) { @@ -170,16 +170,27 @@ static void parse_query_params(aws_sigv4_str_t *query_str, } else if (*c_ptr == '&') { - query_params[idx].value.len = c_ptr - query_params[idx].value.data; - query_params[++idx].key.data = ++c_ptr; + if (idx + 1 >= AWS_SIGV4_MAX_NUM_QUERY_COMPONENTS) + { + return AWS_SIGV4_BUFFER_OVERFLOW_ERROR; + } + if (query_params[idx].value.data != NULL) + { + query_params[idx].value.len = c_ptr - query_params[idx].value.data; + } + query_params[++idx] = (aws_sigv4_kv_t){.key = {.data = ++c_ptr}}; } else { c_ptr++; } } - query_params[idx].value.len = c_ptr - query_params[idx].value.data; + if (query_params[idx].value.data != NULL) + { + query_params[idx].value.len = c_ptr - query_params[idx].value.data; + } *arr_len = idx + 1; + return AWS_SIGV4_OK; } void get_hexdigest(aws_sigv4_str_t *str_in, aws_sigv4_str_t *hex_out) @@ -254,7 +265,7 @@ void get_signed_headers(aws_sigv4_params_t *sigv4_params, aws_sigv4_str_t *signed_headers, unsigned char *last) { - aws_sigv4_str_t headers[AWS_SIGV4_MAX_NUM_CANONICAL_HEADERS]; + aws_sigv4_str_t *headers = sigv4_params->scratch->signed_headers; unsigned num_headers = 0; bool has_amz_content_sha256_header = false; @@ -299,7 +310,7 @@ void get_canonical_headers(aws_sigv4_params_t *sigv4_params, aws_sigv4_str_t *canonical_headers, unsigned char *last) { - aws_sigv4_kv_t headers[AWS_SIGV4_MAX_NUM_CANONICAL_HEADERS]; + aws_sigv4_kv_t *headers = sigv4_params->scratch->canonical_headers; unsigned num_headers = 0; aws_sigv4_kv_t *amz_content_sha256_header = NULL; @@ -366,9 +377,13 @@ int get_canonical_request(aws_sigv4_params_t *sigv4_params, /* query string can be empty */ if (!aws_sigv4_empty_str(&sigv4_params->query_str)) { - aws_sigv4_kv_t query_params[AWS_SIGV4_MAX_NUM_QUERY_COMPONENTS]; + aws_sigv4_kv_t *query_params = sigv4_params->scratch->query_params; size_t query_num = 0; - parse_query_params(&sigv4_params->query_str, query_params, &query_num); + int rc = parse_query_params(&sigv4_params->query_str, query_params, &query_num); + if (rc != AWS_SIGV4_OK) + { + return rc; + } sigv4_params->sort(query_params, query_num, sizeof(aws_sigv4_kv_t), (aws_sigv4_compare_func_t)aws_sigv4_kv_cmp); str = construct_query_str(str, last, query_params, query_num); @@ -450,7 +465,7 @@ int get_string_to_sign(aws_sigv4_params_t *sigv4_params, int aws_sigv4_sign(aws_sigv4_params_t *sigv4_params, aws_sigv4_header_t *auth_header) { int rc = AWS_SIGV4_OK; - if (auth_header == NULL || sigv4_params == NULL || aws_sigv4_empty_str(&sigv4_params->secret_access_key) || aws_sigv4_empty_str(&sigv4_params->access_key_id) || aws_sigv4_empty_str(&sigv4_params->method) || aws_sigv4_empty_str(&sigv4_params->uri) || aws_sigv4_empty_str(&sigv4_params->host) || aws_sigv4_empty_str(&sigv4_params->x_amz_date) || aws_sigv4_empty_str(&sigv4_params->region) || aws_sigv4_empty_str(&sigv4_params->service) || sigv4_params->sort == NULL || sigv4_params->sha256 == NULL || sigv4_params->hmac_sha256 == NULL) + if (auth_header == NULL || sigv4_params == NULL || aws_sigv4_empty_str(&sigv4_params->secret_access_key) || aws_sigv4_empty_str(&sigv4_params->access_key_id) || aws_sigv4_empty_str(&sigv4_params->method) || aws_sigv4_empty_str(&sigv4_params->uri) || aws_sigv4_empty_str(&sigv4_params->host) || aws_sigv4_empty_str(&sigv4_params->x_amz_date) || aws_sigv4_empty_str(&sigv4_params->region) || aws_sigv4_empty_str(&sigv4_params->service) || sigv4_params->sort == NULL || sigv4_params->sha256 == NULL || sigv4_params->hmac_sha256 == NULL || sigv4_params->scratch == NULL) { rc = AWS_SIGV4_INVALID_INPUT_ERROR; goto err; @@ -500,7 +515,8 @@ int aws_sigv4_sign(aws_sigv4_params_t *sigv4_params, aws_sigv4_header_t *auth_he goto err; } /* Task 1: Create a canonical request */ - unsigned char canonical_request_buf[AWS_SIGV4_CANONICAL_REQUEST_BUF_LEN] = {0}; + unsigned char *canonical_request_buf = sigv4_params->scratch->canonical_request; + memset(canonical_request_buf, 0, AWS_SIGV4_CANONICAL_REQUEST_BUF_LEN); aws_sigv4_str_t canonical_request = {.data = canonical_request_buf}; rc = get_canonical_request(sigv4_params, &canonical_request, canonical_request_buf + AWS_SIGV4_CANONICAL_REQUEST_BUF_LEN - 1); @@ -509,7 +525,8 @@ int aws_sigv4_sign(aws_sigv4_params_t *sigv4_params, aws_sigv4_header_t *auth_he goto err; } /* Task 2: Create a string to sign */ - unsigned char string_to_sign_buf[AWS_SIGV4_STRING_TO_SIGN_BUF_LEN] = {0}; + unsigned char *string_to_sign_buf = sigv4_params->scratch->string_to_sign; + memset(string_to_sign_buf, 0, AWS_SIGV4_STRING_TO_SIGN_BUF_LEN); aws_sigv4_str_t string_to_sign = {.data = string_to_sign_buf}; rc = get_string_to_sign(sigv4_params, &sigv4_params->x_amz_date, &credential_scope, &canonical_request, &string_to_sign, diff --git a/sigv4.h b/sigv4.h index fcf87c9..cd92fb0 100644 --- a/sigv4.h +++ b/sigv4.h @@ -21,6 +21,11 @@ #define AWS_SIGV4_OK 0 #define AWS_SIGV4_MAX_NUM_HEADERS 24 #define AWS_SIGV4_AUTH_HEADER_MAX_LEN 2048 +#define AWS_SIGV4_CANONICAL_REQUEST_BUF_LEN 4096 // large enough for large session tokens +#define AWS_SIGV4_STRING_TO_SIGN_BUF_LEN 1024 +#define AWS_SIGV4_MAX_NUM_QUERY_COMPONENTS 50 +/* host, x-amz-date and x-amz-content-sha256 are appended to the caller's headers */ +#define AWS_SIGV4_MAX_NUM_CANONICAL_HEADERS (AWS_SIGV4_MAX_NUM_HEADERS + 3) typedef struct aws_sigv4_str_s { @@ -34,6 +39,20 @@ typedef struct aws_sigv4_kv_s aws_sigv4_str_t value; } aws_sigv4_kv_t; +/* Working buffers used while signing. This is ~8 KB, which does not fit on the + stack of every caller -- a Linux kernel task stack is 16 KB in total -- so the + caller owns the storage and passes it in via aws_sigv4_params_t.scratch. The + library itself never allocates. Its contents are meaningless to the caller and + need no initialisation; aws_sigv4_sign() only reads what it has written. */ +typedef struct aws_sigv4_scratch_s +{ + unsigned char canonical_request[AWS_SIGV4_CANONICAL_REQUEST_BUF_LEN]; + unsigned char string_to_sign[AWS_SIGV4_STRING_TO_SIGN_BUF_LEN]; + aws_sigv4_kv_t query_params[AWS_SIGV4_MAX_NUM_QUERY_COMPONENTS]; + aws_sigv4_kv_t canonical_headers[AWS_SIGV4_MAX_NUM_CANONICAL_HEADERS]; + aws_sigv4_str_t signed_headers[AWS_SIGV4_MAX_NUM_CANONICAL_HEADERS]; +} aws_sigv4_scratch_t; + aws_sigv4_str_t aws_sigv4_string(const unsigned char *cstr); int aws_sigv4_strcmp(aws_sigv4_str_t *str1, aws_sigv4_str_t *str2); @@ -86,6 +105,11 @@ typedef struct aws_sigv4_params_s const unsigned char *key, size_t key_len, unsigned char *out, size_t *out_len); + /* Caller-owned working buffers, see aws_sigv4_scratch_t. Must be non-NULL; + aws_sigv4_sign() rejects the request with AWS_SIGV4_INVALID_INPUT_ERROR + otherwise. */ + aws_sigv4_scratch_t *scratch; + } aws_sigv4_params_t; /** @brief get hex encoding of a given string diff --git a/test.c b/test.c index f4504d0..cd43dcb 100644 --- a/test.c +++ b/test.c @@ -4,6 +4,10 @@ #include "sigv4.h" +/* the signer works out of caller-owned buffers; ~8 KB, so keep it out of + automatic storage -- on a kernel stack it would not fit at all */ +static aws_sigv4_scratch_t scratch; + int HMAC_SHA256(const unsigned char *data, size_t data_len, const unsigned char *key, size_t key_len, unsigned char *out, size_t *out_len) @@ -29,6 +33,7 @@ START_TEST(AwsSigv4Test_AwsSigv4Sign) .hmac_sha256 = HMAC_SHA256, .sha256 = (void *)SHA256, .sort = qsort, + .scratch = &scratch, }; char auth_buf[AWS_SIGV4_AUTH_HEADER_MAX_LEN] = {0}; @@ -80,6 +85,7 @@ START_TEST(AwsSigv4Test_AdditionalHeadersAreSigned) .hmac_sha256 = HMAC_SHA256, .sha256 = (void *)SHA256, .sort = qsort, + .scratch = &scratch, }; char auth_buf[AWS_SIGV4_AUTH_HEADER_MAX_LEN] = {0}; @@ -131,6 +137,7 @@ START_TEST(AwsSigv4Test_PrefixHeaderNamesAreOrdered) .hmac_sha256 = HMAC_SHA256, .sha256 = (void *)SHA256, .sort = qsort, + .scratch = &scratch, }; char auth_buf[AWS_SIGV4_AUTH_HEADER_MAX_LEN] = {0}; @@ -165,6 +172,7 @@ START_TEST(AwsSigv4Test_TooLargeCanonicalRequestFails) .hmac_sha256 = HMAC_SHA256, .sha256 = (void *)SHA256, .sort = qsort, + .scratch = &scratch, }; unsigned int i; @@ -187,6 +195,73 @@ START_TEST(AwsSigv4Test_TooLargeCanonicalRequestFails) } END_TEST +/* the scratch buffers are mandatory: without them the signer has nowhere to build the + canonical request, so it must refuse rather than dereference NULL */ +START_TEST(AwsSigv4Test_MissingScratchIsRejected) +{ + aws_sigv4_params_t sigv4_params = { + .access_key_id = aws_sigv4_string((unsigned char *)"AKIDEXAMPLE"), + .secret_access_key = aws_sigv4_string((unsigned char *)"wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY"), + .method = aws_sigv4_string((unsigned char *)"GET"), + .uri = aws_sigv4_string((unsigned char *)"/"), + .host = aws_sigv4_string((unsigned char *)"riptides-logos.s3.eu-central-1.amazonaws.com"), + .region = aws_sigv4_string((unsigned char *)"eu-central-1"), + .service = aws_sigv4_string((unsigned char *)"s3"), + .x_amz_date = aws_sigv4_string((unsigned char *)"20260803T120000Z"), + .hmac_sha256 = HMAC_SHA256, + .sha256 = (void *)SHA256, + .sort = qsort, + .scratch = NULL, + }; + + char auth_buf[AWS_SIGV4_AUTH_HEADER_MAX_LEN] = {0}; + aws_sigv4_header_t auth_header = { + .value = aws_sigv4_string((unsigned char *)auth_buf)}; + + int rc = aws_sigv4_sign(&sigv4_params, &auth_header); + ck_assert_int_eq(rc, AWS_SIGV4_INVALID_INPUT_ERROR); +} +END_TEST + +/* a query string with more components than the parser can hold must fail the signing: + silently dropping the tail would write past the scratch array and produce a + canonical request that does not match what the request actually carries */ +START_TEST(AwsSigv4Test_TooManyQueryParamsFails) +{ + static char query[8 * 1024]; + char *w = query; + int i; + for (i = 0; i < AWS_SIGV4_MAX_NUM_QUERY_COMPONENTS + 5; i++) + { + w += snprintf(w, sizeof(query) - (w - query), "%sk%02d=v%02d", i ? "&" : "", i, i); + } + + aws_sigv4_params_t sigv4_params = { + .access_key_id = aws_sigv4_string((unsigned char *)"AKIDEXAMPLE"), + .secret_access_key = aws_sigv4_string((unsigned char *)"wJalrXUtnFEMI/K7MDENG+bPxRfiCYEXAMPLEKEY"), + .method = aws_sigv4_string((unsigned char *)"GET"), + .uri = aws_sigv4_string((unsigned char *)"/"), + .query_str = aws_sigv4_string((unsigned char *)query), + .host = aws_sigv4_string((unsigned char *)"riptides-logos.s3.eu-central-1.amazonaws.com"), + .region = aws_sigv4_string((unsigned char *)"eu-central-1"), + .service = aws_sigv4_string((unsigned char *)"s3"), + .x_amz_date = aws_sigv4_string((unsigned char *)"20260803T120000Z"), + .unsigned_payload = true, + .hmac_sha256 = HMAC_SHA256, + .sha256 = (void *)SHA256, + .sort = qsort, + .scratch = &scratch, + }; + + char auth_buf[AWS_SIGV4_AUTH_HEADER_MAX_LEN] = {0}; + aws_sigv4_header_t auth_header = { + .value = aws_sigv4_string((unsigned char *)auth_buf)}; + + int rc = aws_sigv4_sign(&sigv4_params, &auth_header); + ck_assert_int_eq(rc, AWS_SIGV4_BUFFER_OVERFLOW_ERROR); +} +END_TEST + Suite *aws_sigv4_test_suite(void) { Suite *s; @@ -197,6 +272,8 @@ Suite *aws_sigv4_test_suite(void) tcase_add_test(tc_aws_sigv4_sign, AwsSigv4Test_AdditionalHeadersAreSigned); tcase_add_test(tc_aws_sigv4_sign, AwsSigv4Test_PrefixHeaderNamesAreOrdered); tcase_add_test(tc_aws_sigv4_sign, AwsSigv4Test_TooLargeCanonicalRequestFails); + tcase_add_test(tc_aws_sigv4_sign, AwsSigv4Test_MissingScratchIsRejected); + tcase_add_test(tc_aws_sigv4_sign, AwsSigv4Test_TooManyQueryParamsFails); suite_add_tcase(s, tc_aws_sigv4_sign); return s; } From 6b4daa4db0ac1ffeed40d808abe60d42550f0cb2 Mon Sep 17 00:00:00 2001 From: Nandor Kracser Date: Tue, 4 Aug 2026 16:09:21 +0200 Subject: [PATCH 2/2] fix: keep the scratch-dependent helpers internal to sigv4.c get_signed_headers(), get_canonical_headers() and get_canonical_request() were declared in sigv4.h, so after the previous commit a caller could invoke them directly with params.scratch == NULL and get a NULL dereference. They are static now and no longer exported. Making them fall back to a local array when scratch is absent would have been the other option, but it defeats the point: the compiler reserves the array in the prologue whichever branch runs, so the frame stays large even for callers that do pass scratch. A 50-entry conditional fallback measures 1648 bytes against 64 for the scratch-only form -- the kernel caller would still overflow. These three are only meaningful part-way through a signing pass and none of them is usable on its own; aws_sigv4_sign() owns the sequencing and already validates scratch. get_hexdigest(), get_hex_sha256(), get_signing_key(), get_credential_scope() and get_string_to_sign() stay exported -- they take their output buffers from the caller and touch no scratch. --- sigv4.c | 6 +++--- sigv4.h | 34 ++++------------------------------ 2 files changed, 7 insertions(+), 33 deletions(-) diff --git a/sigv4.c b/sigv4.c index bf2fc0d..d01f944 100644 --- a/sigv4.c +++ b/sigv4.c @@ -261,7 +261,7 @@ void get_credential_scope(aws_sigv4_params_t *sigv4_params, credential_scope->len = str - credential_scope->data; } -void get_signed_headers(aws_sigv4_params_t *sigv4_params, +static void get_signed_headers(aws_sigv4_params_t *sigv4_params, aws_sigv4_str_t *signed_headers, unsigned char *last) { @@ -306,7 +306,7 @@ void get_signed_headers(aws_sigv4_params_t *sigv4_params, signed_headers->len = str - signed_headers->data; } -void get_canonical_headers(aws_sigv4_params_t *sigv4_params, +static void get_canonical_headers(aws_sigv4_params_t *sigv4_params, aws_sigv4_str_t *canonical_headers, unsigned char *last) { @@ -362,7 +362,7 @@ void get_canonical_headers(aws_sigv4_params_t *sigv4_params, canonical_headers->len = str - canonical_headers->data; } -int get_canonical_request(aws_sigv4_params_t *sigv4_params, +static int get_canonical_request(aws_sigv4_params_t *sigv4_params, aws_sigv4_str_t *canonical_request, unsigned char *last) { diff --git a/sigv4.h b/sigv4.h index cd92fb0..177ce3b 100644 --- a/sigv4.h +++ b/sigv4.h @@ -148,36 +148,10 @@ void get_credential_scope(aws_sigv4_params_t *sigv4_params, aws_sigv4_str_t *credential_scope, unsigned char *last); -/** @brief get signed headers string - * - * @param[in] sigv4_params Pointer to a struct of sigv4 parameters - * @param[out] signed_headers Struct of buffer to store signed headers string - * @param[in] last End of the writable output buffer (exclusive) - */ -void get_signed_headers(aws_sigv4_params_t *sigv4_params, - aws_sigv4_str_t *signed_headers, - unsigned char *last); - -/** @brief get canonical headers string - * - * @param[in] sigv4_params Pointer to a struct of sigv4 parameters - * @param[out] canonical_headers Struct of buffer to store canonical headers string - * @param[in] last End of the writable output buffer (exclusive) - */ -void get_canonical_headers(aws_sigv4_params_t *sigv4_params, - aws_sigv4_str_t *canonical_headers, - unsigned char *last); - -/** @brief get canonical request string - * - * @param[in] sigv4_params Pointer to a struct of sigv4 parameters - * @param[out] canonical_request Struct of buffer to store canonical request string - * @param[in] last End of the writable output buffer (exclusive) - * @return Status code where zero for success and non-zero for failure - */ -int get_canonical_request(aws_sigv4_params_t *sigv4_params, - aws_sigv4_str_t *canonical_request, - unsigned char *last); +/* get_signed_headers(), get_canonical_headers() and get_canonical_request() build + their intermediate results in aws_sigv4_params_t.scratch and are only meaningful + part-way through a signing pass, so they are internal to sigv4.c rather than + exported. Call aws_sigv4_sign(), which owns the sequencing and validates scratch. */ /** @brief get string to sign *