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 
sha256_to_hex(char *dst, unsigned char *sha)65 static 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 
find_date_hdr(struct Curl_easy *data, const char *sig_hdr)71 static 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 */
trim_headers(struct curl_slist *head)81 static 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 */
make_headers(struct Curl_easy *data, const char *hostname, char *timestamp, char *provider1, char **date_header, char *content_sha256_header, struct dynbuf *canonical_headers, struct dynbuf *signed_headers)133 static 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;
310 fail:
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 */
parse_content_sha_hdr(struct Curl_easy *data, const char *provider1, size_t *value_len)322 static 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 
calc_payload_hash(struct Curl_easy *data, unsigned char *sha_hash, char *sha_hex)353 static 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 
calc_s3_payload_hash(struct Curl_easy *data, Curl_HttpReq httpreq, char *provider1, unsigned char *sha_hash, char *sha_hex, char *header)375 static 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;
406 fail:
407   return ret;
408 }
409 
410 struct pair {
411   const char *p;
412   size_t len;
413 };
414 
compare_func(const void *a, const void *b)415 static 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 
canon_query(struct Curl_easy *data, const char *query, struct dynbuf *dq)429 static 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 
Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)527 CURLcode 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 
804 fail:
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