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