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