]> git.ipfire.org Git - thirdparty/curl.git/commitdiff
aws_sigv4: fix header computation
authorMatthias Gatto <matthias.gatto@outscale.com>
Thu, 13 Jan 2022 14:53:52 +0000 (15:53 +0100)
committerDaniel Stenberg <daniel@haxx.se>
Tue, 11 Oct 2022 12:33:46 +0000 (14:33 +0200)
Handle canonical headers and signed headers creation as explained here:
https://docs.aws.amazon.com/general/latest/gr/sigv4-create-canonical-request.html

The algo tells that signed and canonical must contain at last host and
x-amz-date.

So we check whatever thoses are present in the curl http headers list.
If they are, we use the one enter by curl user, otherwise we generate
them.  then we to lower, and remove space from each http headers plus
host and x-amz-date, then sort them all by alphabetical order.

This patch also fix a bug with host header, which was ignoring the port.

Closes #7966

17 files changed:
lib/http_aws_sigv4.c
tests/data/Makefile.inc
tests/data/test1933
tests/data/test1934
tests/data/test1935
tests/data/test1936
tests/data/test1937
tests/data/test1938
tests/data/test1955 [new file with mode: 0644]
tests/libtest/Makefile.inc
tests/libtest/lib1933.c
tests/libtest/lib1934.c
tests/libtest/lib1935.c
tests/libtest/lib1936.c
tests/libtest/lib1937.c
tests/libtest/lib1938.c
tests/libtest/lib1955.c [new file with mode: 0644]

index d144fb817aa9bb9c6e2a7dffa6e48f5b63ceb7f8..edd0ebe470d5c07eabebe1f297ac5d6756870378 100644 (file)
@@ -44,6 +44,8 @@
 #include "curl_memory.h"
 #include "memdebug.h"
 
