quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

http_aws_sigv4.c (34359B)


      1 /***************************************************************************
      2  *                                  _   _ ____  _
      3  *  Project                     ___| | | |  _ \| |
      4  *                             / __| | | | |_) | |
      5  *                            | (__| |_| |  _ <| |___
      6  *                             \___|\___/|_| \_\_____|
      7  *
      8  * Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
      9  *
     10  * This software is licensed as described in the file COPYING, which
     11  * you should have received as part of this distribution. The terms
     12  * are also available at https://curl.se/docs/copyright.html.
     13  *
     14  * You may opt to use, copy, modify, merge, publish, distribute and/or sell
     15  * copies of the Software, and permit persons to whom the Software is
     16  * furnished to do so, under the terms of the COPYING file.
     17  *
     18  * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
     19  * KIND, either express or implied.
     20  *
     21  * SPDX-License-Identifier: curl
     22  *
     23  ***************************************************************************/
     24 
     25 #include "curl_setup.h"
     26 
     27 #if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_AWS)
     28 
     29 #include "urldata.h"
     30 #include "strcase.h"
     31 #include "strdup.h"
     32 #include "http_aws_sigv4.h"
     33 #include "curl_sha256.h"
     34 #include "transfer.h"
     35 #include "parsedate.h"
     36 #include "sendf.h"
     37 #include "escape.h"
     38 #include "curlx/strparse.h"
     39 
     40 #include <time.h>
     41 
     42 /* The last 3 #include files should be in this order */
     43 #include "curl_printf.h"
     44 #include "curl_memory.h"
     45 #include "memdebug.h"
     46 
     47 #include "slist.h"
     48 
     49 #define HMAC_SHA256(k, kl, d, dl, o)                \
     50   do {                                              \
     51     result = Curl_hmacit(&Curl_HMAC_SHA256,         \
     52                          (const unsigned char *)k,  \
     53                          kl,                        \
     54                          (const unsigned char *)d,  \
     55                          dl, o);                    \
     56     if(result) {                                    \
     57       goto fail;                                    \
     58     }                                               \
     59   } while(0)
     60 
     61 #define TIMESTAMP_SIZE 17
     62 
     63 /* hex-encoded with trailing null */
     64 #define SHA256_HEX_LENGTH (2 * CURL_SHA256_DIGEST_LENGTH + 1)
     65 
     66 #define MAX_QUERY_COMPONENTS 128
     67 
     68 struct pair {
     69   struct dynbuf key;
     70   struct dynbuf value;
     71 };
     72 
     73 static void dyn_array_free(struct dynbuf *db, size_t num_elements);
     74 static void pair_array_free(struct pair *pair_array, size_t num_elements);
     75 static CURLcode split_to_dyn_array(const char *source,
     76                                    struct dynbuf db[MAX_QUERY_COMPONENTS],
     77                                    size_t *num_splits);
     78 static bool is_reserved_char(const char c);
     79 static CURLcode uri_encode_path(struct Curl_str *original_path,
     80                                 struct dynbuf *new_path);
     81 static CURLcode encode_query_component(char *component, size_t len,
     82                                        struct dynbuf *db);
     83 static CURLcode http_aws_decode_encode(const char *in, size_t in_len,
     84                                        struct dynbuf *out);
     85 static bool should_urlencode(struct Curl_str *service_name);
     86 
     87 static void sha256_to_hex(char *dst, unsigned char *sha)
     88 {
     89   Curl_hexencode(sha, CURL_SHA256_DIGEST_LENGTH,
     90                  (unsigned char *)dst, SHA256_HEX_LENGTH);
     91 }
     92 
     93 static char *find_date_hdr(struct Curl_easy *data, const char *sig_hdr)
     94 {
     95   char *tmp = Curl_checkheaders(data, sig_hdr, strlen(sig_hdr));
     96 
     97   if(tmp)
     98     return tmp;
     99   return Curl_checkheaders(data, STRCONST("Date"));
    100 }
    101 
    102 /* remove whitespace, and lowercase all headers */
    103 static void trim_headers(struct curl_slist *head)
    104 {
    105   struct curl_slist *l;
    106   for(l = head; l; l = l->next) {
    107     const char *value; /* to read from */
    108     char *store;
    109     size_t colon = strcspn(l->data, ":");
    110     Curl_strntolower(l->data, l->data, colon);
    111 
    112     value = &l->data[colon];
    113     if(!*value)
    114       continue;
    115     ++value;
    116     store = (char *)CURL_UNCONST(value);
    117 
    118     /* skip leading whitespace */
    119     curlx_str_passblanks(&value);
    120 
    121     while(*value) {
    122       int space = 0;
    123       while(ISBLANK(*value)) {
    124         value++;
    125         space++;
    126       }
    127       if(space) {
    128         /* replace any number of consecutive whitespace with a single space,
    129            unless at the end of the string, then nothing */
    130         if(*value)
    131           *store++ = ' ';
    132       }
    133       else
    134         *store++ = *value++;
    135     }
    136     *store = 0; /* null-terminate */
    137   }
    138 }
    139 
    140 /* maximum length for the aws sivg4 parts */
    141 #define MAX_SIGV4_LEN 64
    142 #define DATE_HDR_KEY_LEN (MAX_SIGV4_LEN + sizeof("X--Date"))
    143 
    144 /* string been x-PROVIDER-date:TIMESTAMP, I need +1 for ':' */
    145 #define DATE_FULL_HDR_LEN (DATE_HDR_KEY_LEN + TIMESTAMP_SIZE + 1)
    146 
    147 /* alphabetically compare two headers by their name, expecting
    148    headers to use ':' at this point */
    149 static int compare_header_names(const char *a, const char *b)
    150 {
    151   const char *colon_a;
    152   const char *colon_b;
    153   size_t len_a;
    154   size_t len_b;
    155   size_t min_len;
    156   int cmp;
    157 
    158   colon_a = strchr(a, ':');
    159   colon_b = strchr(b, ':');
    160 
    161   DEBUGASSERT(colon_a);
    162   DEBUGASSERT(colon_b);
    163 
    164   len_a = colon_a ? (size_t)(colon_a - a) : strlen(a);
    165   len_b = colon_b ? (size_t)(colon_b - b) : strlen(b);
    166 
    167   min_len = (len_a < len_b) ? len_a : len_b;
    168 
    169   cmp = strncmp(a, b, min_len);
    170 
    171   /* return the shorter of the two if one is shorter */
    172   if(!cmp)
    173     return (int)(len_a - len_b);
    174 
    175   return cmp;
    176 }
    177 
    178 /* Merge duplicate header definitions by comma delimiting their values
    179    in the order defined the headers are defined, expecting headers to
    180    be alpha-sorted and use ':' at this point */
    181 static CURLcode merge_duplicate_headers(struct curl_slist *head)
    182 {
    183   struct curl_slist *curr = head;
    184   CURLcode result = CURLE_OK;
    185 
    186   while(curr) {
    187     struct curl_slist *next = curr->next;
    188     if(!next)
    189       break;
    190 
    191     if(compare_header_names(curr->data, next->data) == 0) {
    192       struct dynbuf buf;
    193       char *colon_next;
    194       char *val_next;
    195 
    196       curlx_dyn_init(&buf, CURL_MAX_HTTP_HEADER);
    197 
    198       result = curlx_dyn_add(&buf, curr->data);
    199       if(result)
    200         return result;
    201 
    202       colon_next = strchr(next->data, ':');
    203       DEBUGASSERT(colon_next);
    204       val_next = colon_next + 1;
    205 
    206       result = curlx_dyn_addn(&buf, ",", 1);
    207       if(result)
    208         return result;
    209 
    210       result = curlx_dyn_add(&buf, val_next);
    211       if(result)
    212         return result;
    213 
    214       free(curr->data);
    215       curr->data = curlx_dyn_ptr(&buf);
    216 
    217       curr->next = next->next;
    218       free(next->data);
    219       free(next);
    220     }
    221     else {
    222       curr = curr->next;
    223     }
    224   }
    225 
    226   return CURLE_OK;
    227 }
    228 
    229 /* timestamp should point to a buffer of at last TIMESTAMP_SIZE bytes */
    230 static CURLcode make_headers(struct Curl_easy *data,
    231                              const char *hostname,
    232                              char *timestamp,
    233                              const char *provider1,
    234                              size_t plen, /* length of provider1 */
    235                              char **date_header,
    236                              char *content_sha256_header,
    237                              struct dynbuf *canonical_headers,
    238                              struct dynbuf *signed_headers)
    239 {
    240   char date_hdr_key[DATE_HDR_KEY_LEN];
    241   char date_full_hdr[DATE_FULL_HDR_LEN];
    242   struct curl_slist *head = NULL;
    243   struct curl_slist *tmp_head = NULL;
    244   CURLcode ret = CURLE_OUT_OF_MEMORY;
    245   struct curl_slist *l;
    246   bool again = TRUE;
    247 
    248   msnprintf(date_hdr_key, DATE_HDR_KEY_LEN, "X-%.*s-Date",
    249             (int)plen, provider1);
    250   /* provider1 ucfirst */
    251   Curl_strntolower(&date_hdr_key[2], provider1, plen);
    252   date_hdr_key[2] = Curl_raw_toupper(provider1[0]);
    253 
    254   msnprintf(date_full_hdr, DATE_FULL_HDR_LEN,
    255             "x-%.*s-date:%s", (int)plen, provider1, timestamp);
    256   /* provider1 lowercase */
    257   Curl_strntolower(&date_full_hdr[2], provider1, plen);
    258 
    259   if(!Curl_checkheaders(data, STRCONST("Host"))) {
    260     char *fullhost;
    261 
    262     if(data->state.aptr.host) {
    263       /* remove /r/n as the separator for canonical request must be '\n' */
    264       size_t pos = strcspn(data->state.aptr.host, "\n\r");
    265       fullhost = Curl_memdup0(data->state.aptr.host, pos);
    266     }
    267     else
    268       fullhost = aprintf("host:%s", hostname);
    269 
    270     if(fullhost)
    271       head = Curl_slist_append_nodup(NULL, fullhost);
    272     if(!head) {
    273       free(fullhost);
    274       goto fail;
    275     }
    276   }
    277 
    278 
    279   if(*content_sha256_header) {
    280     tmp_head = curl_slist_append(head, content_sha256_header);
    281     if(!tmp_head)
    282       goto fail;
    283     head = tmp_head;
    284   }
    285 
    286   /* copy user headers to our header list. the logic is based on how http.c
    287      handles user headers.
    288 
    289      user headers in format 'name:' with no value are used to signal that an
    290      internal header of that name should be removed. those user headers are not
    291      added to this list.
    292 
    293      user headers in format 'name;' with no value are used to signal that a
    294      header of that name with no value should be sent. those user headers are
    295      added to this list but in the format that they will be sent, ie the
    296      semi-colon is changed to a colon for format 'name:'.
    297 
    298      user headers with a value of whitespace only, or without a colon or
    299      semi-colon, are not added to this list.
    300      */
    301   for(l = data->set.headers; l; l = l->next) {
    302     char *dupdata, *ptr;
    303     char *sep = strchr(l->data, ':');
    304     if(!sep)
    305       sep = strchr(l->data, ';');
    306     if(!sep || (*sep == ':' && !*(sep + 1)))
    307       continue;
    308     for(ptr = sep + 1; ISBLANK(*ptr); ++ptr)
    309       ;
    310     if(!*ptr && ptr != sep + 1) /* a value of whitespace only */
    311       continue;
    312     dupdata = strdup(l->data);
    313     if(!dupdata)
    314       goto fail;
    315     dupdata[sep - l->data] = ':';
    316     tmp_head = Curl_slist_append_nodup(head, dupdata);
    317     if(!tmp_head) {
    318       free(dupdata);
    319       goto fail;
    320     }
    321     head = tmp_head;
    322   }
    323 
    324   trim_headers(head);
    325 
    326   *date_header = find_date_hdr(data, date_hdr_key);
    327   if(!*date_header) {
    328     tmp_head = curl_slist_append(head, date_full_hdr);
    329     if(!tmp_head)
    330       goto fail;
    331     head = tmp_head;
    332     *date_header = aprintf("%s: %s\r\n", date_hdr_key, timestamp);
    333   }
    334   else {
    335     const char *value;
    336     const char *endp;
    337     value = strchr(*date_header, ':');
    338     if(!value) {
    339       *date_header = NULL;
    340       goto fail;
    341     }
    342     ++value;
    343     curlx_str_passblanks(&value);
    344     endp = value;
    345     while(*endp && ISALNUM(*endp))
    346       ++endp;
    347     /* 16 bytes => "19700101T000000Z" */
    348     if((endp - value) == TIMESTAMP_SIZE - 1) {
    349       memcpy(timestamp, value, TIMESTAMP_SIZE - 1);
    350       timestamp[TIMESTAMP_SIZE - 1] = 0;
    351     }
    352     else
    353       /* bad timestamp length */
    354       timestamp[0] = 0;
    355     *date_header = NULL;
    356   }
    357 
    358   /* alpha-sort by header name in a case sensitive manner */
    359   do {
    360     again = FALSE;
    361     for(l = head; l; l = l->next) {
    362       struct curl_slist *next = l->next;
    363 
    364       if(next && compare_header_names(l->data, next->data) > 0) {
    365         char *tmp = l->data;
    366 
    367         l->data = next->data;
    368         next->data = tmp;
    369         again = TRUE;
    370       }
    371     }
    372   } while(again);
    373 
    374   ret = merge_duplicate_headers(head);
    375   if(ret)
    376     goto fail;
    377 
    378   for(l = head; l; l = l->next) {
    379     char *tmp;
    380 
    381     if(curlx_dyn_add(canonical_headers, l->data))
    382       goto fail;
    383     if(curlx_dyn_add(canonical_headers, "\n"))
    384       goto fail;
    385 
    386     tmp = strchr(l->data, ':');
    387     if(tmp)
    388       *tmp = 0;
    389 
    390     if(l != head) {
    391       if(curlx_dyn_add(signed_headers, ";"))
    392         goto fail;
    393     }
    394     if(curlx_dyn_add(signed_headers, l->data))
    395       goto fail;
    396   }
    397 
    398   ret = CURLE_OK;
    399 fail:
    400   curl_slist_free_all(head);
    401 
    402   return ret;
    403 }
    404 
    405 #define CONTENT_SHA256_KEY_LEN (MAX_SIGV4_LEN + sizeof("X--Content-Sha256"))
    406 /* add 2 for ": " between header name and value */
    407 #define CONTENT_SHA256_HDR_LEN (CONTENT_SHA256_KEY_LEN + 2 + \
    408                                 SHA256_HEX_LENGTH)
    409 
    410 /* try to parse a payload hash from the content-sha256 header */
    411 static const char *parse_content_sha_hdr(struct Curl_easy *data,
    412                                          const char *provider1,
    413                                          size_t plen,
    414                                          size_t *value_len) {
    415   char key[CONTENT_SHA256_KEY_LEN];
    416   size_t key_len;
    417   const char *value;
    418   size_t len;
    419 
    420   key_len = msnprintf(key, sizeof(key), "x-%.*s-content-sha256",
    421                       (int)plen, provider1);
    422 
    423   value = Curl_checkheaders(data, key, key_len);
    424   if(!value)
    425     return NULL;
    426 
    427   value = strchr(value, ':');
    428   if(!value)
    429     return NULL;
    430   ++value;
    431 
    432   curlx_str_passblanks(&value);
    433 
    434   len = strlen(value);
    435   while(len > 0 && ISBLANK(value[len-1]))
    436     --len;
    437 
    438   *value_len = len;
    439   return value;
    440 }
    441 
    442 static CURLcode calc_payload_hash(struct Curl_easy *data,
    443                                   unsigned char *sha_hash, char *sha_hex)
    444 {
    445   const char *post_data = data->set.postfields;
    446   size_t post_data_len = 0;
    447   CURLcode result;
    448 
    449   if(post_data) {
    450     if(data->set.postfieldsize < 0)
    451       post_data_len = strlen(post_data);
    452     else
    453       post_data_len = (size_t)data->set.postfieldsize;
    454   }
    455   result = Curl_sha256it(sha_hash, (const unsigned char *) post_data,
    456                          post_data_len);
    457   if(!result)
    458     sha256_to_hex(sha_hex, sha_hash);
    459   return result;
    460 }
    461 
    462 #define S3_UNSIGNED_PAYLOAD "UNSIGNED-PAYLOAD"
    463 
    464 static CURLcode calc_s3_payload_hash(struct Curl_easy *data,
    465                                      Curl_HttpReq httpreq,
    466                                      const char *provider1,
    467                                      size_t plen,
    468                                      unsigned char *sha_hash,
    469                                      char *sha_hex, char *header)
    470 {
    471   bool empty_method = (httpreq == HTTPREQ_GET || httpreq == HTTPREQ_HEAD);
    472   /* The request method or filesize indicate no request payload */
    473   bool empty_payload = (empty_method || data->set.filesize == 0);
    474   /* The POST payload is in memory */
    475   bool post_payload = (httpreq == HTTPREQ_POST && data->set.postfields);
    476   CURLcode ret = CURLE_OUT_OF_MEMORY;
    477 
    478   if(empty_payload || post_payload) {
    479     /* Calculate a real hash when we know the request payload */
    480     ret = calc_payload_hash(data, sha_hash, sha_hex);
    481     if(ret)
    482       goto fail;
    483   }
    484   else {
    485     /* Fall back to s3's UNSIGNED-PAYLOAD */
    486     size_t len = sizeof(S3_UNSIGNED_PAYLOAD) - 1;
    487     DEBUGASSERT(len < SHA256_HEX_LENGTH); /* 16 < 65 */
    488     memcpy(sha_hex, S3_UNSIGNED_PAYLOAD, len);
    489     sha_hex[len] = 0;
    490   }
    491 
    492   /* format the required content-sha256 header */
    493   msnprintf(header, CONTENT_SHA256_HDR_LEN,
    494             "x-%.*s-content-sha256: %s", (int)plen, provider1, sha_hex);
    495 
    496   ret = CURLE_OK;
    497 fail:
    498   return ret;
    499 }
    500 
    501 static int compare_func(const void *a, const void *b)
    502 {
    503 
    504   const struct pair *aa = a;
    505   const struct pair *bb = b;
    506   const size_t aa_key_len = curlx_dyn_len(&aa->key);
    507   const size_t bb_key_len = curlx_dyn_len(&bb->key);
    508   const size_t aa_value_len = curlx_dyn_len(&aa->value);
    509   const size_t bb_value_len = curlx_dyn_len(&bb->value);
    510   int compare;
    511 
    512   /* If one element is empty, the other is always sorted higher */
    513 
    514   /* Compare keys */
    515   if((aa_key_len == 0) && (bb_key_len == 0))
    516     return 0;
    517   if(aa_key_len == 0)
    518     return -1;
    519   if(bb_key_len == 0)
    520     return 1;
    521   compare = strcmp(curlx_dyn_ptr(&aa->key), curlx_dyn_ptr(&bb->key));
    522   if(compare) {
    523     return compare;
    524   }
    525 
    526   /* Compare values */
    527   if((aa_value_len == 0) && (bb_value_len == 0))
    528     return 0;
    529   if(aa_value_len == 0)
    530     return -1;
    531   if(bb_value_len == 0)
    532     return 1;
    533   compare = strcmp(curlx_dyn_ptr(&aa->value), curlx_dyn_ptr(&bb->value));
    534 
    535   return compare;
    536 
    537 }
    538 
    539 UNITTEST CURLcode canon_path(const char *q, size_t len,
    540                              struct dynbuf *new_path,
    541                              bool do_uri_encode)
    542 {
    543   CURLcode result = CURLE_OK;
    544 
    545   struct Curl_str original_path;
    546 
    547   curlx_str_assign(&original_path, q, len);
    548 
    549   /* Normalized path will be either the same or shorter than the original
    550    * path, plus trailing slash */
    551 
    552   if(do_uri_encode)
    553     result = uri_encode_path(&original_path, new_path);
    554   else
    555     result = curlx_dyn_addn(new_path, q, len);
    556 
    557   if(!result) {
    558     if(curlx_dyn_len(new_path) == 0)
    559       result = curlx_dyn_add(new_path, "/");
    560   }
    561 
    562   return result;
    563 }
    564 
    565 UNITTEST CURLcode canon_query(const char *query, struct dynbuf *dq)
    566 {
    567   CURLcode result = CURLE_OK;
    568 
    569   struct dynbuf query_array[MAX_QUERY_COMPONENTS];
    570   struct pair encoded_query_array[MAX_QUERY_COMPONENTS];
    571   size_t num_query_components;
    572   size_t counted_query_components = 0;
    573   size_t index;
    574 
    575   if(!query)
    576     return result;
    577 
    578   result = split_to_dyn_array(query, &query_array[0],
    579                               &num_query_components);
    580   if(result) {
    581     goto fail;
    582   }
    583 
    584   /* Create list of pairs, each pair containing an encoded query
    585     * component */
    586 
    587   for(index = 0; index < num_query_components; index++) {
    588     const char *in_key;
    589     size_t in_key_len;
    590     char *offset;
    591     size_t query_part_len = curlx_dyn_len(&query_array[index]);
    592     char *query_part = curlx_dyn_ptr(&query_array[index]);
    593 
    594     in_key = query_part;
    595 
    596     offset = strchr(query_part, '=');
    597     /* If there is no equals, this key has no value */
    598     if(!offset) {
    599       in_key_len = strlen(in_key);
    600     }
    601     else {
    602       in_key_len = offset - in_key;
    603     }
    604 
    605     curlx_dyn_init(&encoded_query_array[index].key, query_part_len*3 + 1);
    606     curlx_dyn_init(&encoded_query_array[index].value, query_part_len*3 + 1);
    607     counted_query_components++;
    608 
    609     /* Decode/encode the key */
    610     result = http_aws_decode_encode(in_key, in_key_len,
    611                                     &encoded_query_array[index].key);
    612     if(result) {
    613       goto fail;
    614     }
    615 
    616     /* Decode/encode the value if it exists */
    617     if(offset && offset != (query_part + query_part_len - 1)) {
    618       size_t in_value_len;
    619       const char *in_value = offset + 1;
    620       in_value_len = query_part + query_part_len - (offset + 1);
    621       result = http_aws_decode_encode(in_value, in_value_len,
    622                                       &encoded_query_array[index].value);
    623       if(result) {
    624         goto fail;
    625       }
    626     }
    627     else {
    628       /* If there is no value, the value is an empty string */
    629       curlx_dyn_init(&encoded_query_array[index].value, 2);
    630       result = curlx_dyn_addn(&encoded_query_array[index].value, "", 1);
    631     }
    632 
    633     if(result) {
    634       goto fail;
    635     }
    636   }
    637 
    638   /* Sort the encoded query components by key and value */
    639   qsort(&encoded_query_array, num_query_components,
    640         sizeof(struct pair), compare_func);
    641 
    642   /* Append the query components together to make a full query string */
    643   for(index = 0; index < num_query_components; index++) {
    644 
    645     if(index)
    646       result = curlx_dyn_addn(dq, "&", 1);
    647     if(!result) {
    648       char *key_ptr = curlx_dyn_ptr(&encoded_query_array[index].key);
    649       char *value_ptr = curlx_dyn_ptr(&encoded_query_array[index].value);
    650       size_t vlen = curlx_dyn_len(&encoded_query_array[index].value);
    651       if(value_ptr && vlen) {
    652         result = curlx_dyn_addf(dq, "%s=%s", key_ptr, value_ptr);
    653       }
    654       else {
    655         /* Empty value is always encoded to key= */
    656         result = curlx_dyn_addf(dq, "%s=", key_ptr);
    657       }
    658     }
    659     if(result)
    660       break;
    661   }
    662 
    663 fail:
    664   if(counted_query_components)
    665     /* the encoded_query_array might not be initialized yet */
    666     pair_array_free(&encoded_query_array[0], counted_query_components);
    667   dyn_array_free(&query_array[0], num_query_components);
    668   return result;
    669 }
    670 
    671 CURLcode Curl_output_aws_sigv4(struct Curl_easy *data)
    672 {
    673   CURLcode result = CURLE_OUT_OF_MEMORY;
    674   struct connectdata *conn = data->conn;
    675   const char *line;
    676   struct Curl_str provider0;
    677   struct Curl_str provider1;
    678   struct Curl_str region = { NULL, 0};
    679   struct Curl_str service = { NULL, 0};
    680   const char *hostname = conn->host.name;
    681   time_t clock;
    682   struct tm tm;
    683   char timestamp[TIMESTAMP_SIZE];
    684   char date[9];
    685   struct dynbuf canonical_headers;
    686   struct dynbuf signed_headers;
    687   struct dynbuf canonical_query;
    688   struct dynbuf canonical_path;
    689   char *date_header = NULL;
    690   Curl_HttpReq httpreq;
    691   const char *method = NULL;
    692   const char *payload_hash = NULL;
    693   size_t payload_hash_len = 0;
    694   unsigned char sha_hash[CURL_SHA256_DIGEST_LENGTH];
    695   char sha_hex[SHA256_HEX_LENGTH];
    696   char content_sha256_hdr[CONTENT_SHA256_HDR_LEN + 2] = ""; /* add \r\n */
    697   char *canonical_request = NULL;
    698   char *request_type = NULL;
    699   char *credential_scope = NULL;
    700   char *str_to_sign = NULL;
    701   const char *user = data->state.aptr.user ? data->state.aptr.user : "";
    702   char *secret = NULL;
    703   unsigned char sign0[CURL_SHA256_DIGEST_LENGTH] = {0};
    704   unsigned char sign1[CURL_SHA256_DIGEST_LENGTH] = {0};
    705   char *auth_headers = NULL;
    706 
    707   if(data->set.path_as_is) {
    708     failf(data, "Cannot use sigv4 authentication with path-as-is flag");
    709     return CURLE_BAD_FUNCTION_ARGUMENT;
    710   }
    711 
    712   if(Curl_checkheaders(data, STRCONST("Authorization"))) {
    713     /* Authorization already present, Bailing out */
    714     return CURLE_OK;
    715   }
    716 
    717   /* we init those buffers here, so goto fail will free initialized dynbuf */
    718   curlx_dyn_init(&canonical_headers, CURL_MAX_HTTP_HEADER);
    719   curlx_dyn_init(&canonical_query, CURL_MAX_HTTP_HEADER);
    720   curlx_dyn_init(&signed_headers, CURL_MAX_HTTP_HEADER);
    721   curlx_dyn_init(&canonical_path, CURL_MAX_HTTP_HEADER);
    722 
    723   /*
    724    * Parameters parsing
    725    * Google and Outscale use the same OSC or GOOG,
    726    * but Amazon uses AWS and AMZ for header arguments.
    727    * AWS is the default because most of non-amazon providers
    728    * are still using aws:amz as a prefix.
    729    */
    730   line = data->set.str[STRING_AWS_SIGV4];
    731   if(!line || !*line)
    732     line = "aws:amz";
    733 
    734   /* provider0[:provider1[:region[:service]]]
    735 
    736      No string can be longer than N bytes of non-whitespace
    737   */
    738   if(curlx_str_until(&line, &provider0, MAX_SIGV4_LEN, ':')) {
    739     failf(data, "first aws-sigv4 provider cannot be empty");
    740     result = CURLE_BAD_FUNCTION_ARGUMENT;
    741     goto fail;
    742   }
    743   if(curlx_str_single(&line, ':') ||
    744      curlx_str_until(&line, &provider1, MAX_SIGV4_LEN, ':')) {
    745     provider1 = provider0;
    746   }
    747   else if(curlx_str_single(&line, ':') ||
    748           curlx_str_until(&line, &region, MAX_SIGV4_LEN, ':') ||
    749           curlx_str_single(&line, ':') ||
    750           curlx_str_until(&line, &service, MAX_SIGV4_LEN, ':')) {
    751     /* nothing to do */
    752   }
    753 
    754   if(!curlx_strlen(&service)) {
    755     const char *p = hostname;
    756     if(curlx_str_until(&p, &service, MAX_SIGV4_LEN, '.') ||
    757        curlx_str_single(&p, '.')) {
    758       failf(data, "aws-sigv4: service missing in parameters and hostname");
    759       result = CURLE_URL_MALFORMAT;
    760       goto fail;
    761     }
    762 
    763     infof(data, "aws_sigv4: picked service %.*s from host",
    764           (int)curlx_strlen(&service), curlx_str(&service));
    765 
    766     if(!curlx_strlen(&region)) {
    767       if(curlx_str_until(&p, &region, MAX_SIGV4_LEN, '.') ||
    768          curlx_str_single(&p, '.')) {
    769         failf(data, "aws-sigv4: region missing in parameters and hostname");
    770         result = CURLE_URL_MALFORMAT;
    771         goto fail;
    772       }
    773       infof(data, "aws_sigv4: picked region %.*s from host",
    774             (int)curlx_strlen(&region), curlx_str(&region));
    775     }
    776   }
    777 
    778   Curl_http_method(data, conn, &method, &httpreq);
    779 
    780   payload_hash =
    781     parse_content_sha_hdr(data, curlx_str(&provider1),
    782                           curlx_strlen(&provider1), &payload_hash_len);
    783 
    784   if(!payload_hash) {
    785     /* AWS S3 requires a x-amz-content-sha256 header, and supports special
    786      * values like UNSIGNED-PAYLOAD */
    787     bool sign_as_s3 = curlx_str_casecompare(&provider0, "aws") &&
    788       curlx_str_casecompare(&service, "s3");
    789 
    790     if(sign_as_s3)
    791       result = calc_s3_payload_hash(data, httpreq, curlx_str(&provider1),
    792                                     curlx_strlen(&provider1), sha_hash,
    793                                     sha_hex, content_sha256_hdr);
    794     else
    795       result = calc_payload_hash(data, sha_hash, sha_hex);
    796     if(result)
    797       goto fail;
    798 
    799     payload_hash = sha_hex;
    800     /* may be shorter than SHA256_HEX_LENGTH, like S3_UNSIGNED_PAYLOAD */
    801     payload_hash_len = strlen(sha_hex);
    802   }
    803 
    804 #ifdef DEBUGBUILD
    805   {
    806     char *force_timestamp = getenv("CURL_FORCETIME");
    807     if(force_timestamp)
    808       clock = 0;
    809     else
    810       clock = time(NULL);
    811   }
    812 #else
    813   clock = time(NULL);
    814 #endif
    815   result = Curl_gmtime(clock, &tm);
    816   if(result) {
    817     goto fail;
    818   }
    819   if(!strftime(timestamp, sizeof(timestamp), "%Y%m%dT%H%M%SZ", &tm)) {
    820     result = CURLE_OUT_OF_MEMORY;
    821     goto fail;
    822   }
    823 
    824   result = make_headers(data, hostname, timestamp,
    825                         curlx_str(&provider1), curlx_strlen(&provider1),
    826                         &date_header, content_sha256_hdr,
    827                         &canonical_headers, &signed_headers);
    828   if(result)
    829     goto fail;
    830 
    831   if(*content_sha256_hdr) {
    832     /* make_headers() needed this without the \r\n for canonicalization */
    833     size_t hdrlen = strlen(content_sha256_hdr);
    834     DEBUGASSERT(hdrlen + 3 < sizeof(content_sha256_hdr));
    835     memcpy(content_sha256_hdr + hdrlen, "\r\n", 3);
    836   }
    837 
    838   memcpy(date, timestamp, sizeof(date));
    839   date[sizeof(date) - 1] = 0;
    840 
    841   result = canon_query(data->state.up.query, &canonical_query);
    842   if(result)
    843     goto fail;
    844 
    845   result = canon_path(data->state.up.path, strlen(data->state.up.path),
    846                         &canonical_path,
    847                         should_urlencode(&service));
    848   if(result)
    849     goto fail;
    850   result = CURLE_OUT_OF_MEMORY;
    851 
    852   canonical_request =
    853     aprintf("%s\n" /* HTTPRequestMethod */
    854             "%s\n" /* CanonicalURI */
    855             "%s\n" /* CanonicalQueryString */
    856             "%s\n" /* CanonicalHeaders */
    857             "%s\n" /* SignedHeaders */
    858             "%.*s",  /* HashedRequestPayload in hex */
    859             method,
    860             curlx_dyn_ptr(&canonical_path),
    861             curlx_dyn_ptr(&canonical_query) ?
    862             curlx_dyn_ptr(&canonical_query) : "",
    863             curlx_dyn_ptr(&canonical_headers),
    864             curlx_dyn_ptr(&signed_headers),
    865             (int)payload_hash_len, payload_hash);
    866   if(!canonical_request)
    867     goto fail;
    868 
    869   infof(data, "aws_sigv4: Canonical request (enclosed in []) - [%s]",
    870     canonical_request);
    871 
    872   request_type = aprintf("%.*s4_request",
    873                          (int)curlx_strlen(&provider0), curlx_str(&provider0));
    874   if(!request_type)
    875     goto fail;
    876 
    877   /* provider0 is lowercased *after* aprintf() so that the buffer can be
    878      written to */
    879   Curl_strntolower(request_type, request_type, curlx_strlen(&provider0));
    880 
    881   credential_scope = aprintf("%s/%.*s/%.*s/%s", date,
    882                              (int)curlx_strlen(&region), curlx_str(&region),
    883                              (int)curlx_strlen(&service), curlx_str(&service),
    884                              request_type);
    885   if(!credential_scope)
    886     goto fail;
    887 
    888   if(Curl_sha256it(sha_hash, (unsigned char *) canonical_request,
    889                    strlen(canonical_request)))
    890     goto fail;
    891 
    892   sha256_to_hex(sha_hex, sha_hash);
    893 
    894   /*
    895    * Google allows using RSA key instead of HMAC, so this code might change
    896    * in the future. For now we only support HMAC.
    897    */
    898   str_to_sign = aprintf("%.*s4-HMAC-SHA256\n" /* Algorithm */
    899                         "%s\n" /* RequestDateTime */
    900                         "%s\n" /* CredentialScope */
    901                         "%s",  /* HashedCanonicalRequest in hex */
    902                         (int)curlx_strlen(&provider0), curlx_str(&provider0),
    903                         timestamp,
    904                         credential_scope,
    905                         sha_hex);
    906   if(!str_to_sign)
    907     goto fail;
    908 
    909   /* make provider0 part done uppercase */
    910   Curl_strntoupper(str_to_sign, curlx_str(&provider0),
    911                    curlx_strlen(&provider0));
    912 
    913   infof(data, "aws_sigv4: String to sign (enclosed in []) - [%s]",
    914     str_to_sign);
    915 
    916   secret = aprintf("%.*s4%s", (int)curlx_strlen(&provider0),
    917                    curlx_str(&provider0), data->state.aptr.passwd ?
    918                    data->state.aptr.passwd : "");
    919   if(!secret)
    920     goto fail;
    921   /* make provider0 part done uppercase */
    922   Curl_strntoupper(secret, curlx_str(&provider0), curlx_strlen(&provider0));
    923 
    924   HMAC_SHA256(secret, strlen(secret), date, strlen(date), sign0);
    925   HMAC_SHA256(sign0, sizeof(sign0),
    926               curlx_str(&region), curlx_strlen(&region), sign1);
    927   HMAC_SHA256(sign1, sizeof(sign1),
    928               curlx_str(&service), curlx_strlen(&service), sign0);
    929   HMAC_SHA256(sign0, sizeof(sign0), request_type, strlen(request_type), sign1);
    930   HMAC_SHA256(sign1, sizeof(sign1), str_to_sign, strlen(str_to_sign), sign0);
    931 
    932   sha256_to_hex(sha_hex, sign0);
    933 
    934   infof(data, "aws_sigv4: Signature - %s", sha_hex);
    935 
    936   auth_headers = aprintf("Authorization: %.*s4-HMAC-SHA256 "
    937                          "Credential=%s/%s, "
    938                          "SignedHeaders=%s, "
    939                          "Signature=%s\r\n"
    940                          /*
    941                           * date_header is added here, only if it was not
    942                           * user-specified (using CURLOPT_HTTPHEADER).
    943                           * date_header includes \r\n
    944                           */
    945                          "%s"
    946                          "%s", /* optional sha256 header includes \r\n */
    947                          (int)curlx_strlen(&provider0), curlx_str(&provider0),
    948                          user,
    949                          credential_scope,
    950                          curlx_dyn_ptr(&signed_headers),
    951                          sha_hex,
    952                          date_header ? date_header : "",
    953                          content_sha256_hdr);
    954   if(!auth_headers) {
    955     goto fail;
    956   }
    957   /* provider 0 uppercase */
    958   Curl_strntoupper(&auth_headers[sizeof("Authorization: ") - 1],
    959                    curlx_str(&provider0), curlx_strlen(&provider0));
    960 
    961   free(data->state.aptr.userpwd);
    962   data->state.aptr.userpwd = auth_headers;
    963   data->state.authhost.done = TRUE;
    964   result = CURLE_OK;
    965 
    966 fail:
    967   curlx_dyn_free(&canonical_query);
    968   curlx_dyn_free(&canonical_path);
    969   curlx_dyn_free(&canonical_headers);
    970   curlx_dyn_free(&signed_headers);
    971   free(canonical_request);
    972   free(request_type);
    973   free(credential_scope);
    974   free(str_to_sign);
    975   free(secret);
    976   free(date_header);
    977   return result;
    978 }
    979 
    980 /*
    981 * Frees all allocated strings in a dynbuf pair array, and the dynbuf itself
    982 */
    983 
    984 static void pair_array_free(struct pair *pair_array, size_t num_elements)
    985 {
    986   size_t index;
    987 
    988   for(index = 0; index != num_elements; index++) {
    989     curlx_dyn_free(&pair_array[index].key);
    990     curlx_dyn_free(&pair_array[index].value);
    991   }
    992 
    993 }
    994 
    995 /*
    996 * Frees all allocated strings in a split dynbuf, and the dynbuf itself
    997 */
    998 
    999 static void dyn_array_free(struct dynbuf *db, size_t num_elements)
   1000 {
   1001   size_t index;
   1002 
   1003   for(index = 0; index < num_elements; index++)
   1004     curlx_dyn_free((&db[index]));
   1005 }
   1006 
   1007 /*
   1008 * Splits source string by SPLIT_BY, and creates an array of dynbuf in db.
   1009 * db is initialized by this function.
   1010 * Caller is responsible for freeing the array elements with dyn_array_free
   1011 */
   1012 
   1013 #define SPLIT_BY '&'
   1014 
   1015 static CURLcode split_to_dyn_array(const char *source,
   1016                                    struct dynbuf db[MAX_QUERY_COMPONENTS],
   1017                                    size_t *num_splits_out)
   1018 {
   1019   CURLcode result = CURLE_OK;
   1020   size_t len = strlen(source);
   1021   size_t pos;         /* Position in result buffer */
   1022   size_t start = 0;   /* Start of current segment */
   1023   size_t segment_length = 0;
   1024   size_t index = 0;
   1025   size_t num_splits = 0;
   1026 
   1027   /* Split source_ptr on SPLIT_BY and store the segment offsets and length in
   1028    * array */
   1029   for(pos = 0; pos < len; pos++) {
   1030     if(source[pos] == SPLIT_BY) {
   1031       if(segment_length) {
   1032         curlx_dyn_init(&db[index], segment_length + 1);
   1033         result = curlx_dyn_addn(&db[index], &source[start],
   1034                                 segment_length);
   1035         if(result)
   1036           goto fail;
   1037 
   1038         segment_length = 0;
   1039         index++;
   1040         if(++num_splits == MAX_QUERY_COMPONENTS) {
   1041           result = CURLE_TOO_LARGE;
   1042           goto fail;
   1043         }
   1044       }
   1045       start = pos + 1;
   1046     }
   1047     else {
   1048       segment_length++;
   1049     }
   1050   }
   1051 
   1052   if(segment_length) {
   1053     curlx_dyn_init(&db[index], segment_length + 1);
   1054     result = curlx_dyn_addn(&db[index], &source[start], segment_length);
   1055     if(!result) {
   1056       if(++num_splits == MAX_QUERY_COMPONENTS)
   1057         result = CURLE_TOO_LARGE;
   1058     }
   1059   }
   1060 fail:
   1061   *num_splits_out = num_splits;
   1062   return result;
   1063 }
   1064 
   1065 
   1066 static bool is_reserved_char(const char c)
   1067 {
   1068   return (ISALNUM(c) || ISURLPUNTCS(c));
   1069 }
   1070 
   1071 static CURLcode uri_encode_path(struct Curl_str *original_path,
   1072                                 struct dynbuf *new_path)
   1073 {
   1074   const char *p = curlx_str(original_path);
   1075   size_t i;
   1076 
   1077   for(i = 0; i < curlx_strlen(original_path); i++) {
   1078     /* Do not encode slashes or unreserved chars from RFC 3986 */
   1079     CURLcode result = CURLE_OK;
   1080     unsigned char c = p[i];
   1081     if(is_reserved_char(c) || c == '/')
   1082       result = curlx_dyn_addn(new_path, &c, 1);
   1083     else
   1084       result = curlx_dyn_addf(new_path, "%%%02X", c);
   1085     if(result)
   1086       return result;
   1087   }
   1088 
   1089   return CURLE_OK;
   1090 }
   1091 
   1092 
   1093 static CURLcode encode_query_component(char *component, size_t len,
   1094                                        struct dynbuf *db)
   1095 {
   1096   size_t i;
   1097   for(i = 0; i < len; i++) {
   1098     CURLcode result = CURLE_OK;
   1099     unsigned char this_char = component[i];
   1100 
   1101     if(is_reserved_char(this_char))
   1102       /* Escape unreserved chars from RFC 3986 */
   1103       result = curlx_dyn_addn(db, &this_char, 1);
   1104     else if(this_char == '+')
   1105       /* Encode '+' as space */
   1106       result = curlx_dyn_add(db, "%20");
   1107     else
   1108       result = curlx_dyn_addf(db, "%%%02X", this_char);
   1109     if(result)
   1110       return result;
   1111   }
   1112 
   1113   return CURLE_OK;
   1114 }
   1115 
   1116 /*
   1117 * Populates a dynbuf containing url_encode(url_decode(in))
   1118 */
   1119 
   1120 static CURLcode http_aws_decode_encode(const char *in, size_t in_len,
   1121                                        struct dynbuf *out)
   1122 {
   1123   char *out_s;
   1124   size_t out_s_len;
   1125   CURLcode result =
   1126     Curl_urldecode(in, in_len, &out_s, &out_s_len, REJECT_NADA);
   1127 
   1128   if(!result) {
   1129     result = encode_query_component(out_s, out_s_len, out);
   1130     Curl_safefree(out_s);
   1131   }
   1132   return result;
   1133 }
   1134 
   1135 static bool should_urlencode(struct Curl_str *service_name)
   1136 {
   1137   /*
   1138    * These services require unmodified (not additionally url encoded) URL
   1139    * paths.
   1140    * should_urlencode == true is equivalent to should_urlencode_uri_path
   1141    * from the AWS SDK. Urls are already normalized by the curl url parser
   1142    */
   1143 
   1144   if(curlx_str_cmp(service_name, "s3") ||
   1145      curlx_str_cmp(service_name, "s3-express") ||
   1146      curlx_str_cmp(service_name, "s3-outposts")) {
   1147     return false;
   1148   }
   1149   return true;
   1150 }
   1151 
   1152 #endif /* !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_AWS) */