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