xref: /curl/lib/http_aws_sigv4.c (revision 87d9e540)
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 #include "strparse.h"
39 
40 #include <time.h>
41 
42 /* The last 3 #include files should be in this order */
43 #include "curl_printf.h"
44 #include "curl_memory.h"
45 #include "memdebug.h"
46 
47 #include "slist.h"
48 
49 #define HMAC_SHA256(k, kl, d, dl, o)           \
50   do {                                         \
51     result = Curl_hmacit(&Curl_HMAC_SHA256,    \
52                          (unsigned char *)k,   \
53                          kl,                   \
54                          (unsigned char *)d,   \
55                          dl, o);               \
56     if(result) {                               \
57       goto fail;                               \
58     }                                          \
59   } while(0)
60 
61 #define TIMESTAMP_SIZE 17
62 
63 /* hex-encoded with trailing null */
64 #define SHA256_HEX_LENGTH (2 * CURL_SHA256_DIGEST_LENGTH + 1)
65 
sha256_to_hex(char * dst,unsigned char * sha)66 static void sha256_to_hex(char *dst, unsigned char *sha)
67 {
68   Curl_hexencode(sha, CURL_SHA256_DIGEST_LENGTH,
69                  (unsigned char *)dst, SHA256_HEX_LENGTH);
70 }
71 
find_date_hdr(struct Curl_easy * data,const char * sig_hdr)72 static char *find_date_hdr(struct Curl_easy *data, const char *sig_hdr)
73 {
74   char *tmp = Curl_checkheaders(data, sig_hdr, strlen(sig_hdr));
75 
76   if(tmp)
77     return tmp;
78   return Curl_checkheaders(data, STRCONST("Date"));
79 }
80 
81 /* remove whitespace, and lowercase all headers */
trim_headers(struct curl_slist * head)82 static void trim_headers(struct curl_slist *head)
83 {
84   struct curl_slist *l;
85   for(l = head; l; l = l->next) {
86     char *value; /* to read from */
87     char *store;
88     size_t colon = strcspn(l->data, ":");
89     Curl_strntolower(l->data, l->data, colon);
90 
91     value = &l->data[colon];
92     if(!*value)
93       continue;
94     ++value;
95     store = value;
96 
97     /* skip leading whitespace */
98     while(*value && ISBLANK(*value))
99       value++;
100 
101     while(*value) {
102       int space = 0;
103       while(*value && ISBLANK(*value)) {
104         value++;
105         space++;
106       }
107       if(space) {
108         /* replace any number of consecutive whitespace with a single space,
109            unless at the end of the string, then nothing */
110         if(*value)
111           *store++ = ' ';
112       }
113       else
114         *store++ = *value++;
115     }
116     *store = 0; /* null terminate */
117   }
118 }
119 
120 /* maximum length for the aws sivg4 parts */
121 #define MAX_SIGV4_LEN 64
122 #define DATE_HDR_KEY_LEN (MAX_SIGV4_LEN + sizeof("X--Date"))
123 
124 /* string been x-PROVIDER-date:TIMESTAMP, I need +1 for ':' */
125 #define DATE_FULL_HDR_LEN (DATE_HDR_KEY_LEN + TIMESTAMP_SIZE + 1)
126 
127 /* alphabetically compare two headers by their name, expecting
128    headers to use ':' at this point */
compare_header_names(const char * a,const char * b)129 static int compare_header_names(const char *a, const char *b)
130 {
131   const char *colon_a;
132   const char *colon_b;
133   size_t len_a;
134   size_t len_b;
135   size_t min_len;
136   int cmp;
137 
138   colon_a = strchr(a, ':');
139   colon_b = strchr(b, ':');
140 
141   DEBUGASSERT(colon_a);
142   DEBUGASSERT(colon_b);
143 
144   len_a = colon_a ? (size_t)(colon_a - a) : strlen(a);
145   len_b = colon_b ? (size_t)(colon_b - b) : strlen(b);
146 
147   min_len = (len_a < len_b) ? len_a : len_b;
148 
149   cmp = strncmp(a, b, min_len);
150 
151   /* return the shorter of the two if one is shorter */
152   if(!cmp)
153     return (int)(len_a - len_b);
154 
155   return cmp;
156 }
157 
158 /* timestamp should point to a buffer of at last TIMESTAMP_SIZE bytes */
make_headers(struct Curl_easy * data,const char * hostname,char * timestamp,const char * provider1,size_t plen,char ** date_header,char * content_sha256_header,struct dynbuf * canonical_headers,struct dynbuf * signed_headers)159 static CURLcode make_headers(struct Curl_easy *data,
160                              const char *hostname,
161                              char *timestamp,
162                              const char *provider1,
163                              size_t plen, /* length of 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   msnprintf(date_hdr_key, DATE_HDR_KEY_LEN, "X-%.*s-Date",
178             (int)plen, provider1);
179   /* provider1 ucfirst */
180   Curl_strntolower(&date_hdr_key[2], provider1, plen);
181   date_hdr_key[2] = Curl_raw_toupper(provider1[0]);
182 
183   msnprintf(date_full_hdr, DATE_FULL_HDR_LEN,
184             "x-%.*s-date:%s", (int)plen, provider1, timestamp);
185   /* provider1 lowercase */
186   Curl_strntolower(&date_full_hdr[2], provider1, plen);
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 plen,size_t * value_len)337 static char *parse_content_sha_hdr(struct Curl_easy *data,
338                                    const char *provider1,
339                                    size_t plen,
340                                    size_t *value_len)
341 {
342   char key[CONTENT_SHA256_KEY_LEN];
343   size_t key_len;
344   char *value;
345   size_t len;
346 
347   key_len = msnprintf(key, sizeof(key), "x-%.*s-content-sha256",
348                       (int)plen, provider1);
349 
350   value = Curl_checkheaders(data, key, key_len);
351   if(!value)
352     return NULL;
353 
354   value = strchr(value, ':');
355   if(!value)
356     return NULL;
357   ++value;
358 
359   while(*value && ISBLANK(*value))
360     ++value;
361 
362   len = strlen(value);
363   while(len > 0 && ISBLANK(value[len-1]))
364     --len;
365 
366   *value_len = len;
367   return value;
368 }
369 
calc_payload_hash(struct Curl_easy * data,unsigned char * sha_hash,char * sha_hex)370 static CURLcode calc_payload_hash(struct Curl_easy *data,
371                                   unsigned char *sha_hash, char *sha_hex)
372 {
373   const char *post_data = data->set.postfields;
374   size_t post_data_len = 0;
375   CURLcode result;
376 
377   if(post_data) {
378     if(data->set.postfieldsize < 0)
379       post_data_len = strlen(post_data);
380     else
381       post_data_len = (size_t)data->set.postfieldsize;
382   }
383   result = Curl_sha256it(sha_hash, (const unsigned char *) post_data,
384                          post_data_len);
385   if(!result)
386     sha256_to_hex(sha_hex, sha_hash);
387   return result;
388 }
389 
390 #define S3_UNSIGNED_PAYLOAD "UNSIGNED-PAYLOAD"
391 
calc_s3_payload_hash(struct Curl_easy * data,Curl_HttpReq httpreq,char * provider1,size_t plen,unsigned char * sha_hash,char * sha_hex,char * header)392 static CURLcode calc_s3_payload_hash(struct Curl_easy *data,
393                                      Curl_HttpReq httpreq, char *provider1,
394                                      size_t plen,
395                                      unsigned char *sha_hash,
396                                      char *sha_hex, char *header)
397 {
398   bool empty_method = (httpreq == HTTPREQ_GET || httpreq == HTTPREQ_HEAD);
399   /* The request method or filesize indicate no request payload */
400   bool empty_payload = (empty_method || data->set.filesize == 0);
401   /* The POST payload is in memory */
402   bool post_payload = (httpreq == HTTPREQ_POST && data->set.postfields);
403   CURLcode ret = CURLE_OUT_OF_MEMORY;
404 
405   if(empty_payload || post_payload) {
406     /* Calculate a real hash when we know the request payload */
407     ret = calc_payload_hash(data, sha_hash, sha_hex);
408     if(ret)
409       goto fail;
410   }
411   else {
412     /* Fall back to s3's UNSIGNED-PAYLOAD */
413     size_t len = sizeof(S3_UNSIGNED_PAYLOAD) - 1;
414     DEBUGASSERT(len < SHA256_HEX_LENGTH); /* 16 < 65 */
415     memcpy(sha_hex, S3_UNSIGNED_PAYLOAD, len);
416     sha_hex[len] = 0;
417   }
418 
419   /* format the required content-sha256 header */
420   msnprintf(header, CONTENT_SHA256_HDR_LEN,
421             "x-%.*s-content-sha256: %s", (int)plen, provider1, sha_hex);
422 
423   ret = CURLE_OK;
424 fail:
425   return ret;
426 }
427 
428 struct pair {
429   const char *p;
430   size_t len;
431 };
432 
compare_func(const void * a,const void * b)433 static int compare_func(const void *a, const void *b)
434 {
435   const struct pair *aa = a;
436   const struct pair *bb = b;
437   /* If one element is empty, the other is always sorted higher */
438   if(aa->len == 0 && bb->len == 0)
439     return 0;
440   if(aa->len == 0)
441     return -1;
442   if(bb->len == 0)
443     return 1;
444   return strncmp(aa->p, bb->p, aa->len < bb->len ? aa->len : bb->len);
445 }
446 
447 #define MAX_QUERYPAIRS 64
448 
449 /**
450  * found_equals have a double meaning,
451  * detect if an equal have been found when called from canon_query,
452  * and mark that this function is called to compute the path,
453  * if found_equals is NULL.
454  */
canon_string(const char * q,size_t len,struct dynbuf * dq,bool * found_equals)455 static CURLcode canon_string(const char *q, size_t len,
456                              struct dynbuf *dq, bool *found_equals)
457 {
458   CURLcode result = CURLE_OK;
459 
460   for(; len && !result; q++, len--) {
461     if(ISALNUM(*q))
462       result = Curl_dyn_addn(dq, q, 1);
463     else {
464       switch(*q) {
465       case '-':
466       case '.':
467       case '_':
468       case '~':
469         /* allowed as-is */
470         result = Curl_dyn_addn(dq, q, 1);
471         break;
472       case '%':
473         /* uppercase the following if hexadecimal */
474         if(ISXDIGIT(q[1]) && ISXDIGIT(q[2])) {
475           char tmp[3]="%";
476           tmp[1] = Curl_raw_toupper(q[1]);
477           tmp[2] = Curl_raw_toupper(q[2]);
478           result = Curl_dyn_addn(dq, tmp, 3);
479           q += 2;
480           len -= 2;
481         }
482         else
483           /* '%' without a following two-digit hex, encode it */
484           result = Curl_dyn_addn(dq, "%25", 3);
485         break;
486       default: {
487         const char hex[] = "0123456789ABCDEF";
488         char out[3]={'%'};
489 
490         if(!found_equals) {
491           /* if found_equals is NULL assuming, been in path */
492           if(*q == '/') {
493             /* allowed as if */
494             result = Curl_dyn_addn(dq, q, 1);
495             break;
496           }
497         }
498         else {
499           /* allowed as-is */
500           if(*q == '=') {
501             result = Curl_dyn_addn(dq, q, 1);
502             *found_equals = TRUE;
503             break;
504           }
505         }
506         /* URL encode */
507         out[1] = hex[((unsigned char)*q) >> 4];
508         out[2] = hex[*q & 0xf];
509         result = Curl_dyn_addn(dq, out, 3);
510         break;
511       }
512       }
513     }
514   }
515   return result;
516 }
517 
518 
canon_query(struct Curl_easy * data,const char * query,struct dynbuf * dq)519 static CURLcode canon_query(struct Curl_easy *data,
520                             const char *query, struct dynbuf *dq)
521 {
522   CURLcode result = CURLE_OK;
523   int entry = 0;
524   int i;
525   const char *p = query;
526   struct pair array[MAX_QUERYPAIRS];
527   struct pair *ap = &array[0];
528   if(!query)
529     return result;
530 
531   /* sort the name=value pairs first */
532   do {
533     char *amp;
534     entry++;
535     ap->p = p;
536     amp = strchr(p, '&');
537     if(amp)
538       ap->len = amp - p; /* excluding the ampersand */
539     else {
540       ap->len = strlen(p);
541       break;
542     }
543     ap++;
544     p = amp + 1;
545   } while(entry < MAX_QUERYPAIRS);
546   if(entry == MAX_QUERYPAIRS) {
547     /* too many query pairs for us */
548     failf(data, "aws-sigv4: too many query pairs in URL");
549     return CURLE_URL_MALFORMAT;
550   }
551 
552   qsort(&array[0], entry, sizeof(struct pair), compare_func);
553 
554   ap = &array[0];
555   for(i = 0; !result && (i < entry); i++, ap++) {
556     const char *q = ap->p;
557     bool found_equals = FALSE;
558     if(!ap->len)
559       continue;
560     result = canon_string(q, ap->len, dq, &found_equals);
561     if(!result && !found_equals) {
562       /* queries without value still need an equals */
563       result = Curl_dyn_addn(dq, "=", 1);
564     }
565     if(!result && i < entry - 1) {
566       /* insert ampersands between query pairs */
567       result = Curl_dyn_addn(dq, "&", 1);
568     }
569   }
570   return result;
571 }
572 
573 
Curl_output_aws_sigv4(struct Curl_easy * data,bool proxy)574 CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
575 {
576   CURLcode result = CURLE_OUT_OF_MEMORY;
577   struct connectdata *conn = data->conn;
578   size_t len;
579   char *line;
580   struct Curl_str provider0;
581   struct Curl_str provider1;
582   struct Curl_str region = { NULL, 0};
583   struct Curl_str service = { NULL, 0};
584   const char *hostname = conn->host.name;
585   time_t clock;
586   struct tm tm;
587   char timestamp[TIMESTAMP_SIZE];
588   char date[9];
589   struct dynbuf canonical_headers;
590   struct dynbuf signed_headers;
591   struct dynbuf canonical_query;
592   struct dynbuf canonical_path;
593   char *date_header = NULL;
594   Curl_HttpReq httpreq;
595   const char *method = NULL;
596   char *payload_hash = NULL;
597   size_t payload_hash_len = 0;
598   unsigned char sha_hash[CURL_SHA256_DIGEST_LENGTH];
599   char sha_hex[SHA256_HEX_LENGTH];
600   char content_sha256_hdr[CONTENT_SHA256_HDR_LEN + 2] = ""; /* add \r\n */
601   char *canonical_request = NULL;
602   char *request_type = NULL;
603   char *credential_scope = NULL;
604   char *str_to_sign = NULL;
605   const char *user = data->state.aptr.user ? data->state.aptr.user : "";
606   char *secret = NULL;
607   unsigned char sign0[CURL_SHA256_DIGEST_LENGTH] = {0};
608   unsigned char sign1[CURL_SHA256_DIGEST_LENGTH] = {0};
609   char *auth_headers = NULL;
610 
611   DEBUGASSERT(!proxy);
612   (void)proxy;
613 
614   if(Curl_checkheaders(data, STRCONST("Authorization"))) {
615     /* Authorization already present, Bailing out */
616     return CURLE_OK;
617   }
618 
619   /* we init those buffers here, so goto fail will free initialized dynbuf */
620   Curl_dyn_init(&canonical_headers, CURL_MAX_HTTP_HEADER);
621   Curl_dyn_init(&canonical_query, CURL_MAX_HTTP_HEADER);
622   Curl_dyn_init(&signed_headers, CURL_MAX_HTTP_HEADER);
623   Curl_dyn_init(&canonical_path, CURL_MAX_HTTP_HEADER);
624 
625   /*
626    * Parameters parsing
627    * Google and Outscale use the same OSC or GOOG,
628    * but Amazon uses AWS and AMZ for header arguments.
629    * AWS is the default because most of non-amazon providers
630    * are still using aws:amz as a prefix.
631    */
632   line = data->set.str[STRING_AWS_SIGV4] ?
633     data->set.str[STRING_AWS_SIGV4] : (char *)"aws:amz";
634 
635   /* provider0[:provider1[:region[:service]]]
636 
637      No string can be longer than N bytes of non-whitespace
638   */
639   if(Curl_str_until(&line, &provider0, MAX_SIGV4_LEN, ':')) {
640     failf(data, "first aws-sigv4 provider cannot be empty");
641     result = CURLE_BAD_FUNCTION_ARGUMENT;
642     goto fail;
643   }
644   if(Curl_str_single(&line, ':') ||
645      Curl_str_until(&line, &provider1, MAX_SIGV4_LEN, ':')) {
646     provider1.str = provider0.str;
647     provider1.len = provider0.len;
648   }
649   else if(Curl_str_single(&line, ':') ||
650           Curl_str_until(&line, &region, MAX_SIGV4_LEN, ':') ||
651           Curl_str_single(&line, ':') ||
652           Curl_str_until(&line, &service, MAX_SIGV4_LEN, ':')) {
653     /* nothing to do */
654   }
655 
656   if(!service.len) {
657     char *hostdot = strchr(hostname, '.');
658     if(!hostdot) {
659       failf(data, "aws-sigv4: service missing in parameters and hostname");
660       result = CURLE_URL_MALFORMAT;
661       goto fail;
662     }
663     len = hostdot - hostname;
664     if(len > MAX_SIGV4_LEN) {
665       failf(data, "aws-sigv4: service too long in hostname");
666       result = CURLE_URL_MALFORMAT;
667       goto fail;
668     }
669     service.str = (char *)hostname;
670     service.len = len;
671 
672     infof(data, "aws_sigv4: picked service %.*s from host",
673           (int)service.len, service.str);
674 
675     if(!region.len) {
676       const char *reg = hostdot + 1;
677       const char *hostreg = strchr(reg, '.');
678       if(!hostreg) {
679         failf(data, "aws-sigv4: region missing in parameters and hostname");
680         result = CURLE_URL_MALFORMAT;
681         goto fail;
682       }
683       len = hostreg - reg;
684       if(len > MAX_SIGV4_LEN) {
685         failf(data, "aws-sigv4: region too long in hostname");
686         result = CURLE_URL_MALFORMAT;
687         goto fail;
688       }
689       region.str = (char *)reg;
690       region.len = len;
691       infof(data, "aws_sigv4: picked region %.*s from host",
692             (int)region.len, region.str);
693     }
694   }
695 
696   Curl_http_method(data, conn, &method, &httpreq);
697 
698   payload_hash = parse_content_sha_hdr(data, provider1.str, provider1.len,
699                                        &payload_hash_len);
700 
701   if(!payload_hash) {
702     /* AWS S3 requires a x-amz-content-sha256 header, and supports special
703      * values like UNSIGNED-PAYLOAD */
704     bool sign_as_s3 = ((provider0.len == 3) &&
705                        strncasecompare(provider0.str, "aws", 3)) &&
706       ((service.len == 2) && strncasecompare(service.str, "s3", 2));
707 
708     if(sign_as_s3)
709       result = calc_s3_payload_hash(data, httpreq,
710                                     provider1.str, provider1.len,
711                                     sha_hash, 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,
743                         provider1.str, provider1.len,
744                         &date_header, content_sha256_hdr,
745                         &canonical_headers, &signed_headers);
746   if(result)
747     goto fail;
748 
749   if(*content_sha256_hdr) {
750     /* make_headers() needed this without the \r\n for canonicalization */
751     size_t hdrlen = strlen(content_sha256_hdr);
752     DEBUGASSERT(hdrlen + 3 < sizeof(content_sha256_hdr));
753     memcpy(content_sha256_hdr + hdrlen, "\r\n", 3);
754   }
755 
756   memcpy(date, timestamp, sizeof(date));
757   date[sizeof(date) - 1] = 0;
758 
759   result = canon_query(data, data->state.up.query, &canonical_query);
760   if(result)
761     goto fail;
762 
763   result = canon_string(data->state.up.path, strlen(data->state.up.path),
764                         &canonical_path, NULL);
765   if(result)
766     goto fail;
767   result = CURLE_OUT_OF_MEMORY;
768 
769   canonical_request =
770     aprintf("%s\n" /* HTTPRequestMethod */
771             "%s\n" /* CanonicalURI */
772             "%s\n" /* CanonicalQueryString */
773             "%s\n" /* CanonicalHeaders */
774             "%s\n" /* SignedHeaders */
775             "%.*s",  /* HashedRequestPayload in hex */
776             method,
777             Curl_dyn_ptr(&canonical_path),
778             Curl_dyn_ptr(&canonical_query) ?
779             Curl_dyn_ptr(&canonical_query) : "",
780             Curl_dyn_ptr(&canonical_headers),
781             Curl_dyn_ptr(&signed_headers),
782             (int)payload_hash_len, payload_hash);
783   if(!canonical_request)
784     goto fail;
785 
786   DEBUGF(infof(data, "Canonical request: %s", canonical_request));
787 
788   request_type = aprintf("%.*s4_request", (int)provider0.len, provider0.str);
789   if(!request_type)
790     goto fail;
791 
792   /* provider0 is lowercased *after* aprintf() so that the buffer can be
793      written to */
794   Curl_strntolower(request_type, request_type, provider0.len);
795 
796   credential_scope = aprintf("%s/%.*s/%.*s/%s",
797                              date, (int)region.len, region.str,
798                              (int)service.len, service.str,
799                              request_type);
800   if(!credential_scope)
801     goto fail;
802 
803   if(Curl_sha256it(sha_hash, (unsigned char *) canonical_request,
804                    strlen(canonical_request)))
805     goto fail;
806 
807   sha256_to_hex(sha_hex, sha_hash);
808 
809   /*
810    * Google allows using RSA key instead of HMAC, so this code might change
811    * in the future. For now we only support HMAC.
812    */
813   str_to_sign = aprintf("%.*s4-HMAC-SHA256\n" /* Algorithm */
814                         "%s\n" /* RequestDateTime */
815                         "%s\n" /* CredentialScope */
816                         "%s",  /* HashedCanonicalRequest in hex */
817                         (int)provider0.len, provider0.str,
818                         timestamp,
819                         credential_scope,
820                         sha_hex);
821   if(!str_to_sign)
822     goto fail;
823 
824   /* make provider0 part done uppercase */
825   Curl_strntoupper(str_to_sign, provider0.str, provider0.len);
826 
827   secret = aprintf("%.*s4%s", (int)provider0.len, provider0.str,
828                    data->state.aptr.passwd ?
829                    data->state.aptr.passwd : "");
830   if(!secret)
831     goto fail;
832   /* make provider0 part done uppercase */
833   Curl_strntoupper(secret, provider0.str, provider0.len);
834 
835   HMAC_SHA256(secret, strlen(secret), date, strlen(date), sign0);
836   HMAC_SHA256(sign0, sizeof(sign0), region.str, region.len, sign1);
837   HMAC_SHA256(sign1, sizeof(sign1), service.str, service.len, sign0);
838   HMAC_SHA256(sign0, sizeof(sign0), request_type, strlen(request_type), sign1);
839   HMAC_SHA256(sign1, sizeof(sign1), str_to_sign, strlen(str_to_sign), sign0);
840 
841   sha256_to_hex(sha_hex, sign0);
842 
843   auth_headers = aprintf("Authorization: %.*s4-HMAC-SHA256 "
844                          "Credential=%s/%s, "
845                          "SignedHeaders=%s, "
846                          "Signature=%s\r\n"
847                          /*
848                           * date_header is added here, only if it was not
849                           * user-specified (using CURLOPT_HTTPHEADER).
850                           * date_header includes \r\n
851                           */
852                          "%s"
853                          "%s", /* optional sha256 header includes \r\n */
854                          (int)provider0.len, provider0.str,
855                          user,
856                          credential_scope,
857                          Curl_dyn_ptr(&signed_headers),
858                          sha_hex,
859                          date_header ? date_header : "",
860                          content_sha256_hdr);
861   if(!auth_headers) {
862     goto fail;
863   }
864   /* provider 0 uppercase */
865   Curl_strntoupper(&auth_headers[sizeof("Authorization: ") - 1],
866                    provider0.str, provider0.len);
867 
868   Curl_safefree(data->state.aptr.userpwd);
869   data->state.aptr.userpwd = auth_headers;
870   data->state.authhost.done = TRUE;
871   result = CURLE_OK;
872 
873 fail:
874   Curl_dyn_free(&canonical_query);
875   Curl_dyn_free(&canonical_path);
876   Curl_dyn_free(&canonical_headers);
877   Curl_dyn_free(&signed_headers);
878   free(canonical_request);
879   free(request_type);
880   free(credential_scope);
881   free(str_to_sign);
882   free(secret);
883   free(date_header);
884   return result;
885 }
886 
887 #endif /* !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_AWS) */
888