VirtualBox

source: vbox/trunk/src/libs/curl-8.0.1/lib/http_aws_sigv4.c@ 100347

Last change on this file since 100347 was 99344, checked in by vboxsync, 2 years ago

curl-8.0.1: Applied and adjusted our curl changes to 7.87.0 bugref:10417

  • Property svn:eol-style set to native
File size: 18.5 KB
Line 
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_CRYPTO_AUTH)
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
38#include <time.h>
39
40/* The last 3 #include files should be in this order */
41#include "curl_printf.h"
42#include "curl_memory.h"
43#include "memdebug.h"
44
45#include "slist.h"
46
47#define HMAC_SHA256(k, kl, d, dl, o) \
48 do { \
49 ret = Curl_hmacit(Curl_HMAC_SHA256, \
50 (unsigned char *)k, \
51 kl, \
52 (unsigned char *)d, \
53 dl, o); \
54 if(ret) { \
55 goto fail; \
56 } \
57 } while(0)
58
59#define TIMESTAMP_SIZE 17
60
61/* hex-encoded with trailing null */
62#define SHA256_HEX_LENGTH (2 * SHA256_DIGEST_LENGTH + 1)
63
64static void sha256_to_hex(char *dst, unsigned char *sha)
65{
66 int i;
67
68 for(i = 0; i < SHA256_DIGEST_LENGTH; ++i) {
69 msnprintf(dst + (i * 2), SHA256_HEX_LENGTH - (i * 2), "%02x", sha[i]);
70 }
71}
72
73static char *find_date_hdr(struct Curl_easy *data, const char *sig_hdr)
74{
75 char *tmp = Curl_checkheaders(data, sig_hdr, strlen(sig_hdr));
76
77 if(tmp)
78 return tmp;
79 return Curl_checkheaders(data, STRCONST("Date"));
80}
81
82/* remove whitespace, and lowercase all headers */
83static void trim_headers(struct curl_slist *head)
84{
85 struct curl_slist *l;
86 for(l = head; l; l = l->next) {
87 char *value; /* to read from */
88 char *store;
89 size_t colon = strcspn(l->data, ":");
90 Curl_strntolower(l->data, l->data, colon);
91
92 value = &l->data[colon];
93 if(!*value)
94 continue;
95 ++value;
96 store = value;
97
98 /* skip leading whitespace */
99 while(*value && ISBLANK(*value))
100 value++;
101
102 while(*value) {
103 int space = 0;
104 while(*value && ISBLANK(*value)) {
105 value++;
106 space++;
107 }
108 if(space) {
109 /* replace any number of consecutive whitespace with a single space,
110 unless at the end of the string, then nothing */
111 if(*value)
112 *store++ = ' ';
113 }
114 else
115 *store++ = *value++;
116 }
117 *store = 0; /* null terminate */
118 }
119}
120
121/* maximum length for the aws sivg4 parts */
122#define MAX_SIGV4_LEN 64
123#define MAX_SIGV4_LEN_TXT "64"
124
125#define DATE_HDR_KEY_LEN (MAX_SIGV4_LEN + sizeof("X--Date"))
126
127#define MAX_HOST_LEN 255
128/* FQDN + host: */
129#define FULL_HOST_LEN (MAX_HOST_LEN + sizeof("host:"))
130
131/* string been x-PROVIDER-date:TIMESTAMP, I need +1 for ':' */
132#define DATE_FULL_HDR_LEN (DATE_HDR_KEY_LEN + TIMESTAMP_SIZE + 1)
133
134/* timestamp should point to a buffer of at last TIMESTAMP_SIZE bytes */
135static CURLcode make_headers(struct Curl_easy *data,
136 const char *hostname,
137 char *timestamp,
138 char *provider1,
139 char **date_header,
140 char *content_sha256_header,
141 struct dynbuf *canonical_headers,
142 struct dynbuf *signed_headers)
143{
144 char date_hdr_key[DATE_HDR_KEY_LEN];
145 char date_full_hdr[DATE_FULL_HDR_LEN];
146 struct curl_slist *head = NULL;
147 struct curl_slist *tmp_head = NULL;
148 CURLcode ret = CURLE_OUT_OF_MEMORY;
149 struct curl_slist *l;
150 int again = 1;
151
152 /* provider1 mid */
153 Curl_strntolower(provider1, provider1, strlen(provider1));
154 provider1[0] = Curl_raw_toupper(provider1[0]);
155
156 msnprintf(date_hdr_key, DATE_HDR_KEY_LEN, "X-%s-Date", provider1);
157
158 /* provider1 lowercase */
159 Curl_strntolower(provider1, provider1, 1); /* first byte only */
160 msnprintf(date_full_hdr, DATE_FULL_HDR_LEN,
161 "x-%s-date:%s", provider1, timestamp);
162
163 if(Curl_checkheaders(data, STRCONST("Host"))) {
164 head = NULL;
165 }
166 else {
167 char full_host[FULL_HOST_LEN + 1];
168
169 if(data->state.aptr.host) {
170 size_t pos;
171
172 if(strlen(data->state.aptr.host) > FULL_HOST_LEN) {
173 ret = CURLE_URL_MALFORMAT;
174 goto fail;
175 }
176 strcpy(full_host, data->state.aptr.host);
177 /* remove /r/n as the separator for canonical request must be '\n' */
178 pos = strcspn(full_host, "\n\r");
179 full_host[pos] = 0;
180 }
181 else {
182 if(strlen(hostname) > MAX_HOST_LEN) {
183 ret = CURLE_URL_MALFORMAT;
184 goto fail;
185 }
186 msnprintf(full_host, FULL_HOST_LEN, "host:%s", hostname);
187 }
188
189 head = curl_slist_append(NULL, full_host);
190 if(!head)
191 goto fail;
192 }
193
194
195 if (*content_sha256_header) {
196 tmp_head = curl_slist_append(head, content_sha256_header);
197 if(!tmp_head)
198 goto fail;
199 head = tmp_head;
200 }
201
202 for(l = data->set.headers; l; l = l->next) {
203 tmp_head = curl_slist_append(head, l->data);
204 if(!tmp_head)
205 goto fail;
206 head = tmp_head;
207 }
208
209 trim_headers(head);
210
211 *date_header = find_date_hdr(data, date_hdr_key);
212 if(!*date_header) {
213 tmp_head = curl_slist_append(head, date_full_hdr);
214 if(!tmp_head)
215 goto fail;
216 head = tmp_head;
217 *date_header = curl_maprintf("%s: %s", date_hdr_key, timestamp);
218 }
219 else {
220 char *value;
221
222 *date_header = strdup(*date_header);
223 if(!*date_header)
224 goto fail;
225
226 value = strchr(*date_header, ':');
227 if(!value)
228 goto fail;
229 ++value;
230 while(ISBLANK(*value))
231 ++value;
232 strncpy(timestamp, value, TIMESTAMP_SIZE - 1);
233 timestamp[TIMESTAMP_SIZE - 1] = 0;
234 }
235
236 /* alpha-sort in a case sensitive manner */
237 do {
238 again = 0;
239 for(l = head; l; l = l->next) {
240 struct curl_slist *next = l->next;
241
242 if(next && strcmp(l->data, next->data) > 0) {
243 char *tmp = l->data;
244
245 l->data = next->data;
246 next->data = tmp;
247 again = 1;
248 }
249 }
250 } while(again);
251
252 for(l = head; l; l = l->next) {
253 char *tmp;
254
255 if(Curl_dyn_add(canonical_headers, l->data))
256 goto fail;
257 if(Curl_dyn_add(canonical_headers, "\n"))
258 goto fail;
259
260 tmp = strchr(l->data, ':');
261 if(tmp)
262 *tmp = 0;
263
264 if(l != head) {
265 if(Curl_dyn_add(signed_headers, ";"))
266 goto fail;
267 }
268 if(Curl_dyn_add(signed_headers, l->data))
269 goto fail;
270 }
271
272 ret = CURLE_OK;
273fail:
274 curl_slist_free_all(head);
275
276 return ret;
277}
278
279#define CONTENT_SHA256_KEY_LEN (MAX_SIGV4_LEN + sizeof("X--Content-Sha256"))
280/* add 2 for ": " between header name and value */
281#define CONTENT_SHA256_HDR_LEN (CONTENT_SHA256_KEY_LEN + 2 + \
282 SHA256_HEX_LENGTH)
283
284/* try to parse a payload hash from the content-sha256 header */
285static char *parse_content_sha_hdr(struct Curl_easy *data,
286 const char *provider1,
287 size_t *value_len)
288{
289 char key[CONTENT_SHA256_KEY_LEN];
290 size_t key_len;
291 char *value;
292 size_t len;
293
294 key_len = msnprintf(key, sizeof(key), "x-%s-content-sha256", provider1);
295
296 value = Curl_checkheaders(data, key, key_len);
297 if(!value)
298 return NULL;
299
300 value = strchr(value, ':');
301 if(!value)
302 return NULL;
303 ++value;
304
305 while(*value && ISBLANK(*value))
306 ++value;
307
308 len = strlen(value);
309 while(len > 0 && ISBLANK(value[len-1]))
310 --len;
311
312 *value_len = len;
313 return value;
314}
315
316static CURLcode calc_payload_hash(struct Curl_easy *data,
317 unsigned char *sha_hash, char *sha_hex)
318{
319 const char *post_data = data->set.postfields;
320 size_t post_data_len = 0;
321 CURLcode result;
322
323 if(post_data) {
324 if(data->set.postfieldsize < 0)
325 post_data_len = strlen(post_data);
326 else
327 post_data_len = (size_t)data->set.postfieldsize;
328 }
329 result = Curl_sha256it(sha_hash, (const unsigned char *) post_data,
330 post_data_len);
331 if(!result)
332 sha256_to_hex(sha_hex, sha_hash);
333 return result;
334}
335
336#define S3_UNSIGNED_PAYLOAD "UNSIGNED-PAYLOAD"
337
338static CURLcode calc_s3_payload_hash(struct Curl_easy *data,
339 Curl_HttpReq httpreq, char *provider1,
340 unsigned char *sha_hash,
341 char *sha_hex, char *header)
342{
343 bool empty_method = (httpreq == HTTPREQ_GET || httpreq == HTTPREQ_HEAD);
344 /* The request method or filesize indicate no request payload */
345 bool empty_payload = (empty_method || data->set.filesize == 0);
346 /* The POST payload is in memory */
347 bool post_payload = (httpreq == HTTPREQ_POST && data->set.postfields);
348 CURLcode ret = CURLE_OUT_OF_MEMORY;
349
350 if(empty_payload || post_payload) {
351 /* Calculate a real hash when we know the request payload */
352 ret = calc_payload_hash(data, sha_hash, sha_hex);
353 if(ret)
354 goto fail;
355 }
356 else {
357 /* Fall back to s3's UNSIGNED-PAYLOAD */
358 size_t len = sizeof(S3_UNSIGNED_PAYLOAD) - 1;
359 DEBUGASSERT(len < SHA256_HEX_LENGTH); /* 16 < 65 */
360 memcpy(sha_hex, S3_UNSIGNED_PAYLOAD, len);
361 sha_hex[len] = 0;
362 }
363
364 /* format the required content-sha256 header */
365 msnprintf(header, CONTENT_SHA256_HDR_LEN,
366 "x-%s-content-sha256: %s", provider1, sha_hex);
367
368 ret = CURLE_OK;
369fail:
370 return ret;
371}
372
373CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
374{
375 CURLcode ret = CURLE_OUT_OF_MEMORY;
376 struct connectdata *conn = data->conn;
377 size_t len;
378 const char *arg;
379 char provider0[MAX_SIGV4_LEN + 1]="";
380 char provider1[MAX_SIGV4_LEN + 1]="";
381 char region[MAX_SIGV4_LEN + 1]="";
382 char service[MAX_SIGV4_LEN + 1]="";
383 bool sign_as_s3 = false;
384 const char *hostname = conn->host.name;
385 time_t clock;
386 struct tm tm;
387 char timestamp[TIMESTAMP_SIZE];
388 char date[9];
389 struct dynbuf canonical_headers;
390 struct dynbuf signed_headers;
391 char *date_header = NULL;
392 Curl_HttpReq httpreq;
393 const char *method = NULL;
394 char *payload_hash = NULL;
395 size_t payload_hash_len = 0;
396 unsigned char sha_hash[SHA256_DIGEST_LENGTH];
397 char sha_hex[SHA256_HEX_LENGTH];
398 char content_sha256_hdr[CONTENT_SHA256_HDR_LEN + 2] = ""; /* add \r\n */
399 char *canonical_request = NULL;
400 char *request_type = NULL;
401 char *credential_scope = NULL;
402 char *str_to_sign = NULL;
403 const char *user = data->state.aptr.user ? data->state.aptr.user : "";
404 char *secret = NULL;
405 unsigned char sign0[SHA256_DIGEST_LENGTH] = {0};
406 unsigned char sign1[SHA256_DIGEST_LENGTH] = {0};
407 char *auth_headers = NULL;
408
409 DEBUGASSERT(!proxy);
410 (void)proxy;
411
412 if(Curl_checkheaders(data, STRCONST("Authorization"))) {
413 /* Authorization already present, Bailing out */
414 return CURLE_OK;
415 }
416
417 /* we init those buffers here, so goto fail will free initialized dynbuf */
418 Curl_dyn_init(&canonical_headers, CURL_MAX_HTTP_HEADER);
419 Curl_dyn_init(&signed_headers, CURL_MAX_HTTP_HEADER);
420
421 /*
422 * Parameters parsing
423 * Google and Outscale use the same OSC or GOOG,
424 * but Amazon uses AWS and AMZ for header arguments.
425 * AWS is the default because most of non-amazon providers
426 * are still using aws:amz as a prefix.
427 */
428 arg = data->set.str[STRING_AWS_SIGV4] ?
429 data->set.str[STRING_AWS_SIGV4] : "aws:amz";
430
431 /* provider1[:provider2[:region[:service]]]
432
433 No string can be longer than N bytes of non-whitespace
434 */
435 (void)sscanf(arg, "%" MAX_SIGV4_LEN_TXT "[^:]"
436 ":%" MAX_SIGV4_LEN_TXT "[^:]"
437 ":%" MAX_SIGV4_LEN_TXT "[^:]"
438 ":%" MAX_SIGV4_LEN_TXT "s",
439 provider0, provider1, region, service);
440 if(!provider0[0]) {
441 failf(data, "first provider can't be empty");
442 ret = CURLE_BAD_FUNCTION_ARGUMENT;
443 goto fail;
444 }
445 else if(!provider1[0])
446 strcpy(provider1, provider0);
447
448 if(!service[0]) {
449 char *hostdot = strchr(hostname, '.');
450 if(!hostdot) {
451 failf(data, "service missing in parameters and hostname");
452 ret = CURLE_URL_MALFORMAT;
453 goto fail;
454 }
455 len = hostdot - hostname;
456 if(len > MAX_SIGV4_LEN) {
457 failf(data, "service too long in hostname");
458 ret = CURLE_URL_MALFORMAT;
459 goto fail;
460 }
461 strncpy(service, hostname, len);
462 service[len] = '\0';
463
464 if(!region[0]) {
465 const char *reg = hostdot + 1;
466 const char *hostreg = strchr(reg, '.');
467 if(!hostreg) {
468 failf(data, "region missing in parameters and hostname");
469 ret = CURLE_URL_MALFORMAT;
470 goto fail;
471 }
472 len = hostreg - reg;
473 if(len > MAX_SIGV4_LEN) {
474 failf(data, "region too long in hostname");
475 ret = CURLE_URL_MALFORMAT;
476 goto fail;
477 }
478 strncpy(region, reg, len);
479 region[len] = '\0';
480 }
481 }
482
483 Curl_http_method(data, conn, &method, &httpreq);
484
485 /* AWS S3 requires a x-amz-content-sha256 header, and supports special
486 * values like UNSIGNED-PAYLOAD */
487 sign_as_s3 = (strcasecompare(provider0, "aws") &&
488 strcasecompare(service, "s3"));
489
490 payload_hash = parse_content_sha_hdr(data, provider1, &payload_hash_len);
491
492 if(!payload_hash) {
493 if(sign_as_s3)
494 ret = calc_s3_payload_hash(data, httpreq, provider1, sha_hash,
495 sha_hex, content_sha256_hdr);
496 else
497 ret = calc_payload_hash(data, sha_hash, sha_hex);
498 if(ret)
499 goto fail;
500
501 payload_hash = sha_hex;
502 /* may be shorter than SHA256_HEX_LENGTH, like S3_UNSIGNED_PAYLOAD */
503 payload_hash_len = strlen(sha_hex);
504 }
505
506#ifdef DEBUGBUILD
507 {
508 char *force_timestamp = getenv("CURL_FORCETIME");
509 if(force_timestamp)
510 clock = 0;
511 else
512 time(&clock);
513 }
514#else
515 time(&clock);
516#endif
517 ret = Curl_gmtime(clock, &tm);
518 if(ret) {
519 goto fail;
520 }
521 if(!strftime(timestamp, sizeof(timestamp), "%Y%m%dT%H%M%SZ", &tm)) {
522 ret = CURLE_OUT_OF_MEMORY;
523 goto fail;
524 }
525
526 ret = make_headers(data, hostname, timestamp, provider1,
527 &date_header, content_sha256_hdr,
528 &canonical_headers, &signed_headers);
529 if(ret)
530 goto fail;
531 ret = CURLE_OUT_OF_MEMORY;
532
533 if(*content_sha256_hdr) {
534 /* make_headers() needed this without the \r\n for canonicalization */
535 size_t hdrlen = strlen(content_sha256_hdr);
536 DEBUGASSERT(hdrlen + 3 < sizeof(content_sha256_hdr));
537 memcpy(content_sha256_hdr + hdrlen, "\r\n", 3);
538 }
539
540 memcpy(date, timestamp, sizeof(date));
541 date[sizeof(date) - 1] = 0;
542
543 canonical_request =
544 curl_maprintf("%s\n" /* HTTPRequestMethod */
545 "%s\n" /* CanonicalURI */
546 "%s\n" /* CanonicalQueryString */
547 "%s\n" /* CanonicalHeaders */
548 "%s\n" /* SignedHeaders */
549 "%.*s", /* HashedRequestPayload in hex */
550 method,
551 data->state.up.path,
552 data->state.up.query ? data->state.up.query : "",
553 Curl_dyn_ptr(&canonical_headers),
554 Curl_dyn_ptr(&signed_headers),
555 (int)payload_hash_len, payload_hash);
556 if(!canonical_request)
557 goto fail;
558
559 /* provider 0 lowercase */
560 Curl_strntolower(provider0, provider0, strlen(provider0));
561 request_type = curl_maprintf("%s4_request", provider0);
562 if(!request_type)
563 goto fail;
564
565 credential_scope = curl_maprintf("%s/%s/%s/%s",
566 date, region, service, request_type);
567 if(!credential_scope)
568 goto fail;
569
570 if(Curl_sha256it(sha_hash, (unsigned char *) canonical_request,
571 strlen(canonical_request)))
572 goto fail;
573
574 sha256_to_hex(sha_hex, sha_hash);
575
576 /* provider 0 uppercase */
577 Curl_strntoupper(provider0, provider0, strlen(provider0));
578
579 /*
580 * Google allows using RSA key instead of HMAC, so this code might change
581 * in the future. For now we only support HMAC.
582 */
583 str_to_sign = curl_maprintf("%s4-HMAC-SHA256\n" /* Algorithm */
584 "%s\n" /* RequestDateTime */
585 "%s\n" /* CredentialScope */
586 "%s", /* HashedCanonicalRequest in hex */
587 provider0,
588 timestamp,
589 credential_scope,
590 sha_hex);
591 if(!str_to_sign) {
592 goto fail;
593 }
594
595 /* provider 0 uppercase */
596 secret = curl_maprintf("%s4%s", provider0,
597 data->state.aptr.passwd ?
598 data->state.aptr.passwd : "");
599 if(!secret)
600 goto fail;
601
602 HMAC_SHA256(secret, strlen(secret), date, strlen(date), sign0);
603 HMAC_SHA256(sign0, sizeof(sign0), region, strlen(region), sign1);
604 HMAC_SHA256(sign1, sizeof(sign1), service, strlen(service), sign0);
605 HMAC_SHA256(sign0, sizeof(sign0), request_type, strlen(request_type), sign1);
606 HMAC_SHA256(sign1, sizeof(sign1), str_to_sign, strlen(str_to_sign), sign0);
607
608 sha256_to_hex(sha_hex, sign0);
609
610 /* provider 0 uppercase */
611 auth_headers = curl_maprintf("Authorization: %s4-HMAC-SHA256 "
612 "Credential=%s/%s, "
613 "SignedHeaders=%s, "
614 "Signature=%s\r\n"
615 "%s\r\n"
616 "%s", /* optional sha256 header includes \r\n */
617 provider0,
618 user,
619 credential_scope,
620 Curl_dyn_ptr(&signed_headers),
621 sha_hex,
622 date_header,
623 content_sha256_hdr);
624 if(!auth_headers) {
625 goto fail;
626 }
627
628 Curl_safefree(data->state.aptr.userpwd);
629 data->state.aptr.userpwd = auth_headers;
630 data->state.authhost.done = TRUE;
631 ret = CURLE_OK;
632
633fail:
634 Curl_dyn_free(&canonical_headers);
635 Curl_dyn_free(&signed_headers);
636 free(canonical_request);
637 free(request_type);
638 free(credential_scope);
639 free(str_to_sign);
640 free(secret);
641 free(date_header);
642 return ret;
643}
644
645#endif /* !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH) */
Note: See TracBrowser for help on using the repository browser.

© 2025 Oracle Support Privacy / Do Not Sell My Info Terms of Use Trademark Policy Automated Access Etiquette