From 7f8e6da6dc0befc94a2adc3be3aac7d3a308c060 Mon Sep 17 00:00:00 2001 From: Casey Bodley Date: Tue, 25 Oct 2022 18:46:58 -0400 Subject: [PATCH] aws_sigv4: consult x-%s-content-sha256 for payload hash `Curl_output_aws_sigv4()` doesn't always have the whole payload in memory to generate a real payload hash. this commit allows the user to pass in a header like `x-amz-content-sha256` to provide their desired payload hash some services like s3 require this header, and may support other values like s3's `UNSIGNED-PAYLOAD` and `STREAMING-AWS4-HMAC-SHA256-PAYLOAD` with special semantics. servers use this header's value as the payload hash during signature validation, so it must match what the client uses to generate the signature CURLOPT_AWS_SIGV4.3 now describes the content-sha256 interaction Signed-off-by: Casey Bodley Closes #9804 --- docs/libcurl/opts/CURLOPT_AWS_SIGV4.3 | 8 +++- lib/http_aws_sigv4.c | 66 ++++++++++++++++++++++----- 2 files changed, 61 insertions(+), 13 deletions(-) diff --git a/docs/libcurl/opts/CURLOPT_AWS_SIGV4.3 b/docs/libcurl/opts/CURLOPT_AWS_SIGV4.3 index 415f86e965..900e1840fb 100644 --- a/docs/libcurl/opts/CURLOPT_AWS_SIGV4.3 +++ b/docs/libcurl/opts/CURLOPT_AWS_SIGV4.3 @@ -53,7 +53,7 @@ Calling \fICURLOPT_HTTPAUTH(3)\fP with CURLAUTH_AWS_SIGV4 is the same as calling this with \fB"aws:amz"\fP in parameter. .PP Example with "Test:Try", when curl will do the algorithm, it will generate -\fB"TEST-HMAC-SHA256"\đP for "Algorithm", \fB"x-try-date"\fP and +\fB"TEST-HMAC-SHA256"\fP for "Algorithm", \fB"x-try-date"\fP and \fB"X-Try-Date"\fP for "date", \fB"test4_request"\fP for "request type", \fB"SignedHeaders=content-type;host;x-try-date"\fP for "signed headers" .PP @@ -95,5 +95,11 @@ Returns CURLE_OK if the option is supported, and CURLE_UNKNOWN_OPTION if not. This option overrides the other auth types you might have set in \fICURLOPT_HTTPAUTH(3)\fP which should be highlighted as this makes this auth method special. This method cannot be combined with other auth types. +.PP +A sha256 checksum of the request payload is used as input to the signature +calculation. For POST requests, this is a checksum of the provided +\fICURLOPT_POSTFIELDS(3)\fP. Otherwise, it's the checksum of an empty buffer. +For requests like PUT, you can provide your own checksum in a HTTP header named +\fBx-provider2-content-sha256\fP. .SH "SEE ALSO" .BR CURLOPT_HEADEROPT "(3), " CURLOPT_HTTPHEADER "(3), " diff --git a/lib/http_aws_sigv4.c b/lib/http_aws_sigv4.c index 70832c0097..398feff921 100644 --- a/lib/http_aws_sigv4.c +++ b/lib/http_aws_sigv4.c @@ -266,6 +266,40 @@ fail: return ret; } +#define CONTENT_SHA256_KEY_LEN (MAX_SIGV4_LEN + sizeof("X--Content-Sha256")) + +/* try to parse a payload hash from the content-sha256 header */ +static char *parse_content_sha_hdr(struct Curl_easy *data, + const char *provider1, + size_t *value_len) +{ + char key[CONTENT_SHA256_KEY_LEN]; + size_t key_len; + char *value; + size_t len; + + key_len = msnprintf(key, sizeof(key), "x-%s-content-sha256", provider1); + + value = Curl_checkheaders(data, key, key_len); + if(!value) + return NULL; + + value = strchr(value, ':'); + if(!value) + return NULL; + ++value; + + while(*value && ISBLANK(*value)) + ++value; + + len = strlen(value); + while(len > 0 && ISBLANK(value[len-1])) + --len; + + *value_len = len; + return value; +} + CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) { CURLcode ret = CURLE_OUT_OF_MEMORY; @@ -284,6 +318,8 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) struct dynbuf canonical_headers; struct dynbuf signed_headers; char *date_header = NULL; + char *payload_hash = NULL; + size_t payload_hash_len = 0; const char *post_data = data->set.postfields; size_t post_data_len = 0; unsigned char sha_hash[32]; @@ -401,17 +437,23 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) memcpy(date, timestamp, sizeof(date)); date[sizeof(date) - 1] = 0; - if(post_data) { - if(data->set.postfieldsize < 0) - post_data_len = strlen(post_data); - else - post_data_len = (size_t)data->set.postfieldsize; - } - if(Curl_sha256it(sha_hash, (const unsigned char *) post_data, - post_data_len)) - goto fail; + payload_hash = parse_content_sha_hdr(data, provider1, &payload_hash_len); - sha256_to_hex(sha_hex, sha_hash, sizeof(sha_hex)); + if(!payload_hash) { + if(post_data) { + if(data->set.postfieldsize < 0) + post_data_len = strlen(post_data); + else + post_data_len = (size_t)data->set.postfieldsize; + } + if(Curl_sha256it(sha_hash, (const unsigned char *) post_data, + post_data_len)) + goto fail; + + sha256_to_hex(sha_hex, sha_hash, sizeof(sha_hex)); + payload_hash = sha_hex; + payload_hash_len = strlen(sha_hex); + } { Curl_HttpReq httpreq; @@ -425,13 +467,13 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) "%s\n" /* CanonicalQueryString */ "%s\n" /* CanonicalHeaders */ "%s\n" /* SignedHeaders */ - "%s", /* HashedRequestPayload in hex */ + "%.*s", /* HashedRequestPayload in hex */ method, data->state.up.path, data->state.up.query ? data->state.up.query : "", Curl_dyn_ptr(&canonical_headers), Curl_dyn_ptr(&signed_headers), - sha_hex); + (int)payload_hash_len, payload_hash); if(!canonical_request) goto fail; }