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