xref: /third_party/curl/lib/http_aws_sigv4.c (revision 13498266)
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.haxx.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
39#include <time.h>
40
41/* The last 3 #include files should be in this order */
42#include "curl_printf.h"
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  do {                                         \
50    result = Curl_hmacit(Curl_HMAC_SHA256,     \
51                         (unsigned char *)k,   \
52                         kl,                   \
53                         (unsigned char *)d,   \
54                         dl, o);               \
55    if(result) {                               \
56      goto fail;                               \
57    }                                          \
58  } while(0)
59
60#define TIMESTAMP_SIZE 17
61
62/* hex-encoded with trailing null */
63#define SHA256_HEX_LENGTH (2 * SHA256_DIGEST_LENGTH + 1)
64
65static void sha256_to_hex(char *dst, unsigned char *sha)
66{
67  Curl_hexencode(sha, SHA256_DIGEST_LENGTH,
68                 (unsigned char *)dst, SHA256_HEX_LENGTH);
69}
70
71static char *find_date_hdr(struct Curl_easy *data, const char *sig_hdr)
72{
73  char *tmp = Curl_checkheaders(data, sig_hdr, strlen(sig_hdr));
74
75  if(tmp)
76    return tmp;
77  return Curl_checkheaders(data, STRCONST("Date"));
78}
79
80/* remove whitespace, and lowercase all headers */
81static void trim_headers(struct curl_slist *head)
82{
83  struct curl_slist *l;
84  for(l = head; l; l = l->next) {
85    char *value; /* to read from */
86    char *store;
87    size_t colon = strcspn(l->data, ":");
88    Curl_strntolower(l->data, l->data, colon);
89
90    value = &l->data[colon];
91    if(!*value)
92      continue;
93    ++value;
94    store = value;
95
96    /* skip leading whitespace */
97    while(*value && ISBLANK(*value))
98      value++;
99
100    while(*value) {
101      int space = 0;
102      while(*value && ISBLANK(*value)) {
103        value++;
104        space++;
105      }
106      if(space) {
107        /* replace any number of consecutive whitespace with a single space,
108           unless at the end of the string, then nothing */
109        if(*value)
110          *store++ = ' ';
111      }
112      else
113        *store++ = *value++;
114    }
115    *store = 0; /* null terminate */
116  }
117}
118
119/* maximum length for the aws sivg4 parts */
120#define MAX_SIGV4_LEN 64
121#define MAX_SIGV4_LEN_TXT "64"
122
123#define DATE_HDR_KEY_LEN (MAX_SIGV4_LEN + sizeof("X--Date"))
124
125#define MAX_HOST_LEN 255
126/* FQDN + host: */
127#define FULL_HOST_LEN (MAX_HOST_LEN + sizeof("host:"))
128
129/* string been x-PROVIDER-date:TIMESTAMP, I need +1 for ':' */
130#define DATE_FULL_HDR_LEN (DATE_HDR_KEY_LEN + TIMESTAMP_SIZE + 1)
131
132/* timestamp should point to a buffer of at last TIMESTAMP_SIZE bytes */
133static CURLcode make_headers(struct Curl_easy *data,
134                             const char *hostname,
135                             char *timestamp,
136                             char *provider1,
137                             char **date_header,
138                             char *content_sha256_header,
139                             struct dynbuf *canonical_headers,
140                             struct dynbuf *signed_headers)
141{
142  char date_hdr_key[DATE_HDR_KEY_LEN];
143  char date_full_hdr[DATE_FULL_HDR_LEN];
144  struct curl_slist *head = NULL;
145  struct curl_slist *tmp_head = NULL;
146  CURLcode ret = CURLE_OUT_OF_MEMORY;
147  struct curl_slist *l;
148  int again = 1;
149
150  /* provider1 mid */
151  Curl_strntolower(provider1, provider1, strlen(provider1));
152  provider1[0] = Curl_raw_toupper(provider1[0]);
153
154  msnprintf(date_hdr_key, DATE_HDR_KEY_LEN, "X-%s-Date", provider1);
155
156  /* provider1 lowercase */
157  Curl_strntolower(provider1, provider1, 1); /* first byte only */
158  msnprintf(date_full_hdr, DATE_FULL_HDR_LEN,
159            "x-%s-date:%s", provider1, timestamp);
160
161  if(Curl_checkheaders(data, STRCONST("Host"))) {
162    head = NULL;
163  }
164  else {
165    char full_host[FULL_HOST_LEN + 1];
166
167    if(data->state.aptr.host) {
168      size_t pos;
169
170      if(strlen(data->state.aptr.host) > FULL_HOST_LEN) {
171        ret = CURLE_URL_MALFORMAT;
172        goto fail;
173      }
174      strcpy(full_host, data->state.aptr.host);
175      /* remove /r/n as the separator for canonical request must be '\n' */
176      pos = strcspn(full_host, "\n\r");
177      full_host[pos] = 0;
178    }
179    else {
180      if(strlen(hostname) > MAX_HOST_LEN) {
181        ret = CURLE_URL_MALFORMAT;
182        goto fail;
183      }
184      msnprintf(full_host, FULL_HOST_LEN, "host:%s", hostname);
185    }
186
187    head = curl_slist_append(NULL, full_host);
188    if(!head)
189      goto fail;
190  }
191
192
193  if(*content_sha256_header) {
194    tmp_head = curl_slist_append(head, content_sha256_header);
195    if(!tmp_head)
196      goto fail;
197    head = tmp_head;
198  }
199
200  /* copy user headers to our header list. the logic is based on how http.c
201     handles user headers.
202
203     user headers in format 'name:' with no value are used to signal that an
204     internal header of that name should be removed. those user headers are not
205     added to this list.
206
207     user headers in format 'name;' with no value are used to signal that a
208     header of that name with no value should be sent. those user headers are
209     added to this list but in the format that they will be sent, ie the
210     semi-colon is changed to a colon for format 'name:'.
211
212     user headers with a value of whitespace only, or without a colon or
213     semi-colon, are not added to this list.
214     */
215  for(l = data->set.headers; l; l = l->next) {
216    char *dupdata, *ptr;
217    char *sep = strchr(l->data, ':');
218    if(!sep)
219      sep = strchr(l->data, ';');
220    if(!sep || (*sep == ':' && !*(sep + 1)))
221      continue;
222    for(ptr = sep + 1; ISSPACE(*ptr); ++ptr)
223      ;
224    if(!*ptr && ptr != sep + 1) /* a value of whitespace only */
225      continue;
226    dupdata = strdup(l->data);
227    if(!dupdata)
228      goto fail;
229    dupdata[sep - l->data] = ':';
230    tmp_head = Curl_slist_append_nodup(head, dupdata);
231    if(!tmp_head) {
232      free(dupdata);
233      goto fail;
234    }
235    head = tmp_head;
236  }
237
238  trim_headers(head);
239
240  *date_header = find_date_hdr(data, date_hdr_key);
241  if(!*date_header) {
242    tmp_head = curl_slist_append(head, date_full_hdr);
243    if(!tmp_head)
244      goto fail;
245    head = tmp_head;
246    *date_header = curl_maprintf("%s: %s\r\n", date_hdr_key, timestamp);
247  }
248  else {
249    char *value;
250    char *endp;
251    value = strchr(*date_header, ':');
252    if(!value) {
253      *date_header = NULL;
254      goto fail;
255    }
256    ++value;
257    while(ISBLANK(*value))
258      ++value;
259    endp = value;
260    while(*endp && ISALNUM(*endp))
261      ++endp;
262    /* 16 bytes => "19700101T000000Z" */
263    if((endp - value) == TIMESTAMP_SIZE - 1) {
264      memcpy(timestamp, value, TIMESTAMP_SIZE - 1);
265      timestamp[TIMESTAMP_SIZE - 1] = 0;
266    }
267    else
268      /* bad timestamp length */
269      timestamp[0] = 0;
270    *date_header = NULL;
271  }
272
273  /* alpha-sort in a case sensitive manner */
274  do {
275    again = 0;
276    for(l = head; l; l = l->next) {
277      struct curl_slist *next = l->next;
278
279      if(next && strcmp(l->data, next->data) > 0) {
280        char *tmp = l->data;
281
282        l->data = next->data;
283        next->data = tmp;
284        again = 1;
285      }
286    }
287  } while(again);
288
289  for(l = head; l; l = l->next) {
290    char *tmp;
291
292    if(Curl_dyn_add(canonical_headers, l->data))
293      goto fail;
294    if(Curl_dyn_add(canonical_headers, "\n"))
295      goto fail;
296
297    tmp = strchr(l->data, ':');
298    if(tmp)
299      *tmp = 0;
300
301    if(l != head) {
302      if(Curl_dyn_add(signed_headers, ";"))
303        goto fail;
304    }
305    if(Curl_dyn_add(signed_headers, l->data))
306      goto fail;
307  }
308
309  ret = CURLE_OK;
310fail:
311  curl_slist_free_all(head);
312
313  return ret;
314}
315
316#define CONTENT_SHA256_KEY_LEN (MAX_SIGV4_LEN + sizeof("X--Content-Sha256"))
317/* add 2 for ": " between header name and value */
318#define CONTENT_SHA256_HDR_LEN (CONTENT_SHA256_KEY_LEN + 2 + \
319                                SHA256_HEX_LENGTH)
320
321/* try to parse a payload hash from the content-sha256 header */
322static char *parse_content_sha_hdr(struct Curl_easy *data,
323                                   const char *provider1,
324                                   size_t *value_len)
325{
326  char key[CONTENT_SHA256_KEY_LEN];
327  size_t key_len;
328  char *value;
329  size_t len;
330
331  key_len = msnprintf(key, sizeof(key), "x-%s-content-sha256", provider1);
332
333  value = Curl_checkheaders(data, key, key_len);
334  if(!value)
335    return NULL;
336
337  value = strchr(value, ':');
338  if(!value)
339    return NULL;
340  ++value;
341
342  while(*value && ISBLANK(*value))
343    ++value;
344
345  len = strlen(value);
346  while(len > 0 && ISBLANK(value[len-1]))
347    --len;
348
349  *value_len = len;
350  return value;
351}
352
353static CURLcode calc_payload_hash(struct Curl_easy *data,
354                                  unsigned char *sha_hash, char *sha_hex)
355{
356  const char *post_data = data->set.postfields;
357  size_t post_data_len = 0;
358  CURLcode result;
359
360  if(post_data) {
361    if(data->set.postfieldsize < 0)
362      post_data_len = strlen(post_data);
363    else
364      post_data_len = (size_t)data->set.postfieldsize;
365  }
366  result = Curl_sha256it(sha_hash, (const unsigned char *) post_data,
367                         post_data_len);
368  if(!result)
369    sha256_to_hex(sha_hex, sha_hash);
370  return result;
371}
372
373#define S3_UNSIGNED_PAYLOAD "UNSIGNED-PAYLOAD"
374
375static CURLcode calc_s3_payload_hash(struct Curl_easy *data,
376                                     Curl_HttpReq httpreq, char *provider1,
377                                     unsigned char *sha_hash,
378                                     char *sha_hex, char *header)
379{
380  bool empty_method = (httpreq == HTTPREQ_GET || httpreq == HTTPREQ_HEAD);
381  /* The request method or filesize indicate no request payload */
382  bool empty_payload = (empty_method || data->set.filesize == 0);
383  /* The POST payload is in memory */
384  bool post_payload = (httpreq == HTTPREQ_POST && data->set.postfields);
385  CURLcode ret = CURLE_OUT_OF_MEMORY;
386
387  if(empty_payload || post_payload) {
388    /* Calculate a real hash when we know the request payload */
389    ret = calc_payload_hash(data, sha_hash, sha_hex);
390    if(ret)
391      goto fail;
392  }
393  else {
394    /* Fall back to s3's UNSIGNED-PAYLOAD */
395    size_t len = sizeof(S3_UNSIGNED_PAYLOAD) - 1;
396    DEBUGASSERT(len < SHA256_HEX_LENGTH); /* 16 < 65 */
397    memcpy(sha_hex, S3_UNSIGNED_PAYLOAD, len);
398    sha_hex[len] = 0;
399  }
400
401  /* format the required content-sha256 header */
402  msnprintf(header, CONTENT_SHA256_HDR_LEN,
403            "x-%s-content-sha256: %s", provider1, sha_hex);
404
405  ret = CURLE_OK;
406fail:
407  return ret;
408}
409
410struct pair {
411  const char *p;
412  size_t len;
413};
414
415static int compare_func(const void *a, const void *b)
416{
417  const struct pair *aa = a;
418  const struct pair *bb = b;
419  /* If one element is empty, the other is always sorted higher */
420  if(aa->len == 0)
421    return -1;
422  if(bb->len == 0)
423    return 1;
424  return strncmp(aa->p, bb->p, aa->len < bb->len ? aa->len : bb->len);
425}
426
427#define MAX_QUERYPAIRS 64
428
429static CURLcode canon_query(struct Curl_easy *data,
430                            const char *query, struct dynbuf *dq)
431{
432  CURLcode result = CURLE_OK;
433  int entry = 0;
434  int i;
435  const char *p = query;
436  struct pair array[MAX_QUERYPAIRS];
437  struct pair *ap = &array[0];
438  if(!query)
439    return result;
440
441  /* sort the name=value pairs first */
442  do {
443    char *amp;
444    entry++;
445    ap->p = p;
446    amp = strchr(p, '&');
447    if(amp)
448      ap->len = amp - p; /* excluding the ampersand */
449    else {
450      ap->len = strlen(p);
451      break;
452    }
453    ap++;
454    p = amp + 1;
455  } while(entry < MAX_QUERYPAIRS);
456  if(entry == MAX_QUERYPAIRS) {
457    /* too many query pairs for us */
458    failf(data, "aws-sigv4: too many query pairs in URL");
459    return CURLE_URL_MALFORMAT;
460  }
461
462  qsort(&array[0], entry, sizeof(struct pair), compare_func);
463
464  ap = &array[0];
465  for(i = 0; !result && (i < entry); i++, ap++) {
466    size_t len;
467    const char *q = ap->p;
468    bool found_equals = false;
469    if(!ap->len)
470      continue;
471    for(len = ap->len; len && !result; q++, len--) {
472      if(ISALNUM(*q))
473        result = Curl_dyn_addn(dq, q, 1);
474      else {
475        switch(*q) {
476        case '-':
477        case '.':
478        case '_':
479        case '~':
480          /* allowed as-is */
481          result = Curl_dyn_addn(dq, q, 1);
482          break;
483        case '=':
484          /* allowed as-is */
485          result = Curl_dyn_addn(dq, q, 1);
486          found_equals = true;
487          break;
488        case '%':
489          /* uppercase the following if hexadecimal */
490          if(ISXDIGIT(q[1]) && ISXDIGIT(q[2])) {
491            char tmp[3]="%";
492            tmp[1] = Curl_raw_toupper(q[1]);
493            tmp[2] = Curl_raw_toupper(q[2]);
494            result = Curl_dyn_addn(dq, tmp, 3);
495            q += 2;
496            len -= 2;
497          }
498          else
499            /* '%' without a following two-digit hex, encode it */
500            result = Curl_dyn_addn(dq, "%25", 3);
501          break;
502        default: {
503          /* URL encode */
504          const char hex[] = "0123456789ABCDEF";
505          char out[3]={'%'};
506          out[1] = hex[((unsigned char)*q)>>4];
507          out[2] = hex[*q & 0xf];
508          result = Curl_dyn_addn(dq, out, 3);
509          break;
510        }
511        }
512      }
513    }
514    if(!result && !found_equals) {
515      /* queries without value still need an equals */
516      result = Curl_dyn_addn(dq, "=", 1);
517    }
518    if(!result && i < entry - 1) {
519      /* insert ampersands between query pairs */
520      result = Curl_dyn_addn(dq, "&", 1);
521    }
522  }
523  return result;
524}
525
526
527CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
528{
529  CURLcode result = CURLE_OUT_OF_MEMORY;
530  struct connectdata *conn = data->conn;
531  size_t len;
532  const char *arg;
533  char provider0[MAX_SIGV4_LEN + 1]="";
534  char provider1[MAX_SIGV4_LEN + 1]="";
535  char region[MAX_SIGV4_LEN + 1]="";
536  char service[MAX_SIGV4_LEN + 1]="";
537  bool sign_as_s3 = false;
538  const char *hostname = conn->host.name;
539  time_t clock;
540  struct tm tm;
541  char timestamp[TIMESTAMP_SIZE];
542  char date[9];
543  struct dynbuf canonical_headers;
544  struct dynbuf signed_headers;
545  struct dynbuf canonical_query;
546  char *date_header = NULL;
547  Curl_HttpReq httpreq;
548  const char *method = NULL;
549  char *payload_hash = NULL;
550  size_t payload_hash_len = 0;
551  unsigned char sha_hash[SHA256_DIGEST_LENGTH];
552  char sha_hex[SHA256_HEX_LENGTH];
553  char content_sha256_hdr[CONTENT_SHA256_HDR_LEN + 2] = ""; /* add \r\n */
554  char *canonical_request = NULL;
555  char *request_type = NULL;
556  char *credential_scope = NULL;
557  char *str_to_sign = NULL;
558  const char *user = data->state.aptr.user ? data->state.aptr.user : "";
559  char *secret = NULL;
560  unsigned char sign0[SHA256_DIGEST_LENGTH] = {0};
561  unsigned char sign1[SHA256_DIGEST_LENGTH] = {0};
562  char *auth_headers = NULL;
563
564  DEBUGASSERT(!proxy);
565  (void)proxy;
566
567  if(Curl_checkheaders(data, STRCONST("Authorization"))) {
568    /* Authorization already present, Bailing out */
569    return CURLE_OK;
570  }
571
572  /* we init those buffers here, so goto fail will free initialized dynbuf */
573  Curl_dyn_init(&canonical_headers, CURL_MAX_HTTP_HEADER);
574  Curl_dyn_init(&canonical_query, CURL_MAX_HTTP_HEADER);
575  Curl_dyn_init(&signed_headers, CURL_MAX_HTTP_HEADER);
576
577  /*
578   * Parameters parsing
579   * Google and Outscale use the same OSC or GOOG,
580   * but Amazon uses AWS and AMZ for header arguments.
581   * AWS is the default because most of non-amazon providers
582   * are still using aws:amz as a prefix.
583   */
584  arg = data->set.str[STRING_AWS_SIGV4] ?
585    data->set.str[STRING_AWS_SIGV4] : "aws:amz";
586
587  /* provider1[:provider2[:region[:service]]]
588
589     No string can be longer than N bytes of non-whitespace
590  */
591  (void)sscanf(arg, "%" MAX_SIGV4_LEN_TXT "[^:]"
592               ":%" MAX_SIGV4_LEN_TXT "[^:]"
593               ":%" MAX_SIGV4_LEN_TXT "[^:]"
594               ":%" MAX_SIGV4_LEN_TXT "s",
595               provider0, provider1, region, service);
596  if(!provider0[0]) {
597    failf(data, "first aws-sigv4 provider can't be empty");
598    result = CURLE_BAD_FUNCTION_ARGUMENT;
599    goto fail;
600  }
601  else if(!provider1[0])
602    strcpy(provider1, provider0);
603
604  if(!service[0]) {
605    char *hostdot = strchr(hostname, '.');
606    if(!hostdot) {
607      failf(data, "aws-sigv4: service missing in parameters and hostname");
608      result = CURLE_URL_MALFORMAT;
609      goto fail;
610    }
611    len = hostdot - hostname;
612    if(len > MAX_SIGV4_LEN) {
613      failf(data, "aws-sigv4: service too long in hostname");
614      result = CURLE_URL_MALFORMAT;
615      goto fail;
616    }
617    memcpy(service, hostname, len);
618    service[len] = '\0';
619
620    infof(data, "aws_sigv4: picked service %s from host", service);
621
622    if(!region[0]) {
623      const char *reg = hostdot + 1;
624      const char *hostreg = strchr(reg, '.');
625      if(!hostreg) {
626        failf(data, "aws-sigv4: region missing in parameters and hostname");
627        result = CURLE_URL_MALFORMAT;
628        goto fail;
629      }
630      len = hostreg - reg;
631      if(len > MAX_SIGV4_LEN) {
632        failf(data, "aws-sigv4: region too long in hostname");
633        result = CURLE_URL_MALFORMAT;
634        goto fail;
635      }
636      memcpy(region, reg, len);
637      region[len] = '\0';
638      infof(data, "aws_sigv4: picked region %s from host", region);
639    }
640  }
641
642  Curl_http_method(data, conn, &method, &httpreq);
643
644  /* AWS S3 requires a x-amz-content-sha256 header, and supports special
645   * values like UNSIGNED-PAYLOAD */
646  sign_as_s3 = (strcasecompare(provider0, "aws") &&
647                strcasecompare(service, "s3"));
648
649  payload_hash = parse_content_sha_hdr(data, provider1, &payload_hash_len);
650
651  if(!payload_hash) {
652    if(sign_as_s3)
653      result = calc_s3_payload_hash(data, httpreq, provider1, sha_hash,
654                                    sha_hex, content_sha256_hdr);
655    else
656      result = calc_payload_hash(data, sha_hash, sha_hex);
657    if(result)
658      goto fail;
659
660    payload_hash = sha_hex;
661    /* may be shorter than SHA256_HEX_LENGTH, like S3_UNSIGNED_PAYLOAD */
662    payload_hash_len = strlen(sha_hex);
663  }
664
665#ifdef DEBUGBUILD
666  {
667    char *force_timestamp = getenv("CURL_FORCETIME");
668    if(force_timestamp)
669      clock = 0;
670    else
671      time(&clock);
672  }
673#else
674  time(&clock);
675#endif
676  result = Curl_gmtime(clock, &tm);
677  if(result) {
678    goto fail;
679  }
680  if(!strftime(timestamp, sizeof(timestamp), "%Y%m%dT%H%M%SZ", &tm)) {
681    result = CURLE_OUT_OF_MEMORY;
682    goto fail;
683  }
684
685  result = make_headers(data, hostname, timestamp, provider1,
686                        &date_header, content_sha256_hdr,
687                        &canonical_headers, &signed_headers);
688  if(result)
689    goto fail;
690
691  if(*content_sha256_hdr) {
692    /* make_headers() needed this without the \r\n for canonicalization */
693    size_t hdrlen = strlen(content_sha256_hdr);
694    DEBUGASSERT(hdrlen + 3 < sizeof(content_sha256_hdr));
695    memcpy(content_sha256_hdr + hdrlen, "\r\n", 3);
696  }
697
698  memcpy(date, timestamp, sizeof(date));
699  date[sizeof(date) - 1] = 0;
700
701  result = canon_query(data, data->state.up.query, &canonical_query);
702  if(result)
703    goto fail;
704  result = CURLE_OUT_OF_MEMORY;
705
706  canonical_request =
707    curl_maprintf("%s\n" /* HTTPRequestMethod */
708                  "%s\n" /* CanonicalURI */
709                  "%s\n" /* CanonicalQueryString */
710                  "%s\n" /* CanonicalHeaders */
711                  "%s\n" /* SignedHeaders */
712                  "%.*s",  /* HashedRequestPayload in hex */
713                  method,
714                  data->state.up.path,
715                  Curl_dyn_ptr(&canonical_query) ?
716                  Curl_dyn_ptr(&canonical_query) : "",
717                  Curl_dyn_ptr(&canonical_headers),
718                  Curl_dyn_ptr(&signed_headers),
719                  (int)payload_hash_len, payload_hash);
720  if(!canonical_request)
721    goto fail;
722
723  DEBUGF(infof(data, "Canonical request: %s", canonical_request));
724
725  /* provider 0 lowercase */
726  Curl_strntolower(provider0, provider0, strlen(provider0));
727  request_type = curl_maprintf("%s4_request", provider0);
728  if(!request_type)
729    goto fail;
730
731  credential_scope = curl_maprintf("%s/%s/%s/%s",
732                                   date, region, service, request_type);
733  if(!credential_scope)
734    goto fail;
735
736  if(Curl_sha256it(sha_hash, (unsigned char *) canonical_request,
737                   strlen(canonical_request)))
738    goto fail;
739
740  sha256_to_hex(sha_hex, sha_hash);
741
742  /* provider 0 uppercase */
743  Curl_strntoupper(provider0, provider0, strlen(provider0));
744
745  /*
746   * Google allows using RSA key instead of HMAC, so this code might change
747   * in the future. For now we only support HMAC.
748   */
749  str_to_sign = curl_maprintf("%s4-HMAC-SHA256\n" /* Algorithm */
750                              "%s\n" /* RequestDateTime */
751                              "%s\n" /* CredentialScope */
752                              "%s",  /* HashedCanonicalRequest in hex */
753                              provider0,
754                              timestamp,
755                              credential_scope,
756                              sha_hex);
757  if(!str_to_sign) {
758    goto fail;
759  }
760
761  /* provider 0 uppercase */
762  secret = curl_maprintf("%s4%s", provider0,
763                         data->state.aptr.passwd ?
764                         data->state.aptr.passwd : "");
765  if(!secret)
766    goto fail;
767
768  HMAC_SHA256(secret, strlen(secret), date, strlen(date), sign0);
769  HMAC_SHA256(sign0, sizeof(sign0), region, strlen(region), sign1);
770  HMAC_SHA256(sign1, sizeof(sign1), service, strlen(service), sign0);
771  HMAC_SHA256(sign0, sizeof(sign0), request_type, strlen(request_type), sign1);
772  HMAC_SHA256(sign1, sizeof(sign1), str_to_sign, strlen(str_to_sign), sign0);
773
774  sha256_to_hex(sha_hex, sign0);
775
776  /* provider 0 uppercase */
777  auth_headers = curl_maprintf("Authorization: %s4-HMAC-SHA256 "
778                               "Credential=%s/%s, "
779                               "SignedHeaders=%s, "
780                               "Signature=%s\r\n"
781                               /*
782                                * date_header is added here, only if it wasn't
783                                * user-specified (using CURLOPT_HTTPHEADER).
784                                * date_header includes \r\n
785                                */
786                               "%s"
787                               "%s", /* optional sha256 header includes \r\n */
788                               provider0,
789                               user,
790                               credential_scope,
791                               Curl_dyn_ptr(&signed_headers),
792                               sha_hex,
793                               date_header ? date_header : "",
794                               content_sha256_hdr);
795  if(!auth_headers) {
796    goto fail;
797  }
798
799  Curl_safefree(data->state.aptr.userpwd);
800  data->state.aptr.userpwd = auth_headers;
801  data->state.authhost.done = TRUE;
802  result = CURLE_OK;
803
804fail:
805  Curl_dyn_free(&canonical_query);
806  Curl_dyn_free(&canonical_headers);
807  Curl_dyn_free(&signed_headers);
808  free(canonical_request);
809  free(request_type);
810  free(credential_scope);
811  free(str_to_sign);
812  free(secret);
813  free(date_header);
814  return result;
815}
816
817#endif /* !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_AWS) */
818