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