Coverage Report

Created: 2025-11-24 06:58

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