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