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