+#include "slist.h"
+
 #define HMAC_SHA256(k, kl, d, dl, o)        \
   do {                                      \
     ret = Curl_hmacit(Curl_HMAC_SHA256,     \
                       (unsigned int)kl,     \
                       (unsigned char *)d,   \
                       (unsigned int)dl, o); \
-    if(ret != CURLE_OK) {                   \
+    if(ret) {                               \
       goto fail;                            \
     }                                       \
   } while(0)
 
+#define TIMESTAMP_SIZE 17
+
 static void sha256_to_hex(char *dst, unsigned char *sha, size_t dst_l)
 {
   int i;
 
   DEBUGASSERT(dst_l >= 65);
   for(i = 0; i < 32; ++i) {
-    curl_msnprintf(dst + (i * 2), dst_l - (i * 2), "%02x", sha[i]);
+    msnprintf(dst + (i * 2), dst_l - (i * 2), "%02x", sha[i]);
+  }
+}
+
+static char *find_date_hdr(struct Curl_easy *data, const char *sig_hdr)
+{
+  char *tmp = Curl_checkheaders(data, sig_hdr, strlen(sig_hdr));
+
+  if(tmp)
+    return tmp;
+  return Curl_checkheaders(data, STRCONST("Date"));
+}
+
+/* remove whitespace, and lowercase all headers */
+static void trim_headers(struct curl_slist *head)
+{
+  struct curl_slist *l;
+  for(l = head; l; l = l->next) {
+    char *value; /* to read from */
+    char *store;
+    size_t colon = strcspn(l->data, ":");
+    Curl_strntolower(l->data, l->data, colon);
+
+    value = &l->data[colon];
+    if(!*value)
+      continue;
+    ++value;
+    store = value;
+
+    /* skip leading whitespace */
+    while(*value && ISBLANK(*value))
+      value++;
+
+    while(*value) {
+      int space = 0;
+      while(*value && ISBLANK(*value)) {
+        value++;
+        space++;
+      }
+      if(space) {
+        /* replace any number of consecutive whitespace with a single space,
+           unless at the end of the string, then nothing */
+        if(*value)
+          *store++ = ' ';
+      }
+      else
+        *store++ = *value++;
+    }
+    *store = 0; /* null terminate */
   }
 }
 
+/* maximum lenth for the aws sivg4 parts */
+#define MAX_SIGV4_LEN 64
+#define MAX_SIGV4_LEN_TXT "64"
+
+#define DATE_HDR_KEY_LEN (MAX_SIGV4_LEN + sizeof("X--Date"))
+
+/* FQDN + host: */
+#define FULL_HOST_LEN (255 + sizeof("host:"))
+
+/* string been x-PROVIDER-date:TIMESTAMP, I need +1 for ':' */
+#define DATE_FULL_HDR_LEN (DATE_HDR_KEY_LEN + TIMESTAMP_SIZE + 1)
+
+/* timestamp should point to a buffer of at last TIMESTAMP_SIZE bytes */
+static CURLcode make_headers(struct Curl_easy *data,
+                             const char *hostname,
+                             char *timestamp,
+                             char *provider1,
+                             char **date_header,
+                             struct dynbuf *canonical_headers,
+                             struct dynbuf *signed_headers)
+{
+  char date_hdr_key[DATE_HDR_KEY_LEN];
+  char date_full_hdr[DATE_FULL_HDR_LEN];
+  struct curl_slist *head = NULL;
+  struct curl_slist *tmp_head = NULL;
+  CURLcode ret = CURLE_OUT_OF_MEMORY;
+  struct curl_slist *l;
+  int again = 1;
+
+  /* provider1 mid */
+  Curl_strntolower(provider1, provider1, strlen(provider1));
+  provider1[0] = Curl_raw_toupper(provider1[0]);
+
+  msnprintf(date_hdr_key, DATE_HDR_KEY_LEN, "X-%s-Date", provider1);
+
+  /* provider1 lowercase */
+  Curl_strntolower(provider1, provider1, 1); /* first byte only */
+  msnprintf(date_full_hdr, DATE_FULL_HDR_LEN,
+            "x-%s-date:%s", provider1, timestamp);
+
+  if(Curl_checkheaders(data, STRCONST("Host"))) {
+    head = NULL;
+  }
+  else {
+    char full_host[FULL_HOST_LEN];
+
+    if(data->state.aptr.host) {
+      size_t pos;
+
+      if(strlen(data->state.aptr.host) > FULL_HOST_LEN) {
+        ret = CURLE_URL_MALFORMAT;
+        goto fail;
+      }
+      strcpy(full_host, data->state.aptr.host);
+      /* remove /r/n as the separator for canonical request must be '\n' */
+      pos = strcspn(full_host, "\n\r");
+      full_host[pos] = 0;
+    }
+    else {
+      if(strlen(hostname) > FULL_HOST_LEN) {
+        ret = CURLE_URL_MALFORMAT;
+        goto fail;
+      }
+      msnprintf(full_host, FULL_HOST_LEN, "host:%s", hostname);
+    }
+
+    head = curl_slist_append(NULL, full_host);
+    if(!head)
+      goto fail;
+  }
+
+
+  for(l = data->set.headers; l; l = l->next) {
+    tmp_head = curl_slist_append(head, l->data);
+    if(!tmp_head)
+      goto fail;
+    head = tmp_head;
+  }
+
+  trim_headers(head);
+
+  *date_header = find_date_hdr(data, date_hdr_key);
+  if(!*date_header) {
+    tmp_head = curl_slist_append(head, date_full_hdr);
+    if(!tmp_head)
+      goto fail;
+    head = tmp_head;
+    *date_header = curl_maprintf("%s: %s", date_hdr_key, timestamp);
+  }
+  else {
+    char *value;
+
+    *date_header = strdup(*date_header);
+    if(!*date_header)
+      goto fail;
+
+    value = strchr(*date_header, ':');
+    if(!value)
+      goto fail;
+    ++value;
+    while(ISBLANK(*value))
+      ++value;
+    strncpy(timestamp, value, TIMESTAMP_SIZE - 1);
+    timestamp[TIMESTAMP_SIZE - 1] = 0;
+  }
+
+  /* alpha-sort 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) {
+        char *tmp = l->data;
+
+        l->data = next->data;
+        next->data = tmp;
+        again = 1;
+      }
+    }
+  } while(again);
+
+  for(l = head; l; l = l->next) {
+    char *tmp;
+
+    if(Curl_dyn_add(canonical_headers, l->data))
+      goto fail;
+    if(Curl_dyn_add(canonical_headers, "\n"))
+      goto fail;
+
+    tmp = strchr(l->data, ':');
+    if(tmp)
+      *tmp = 0;
+
+    if(l != head) {
+      if(Curl_dyn_add(signed_headers, ";"))
+        goto fail;
+    }
+    if(Curl_dyn_add(signed_headers, l->data))
+      goto fail;
+  }
+
+  ret = CURLE_OK;
+fail:
+  curl_slist_free_all(head);
+
+  return ret;
+}
+
 CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
 {
   CURLcode ret = CURLE_OUT_OF_MEMORY;
   struct connectdata *conn = data->conn;
   size_t len;
-  const char *tmp0;
-  const char *tmp1;
-  char *provider0_low = NULL;
-  char *provider0_up = NULL;
-  char *provider1_low = NULL;
-  char *provider1_mid = NULL;
-  char *region = NULL;
-  char *service = NULL;
+  const char *arg;
+  char provider0[MAX_SIGV4_LEN + 1]="";
+  char provider1[MAX_SIGV4_LEN + 1]="";
+  char region[MAX_SIGV4_LEN + 1]="";
+  char service[MAX_SIGV4_LEN + 1]="";
   const char *hostname = conn->host.name;
-#ifdef DEBUGBUILD
-  char *force_timestamp;
-#endif
   time_t clock;
   struct tm tm;
-  char timestamp[17];
+  char timestamp[TIMESTAMP_SIZE];
   char date[9];
-  const char *content_type = Curl_checkheaders(data, STRCONST("Content-Type"));
-  char *canonical_headers = NULL;
-  char *signed_headers = NULL;
-  Curl_HttpReq httpreq;
-  const char *method;
-  size_t post_data_len;
-  const char *post_data = data->set.postfields ? data->set.postfields : "";
+  struct dynbuf canonical_headers;
+  struct dynbuf signed_headers;
+  char *date_header = NULL;
+  const char *post_data = data->set.postfields;
+  size_t post_data_len = 0;
   unsigned char sha_hash[32];
   char sha_hex[65];
   char *canonical_request = NULL;
@@ -101,10 +294,9 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
   char *credential_scope = NULL;
   char *str_to_sign = NULL;
   const char *user = data->state.aptr.user ? data->state.aptr.user : "";
-  const char *passwd = data->state.aptr.passwd ? data->state.aptr.passwd : "";
   char *secret = NULL;
-  unsigned char tmp_sign0[32] = {0};
-  unsigned char tmp_sign1[32] = {0};
+  unsigned char sign0[32] = {0};
+  unsigned char sign1[32] = {0};
   char *auth_headers = NULL;
 
   DEBUGASSERT(!proxy);
@@ -115,6 +307,10 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
     return CURLE_OK;
   }
 
+  /* we init thoses buffers here, so goto fail will free initialized dynbuf */
+  Curl_dyn_init(&canonical_headers, CURL_MAX_HTTP_HEADER);
+  Curl_dyn_init(&signed_headers, CURL_MAX_HTTP_HEADER);
+
   /*
    * Parameters parsing
    * Google and Outscale use the same OSC or GOOG,
@@ -122,223 +318,154 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
    * AWS is the default because most of non-amazon providers
    * are still using aws:amz as a prefix.
    */
-  tmp0 = data->set.str[STRING_AWS_SIGV4] ?
+  arg = data->set.str[STRING_AWS_SIGV4] ?
     data->set.str[STRING_AWS_SIGV4] : "aws:amz";
-  tmp1 = strchr(tmp0, ':');
-  len = tmp1 ? (size_t)(tmp1 - tmp0) : strlen(tmp0);
-  if(len < 1) {
-    infof(data, "first provider can't be empty");
+
+  /* provider1[:provider2[:region[:service]]]
+
+     No string can be longer than N bytes of non-whitespace
+   */
+  (void)sscanf(arg, "%" MAX_SIGV4_LEN_TXT "[^:]"
+               ":%" MAX_SIGV4_LEN_TXT "[^:]"
+               ":%" MAX_SIGV4_LEN_TXT "[^:]"
+               ":%" MAX_SIGV4_LEN_TXT "s",
+               provider0, provider1, region, service);
+  if(!provider0[0]) {
+    failf(data, "first provider can't be empty");
     ret = CURLE_BAD_FUNCTION_ARGUMENT;
     goto fail;
   }
-  provider0_low = malloc(len + 1);
-  provider0_up = malloc(len + 1);
-  if(!provider0_low || !provider0_up) {
-    goto fail;
-  }
-  Curl_strntolower(provider0_low, tmp0, len);
-  provider0_low[len] = '\0';
-  Curl_strntoupper(provider0_up, tmp0, len);
-  provider0_up[len] = '\0';
-
-  if(tmp1) {
-    tmp0 = tmp1 + 1;
-    tmp1 = strchr(tmp0, ':');
-    len = tmp1 ? (size_t)(tmp1 - tmp0) : strlen(tmp0);
-    if(len < 1) {
-      infof(data, "second provider can't be empty");
-      ret = CURLE_BAD_FUNCTION_ARGUMENT;
-      goto fail;
-    }
-    provider1_low = malloc(len + 1);
-    provider1_mid = malloc(len + 1);
-    if(!provider1_low || !provider1_mid) {
-      goto fail;
-    }
-    Curl_strntolower(provider1_low, tmp0, len);
-    provider1_low[len] = '\0';
-    Curl_strntolower(provider1_mid, tmp0, len);
-    provider1_mid[0] = Curl_raw_toupper(provider1_mid[0]);
-    provider1_mid[len] = '\0';
-
-    if(tmp1) {
-      tmp0 = tmp1 + 1;
-      tmp1 = strchr(tmp0, ':');
-      len = tmp1 ? (size_t)(tmp1 - tmp0) : strlen(tmp0);
-      if(len < 1) {
-        infof(data, "region can't be empty");
-        ret = CURLE_BAD_FUNCTION_ARGUMENT;
-        goto fail;
-      }
-      region = Curl_memdup(tmp0, len + 1);
-      if(!region) {
-        goto fail;
-      }
-      region[len] = '\0';
-
-      if(tmp1) {
-        tmp0 = tmp1 + 1;
-        service = strdup(tmp0);
-        if(!service) {
-          goto fail;
-        }
-        if(strlen(service) < 1) {
-          infof(data, "service can't be empty");
-          ret = CURLE_BAD_FUNCTION_ARGUMENT;
-          goto fail;
-        }
-      }
-    }
-  }
-  else {
-    provider1_low = Curl_memdup(provider0_low, len + 1);
-    provider1_mid = Curl_memdup(provider0_low, len + 1);
-    if(!provider1_low || !provider1_mid) {
-      goto fail;
-    }
-    provider1_mid[0] = Curl_raw_toupper(provider1_mid[0]);
-  }
+  else if(!provider1[0])
+    strcpy(provider1, provider0);
 
-  if(!service) {
-    tmp0 = hostname;
-    tmp1 = strchr(tmp0, '.');
-    if(!tmp1) {
-      infof(data, "service missing in parameters or hostname");
+  if(!service[0]) {
+    char *hostdot = strchr(hostname, '.');
+    if(!hostdot) {
+      failf(data, "service missing in parameters and hostname");
       ret = CURLE_URL_MALFORMAT;
       goto fail;
     }
-    len = tmp1 - tmp0;
-    service = Curl_memdup(tmp0, len + 1);
-    if(!service) {
+    len = hostdot - hostname;
+    if(len > MAX_SIGV4_LEN) {
+      failf(data, "service too long in hostname");
+      ret = CURLE_URL_MALFORMAT;
       goto fail;
     }
+    strncpy(service, hostname, len);
     service[len] = '\0';
 
-    if(!region) {
-      tmp0 = tmp1 + 1;
-      tmp1 = strchr(tmp0, '.');
-      if(!tmp1) {
-        infof(data, "region missing in parameters or hostname");
+    if(!region[0]) {
+      const char *reg = hostdot + 1;
+      const char *hostreg = strchr(reg, '.');
+      if(!hostreg) {
+        failf(data, "region missing in parameters and hostname");
         ret = CURLE_URL_MALFORMAT;
         goto fail;
       }
-      len = tmp1 - tmp0;
-      region = Curl_memdup(tmp0, len + 1);
-      if(!region) {
+      len = hostreg - reg;
+      if(len > MAX_SIGV4_LEN) {
+        failf(data, "region too long in hostname");
+        ret = CURLE_URL_MALFORMAT;
         goto fail;
       }
+      strncpy(region, reg, len);
       region[len] = '\0';
     }
   }
 
 #ifdef DEBUGBUILD
-  force_timestamp = getenv("CURL_FORCETIME");
-  if(force_timestamp)
-    clock = 0;
-  else
-    time(&clock);
+  {
+    char *force_timestamp = getenv("CURL_FORCETIME");
+    if(force_timestamp)
+      clock = 0;
+    else
+      time(&clock);
+  }
 #else
   time(&clock);
 #endif
   ret = Curl_gmtime(clock, &tm);
-  if(ret != CURLE_OK) {
+  if(ret) {
     goto fail;
   }
   if(!strftime(timestamp, sizeof(timestamp), "%Y%m%dT%H%M%SZ", &tm)) {
+    ret = CURLE_OUT_OF_MEMORY;
     goto fail;
   }
+
+  ret = make_headers(data, hostname, timestamp, provider1,
+                     &date_header, &canonical_headers, &signed_headers);
+  if(ret)
+    goto fail;
+  ret = CURLE_OUT_OF_MEMORY;
+
   memcpy(date, timestamp, sizeof(date));
   date[sizeof(date) - 1] = 0;
 
-  if(content_type) {
-    content_type = strchr(content_type, ':');
-    if(!content_type) {
-      ret = CURLE_FAILED_INIT;
-      goto fail;
-    }
-    content_type++;
-    /* Skip whitespace now */
-    while(*content_type == ' ' || *content_type == '\t')
-      ++content_type;
-
-    canonical_headers = curl_maprintf("content-type:%s\n"
-                                      "host:%s\n"
-                                      "x-%s-date:%s\n",
-                                      content_type,
-                                      hostname,
-                                      provider1_low, timestamp);
-    signed_headers = curl_maprintf("content-type;host;x-%s-date",
-                                   provider1_low);
-  }
-  else {
-    canonical_headers = curl_maprintf("host:%s\n"
-                                      "x-%s-date:%s\n",
-                                      hostname,
-                                      provider1_low, timestamp);
-    signed_headers = curl_maprintf("host;x-%s-date", provider1_low);
-  }
-
-  if(!canonical_headers || !signed_headers) {
-    goto fail;
+  if(post_data) {
+    if(data->set.postfieldsize < 0)
+      post_data_len = strlen(post_data);
+    else
+      post_data_len = (size_t)data->set.postfieldsize;
   }
-
-  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)) {
+                   post_data_len))
     goto fail;
-  }
 
   sha256_to_hex(sha_hex, sha_hash, sizeof(sha_hex));
 
-  Curl_http_method(data, conn, &method, &httpreq);
-
-  canonical_request =
-    curl_maprintf("%s\n" /* HTTPRequestMethod */
-                  "%s\n" /* CanonicalURI */
-                  "%s\n" /* CanonicalQueryString */
-                  "%s\n" /* CanonicalHeaders */
-                  "%s\n" /* SignedHeaders */
-                  "%s",  /* HashedRequestPayload in hex */
-                  method,
-                  data->state.up.path,
-                  data->state.up.query ? data->state.up.query : "",
-                  canonical_headers,
-                  signed_headers,
-                  sha_hex);
-  if(!canonical_request) {
-    goto fail;
+  {
+    Curl_HttpReq httpreq;
+    const char *method;
+
+    Curl_http_method(data, conn, &method, &httpreq);
+
+    canonical_request =
+      curl_maprintf("%s\n" /* HTTPRequestMethod */
+                    "%s\n" /* CanonicalURI */
+                    "%s\n" /* CanonicalQueryString */
+                    "%s\n" /* CanonicalHeaders */
+                    "%s\n" /* SignedHeaders */
+                    "%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);
+    if(!canonical_request)
+      goto fail;
   }
 
-  request_type = curl_maprintf("%s4_request", provider0_low);
-  if(!request_type) {
+  /* provider 0 lowercase */
+  Curl_strntolower(provider0, provider0, strlen(provider0));
+  request_type = curl_maprintf("%s4_request", provider0);
+  if(!request_type)
     goto fail;
-  }
 
   credential_scope = curl_maprintf("%s/%s/%s/%s",
                                    date, region, service, request_type);
-  if(!credential_scope) {
+  if(!credential_scope)
     goto fail;
-  }
 
   if(Curl_sha256it(sha_hash, (unsigned char *) canonical_request,
-                   strlen(canonical_request))) {
+                   strlen(canonical_request)))
     goto fail;
-  }
 
   sha256_to_hex(sha_hex, sha_hash, sizeof(sha_hex));
 
+  /* provider 0 uppercase */
+  Curl_strntoupper(provider0, provider0, strlen(provider0));
+
   /*
-   * Google allow to use rsa key instead of HMAC, so this code might change
-   * In the future, but for now we support only HMAC version
+   * Google allows using RSA key instead of HMAC, so this code might change
+   * in the future. For now we ony support HMAC.
    */
   str_to_sign = curl_maprintf("%s4-HMAC-SHA256\n" /* Algorithm */
                               "%s\n" /* RequestDateTime */
                               "%s\n" /* CredentialScope */
                               "%s",  /* HashedCanonicalRequest in hex */
-                              provider0_up,
+                              provider0,
                               timestamp,
                               credential_scope,
                               sha_hex);
@@ -346,36 +473,33 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
     goto fail;
   }
 
-  secret = curl_maprintf("%s4%s", provider0_up, passwd);
-  if(!secret) {
+  /* provider 0 uppercase */
+  secret = curl_maprintf("%s4%s", provider0,
+                         data->state.aptr.passwd ?
+                         data->state.aptr.passwd : "");
+  if(!secret)
     goto fail;
-  }
 
-  HMAC_SHA256(secret, strlen(secret),
-              date, strlen(date), tmp_sign0);
-  HMAC_SHA256(tmp_sign0, sizeof(tmp_sign0),
-              region, strlen(region), tmp_sign1);
-  HMAC_SHA256(tmp_sign1, sizeof(tmp_sign1),
-              service, strlen(service), tmp_sign0);
-  HMAC_SHA256(tmp_sign0, sizeof(tmp_sign0),
-              request_type, strlen(request_type), tmp_sign1);
-  HMAC_SHA256(tmp_sign1, sizeof(tmp_sign1),
-              str_to_sign, strlen(str_to_sign), tmp_sign0);
+  HMAC_SHA256(secret, strlen(secret), date, strlen(date), sign0);
+  HMAC_SHA256(sign0, sizeof(sign0), region, strlen(region), sign1);
+  HMAC_SHA256(sign1, sizeof(sign1), service, strlen(service), sign0);
+  HMAC_SHA256(sign0, sizeof(sign0), request_type, strlen(request_type), sign1);
+  HMAC_SHA256(sign1, sizeof(sign1), str_to_sign, strlen(str_to_sign), sign0);
 
-  sha256_to_hex(sha_hex, tmp_sign0, sizeof(sha_hex));
+  sha256_to_hex(sha_hex, sign0, sizeof(sha_hex));
 
+  /* provider 0 uppercase */
   auth_headers = curl_maprintf("Authorization: %s4-HMAC-SHA256 "
                                "Credential=%s/%s, "
                                "SignedHeaders=%s, "
                                "Signature=%s\r\n"
-                               "X-%s-Date: %s\r\n",
-                               provider0_up,
+                               "%s\r\n",
+                               provider0,
                                user,
                                credential_scope,
-                               signed_headers,
+                               Curl_dyn_ptr(&signed_headers),
                                sha_hex,
-                               provider1_mid,
-                               timestamp);
+                               date_header);
   if(!auth_headers) {
     goto fail;
   }
@@ -386,19 +510,14 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
   ret = CURLE_OK;
 
 fail:
-  free(provider0_low);
-  free(provider0_up);
-  free(provider1_low);
-  free(provider1_mid);
-  free(region);
-  free(service);
-  free(canonical_headers);
-  free(signed_headers);
+  Curl_dyn_free(&canonical_headers);
+  Curl_dyn_free(&signed_headers);
   free(canonical_request);
   free(request_type);
   free(credential_scope);
   free(str_to_sign);
   free(secret);
+  free(date_header);
   return ret;
 }
 
index 20cdb9c8e458edb5e343b44d8a2b074e1d67051a..7fe87f43596f1499cbe295fef6fe115a89fb4503 100644 (file)
@@ -225,6 +225,7 @@ test1916 test1917 test1918 test1919 \
 \
 test1933 test1934 test1935 test1936 test1937 test1938 test1939 test1940 \
 test1941 test1942 test1943 test1944 test1945 test1946 test1947 test1948 \
+test1955 \
 \
 test2000 test2001 test2002 test2003 test2004 \
 \
index d2aabb1fed9e57334d5e87e621585a9be1818376..3f3e8ac2cd93a57878b4cc20a3481f8998b00cbe 100644 (file)
@@ -47,7 +47,7 @@ lib%TESTNUMBER
 </tool>
 
 <command>
-http://xxx:yyy@%HOSTIP:%HTTPPORT/%TESTNUMBER/testapi/test
+http://xxx:yyy@127.0.0.1:9000/%TESTNUMBER/testapi/test 127.0.0.1:9000:%HOSTIP:%HTTPPORT
 </command>
 </client>
 
@@ -60,8 +60,8 @@ http://xxx:yyy@%HOSTIP:%HTTPPORT/%TESTNUMBER/testapi/test
 </strip>
 <protocol>
 GET /%TESTNUMBER/testapi/test HTTP/1.1\r
-Host: %HOSTIP:%HTTPPORT\r
-Authorization: XXX4-HMAC-SHA256 Credential=xxx/19700101/0/127/xxx4_request, SignedHeaders=content-type;host;x-xxx-date, Signature=d2c2dff48c59ec49dc31ef94f18c5dc1ac3eae2a70d51633a4342dadc0683664\r
+Host: 127.0.0.1:9000\r
+Authorization: XXX4-HMAC-SHA256 Credential=xxx/19700101/0/127/xxx4_request, SignedHeaders=content-type;host;x-xxx-date, Signature=3d8e00a02e437211a596143dcd590fcc805b731365c68f7f48951ea6eda39c4f\r
 X-Xxx-Date: 19700101T000000Z\r
 \r
 </protocol>
index 35fa318c11e88f4b24481251ae358c6784e81385..a131df49e7ce301edbeb33e74f31597f90955555 100644 (file)
@@ -47,7 +47,7 @@ lib%TESTNUMBER
 </tool>
 
 <command>
-http://%HOSTIP:%HTTPPORT/%TESTNUMBER/testapi/test
+http://127.0.0.1:9000/%TESTNUMBER/testapi/test 127.0.0.1:9000:%HOSTIP:%HTTPPORT
 </command>
 </client>
 
@@ -60,8 +60,8 @@ http://%HOSTIP:%HTTPPORT/%TESTNUMBER/testapi/test
 </strip>
 <protocol>
 GET /%TESTNUMBER/testapi/test HTTP/1.1\r
-Host: %HOSTIP:%HTTPPORT\r
-Authorization: XXX4-HMAC-SHA256 Credential=xxx/19700101/0/127/xxx4_request, SignedHeaders=content-type;host;x-yyy-date, Signature=938937ca7da6bb3dbf15e30928265ec6f061532d035d2afda92fa7cb10feb196\r
+Host: 127.0.0.1:9000\r
+Authorization: XXX4-HMAC-SHA256 Credential=xxx/19700101/0/127/xxx4_request, SignedHeaders=content-type;host;x-yyy-date, Signature=cf8dc9a4af903a1a9bb1385d8e2366d780afb501e266436598438395e502d58c\r
 X-Yyy-Date: 19700101T000000Z\r
 \r
 </protocol>
index 6d48f325ccd58ed314e4c2f32479a99a4cdcf6b3..d39a864715612a27a2ce85e669e292a7e849a6ef 100644 (file)
@@ -47,7 +47,7 @@ lib%TESTNUMBER
 </tool>
 
 <command>
-http://%HOSTIP:%HTTPPORT/%TESTNUMBER/testapi/test
+http://127.0.0.1:9000/%TESTNUMBER/testapi/test 127.0.0.1:9000:%HOSTIP:%HTTPPORT
 </command>
 </client>
 
@@ -60,8 +60,8 @@ http://%HOSTIP:%HTTPPORT/%TESTNUMBER/testapi/test
 </strip>
 <protocol>
 GET /%TESTNUMBER/testapi/test HTTP/1.1\r
-Host: %HOSTIP:%HTTPPORT\r
-Authorization: XXX4-HMAC-SHA256 Credential=xxx/19700101/rrr/127/xxx4_request, SignedHeaders=content-type;host;x-yyy-date, Signature=240750deb9263d4c8ece71c016f3919b56e518249390ef075740f94ef8df846f\r
+Host: 127.0.0.1:9000\r
+Authorization: XXX4-HMAC-SHA256 Credential=xxx/19700101/rrr/127/xxx4_request, SignedHeaders=content-type;host;x-yyy-date, Signature=a0b11b97b54689428d4188b788ed32865d607822d85d3e91cf06141f479dac0b\r
 X-Yyy-Date: 19700101T000000Z\r
 \r
 </protocol>
index 366abd64b27301d1ab12611075332504b4af35f6..a0d38403b7115e60bac37ddd813d20b4f3c485f6 100644 (file)
@@ -47,7 +47,7 @@ lib%TESTNUMBER
 </tool>
 
 <command>
-http://%HOSTIP:%HTTPPORT/%TESTNUMBER/testapi/test
+http://127.0.0.1:9000/%TESTNUMBER/testapi/test 127.0.0.1:9000:%HOSTIP:%HTTPPORT
 </command>
 </client>
 
@@ -60,8 +60,8 @@ http://%HOSTIP:%HTTPPORT/%TESTNUMBER/testapi/test
 </strip>
 <protocol>
 GET /%TESTNUMBER/testapi/test HTTP/1.1\r
-Host: %HOSTIP:%HTTPPORT\r
-Authorization: XXX4-HMAC-SHA256 Credential=xxx/19700101/rrr/sss/xxx4_request, SignedHeaders=content-type;host;x-yyy-date, Signature=f32cf87977cea5d3274b524b53e5d28f4aac54c372f710ae0cc3a9ececaf169f\r
+Host: 127.0.0.1:9000\r
+Authorization: XXX4-HMAC-SHA256 Credential=xxx/19700101/rrr/sss/xxx4_request, SignedHeaders=content-type;host;x-yyy-date, Signature=026b713d76b0789bd224c5e41322f74eed088f8a22fd15183ca68376c575c5b0\r
 X-Yyy-Date: 19700101T000000Z\r
 \r
 </protocol>
index e24445ac6a0ffc2e6a752abaa00564a31668e5e3..ca1214b835197ccf7139a2f2b5d0ba0cdb3e015a 100644 (file)
@@ -48,7 +48,7 @@ lib%TESTNUMBER
 </tool>
 
 <command>
-http://%HOSTIP:%HTTPPORT/%TESTNUMBER/testapi/test
+http://127.0.0.1:9000/%TESTNUMBER/testapi/test 127.0.0.1:9000:%HOSTIP:%HTTPPORT
 </command>
 </client>
 
@@ -61,8 +61,8 @@ http://%HOSTIP:%HTTPPORT/%TESTNUMBER/testapi/test
 </strip>
 <protocol nonewline="yes">
 POST /%TESTNUMBER/testapi/test HTTP/1.1\r
-Host: %HOSTIP:%HTTPPORT\r
-Authorization: PROVIDER14-HMAC-SHA256 Credential=keyId/19700101/region/service/provider14_request, SignedHeaders=content-type;host;x-provider2-date, Signature=391e410177d0e9ee80728082446ef69d6b29157fe71f8b4805fce7c186fd956d\r
+Host: 127.0.0.1:9000\r
+Authorization: PROVIDER14-HMAC-SHA256 Credential=keyId/19700101/region/service/provider14_request, SignedHeaders=content-type;host;x-provider2-date, Signature=4928ccf97a9e71fe27f91db5a3b3c943b6080d25e6f4df8593d4c38e7d1e849b\r
 X-Provider2-Date: 19700101T000000Z\r
 Content-Length: 8\r
 \r
index 5341de00f3eae5125ee0e81b80fb1c8a82d98994..565a54ffe68f95202e8748f6c5863f2bf2f79c32 100644 (file)
Binary files a/tests/data/test1938 and b/tests/data/test1938 differ
diff --git a/tests/data/test1955 b/tests/data/test1955
new file mode 100644 (file)
index 0000000..5ebbc54
--- /dev/null
@@ -0,0 +1,75 @@
+<testcase>
+<info>
+<keywords>
+HTTP
+CURLOPT_AWS_SIGV4
+</keywords>
+</info>
+
+# Server-side
+<reply>
+<data nocheck="yes">
+HTTP/1.1 302 OK
+Date: Thu, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Content-Type: text/html
+Content-Length: 0
+Location: /%TESTNUMBER0002
+
+</data>
+<data2>
+HTTP/1.1 200 OK
+Date: Thu, 09 Nov 2010 14:49:00 GMT
+Server: test-server/fake
+Content-Type: text/html
+Content-Length: 0
+
+</data2>
+</reply>
+
+# Client-side
+<client>
+<server>
+http
+</server>
+# this relies on the debug feature which allow to set the time
+<features>
+SSL
+debug
+crypto
+</features>
+<setenv>
+CURL_FORCEHOST=1
+</setenv>
+
+<name>
+HTTP AWS_SIGV4 with X-Xxx-Content-Sha256
+</name>
+<tool>
+lib%TESTNUMBER
+</tool>
+
+<command>
+http://exam.ple.com:9000/%TESTNUMBER/testapi/test exam.ple.com:9000:%HOSTIP:%HTTPPORT
+</command>
+</client>
+
+# Verify data after the test has been "shot"
+<verify>
+<strip>
+^User-Agent:.*
+^Content-Type:.*
+^Accept:.*
+</strip>
+<protocol>
+GET /%TESTNUMBER/testapi/test HTTP/1.1\r
+Host: exam.ple.com:9000\r
+Authorization: XXX4-HMAC-SHA256 Credential=xxx/19700101/ple/exam/xxx4_request, SignedHeaders=content-type;host;tesmixcase;test0;test1;test2;test_space;x-xxx-date, Signature=819251feec8de52dfaa992320241f23d27cefa979c93e039ae7df03ac486ed16\r
+X-Xxx-Date: 19700101T000000Z\r
+test2: 1234\r
+test_space: t  s  m       end    \r
+tesMixCase: MixCase\r
+\r
+</protocol>
+</verify>
+</testcase>
index 3b9cdd0069606f195730f75861eacade0409d229..484ac178c7bab7c17c939f3f9f2f1df64d79a84c 100644 (file)
@@ -64,7 +64,7 @@ noinst_PROGRAMS = chkhostname libauthretry libntlmconnect                \
          lib1905 lib1906 lib1907 lib1908 lib1910 lib1911 lib1912 lib1913 \
          lib1915 lib1916 lib1917 lib1918 lib1919 \
  lib1933 lib1934 lib1935 lib1936 lib1937 lib1938 lib1939 lib1940 \
- lib1945 lib1946 lib1947 lib1948 \
+ lib1945 lib1946 lib1947 lib1948 lib1955 \
  lib2301 lib2302 \
  lib3010 lib3025 lib3026 lib3027
 
@@ -757,6 +757,10 @@ lib1948_SOURCES = lib1948.c $(SUPPORTFILES)
 lib1948_LDADD = $(TESTUTIL_LIBS)
 lib1948_CPPFLAGS = $(AM_CPPFLAGS)
 
+lib1955_SOURCES = lib1955.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
+lib1955_LDADD = $(TESTUTIL_LIBS)
+lib1955_CPPFLAGS = $(AM_CPPFLAGS)
+
 lib2301_SOURCES = lib2301.c $(SUPPORTFILES)
 lib2301_LDADD = $(TESTUTIL_LIBS)
 
index 3ff2dc17f1203ad5fd558d415a56e17255067f69..39459d001c442ca3383c825cb81e6015207b364d 100644 (file)
@@ -29,6 +29,7 @@ int test(char *URL)
 {
   CURL *curl;
   CURLcode res = TEST_ERR_MAJOR_BAD;
+  struct curl_slist *connect_to = NULL;
   struct curl_slist *list = NULL;
 
   if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
@@ -47,6 +48,10 @@ int test(char *URL)
   test_setopt(curl, CURLOPT_AWS_SIGV4, "xxx");
   test_setopt(curl, CURLOPT_HEADER, 0L);
   test_setopt(curl, CURLOPT_URL, URL);
+  if(libtest_arg2) {
+    connect_to = curl_slist_append(connect_to, libtest_arg2);
+  }
+  test_setopt(curl, CURLOPT_CONNECT_TO, connect_to);
   list = curl_slist_append(list, "Content-Type: application/json");
   test_setopt(curl, CURLOPT_HTTPHEADER, list);
 
@@ -54,6 +59,7 @@ int test(char *URL)
 
 test_cleanup:
 
+  curl_slist_free_all(connect_to);
   curl_slist_free_all(list);
   curl_easy_cleanup(curl);
   curl_global_cleanup();
index d5f3884448f1c8e2d684405091c1f5a0d249c858..917f6e681dfe61d99d908d7ec00910a57f4730b2 100644 (file)
@@ -29,6 +29,7 @@ int test(char *URL)
 {
   CURL *curl;
   CURLcode res = TEST_ERR_MAJOR_BAD;
+  struct curl_slist *connect_to = NULL;
   struct curl_slist *list = NULL;
 
   if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
@@ -48,6 +49,10 @@ int test(char *URL)
   test_setopt(curl, CURLOPT_USERPWD, "xxx:yyy");
   test_setopt(curl, CURLOPT_HEADER, 0L);
   test_setopt(curl, CURLOPT_URL, URL);
+  if(libtest_arg2) {
+    connect_to = curl_slist_append(connect_to, libtest_arg2);
+  }
+  test_setopt(curl, CURLOPT_CONNECT_TO, connect_to);
   list = curl_slist_append(list, "Content-Type: application/json");
   test_setopt(curl, CURLOPT_HTTPHEADER, list);
 
@@ -55,6 +60,7 @@ int test(char *URL)
 
 test_cleanup:
 
+  curl_slist_free_all(connect_to);
   curl_slist_free_all(list);
   curl_easy_cleanup(curl);
   curl_global_cleanup();
index e27be122eb2000f0530e94379a479ad79c18f29c..459ead3935b14183ac8e2ffae270279debffd9c4 100644 (file)
@@ -29,6 +29,7 @@ int test(char *URL)
 {
   CURL *curl;
   CURLcode res = TEST_ERR_MAJOR_BAD;
+  struct curl_slist *connect_to = NULL;
   struct curl_slist *list = NULL;
 
   if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
@@ -48,6 +49,10 @@ int test(char *URL)
   test_setopt(curl, CURLOPT_USERPWD, "xxx:yyy");
   test_setopt(curl, CURLOPT_HEADER, 0L);
   test_setopt(curl, CURLOPT_URL, URL);
+  if(libtest_arg2) {
+    connect_to = curl_slist_append(connect_to, libtest_arg2);
+  }
+  test_setopt(curl, CURLOPT_CONNECT_TO, connect_to);
   list = curl_slist_append(list, "Content-Type: application/json");
   test_setopt(curl, CURLOPT_HTTPHEADER, list);
 
@@ -55,6 +60,7 @@ int test(char *URL)
 
 test_cleanup:
 
+  curl_slist_free_all(connect_to);
   curl_slist_free_all(list);
   curl_easy_cleanup(curl);
   curl_global_cleanup();
index 703e7403aed65b9d3aaa10794ec05c4bb869939e..e522e3817b80688a0f3b9a087e2b9ed206df3daf 100644 (file)
@@ -29,6 +29,7 @@ int test(char *URL)
 {
   CURL *curl;
   CURLcode res = TEST_ERR_MAJOR_BAD;
+  struct curl_slist *connect_to = NULL;
   struct curl_slist *list = NULL;
 
   if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
@@ -48,6 +49,10 @@ int test(char *URL)
   test_setopt(curl, CURLOPT_USERPWD, "xxx:yyy");
   test_setopt(curl, CURLOPT_HEADER, 0L);
   test_setopt(curl, CURLOPT_URL, URL);
+  if(libtest_arg2) {
+    connect_to = curl_slist_append(connect_to, libtest_arg2);
+  }
+  test_setopt(curl, CURLOPT_CONNECT_TO, connect_to);
   list = curl_slist_append(list, "Content-Type: application/json");
   test_setopt(curl, CURLOPT_HTTPHEADER, list);
 
@@ -55,6 +60,7 @@ int test(char *URL)
 
 test_cleanup:
 
+  curl_slist_free_all(connect_to);
   curl_slist_free_all(list);
   curl_easy_cleanup(curl);
   curl_global_cleanup();
index b2d4518a9c9091e4120b7e91acb86cd0f71d4f4d..14b9687436f3b19cc80b472e764f65219bf3231b 100644 (file)
@@ -30,6 +30,7 @@ int test(char *URL)
 {
   CURL *curl;
   CURLcode res = TEST_ERR_MAJOR_BAD;
+  struct curl_slist *connect_to = NULL;
   struct curl_slist *list = NULL;
 
   if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
@@ -50,6 +51,10 @@ int test(char *URL)
   test_setopt(curl, CURLOPT_USERPWD, "keyId:SecretKey");
   test_setopt(curl, CURLOPT_HEADER, 0L);
   test_setopt(curl, CURLOPT_URL, URL);
+  if(libtest_arg2) {
+    connect_to = curl_slist_append(connect_to, libtest_arg2);
+  }
+  test_setopt(curl, CURLOPT_CONNECT_TO, connect_to);
   list = curl_slist_append(list, "Content-Type: application/json");
   test_setopt(curl, CURLOPT_HTTPHEADER, list);
   test_setopt(curl, CURLOPT_POSTFIELDS, "postData");
@@ -58,6 +63,7 @@ int test(char *URL)
 
 test_cleanup:
 
+  curl_slist_free_all(connect_to);
   curl_slist_free_all(list);
   curl_easy_cleanup(curl);
   curl_global_cleanup();
index 3bbde8c10c3564c815978215d8aeafef00d07e75..79dc6cc0ec8a2ea29ea615037f627057c878fbc8 100644 (file)
@@ -30,6 +30,7 @@ int test(char *URL)
 {
   CURL *curl;
   CURLcode res = TEST_ERR_MAJOR_BAD;
+  struct curl_slist *connect_to = NULL;
   struct curl_slist *list = NULL;
   unsigned char data[] = {0x70, 0x6f, 0x73, 0x74, 0, 0x44, 0x61, 0x74, 0x61};
 
@@ -51,6 +52,10 @@ int test(char *URL)
   test_setopt(curl, CURLOPT_USERPWD, "keyId:SecretKey");
   test_setopt(curl, CURLOPT_HEADER, 0L);
   test_setopt(curl, CURLOPT_URL, URL);
+  if(libtest_arg2) {
+    connect_to = curl_slist_append(connect_to, libtest_arg2);
+  }
+  test_setopt(curl, CURLOPT_CONNECT_TO, connect_to);
   list = curl_slist_append(list, "Content-Type: application/json");
   test_setopt(curl, CURLOPT_HTTPHEADER, list);
   test_setopt(curl, CURLOPT_POSTFIELDS, data);
@@ -60,6 +65,7 @@ int test(char *URL)
 
 test_cleanup:
 
+  curl_slist_free_all(connect_to);
   curl_slist_free_all(list);
   curl_easy_cleanup(curl);
   curl_global_cleanup();
diff --git a/tests/libtest/lib1955.c b/tests/libtest/lib1955.c
new file mode 100644 (file)
index 0000000..053c3e1
--- /dev/null
@@ -0,0 +1,76 @@
+/***************************************************************************
+ *                                  _   _ ____  _
+ *  Project                     ___| | | |  _ \| |
+ *                             / __| | | | |_) | |
+ *                            | (__| |_| |  _ <| |___
+ *                             \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2022, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ * SPDX-License-Identifier: curl
+ *
+ ***************************************************************************/
+#include "test.h"
+
+#include "memdebug.h"
+
+int test(char *URL)
+{
+  CURL *curl;
+  CURLcode res = TEST_ERR_MAJOR_BAD;
+  struct curl_slist *list = NULL;
+  struct curl_slist *connect_to = NULL;
+
+  if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
+    fprintf(stderr, "curl_global_init() failed\n");
+    return TEST_ERR_MAJOR_BAD;
+  }
+
+  curl = curl_easy_init();
+  if(!curl) {
+    fprintf(stderr, "curl_easy_init() failed\n");
+    curl_global_cleanup();
+    return TEST_ERR_MAJOR_BAD;
+  }
+
+  test_setopt(curl, CURLOPT_VERBOSE, 1L);
+  test_setopt(curl, CURLOPT_AWS_SIGV4, "xxx");
+  test_setopt(curl, CURLOPT_USERPWD, "xxx");
+  test_setopt(curl, CURLOPT_HEADER, 0L);
+  test_setopt(curl, CURLOPT_URL, URL);
+  list = curl_slist_append(list, "test2: 1234");
+  if(!list)
+    goto test_cleanup;
+  if(libtest_arg2) {
+    connect_to = curl_slist_append(connect_to, libtest_arg2);
+  }
+  test_setopt(curl, CURLOPT_CONNECT_TO, connect_to);
+  curl_slist_append(list, "Content-Type: application/json");
+  curl_slist_append(list, "test1:");
+  curl_slist_append(list, "test0");
+  curl_slist_append(list, "test_space: t\ts  m\t   end    ");
+  curl_slist_append(list, "tesMixCase: MixCase");
+  test_setopt(curl, CURLOPT_HTTPHEADER, list);
+
+  res = curl_easy_perform(curl);
+
+test_cleanup:
+
+  curl_slist_free_all(connect_to);
+  curl_slist_free_all(list);
+  curl_easy_cleanup(curl);
+  curl_global_cleanup();
+
+  return res;
+}