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 <cbodley@redhat.com>

Closes #9804
This commit is contained in:
Casey Bodley 2022-10-25 18:46:58 -04:00 committed by Daniel Stenberg
parent 4c61a8e8f4
commit 7f8e6da6dc
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
2 changed files with 61 additions and 13 deletions

View File

@ -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. as calling this with \fB"aws:amz"\fP in parameter.
.PP .PP
Example with "Test:Try", when curl will do the algorithm, it will generate 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"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" \fB"SignedHeaders=content-type;host;x-try-date"\fP for "signed headers"
.PP .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 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 \fICURLOPT_HTTPAUTH(3)\fP which should be highlighted as this makes this auth
method special. This method cannot be combined with other auth types. 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" .SH "SEE ALSO"
.BR CURLOPT_HEADEROPT "(3), " CURLOPT_HTTPHEADER "(3), " .BR CURLOPT_HEADEROPT "(3), " CURLOPT_HTTPHEADER "(3), "

View File

@ -266,6 +266,40 @@ fail:
return ret; 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 Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
{ {
CURLcode ret = CURLE_OUT_OF_MEMORY; 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 canonical_headers;
struct dynbuf signed_headers; struct dynbuf signed_headers;
char *date_header = NULL; char *date_header = NULL;
char *payload_hash = NULL;
size_t payload_hash_len = 0;
const char *post_data = data->set.postfields; const char *post_data = data->set.postfields;
size_t post_data_len = 0; size_t post_data_len = 0;
unsigned char sha_hash[32]; 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)); memcpy(date, timestamp, sizeof(date));
date[sizeof(date) - 1] = 0; date[sizeof(date) - 1] = 0;
if(post_data) { payload_hash = parse_content_sha_hdr(data, provider1, &payload_hash_len);
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)); 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; Curl_HttpReq httpreq;
@ -425,13 +467,13 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
"%s\n" /* CanonicalQueryString */ "%s\n" /* CanonicalQueryString */
"%s\n" /* CanonicalHeaders */ "%s\n" /* CanonicalHeaders */
"%s\n" /* SignedHeaders */ "%s\n" /* SignedHeaders */
"%s", /* HashedRequestPayload in hex */ "%.*s", /* HashedRequestPayload in hex */
method, method,
data->state.up.path, data->state.up.path,
data->state.up.query ? data->state.up.query : "", data->state.up.query ? data->state.up.query : "",
Curl_dyn_ptr(&canonical_headers), Curl_dyn_ptr(&canonical_headers),
Curl_dyn_ptr(&signed_headers), Curl_dyn_ptr(&signed_headers),
sha_hex); (int)payload_hash_len, payload_hash);
if(!canonical_request) if(!canonical_request)
goto fail; goto fail;
} }