]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
aws_sigv4: fix canon order for headers with same prefix
authorAustin Moore <austincodes@fastmail.com>
Sun, 4 Aug 2024 03:43:45 +0000 (23:43 -0400)
committerDaniel Stenberg <daniel@haxx.se>
Mon, 5 Aug 2024 21:32:10 +0000 (23:32 +0200)
If a request containing two headers that have equivalent prefixes (ex.
"x-amz-meta-test:test" and "x-amz-meta-test-two:test2") AWS expects the
header with the shorter name to come first. The previous implementation
used `strcmp` on the full header. Using the example, this would result
in a comparison between the ':' and '-' chars and sort
"x-amz-meta-test-two" before "x-amz-meta-test", which produces a
different "StringToSign" than the one calculated by AWS.

Test 1976 verifies

Closes #14370

lib/http_aws_sigv4.c
tests/data/Makefile.am
tests/data/test1976 [new file with mode: 0644]

index 9e4f72016560fadd514c22178836fe115f16b8f9..7fe24a4c6fa56b20d34ce6dee52ce7cded8106a4 100644 (file)
@@ -129,6 +129,37 @@ static void trim_headers(struct curl_slist *head)
 /* string been x-PROVIDER-date:TIMESTAMP, I need +1 for ':' */
 #define DATE_FULL_HDR_LEN (DATE_HDR_KEY_LEN + TIMESTAMP_SIZE + 1)
 
+/* alphabetically compare two headers by their name, expecting
+   headers to use ':' at this point */
+static int compare_header_names(const char *a, const char *b)
+{
+  const char *colon_a;
+  const char *colon_b;
+  size_t len_a;
+  size_t len_b;
+  size_t min_len;
+  int cmp;
+
+  colon_a = strchr(a, ':');
+  colon_b = strchr(b, ':');
+
+  DEBUGASSERT(colon_a);
+  DEBUGASSERT(colon_b);
+
+  len_a = colon_a ? (size_t)(colon_a - a) : strlen(a);
+  len_b = colon_b ? (size_t)(colon_b - b) : strlen(b);
+
+  min_len = (len_a < len_b) ? len_a : len_b;
+
+  cmp = strncmp(a, b, min_len);
+
+  /* return the shorter of the two if one is shorter */
+  if(!cmp)
+    return (int)(len_a - len_b);
+
+  return cmp;
+}
+
 /* timestamp should point to a buffer of at last TIMESTAMP_SIZE bytes */
 static CURLcode make_headers(struct Curl_easy *data,
                              const char *hostname,
@@ -267,13 +298,13 @@ static CURLcode make_headers(struct Curl_easy *data,
     *date_header = NULL;
   }
 
-  /* alpha-sort in a case sensitive manner */
+  /* alpha-sort by header name in a case sensitive manner */
   do {
     again = 0;
     for(l = head; l; l = l->next) {
       struct curl_slist *next = l->next;
 
-      if(next && strcmp(l->data, next->data) > 0) {
+      if(next && compare_header_names(l->data, next->data) > 0) {
         char *tmp = l->data;
 
         l->data = next->data;
index 96de5fdcc3e2fcf67f69c7729951ce46e48d6b11..aa329174a5d79920d47067cdf49a71b06d700887 100644 (file)
@@ -233,7 +233,7 @@ test1916 test1917 test1918 test1919 \
 test1933 test1934 test1935 test1936 test1937 test1938 test1939 test1940 \
 test1941 test1942 test1943 test1944 test1945 test1946 test1947 test1948 \
 test1955 test1956 test1957 test1958 test1959 test1960 test1964 \
-test1970 test1971 test1972 test1973 test1974 test1975 \
+test1970 test1971 test1972 test1973 test1974 test1975 test1976 \
 \
 test2000 test2001 test2002 test2003 test2004 \
 \
diff --git a/tests/data/test1976 b/tests/data/test1976
new file mode 100644 (file)
index 0000000..7c04bc6
--- /dev/null
@@ -0,0 +1,60 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+CURLOPT_AWS_SIGV4
+</keywords>
+</info>
+
+# Server-side
+<reply>
+<data>
+HTTP/1.1 200 OK
+Date: Thu, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Content-Length: 0
+
+</data>
+</reply>
+
+# Client-side
+<client>
+<server>
+http
+</server>
+<features>
+SSL
+Debug
+crypto
+</features>
+<name>
+HTTP AWS_SIGV4 canonical request header sorting test
+</name>
+<command>
+-X PUT -H "X-Amz-Meta-Test-Two: test2" -H "x-amz-meta-test: test" --aws-sigv4 "aws:amz:us-east-1:s3" -u "xxx:yyy" http://%HOSTIP:%HTTPPORT/%TESTNUMBER
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+<strip>
+^User-Agent:.*
+^Content-Length:.*
+^Accept:.*
+</strip>
+<strippart>
+# Strip the actual signature. We only care about header order in this test
+s/Signature=[a-f0-9]{64}/Signature=stripped/
+</strippart>
+<protocol crlf="yes">
+PUT /%TESTNUMBER HTTP/1.1
+Host: %HOSTIP:%HTTPPORT
+Authorization: AWS4-HMAC-SHA256 Credential=xxx/19700101/us-east-1/s3/aws4_request, SignedHeaders=host;x-amz-content-sha256;x-amz-date;x-amz-meta-test;x-amz-meta-test-two, Signature=stripped
+X-Amz-Date: 19700101T000000Z
+x-amz-content-sha256: e3b0c44298fc1c149afbf4c8996fb92427ae41e4649b934ca495991b7852b855
+X-Amz-Meta-Test-Two: test2
+x-amz-meta-test: test
+
+</protocol>
+</verify>
+</testcase>