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.
.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), "

View File

@ -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;
}