]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
aws_sigv4: consult x-%s-content-sha256 for payload hash
authorCasey Bodley <cbodley@redhat.com>
Tue, 25 Oct 2022 22:46:58 +0000 (18:46 -0400)
committerDaniel Stenberg <daniel@haxx.se>
Fri, 25 Nov 2022 08:24:07 +0000 (09:24 +0100)
`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

docs/libcurl/opts/CURLOPT_AWS_SIGV4.3
lib/http_aws_sigv4.c

index 415f86e9656f39259ccf73915b482eaf39531987..900e1840fb0bf8cc67bfaee07be31df974292cee 100644 (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), "
index 70832c0097c0d471d40b251e321de6ca1b25b3e8..398feff921bab0a42dee1881948857f158652438 100644 (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;
   }