Coverage Report

Created: 2026-01-10 06:51

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