Coverage Report

Created: 2026-09-01 06:58

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