http_aws_sigv4.c (34359B)
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.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 #include "curlx/strparse.h" 39 40 #include <time.h> 41 42 /* The last 3 #include files should be in this order */ 43 #include "curl_printf.h" 44 #include "curl_memory.h" 45 #include "memdebug.h" 46 47 #include "slist.h" 48 49 #define HMAC_SHA256(k, kl, d, dl, o) \ 50 do { \ 51 result = Curl_hmacit(&Curl_HMAC_SHA256, \ 52 (const unsigned char *)k, \ 53 kl, \ 54 (const unsigned char *)d, \ 55 dl, o); \ 56 if(result) { \ 57 goto fail; \ 58 } \ 59 } while(0) 60 61 #define TIMESTAMP_SIZE 17 62 63 /* hex-encoded with trailing null */ 64 #define SHA256_HEX_LENGTH (2 * CURL_SHA256_DIGEST_LENGTH + 1) 65 66 #define MAX_QUERY_COMPONENTS 128 67 68 struct pair { 69 struct dynbuf key; 70 struct dynbuf value; 71 }; 72 73 static void dyn_array_free(struct dynbuf *db, size_t num_elements); 74 static void pair_array_free(struct pair *pair_array, size_t num_elements); 75 static CURLcode split_to_dyn_array(const char *source, 76 struct dynbuf db[MAX_QUERY_COMPONENTS], 77 size_t *num_splits); 78 static bool is_reserved_char(const char c); 79 static CURLcode uri_encode_path(struct Curl_str *original_path, 80 struct dynbuf *new_path); 81 static CURLcode encode_query_component(char *component, size_t len, 82 struct dynbuf *db); 83 static CURLcode http_aws_decode_encode(const char *in, size_t in_len, 84 struct dynbuf *out); 85 static bool should_urlencode(struct Curl_str *service_name); 86 87 static void sha256_to_hex(char *dst, unsigned char *sha) 88 { 89 Curl_hexencode(sha, CURL_SHA256_DIGEST_LENGTH, 90 (unsigned char *)dst, SHA256_HEX_LENGTH); 91 } 92 93 static char *find_date_hdr(struct Curl_easy *data, const char *sig_hdr) 94 { 95 char *tmp = Curl_checkheaders(data, sig_hdr, strlen(sig_hdr)); 96 97 if(tmp) 98 return tmp; 99 return Curl_checkheaders(data, STRCONST("Date")); 100 } 101 102 /* remove whitespace, and lowercase all headers */ 103 static void trim_headers(struct curl_slist *head) 104 { 105 struct curl_slist *l; 106 for(l = head; l; l = l->next) { 107 const char *value; /* to read from */ 108 char *store; 109 size_t colon = strcspn(l->data, ":"); 110 Curl_strntolower(l->data, l->data, colon); 111 112 value = &l->data[colon]; 113 if(!*value) 114 continue; 115 ++value; 116 store = (char *)CURL_UNCONST(value); 117 118 /* skip leading whitespace */ 119 curlx_str_passblanks(&value); 120 121 while(*value) { 122 int space = 0; 123 while(ISBLANK(*value)) { 124 value++; 125 space++; 126 } 127 if(space) { 128 /* replace any number of consecutive whitespace with a single space, 129 unless at the end of the string, then nothing */ 130 if(*value) 131 *store++ = ' '; 132 } 133 else 134 *store++ = *value++; 135 } 136 *store = 0; /* null-terminate */ 137 } 138 } 139 140 /* maximum length for the aws sivg4 parts */ 141 #define MAX_SIGV4_LEN 64 142 #define DATE_HDR_KEY_LEN (MAX_SIGV4_LEN + sizeof("X--Date")) 143 144 /* string been x-PROVIDER-date:TIMESTAMP, I need +1 for ':' */ 145 #define DATE_FULL_HDR_LEN (DATE_HDR_KEY_LEN + TIMESTAMP_SIZE + 1) 146 147 /* alphabetically compare two headers by their name, expecting 148 headers to use ':' at this point */ 149 static int compare_header_names(const char *a, const char *b) 150 { 151 const char *colon_a; 152 const char *colon_b; 153 size_t len_a; 154 size_t len_b; 155 size_t min_len; 156 int cmp; 157 158 colon_a = strchr(a, ':'); 159 colon_b = strchr(b, ':'); 160 161 DEBUGASSERT(colon_a); 162 DEBUGASSERT(colon_b); 163 164 len_a = colon_a ? (size_t)(colon_a - a) : strlen(a); 165 len_b = colon_b ? (size_t)(colon_b - b) : strlen(b); 166 167 min_len = (len_a < len_b) ? len_a : len_b; 168 169 cmp = strncmp(a, b, min_len); 170 171 /* return the shorter of the two if one is shorter */ 172 if(!cmp) 173 return (int)(len_a - len_b); 174 175 return cmp; 176 } 177 178 /* Merge duplicate header definitions by comma delimiting their values 179 in the order defined the headers are defined, expecting headers to 180 be alpha-sorted and use ':' at this point */ 181 static CURLcode merge_duplicate_headers(struct curl_slist *head) 182 { 183 struct curl_slist *curr = head; 184 CURLcode result = CURLE_OK; 185 186 while(curr) { 187 struct curl_slist *next = curr->next; 188 if(!next) 189 break; 190 191 if(compare_header_names(curr->data, next->data) == 0) { 192 struct dynbuf buf; 193 char *colon_next; 194 char *val_next; 195 196 curlx_dyn_init(&buf, CURL_MAX_HTTP_HEADER); 197 198 result = curlx_dyn_add(&buf, curr->data); 199 if(result) 200 return result; 201 202 colon_next = strchr(next->data, ':'); 203 DEBUGASSERT(colon_next); 204 val_next = colon_next + 1; 205 206 result = curlx_dyn_addn(&buf, ",", 1); 207 if(result) 208 return result; 209 210 result = curlx_dyn_add(&buf, val_next); 211 if(result) 212 return result; 213 214 free(curr->data); 215 curr->data = curlx_dyn_ptr(&buf); 216 217 curr->next = next->next; 218 free(next->data); 219 free(next); 220 } 221 else { 222 curr = curr->next; 223 } 224 } 225 226 return CURLE_OK; 227 } 228 229 /* timestamp should point to a buffer of at last TIMESTAMP_SIZE bytes */ 230 static CURLcode make_headers(struct Curl_easy *data, 231 const char *hostname, 232 char *timestamp, 233 const char *provider1, 234 size_t plen, /* length of provider1 */ 235 char **date_header, 236 char *content_sha256_header, 237 struct dynbuf *canonical_headers, 238 struct dynbuf *signed_headers) 239 { 240 char date_hdr_key[DATE_HDR_KEY_LEN]; 241 char date_full_hdr[DATE_FULL_HDR_LEN]; 242 struct curl_slist *head = NULL; 243 struct curl_slist *tmp_head = NULL; 244 CURLcode ret = CURLE_OUT_OF_MEMORY; 245 struct curl_slist *l; 246 bool again = TRUE; 247 248 msnprintf(date_hdr_key, DATE_HDR_KEY_LEN, "X-%.*s-Date", 249 (int)plen, provider1); 250 /* provider1 ucfirst */ 251 Curl_strntolower(&date_hdr_key[2], provider1, plen); 252 date_hdr_key[2] = Curl_raw_toupper(provider1[0]); 253 254 msnprintf(date_full_hdr, DATE_FULL_HDR_LEN, 255 "x-%.*s-date:%s", (int)plen, provider1, timestamp); 256 /* provider1 lowercase */ 257 Curl_strntolower(&date_full_hdr[2], provider1, plen); 258 259 if(!Curl_checkheaders(data, STRCONST("Host"))) { 260 char *fullhost; 261 262 if(data->state.aptr.host) { 263 /* remove /r/n as the separator for canonical request must be '\n' */ 264 size_t pos = strcspn(data->state.aptr.host, "\n\r"); 265 fullhost = Curl_memdup0(data->state.aptr.host, pos); 266 } 267 else 268 fullhost = aprintf("host:%s", hostname); 269 270 if(fullhost) 271 head = Curl_slist_append_nodup(NULL, fullhost); 272 if(!head) { 273 free(fullhost); 274 goto fail; 275 } 276 } 277 278 279 if(*content_sha256_header) { 280 tmp_head = curl_slist_append(head, content_sha256_header); 281 if(!tmp_head) 282 goto fail; 283 head = tmp_head; 284 } 285 286 /* copy user headers to our header list. the logic is based on how http.c 287 handles user headers. 288 289 user headers in format 'name:' with no value are used to signal that an 290 internal header of that name should be removed. those user headers are not 291 added to this list. 292 293 user headers in format 'name;' with no value are used to signal that a 294 header of that name with no value should be sent. those user headers are 295 added to this list but in the format that they will be sent, ie the 296 semi-colon is changed to a colon for format 'name:'. 297 298 user headers with a value of whitespace only, or without a colon or 299 semi-colon, are not added to this list. 300 */ 301 for(l = data->set.headers; l; l = l->next) { 302 char *dupdata, *ptr; 303 char *sep = strchr(l->data, ':'); 304 if(!sep) 305 sep = strchr(l->data, ';'); 306 if(!sep || (*sep == ':' && !*(sep + 1))) 307 continue; 308 for(ptr = sep + 1; ISBLANK(*ptr); ++ptr) 309 ; 310 if(!*ptr && ptr != sep + 1) /* a value of whitespace only */ 311 continue; 312 dupdata = strdup(l->data); 313 if(!dupdata) 314 goto fail; 315 dupdata[sep - l->data] = ':'; 316 tmp_head = Curl_slist_append_nodup(head, dupdata); 317 if(!tmp_head) { 318 free(dupdata); 319 goto fail; 320 } 321 head = tmp_head; 322 } 323 324 trim_headers(head); 325 326 *date_header = find_date_hdr(data, date_hdr_key); 327 if(!*date_header) { 328 tmp_head = curl_slist_append(head, date_full_hdr); 329 if(!tmp_head) 330 goto fail; 331 head = tmp_head; 332 *date_header = aprintf("%s: %s\r\n", date_hdr_key, timestamp); 333 } 334 else { 335 const char *value; 336 const char *endp; 337 value = strchr(*date_header, ':'); 338 if(!value) { 339 *date_header = NULL; 340 goto fail; 341 } 342 ++value; 343 curlx_str_passblanks(&value); 344 endp = value; 345 while(*endp && ISALNUM(*endp)) 346 ++endp; 347 /* 16 bytes => "19700101T000000Z" */ 348 if((endp - value) == TIMESTAMP_SIZE - 1) { 349 memcpy(timestamp, value, TIMESTAMP_SIZE - 1); 350 timestamp[TIMESTAMP_SIZE - 1] = 0; 351 } 352 else 353 /* bad timestamp length */ 354 timestamp[0] = 0; 355 *date_header = NULL; 356 } 357 358 /* alpha-sort by header name in a case sensitive manner */ 359 do { 360 again = FALSE; 361 for(l = head; l; l = l->next) { 362 struct curl_slist *next = l->next; 363 364 if(next && compare_header_names(l->data, next->data) > 0) { 365 char *tmp = l->data; 366 367 l->data = next->data; 368 next->data = tmp; 369 again = TRUE; 370 } 371 } 372 } while(again); 373 374 ret = merge_duplicate_headers(head); 375 if(ret) 376 goto fail; 377 378 for(l = head; l; l = l->next) { 379 char *tmp; 380 381 if(curlx_dyn_add(canonical_headers, l->data)) 382 goto fail; 383 if(curlx_dyn_add(canonical_headers, "\n")) 384 goto fail; 385 386 tmp = strchr(l->data, ':'); 387 if(tmp) 388 *tmp = 0; 389 390 if(l != head) { 391 if(curlx_dyn_add(signed_headers, ";")) 392 goto fail; 393 } 394 if(curlx_dyn_add(signed_headers, l->data)) 395 goto fail; 396 } 397 398 ret = CURLE_OK; 399 fail: 400 curl_slist_free_all(head); 401 402 return ret; 403 } 404 405 #define CONTENT_SHA256_KEY_LEN (MAX_SIGV4_LEN + sizeof("X--Content-Sha256")) 406 /* add 2 for ": " between header name and value */ 407 #define CONTENT_SHA256_HDR_LEN (CONTENT_SHA256_KEY_LEN + 2 + \ 408 SHA256_HEX_LENGTH) 409 410 /* try to parse a payload hash from the content-sha256 header */ 411 static const char *parse_content_sha_hdr(struct Curl_easy *data, 412 const char *provider1, 413 size_t plen, 414 size_t *value_len) { 415 char key[CONTENT_SHA256_KEY_LEN]; 416 size_t key_len; 417 const char *value; 418 size_t len; 419 420 key_len = msnprintf(key, sizeof(key), "x-%.*s-content-sha256", 421 (int)plen, provider1); 422 423 value = Curl_checkheaders(data, key, key_len); 424 if(!value) 425 return NULL; 426 427 value = strchr(value, ':'); 428 if(!value) 429 return NULL; 430 ++value; 431 432 curlx_str_passblanks(&value); 433 434 len = strlen(value); 435 while(len > 0 && ISBLANK(value[len-1])) 436 --len; 437 438 *value_len = len; 439 return value; 440 } 441 442 static CURLcode calc_payload_hash(struct Curl_easy *data, 443 unsigned char *sha_hash, char *sha_hex) 444 { 445 const char *post_data = data->set.postfields; 446 size_t post_data_len = 0; 447 CURLcode result; 448 449 if(post_data) { 450 if(data->set.postfieldsize < 0) 451 post_data_len = strlen(post_data); 452 else 453 post_data_len = (size_t)data->set.postfieldsize; 454 } 455 result = Curl_sha256it(sha_hash, (const unsigned char *) post_data, 456 post_data_len); 457 if(!result) 458 sha256_to_hex(sha_hex, sha_hash); 459 return result; 460 } 461 462 #define S3_UNSIGNED_PAYLOAD "UNSIGNED-PAYLOAD" 463 464 static CURLcode calc_s3_payload_hash(struct Curl_easy *data, 465 Curl_HttpReq httpreq, 466 const char *provider1, 467 size_t plen, 468 unsigned char *sha_hash, 469 char *sha_hex, char *header) 470 { 471 bool empty_method = (httpreq == HTTPREQ_GET || httpreq == HTTPREQ_HEAD); 472 /* The request method or filesize indicate no request payload */ 473 bool empty_payload = (empty_method || data->set.filesize == 0); 474 /* The POST payload is in memory */ 475 bool post_payload = (httpreq == HTTPREQ_POST && data->set.postfields); 476 CURLcode ret = CURLE_OUT_OF_MEMORY; 477 478 if(empty_payload || post_payload) { 479 /* Calculate a real hash when we know the request payload */ 480 ret = calc_payload_hash(data, sha_hash, sha_hex); 481 if(ret) 482 goto fail; 483 } 484 else { 485 /* Fall back to s3's UNSIGNED-PAYLOAD */ 486 size_t len = sizeof(S3_UNSIGNED_PAYLOAD) - 1; 487 DEBUGASSERT(len < SHA256_HEX_LENGTH); /* 16 < 65 */ 488 memcpy(sha_hex, S3_UNSIGNED_PAYLOAD, len); 489 sha_hex[len] = 0; 490 } 491 492 /* format the required content-sha256 header */ 493 msnprintf(header, CONTENT_SHA256_HDR_LEN, 494 "x-%.*s-content-sha256: %s", (int)plen, provider1, sha_hex); 495 496 ret = CURLE_OK; 497 fail: 498 return ret; 499 } 500 501 static int compare_func(const void *a, const void *b) 502 { 503 504 const struct pair *aa = a; 505 const struct pair *bb = b; 506 const size_t aa_key_len = curlx_dyn_len(&aa->key); 507 const size_t bb_key_len = curlx_dyn_len(&bb->key); 508 const size_t aa_value_len = curlx_dyn_len(&aa->value); 509 const size_t bb_value_len = curlx_dyn_len(&bb->value); 510 int compare; 511 512 /* If one element is empty, the other is always sorted higher */ 513 514 /* Compare keys */ 515 if((aa_key_len == 0) && (bb_key_len == 0)) 516 return 0; 517 if(aa_key_len == 0) 518 return -1; 519 if(bb_key_len == 0) 520 return 1; 521 compare = strcmp(curlx_dyn_ptr(&aa->key), curlx_dyn_ptr(&bb->key)); 522 if(compare) { 523 return compare; 524 } 525 526 /* Compare values */ 527 if((aa_value_len == 0) && (bb_value_len == 0)) 528 return 0; 529 if(aa_value_len == 0) 530 return -1; 531 if(bb_value_len == 0) 532 return 1; 533 compare = strcmp(curlx_dyn_ptr(&aa->value), curlx_dyn_ptr(&bb->value)); 534 535 return compare; 536 537 } 538 539 UNITTEST CURLcode canon_path(const char *q, size_t len, 540 struct dynbuf *new_path, 541 bool do_uri_encode) 542 { 543 CURLcode result = CURLE_OK; 544 545 struct Curl_str original_path; 546 547 curlx_str_assign(&original_path, q, len); 548 549 /* Normalized path will be either the same or shorter than the original 550 * path, plus trailing slash */ 551 552 if(do_uri_encode) 553 result = uri_encode_path(&original_path, new_path); 554 else 555 result = curlx_dyn_addn(new_path, q, len); 556 557 if(!result) { 558 if(curlx_dyn_len(new_path) == 0) 559 result = curlx_dyn_add(new_path, "/"); 560 } 561 562 return result; 563 } 564 565 UNITTEST CURLcode canon_query(const char *query, struct dynbuf *dq) 566 { 567 CURLcode result = CURLE_OK; 568 569 struct dynbuf query_array[MAX_QUERY_COMPONENTS]; 570 struct pair encoded_query_array[MAX_QUERY_COMPONENTS]; 571 size_t num_query_components; 572 size_t counted_query_components = 0; 573 size_t index; 574 575 if(!query) 576 return result; 577 578 result = split_to_dyn_array(query, &query_array[0], 579 &num_query_components); 580 if(result) { 581 goto fail; 582 } 583 584 /* Create list of pairs, each pair containing an encoded query 585 * component */ 586 587 for(index = 0; index < num_query_components; index++) { 588 const char *in_key; 589 size_t in_key_len; 590 char *offset; 591 size_t query_part_len = curlx_dyn_len(&query_array[index]); 592 char *query_part = curlx_dyn_ptr(&query_array[index]); 593 594 in_key = query_part; 595 596 offset = strchr(query_part, '='); 597 /* If there is no equals, this key has no value */ 598 if(!offset) { 599 in_key_len = strlen(in_key); 600 } 601 else { 602 in_key_len = offset - in_key; 603 } 604 605 curlx_dyn_init(&encoded_query_array[index].key, query_part_len*3 + 1); 606 curlx_dyn_init(&encoded_query_array[index].value, query_part_len*3 + 1); 607 counted_query_components++; 608 609 /* Decode/encode the key */ 610 result = http_aws_decode_encode(in_key, in_key_len, 611 &encoded_query_array[index].key); 612 if(result) { 613 goto fail; 614 } 615 616 /* Decode/encode the value if it exists */ 617 if(offset && offset != (query_part + query_part_len - 1)) { 618 size_t in_value_len; 619 const char *in_value = offset + 1; 620 in_value_len = query_part + query_part_len - (offset + 1); 621 result = http_aws_decode_encode(in_value, in_value_len, 622 &encoded_query_array[index].value); 623 if(result) { 624 goto fail; 625 } 626 } 627 else { 628 /* If there is no value, the value is an empty string */ 629 curlx_dyn_init(&encoded_query_array[index].value, 2); 630 result = curlx_dyn_addn(&encoded_query_array[index].value, "", 1); 631 } 632 633 if(result) { 634 goto fail; 635 } 636 } 637 638 /* Sort the encoded query components by key and value */ 639 qsort(&encoded_query_array, num_query_components, 640 sizeof(struct pair), compare_func); 641 642 /* Append the query components together to make a full query string */ 643 for(index = 0; index < num_query_components; index++) { 644 645 if(index) 646 result = curlx_dyn_addn(dq, "&", 1); 647 if(!result) { 648 char *key_ptr = curlx_dyn_ptr(&encoded_query_array[index].key); 649 char *value_ptr = curlx_dyn_ptr(&encoded_query_array[index].value); 650 size_t vlen = curlx_dyn_len(&encoded_query_array[index].value); 651 if(value_ptr && vlen) { 652 result = curlx_dyn_addf(dq, "%s=%s", key_ptr, value_ptr); 653 } 654 else { 655 /* Empty value is always encoded to key= */ 656 result = curlx_dyn_addf(dq, "%s=", key_ptr); 657 } 658 } 659 if(result) 660 break; 661 } 662 663 fail: 664 if(counted_query_components) 665 /* the encoded_query_array might not be initialized yet */ 666 pair_array_free(&encoded_query_array[0], counted_query_components); 667 dyn_array_free(&query_array[0], num_query_components); 668 return result; 669 } 670 671 CURLcode Curl_output_aws_sigv4(struct Curl_easy *data) 672 { 673 CURLcode result = CURLE_OUT_OF_MEMORY; 674 struct connectdata *conn = data->conn; 675 const char *line; 676 struct Curl_str provider0; 677 struct Curl_str provider1; 678 struct Curl_str region = { NULL, 0}; 679 struct Curl_str service = { NULL, 0}; 680 const char *hostname = conn->host.name; 681 time_t clock; 682 struct tm tm; 683 char timestamp[TIMESTAMP_SIZE]; 684 char date[9]; 685 struct dynbuf canonical_headers; 686 struct dynbuf signed_headers; 687 struct dynbuf canonical_query; 688 struct dynbuf canonical_path; 689 char *date_header = NULL; 690 Curl_HttpReq httpreq; 691 const char *method = NULL; 692 const char *payload_hash = NULL; 693 size_t payload_hash_len = 0; 694 unsigned char sha_hash[CURL_SHA256_DIGEST_LENGTH]; 695 char sha_hex[SHA256_HEX_LENGTH]; 696 char content_sha256_hdr[CONTENT_SHA256_HDR_LEN + 2] = ""; /* add \r\n */ 697 char *canonical_request = NULL; 698 char *request_type = NULL; 699 char *credential_scope = NULL; 700 char *str_to_sign = NULL; 701 const char *user = data->state.aptr.user ? data->state.aptr.user : ""; 702 char *secret = NULL; 703 unsigned char sign0[CURL_SHA256_DIGEST_LENGTH] = {0}; 704 unsigned char sign1[CURL_SHA256_DIGEST_LENGTH] = {0}; 705 char *auth_headers = NULL; 706 707 if(data->set.path_as_is) { 708 failf(data, "Cannot use sigv4 authentication with path-as-is flag"); 709 return CURLE_BAD_FUNCTION_ARGUMENT; 710 } 711 712 if(Curl_checkheaders(data, STRCONST("Authorization"))) { 713 /* Authorization already present, Bailing out */ 714 return CURLE_OK; 715 } 716 717 /* we init those buffers here, so goto fail will free initialized dynbuf */ 718 curlx_dyn_init(&canonical_headers, CURL_MAX_HTTP_HEADER); 719 curlx_dyn_init(&canonical_query, CURL_MAX_HTTP_HEADER); 720 curlx_dyn_init(&signed_headers, CURL_MAX_HTTP_HEADER); 721 curlx_dyn_init(&canonical_path, CURL_MAX_HTTP_HEADER); 722 723 /* 724 * Parameters parsing 725 * Google and Outscale use the same OSC or GOOG, 726 * but Amazon uses AWS and AMZ for header arguments. 727 * AWS is the default because most of non-amazon providers 728 * are still using aws:amz as a prefix. 729 */ 730 line = data->set.str[STRING_AWS_SIGV4]; 731 if(!line || !*line) 732 line = "aws:amz"; 733 734 /* provider0[:provider1[:region[:service]]] 735 736 No string can be longer than N bytes of non-whitespace 737 */ 738 if(curlx_str_until(&line, &provider0, MAX_SIGV4_LEN, ':')) { 739 failf(data, "first aws-sigv4 provider cannot be empty"); 740 result = CURLE_BAD_FUNCTION_ARGUMENT; 741 goto fail; 742 } 743 if(curlx_str_single(&line, ':') || 744 curlx_str_until(&line, &provider1, MAX_SIGV4_LEN, ':')) { 745 provider1 = provider0; 746 } 747 else if(curlx_str_single(&line, ':') || 748 curlx_str_until(&line, ®ion, MAX_SIGV4_LEN, ':') || 749 curlx_str_single(&line, ':') || 750 curlx_str_until(&line, &service, MAX_SIGV4_LEN, ':')) { 751 /* nothing to do */ 752 } 753 754 if(!curlx_strlen(&service)) { 755 const char *p = hostname; 756 if(curlx_str_until(&p, &service, MAX_SIGV4_LEN, '.') || 757 curlx_str_single(&p, '.')) { 758 failf(data, "aws-sigv4: service missing in parameters and hostname"); 759 result = CURLE_URL_MALFORMAT; 760 goto fail; 761 } 762 763 infof(data, "aws_sigv4: picked service %.*s from host", 764 (int)curlx_strlen(&service), curlx_str(&service)); 765 766 if(!curlx_strlen(®ion)) { 767 if(curlx_str_until(&p, ®ion, MAX_SIGV4_LEN, '.') || 768 curlx_str_single(&p, '.')) { 769 failf(data, "aws-sigv4: region missing in parameters and hostname"); 770 result = CURLE_URL_MALFORMAT; 771 goto fail; 772 } 773 infof(data, "aws_sigv4: picked region %.*s from host", 774 (int)curlx_strlen(®ion), curlx_str(®ion)); 775 } 776 } 777 778 Curl_http_method(data, conn, &method, &httpreq); 779 780 payload_hash = 781 parse_content_sha_hdr(data, curlx_str(&provider1), 782 curlx_strlen(&provider1), &payload_hash_len); 783 784 if(!payload_hash) { 785 /* AWS S3 requires a x-amz-content-sha256 header, and supports special 786 * values like UNSIGNED-PAYLOAD */ 787 bool sign_as_s3 = curlx_str_casecompare(&provider0, "aws") && 788 curlx_str_casecompare(&service, "s3"); 789 790 if(sign_as_s3) 791 result = calc_s3_payload_hash(data, httpreq, curlx_str(&provider1), 792 curlx_strlen(&provider1), sha_hash, 793 sha_hex, content_sha256_hdr); 794 else 795 result = calc_payload_hash(data, sha_hash, sha_hex); 796 if(result) 797 goto fail; 798 799 payload_hash = sha_hex; 800 /* may be shorter than SHA256_HEX_LENGTH, like S3_UNSIGNED_PAYLOAD */ 801 payload_hash_len = strlen(sha_hex); 802 } 803 804 #ifdef DEBUGBUILD 805 { 806 char *force_timestamp = getenv("CURL_FORCETIME"); 807 if(force_timestamp) 808 clock = 0; 809 else 810 clock = time(NULL); 811 } 812 #else 813 clock = time(NULL); 814 #endif 815 result = Curl_gmtime(clock, &tm); 816 if(result) { 817 goto fail; 818 } 819 if(!strftime(timestamp, sizeof(timestamp), "%Y%m%dT%H%M%SZ", &tm)) { 820 result = CURLE_OUT_OF_MEMORY; 821 goto fail; 822 } 823 824 result = make_headers(data, hostname, timestamp, 825 curlx_str(&provider1), curlx_strlen(&provider1), 826 &date_header, content_sha256_hdr, 827 &canonical_headers, &signed_headers); 828 if(result) 829 goto fail; 830 831 if(*content_sha256_hdr) { 832 /* make_headers() needed this without the \r\n for canonicalization */ 833 size_t hdrlen = strlen(content_sha256_hdr); 834 DEBUGASSERT(hdrlen + 3 < sizeof(content_sha256_hdr)); 835 memcpy(content_sha256_hdr + hdrlen, "\r\n", 3); 836 } 837 838 memcpy(date, timestamp, sizeof(date)); 839 date[sizeof(date) - 1] = 0; 840 841 result = canon_query(data->state.up.query, &canonical_query); 842 if(result) 843 goto fail; 844 845 result = canon_path(data->state.up.path, strlen(data->state.up.path), 846 &canonical_path, 847 should_urlencode(&service)); 848 if(result) 849 goto fail; 850 result = CURLE_OUT_OF_MEMORY; 851 852 canonical_request = 853 aprintf("%s\n" /* HTTPRequestMethod */ 854 "%s\n" /* CanonicalURI */ 855 "%s\n" /* CanonicalQueryString */ 856 "%s\n" /* CanonicalHeaders */ 857 "%s\n" /* SignedHeaders */ 858 "%.*s", /* HashedRequestPayload in hex */ 859 method, 860 curlx_dyn_ptr(&canonical_path), 861 curlx_dyn_ptr(&canonical_query) ? 862 curlx_dyn_ptr(&canonical_query) : "", 863 curlx_dyn_ptr(&canonical_headers), 864 curlx_dyn_ptr(&signed_headers), 865 (int)payload_hash_len, payload_hash); 866 if(!canonical_request) 867 goto fail; 868 869 infof(data, "aws_sigv4: Canonical request (enclosed in []) - [%s]", 870 canonical_request); 871 872 request_type = aprintf("%.*s4_request", 873 (int)curlx_strlen(&provider0), curlx_str(&provider0)); 874 if(!request_type) 875 goto fail; 876 877 /* provider0 is lowercased *after* aprintf() so that the buffer can be 878 written to */ 879 Curl_strntolower(request_type, request_type, curlx_strlen(&provider0)); 880 881 credential_scope = aprintf("%s/%.*s/%.*s/%s", date, 882 (int)curlx_strlen(®ion), curlx_str(®ion), 883 (int)curlx_strlen(&service), curlx_str(&service), 884 request_type); 885 if(!credential_scope) 886 goto fail; 887 888 if(Curl_sha256it(sha_hash, (unsigned char *) canonical_request, 889 strlen(canonical_request))) 890 goto fail; 891 892 sha256_to_hex(sha_hex, sha_hash); 893 894 /* 895 * Google allows using RSA key instead of HMAC, so this code might change 896 * in the future. For now we only support HMAC. 897 */ 898 str_to_sign = aprintf("%.*s4-HMAC-SHA256\n" /* Algorithm */ 899 "%s\n" /* RequestDateTime */ 900 "%s\n" /* CredentialScope */ 901 "%s", /* HashedCanonicalRequest in hex */ 902 (int)curlx_strlen(&provider0), curlx_str(&provider0), 903 timestamp, 904 credential_scope, 905 sha_hex); 906 if(!str_to_sign) 907 goto fail; 908 909 /* make provider0 part done uppercase */ 910 Curl_strntoupper(str_to_sign, curlx_str(&provider0), 911 curlx_strlen(&provider0)); 912 913 infof(data, "aws_sigv4: String to sign (enclosed in []) - [%s]", 914 str_to_sign); 915 916 secret = aprintf("%.*s4%s", (int)curlx_strlen(&provider0), 917 curlx_str(&provider0), data->state.aptr.passwd ? 918 data->state.aptr.passwd : ""); 919 if(!secret) 920 goto fail; 921 /* make provider0 part done uppercase */ 922 Curl_strntoupper(secret, curlx_str(&provider0), curlx_strlen(&provider0)); 923 924 HMAC_SHA256(secret, strlen(secret), date, strlen(date), sign0); 925 HMAC_SHA256(sign0, sizeof(sign0), 926 curlx_str(®ion), curlx_strlen(®ion), sign1); 927 HMAC_SHA256(sign1, sizeof(sign1), 928 curlx_str(&service), curlx_strlen(&service), sign0); 929 HMAC_SHA256(sign0, sizeof(sign0), request_type, strlen(request_type), sign1); 930 HMAC_SHA256(sign1, sizeof(sign1), str_to_sign, strlen(str_to_sign), sign0); 931 932 sha256_to_hex(sha_hex, sign0); 933 934 infof(data, "aws_sigv4: Signature - %s", sha_hex); 935 936 auth_headers = aprintf("Authorization: %.*s4-HMAC-SHA256 " 937 "Credential=%s/%s, " 938 "SignedHeaders=%s, " 939 "Signature=%s\r\n" 940 /* 941 * date_header is added here, only if it was not 942 * user-specified (using CURLOPT_HTTPHEADER). 943 * date_header includes \r\n 944 */ 945 "%s" 946 "%s", /* optional sha256 header includes \r\n */ 947 (int)curlx_strlen(&provider0), curlx_str(&provider0), 948 user, 949 credential_scope, 950 curlx_dyn_ptr(&signed_headers), 951 sha_hex, 952 date_header ? date_header : "", 953 content_sha256_hdr); 954 if(!auth_headers) { 955 goto fail; 956 } 957 /* provider 0 uppercase */ 958 Curl_strntoupper(&auth_headers[sizeof("Authorization: ") - 1], 959 curlx_str(&provider0), curlx_strlen(&provider0)); 960 961 free(data->state.aptr.userpwd); 962 data->state.aptr.userpwd = auth_headers; 963 data->state.authhost.done = TRUE; 964 result = CURLE_OK; 965 966 fail: 967 curlx_dyn_free(&canonical_query); 968 curlx_dyn_free(&canonical_path); 969 curlx_dyn_free(&canonical_headers); 970 curlx_dyn_free(&signed_headers); 971 free(canonical_request); 972 free(request_type); 973 free(credential_scope); 974 free(str_to_sign); 975 free(secret); 976 free(date_header); 977 return result; 978 } 979 980 /* 981 * Frees all allocated strings in a dynbuf pair array, and the dynbuf itself 982 */ 983 984 static void pair_array_free(struct pair *pair_array, size_t num_elements) 985 { 986 size_t index; 987 988 for(index = 0; index != num_elements; index++) { 989 curlx_dyn_free(&pair_array[index].key); 990 curlx_dyn_free(&pair_array[index].value); 991 } 992 993 } 994 995 /* 996 * Frees all allocated strings in a split dynbuf, and the dynbuf itself 997 */ 998 999 static void dyn_array_free(struct dynbuf *db, size_t num_elements) 1000 { 1001 size_t index; 1002 1003 for(index = 0; index < num_elements; index++) 1004 curlx_dyn_free((&db[index])); 1005 } 1006 1007 /* 1008 * Splits source string by SPLIT_BY, and creates an array of dynbuf in db. 1009 * db is initialized by this function. 1010 * Caller is responsible for freeing the array elements with dyn_array_free 1011 */ 1012 1013 #define SPLIT_BY '&' 1014 1015 static CURLcode split_to_dyn_array(const char *source, 1016 struct dynbuf db[MAX_QUERY_COMPONENTS], 1017 size_t *num_splits_out) 1018 { 1019 CURLcode result = CURLE_OK; 1020 size_t len = strlen(source); 1021 size_t pos; /* Position in result buffer */ 1022 size_t start = 0; /* Start of current segment */ 1023 size_t segment_length = 0; 1024 size_t index = 0; 1025 size_t num_splits = 0; 1026 1027 /* Split source_ptr on SPLIT_BY and store the segment offsets and length in 1028 * array */ 1029 for(pos = 0; pos < len; pos++) { 1030 if(source[pos] == SPLIT_BY) { 1031 if(segment_length) { 1032 curlx_dyn_init(&db[index], segment_length + 1); 1033 result = curlx_dyn_addn(&db[index], &source[start], 1034 segment_length); 1035 if(result) 1036 goto fail; 1037 1038 segment_length = 0; 1039 index++; 1040 if(++num_splits == MAX_QUERY_COMPONENTS) { 1041 result = CURLE_TOO_LARGE; 1042 goto fail; 1043 } 1044 } 1045 start = pos + 1; 1046 } 1047 else { 1048 segment_length++; 1049 } 1050 } 1051 1052 if(segment_length) { 1053 curlx_dyn_init(&db[index], segment_length + 1); 1054 result = curlx_dyn_addn(&db[index], &source[start], segment_length); 1055 if(!result) { 1056 if(++num_splits == MAX_QUERY_COMPONENTS) 1057 result = CURLE_TOO_LARGE; 1058 } 1059 } 1060 fail: 1061 *num_splits_out = num_splits; 1062 return result; 1063 } 1064 1065 1066 static bool is_reserved_char(const char c) 1067 { 1068 return (ISALNUM(c) || ISURLPUNTCS(c)); 1069 } 1070 1071 static CURLcode uri_encode_path(struct Curl_str *original_path, 1072 struct dynbuf *new_path) 1073 { 1074 const char *p = curlx_str(original_path); 1075 size_t i; 1076 1077 for(i = 0; i < curlx_strlen(original_path); i++) { 1078 /* Do not encode slashes or unreserved chars from RFC 3986 */ 1079 CURLcode result = CURLE_OK; 1080 unsigned char c = p[i]; 1081 if(is_reserved_char(c) || c == '/') 1082 result = curlx_dyn_addn(new_path, &c, 1); 1083 else 1084 result = curlx_dyn_addf(new_path, "%%%02X", c); 1085 if(result) 1086 return result; 1087 } 1088 1089 return CURLE_OK; 1090 } 1091 1092 1093 static CURLcode encode_query_component(char *component, size_t len, 1094 struct dynbuf *db) 1095 { 1096 size_t i; 1097 for(i = 0; i < len; i++) { 1098 CURLcode result = CURLE_OK; 1099 unsigned char this_char = component[i]; 1100 1101 if(is_reserved_char(this_char)) 1102 /* Escape unreserved chars from RFC 3986 */ 1103 result = curlx_dyn_addn(db, &this_char, 1); 1104 else if(this_char == '+') 1105 /* Encode '+' as space */ 1106 result = curlx_dyn_add(db, "%20"); 1107 else 1108 result = curlx_dyn_addf(db, "%%%02X", this_char); 1109 if(result) 1110 return result; 1111 } 1112 1113 return CURLE_OK; 1114 } 1115 1116 /* 1117 * Populates a dynbuf containing url_encode(url_decode(in)) 1118 */ 1119 1120 static CURLcode http_aws_decode_encode(const char *in, size_t in_len, 1121 struct dynbuf *out) 1122 { 1123 char *out_s; 1124 size_t out_s_len; 1125 CURLcode result = 1126 Curl_urldecode(in, in_len, &out_s, &out_s_len, REJECT_NADA); 1127 1128 if(!result) { 1129 result = encode_query_component(out_s, out_s_len, out); 1130 Curl_safefree(out_s); 1131 } 1132 return result; 1133 } 1134 1135 static bool should_urlencode(struct Curl_str *service_name) 1136 { 1137 /* 1138 * These services require unmodified (not additionally url encoded) URL 1139 * paths. 1140 * should_urlencode == true is equivalent to should_urlencode_uri_path 1141 * from the AWS SDK. Urls are already normalized by the curl url parser 1142 */ 1143 1144 if(curlx_str_cmp(service_name, "s3") || 1145 curlx_str_cmp(service_name, "s3-express") || 1146 curlx_str_cmp(service_name, "s3-outposts")) { 1147 return false; 1148 } 1149 return true; 1150 } 1151 1152 #endif /* !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_AWS) */