diff options
author | Brad King <brad.king@kitware.com> | 2022-10-31 20:11:41 (GMT) |
---|---|---|
committer | Brad King <brad.king@kitware.com> | 2022-10-31 20:11:41 (GMT) |
commit | 9ffe6b0969fc270cc2a1aac2b2c1bf986af291d5 (patch) | |
tree | 75711965f7fd24679383853a840f42efff676e31 /Utilities/cmcurl/lib/http_aws_sigv4.c | |
parent | fa9bbb8627e8af5153367721eb037b6e094670d1 (diff) | |
parent | ec122fff08ab9a8e56fb90126ecedb99c759011b (diff) | |
download | CMake-9ffe6b0969fc270cc2a1aac2b2c1bf986af291d5.zip CMake-9ffe6b0969fc270cc2a1aac2b2c1bf986af291d5.tar.gz CMake-9ffe6b0969fc270cc2a1aac2b2c1bf986af291d5.tar.bz2 |
Merge branch 'upstream-curl' into update-curl
* upstream-curl:
curl 2022-10-26 (cd95ee9f)
Diffstat (limited to 'Utilities/cmcurl/lib/http_aws_sigv4.c')
-rw-r--r-- | Utilities/cmcurl/lib/http_aws_sigv4.c | 542 |
1 files changed, 331 insertions, 211 deletions
diff --git a/Utilities/cmcurl/lib/http_aws_sigv4.c b/Utilities/cmcurl/lib/http_aws_sigv4.c index 210c3db..440eb38 100644 --- a/Utilities/cmcurl/lib/http_aws_sigv4.c +++ b/Utilities/cmcurl/lib/http_aws_sigv4.c @@ -18,6 +18,8 @@ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY * KIND, either express or implied. * + * SPDX-License-Identifier: curl + * ***************************************************************************/ #include "curl_setup.h" @@ -27,8 +29,6 @@ #include "urldata.h" #include "strcase.h" #include "strdup.h" -#include "vauth/vauth.h" -#include "vauth/digest.h" #include "http_aws_sigv4.h" #include "curl_sha256.h" #include "transfer.h" @@ -44,6 +44,8 @@ #include "curl_memory.h" #include "memdebug.h" +#include "slist.h" + #define HMAC_SHA256(k, kl, d, dl, o) \ do { \ ret = Curl_hmacit(Curl_HMAC_SHA256, \ @@ -51,19 +53,219 @@ (unsigned int)kl, \ (unsigned char *)d, \ (unsigned int)dl, o); \ - if(ret != CURLE_OK) { \ + if(ret) { \ goto fail; \ } \ } while(0) +#define TIMESTAMP_SIZE 17 + static void sha256_to_hex(char *dst, unsigned char *sha, size_t dst_l) { int i; DEBUGASSERT(dst_l >= 65); for(i = 0; i < 32; ++i) { - curl_msnprintf(dst + (i * 2), dst_l - (i * 2), "%02x", sha[i]); + msnprintf(dst + (i * 2), dst_l - (i * 2), "%02x", sha[i]); + } +} + +static char *find_date_hdr(struct Curl_easy *data, const char *sig_hdr) +{ + char *tmp = Curl_checkheaders(data, sig_hdr, strlen(sig_hdr)); + + if(tmp) + return tmp; + return Curl_checkheaders(data, STRCONST("Date")); +} + +/* remove whitespace, and lowercase all headers */ +static void trim_headers(struct curl_slist *head) +{ + struct curl_slist *l; + for(l = head; l; l = l->next) { + char *value; /* to read from */ + char *store; + size_t colon = strcspn(l->data, ":"); + Curl_strntolower(l->data, l->data, colon); + + value = &l->data[colon]; + if(!*value) + continue; + ++value; + store = value; + + /* skip leading whitespace */ + while(*value && ISBLANK(*value)) + value++; + + while(*value) { + int space = 0; + while(*value && ISBLANK(*value)) { + value++; + space++; + } + if(space) { + /* replace any number of consecutive whitespace with a single space, + unless at the end of the string, then nothing */ + if(*value) + *store++ = ' '; + } + else + *store++ = *value++; + } + *store = 0; /* null terminate */ + } +} + +/* maximum lenth for the aws sivg4 parts */ +#define MAX_SIGV4_LEN 64 +#define MAX_SIGV4_LEN_TXT "64" + +#define DATE_HDR_KEY_LEN (MAX_SIGV4_LEN + sizeof("X--Date")) + +#define MAX_HOST_LEN 255 +/* FQDN + host: */ +#define FULL_HOST_LEN (MAX_HOST_LEN + sizeof("host:")) + +/* string been x-PROVIDER-date:TIMESTAMP, I need +1 for ':' */ +#define DATE_FULL_HDR_LEN (DATE_HDR_KEY_LEN + TIMESTAMP_SIZE + 1) + +/* timestamp should point to a buffer of at last TIMESTAMP_SIZE bytes */ +static CURLcode make_headers(struct Curl_easy *data, + const char *hostname, + char *timestamp, + char *provider1, + char **date_header, + struct dynbuf *canonical_headers, + struct dynbuf *signed_headers) +{ + char date_hdr_key[DATE_HDR_KEY_LEN]; + char date_full_hdr[DATE_FULL_HDR_LEN]; + struct curl_slist *head = NULL; + struct curl_slist *tmp_head = NULL; + CURLcode ret = CURLE_OUT_OF_MEMORY; + struct curl_slist *l; + int again = 1; + + /* provider1 mid */ + Curl_strntolower(provider1, provider1, strlen(provider1)); + provider1[0] = Curl_raw_toupper(provider1[0]); + + msnprintf(date_hdr_key, DATE_HDR_KEY_LEN, "X-%s-Date", provider1); + + /* provider1 lowercase */ + Curl_strntolower(provider1, provider1, 1); /* first byte only */ + msnprintf(date_full_hdr, DATE_FULL_HDR_LEN, + "x-%s-date:%s", provider1, timestamp); + + if(Curl_checkheaders(data, STRCONST("Host"))) { + head = NULL; + } + else { + char full_host[FULL_HOST_LEN + 1]; + + if(data->state.aptr.host) { + size_t pos; + + if(strlen(data->state.aptr.host) > FULL_HOST_LEN) { + ret = CURLE_URL_MALFORMAT; + goto fail; + } + strcpy(full_host, data->state.aptr.host); + /* remove /r/n as the separator for canonical request must be '\n' */ + pos = strcspn(full_host, "\n\r"); + full_host[pos] = 0; + } + else { + if(strlen(hostname) > MAX_HOST_LEN) { + ret = CURLE_URL_MALFORMAT; + goto fail; + } + msnprintf(full_host, FULL_HOST_LEN, "host:%s", hostname); + } + + head = curl_slist_append(NULL, full_host); + if(!head) + goto fail; + } + + + for(l = data->set.headers; l; l = l->next) { + tmp_head = curl_slist_append(head, l->data); + if(!tmp_head) + goto fail; + head = tmp_head; + } + + trim_headers(head); + + *date_header = find_date_hdr(data, date_hdr_key); + if(!*date_header) { + tmp_head = curl_slist_append(head, date_full_hdr); + if(!tmp_head) + goto fail; + head = tmp_head; + *date_header = curl_maprintf("%s: %s", date_hdr_key, timestamp); + } + else { + char *value; + + *date_header = strdup(*date_header); + if(!*date_header) + goto fail; + + value = strchr(*date_header, ':'); + if(!value) + goto fail; + ++value; + while(ISBLANK(*value)) + ++value; + strncpy(timestamp, value, TIMESTAMP_SIZE - 1); + timestamp[TIMESTAMP_SIZE - 1] = 0; + } + + /* alpha-sort in a case sensitive manner */ + do { + again = 0; + for(l = head; l; l = l->next) { + struct curl_slist *next = l->next; + + if(next && strcmp(l->data, next->data) > 0) { + char *tmp = l->data; + + l->data = next->data; + next->data = tmp; + again = 1; + } + } + } while(again); + + for(l = head; l; l = l->next) { + char *tmp; + + if(Curl_dyn_add(canonical_headers, l->data)) + goto fail; + if(Curl_dyn_add(canonical_headers, "\n")) + goto fail; + + tmp = strchr(l->data, ':'); + if(tmp) + *tmp = 0; + + if(l != head) { + if(Curl_dyn_add(signed_headers, ";")) + goto fail; + } + if(Curl_dyn_add(signed_headers, l->data)) + goto fail; } + + ret = CURLE_OK; +fail: + curl_slist_free_all(head); + + return ret; } CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) @@ -71,29 +273,21 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) CURLcode ret = CURLE_OUT_OF_MEMORY; struct connectdata *conn = data->conn; size_t len; - const char *tmp0; - const char *tmp1; - char *provider0_low = NULL; - char *provider0_up = NULL; - char *provider1_low = NULL; - char *provider1_mid = NULL; - char *region = NULL; - char *service = NULL; + const char *arg; + char provider0[MAX_SIGV4_LEN + 1]=""; + char provider1[MAX_SIGV4_LEN + 1]=""; + char region[MAX_SIGV4_LEN + 1]=""; + char service[MAX_SIGV4_LEN + 1]=""; const char *hostname = conn->host.name; -#ifdef DEBUGBUILD - char *force_timestamp; -#endif time_t clock; struct tm tm; - char timestamp[17]; + char timestamp[TIMESTAMP_SIZE]; char date[9]; - const char *content_type = Curl_checkheaders(data, STRCONST("Content-Type")); - char *canonical_headers = NULL; - char *signed_headers = NULL; - Curl_HttpReq httpreq; - const char *method; - size_t post_data_len; - const char *post_data = data->set.postfields ? data->set.postfields : ""; + struct dynbuf canonical_headers; + struct dynbuf signed_headers; + char *date_header = NULL; + const char *post_data = data->set.postfields; + size_t post_data_len = 0; unsigned char sha_hash[32]; char sha_hex[65]; char *canonical_request = NULL; @@ -101,10 +295,9 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) char *credential_scope = NULL; char *str_to_sign = NULL; const char *user = data->state.aptr.user ? data->state.aptr.user : ""; - const char *passwd = data->state.aptr.passwd ? data->state.aptr.passwd : ""; char *secret = NULL; - unsigned char tmp_sign0[32] = {0}; - unsigned char tmp_sign1[32] = {0}; + unsigned char sign0[32] = {0}; + unsigned char sign1[32] = {0}; char *auth_headers = NULL; DEBUGASSERT(!proxy); @@ -115,6 +308,10 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) return CURLE_OK; } + /* we init thoses buffers here, so goto fail will free initialized dynbuf */ + Curl_dyn_init(&canonical_headers, CURL_MAX_HTTP_HEADER); + Curl_dyn_init(&signed_headers, CURL_MAX_HTTP_HEADER); + /* * Parameters parsing * Google and Outscale use the same OSC or GOOG, @@ -122,223 +319,154 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) * AWS is the default because most of non-amazon providers * are still using aws:amz as a prefix. */ - tmp0 = data->set.str[STRING_AWS_SIGV4] ? + arg = data->set.str[STRING_AWS_SIGV4] ? data->set.str[STRING_AWS_SIGV4] : "aws:amz"; - tmp1 = strchr(tmp0, ':'); - len = tmp1 ? (size_t)(tmp1 - tmp0) : strlen(tmp0); - if(len < 1) { - infof(data, "first provider can't be empty"); + + /* provider1[:provider2[:region[:service]]] + + No string can be longer than N bytes of non-whitespace + */ + (void)sscanf(arg, "%" MAX_SIGV4_LEN_TXT "[^:]" + ":%" MAX_SIGV4_LEN_TXT "[^:]" + ":%" MAX_SIGV4_LEN_TXT "[^:]" + ":%" MAX_SIGV4_LEN_TXT "s", + provider0, provider1, region, service); + if(!provider0[0]) { + failf(data, "first provider can't be empty"); ret = CURLE_BAD_FUNCTION_ARGUMENT; goto fail; } - provider0_low = malloc(len + 1); - provider0_up = malloc(len + 1); - if(!provider0_low || !provider0_up) { - goto fail; - } - Curl_strntolower(provider0_low, tmp0, len); - provider0_low[len] = '\0'; - Curl_strntoupper(provider0_up, tmp0, len); - provider0_up[len] = '\0'; - - if(tmp1) { - tmp0 = tmp1 + 1; - tmp1 = strchr(tmp0, ':'); - len = tmp1 ? (size_t)(tmp1 - tmp0) : strlen(tmp0); - if(len < 1) { - infof(data, "second provider can't be empty"); - ret = CURLE_BAD_FUNCTION_ARGUMENT; - goto fail; - } - provider1_low = malloc(len + 1); - provider1_mid = malloc(len + 1); - if(!provider1_low || !provider1_mid) { - goto fail; - } - Curl_strntolower(provider1_low, tmp0, len); - provider1_low[len] = '\0'; - Curl_strntolower(provider1_mid, tmp0, len); - provider1_mid[0] = Curl_raw_toupper(provider1_mid[0]); - provider1_mid[len] = '\0'; - - if(tmp1) { - tmp0 = tmp1 + 1; - tmp1 = strchr(tmp0, ':'); - len = tmp1 ? (size_t)(tmp1 - tmp0) : strlen(tmp0); - if(len < 1) { - infof(data, "region can't be empty"); - ret = CURLE_BAD_FUNCTION_ARGUMENT; - goto fail; - } - region = Curl_memdup(tmp0, len + 1); - if(!region) { - goto fail; - } - region[len] = '\0'; + else if(!provider1[0]) + strcpy(provider1, provider0); - if(tmp1) { - tmp0 = tmp1 + 1; - service = strdup(tmp0); - if(!service) { - goto fail; - } - if(strlen(service) < 1) { - infof(data, "service can't be empty"); - ret = CURLE_BAD_FUNCTION_ARGUMENT; - goto fail; - } - } - } - } - else { - provider1_low = Curl_memdup(provider0_low, len + 1); - provider1_mid = Curl_memdup(provider0_low, len + 1); - if(!provider1_low || !provider1_mid) { - goto fail; - } - provider1_mid[0] = Curl_raw_toupper(provider1_mid[0]); - } - - if(!service) { - tmp0 = hostname; - tmp1 = strchr(tmp0, '.'); - len = tmp1 - tmp0; - if(!tmp1 || len < 1) { - infof(data, "service missing in parameters or hostname"); + if(!service[0]) { + char *hostdot = strchr(hostname, '.'); + if(!hostdot) { + failf(data, "service missing in parameters and hostname"); ret = CURLE_URL_MALFORMAT; goto fail; } - service = Curl_memdup(tmp0, len + 1); - if(!service) { + len = hostdot - hostname; + if(len > MAX_SIGV4_LEN) { + failf(data, "service too long in hostname"); + ret = CURLE_URL_MALFORMAT; goto fail; } + strncpy(service, hostname, len); service[len] = '\0'; - if(!region) { - tmp0 = tmp1 + 1; - tmp1 = strchr(tmp0, '.'); - len = tmp1 - tmp0; - if(!tmp1 || len < 1) { - infof(data, "region missing in parameters or hostname"); + if(!region[0]) { + const char *reg = hostdot + 1; + const char *hostreg = strchr(reg, '.'); + if(!hostreg) { + failf(data, "region missing in parameters and hostname"); ret = CURLE_URL_MALFORMAT; goto fail; } - region = Curl_memdup(tmp0, len + 1); - if(!region) { + len = hostreg - reg; + if(len > MAX_SIGV4_LEN) { + failf(data, "region too long in hostname"); + ret = CURLE_URL_MALFORMAT; goto fail; } + strncpy(region, reg, len); region[len] = '\0'; } } #ifdef DEBUGBUILD - force_timestamp = getenv("CURL_FORCETIME"); - if(force_timestamp) - clock = 0; - else - time(&clock); + { + char *force_timestamp = getenv("CURL_FORCETIME"); + if(force_timestamp) + clock = 0; + else + time(&clock); + } #else time(&clock); #endif ret = Curl_gmtime(clock, &tm); - if(ret != CURLE_OK) { + if(ret) { goto fail; } if(!strftime(timestamp, sizeof(timestamp), "%Y%m%dT%H%M%SZ", &tm)) { + ret = CURLE_OUT_OF_MEMORY; goto fail; } + + ret = make_headers(data, hostname, timestamp, provider1, + &date_header, &canonical_headers, &signed_headers); + if(ret) + goto fail; + ret = CURLE_OUT_OF_MEMORY; + memcpy(date, timestamp, sizeof(date)); date[sizeof(date) - 1] = 0; - if(content_type) { - content_type = strchr(content_type, ':'); - if(!content_type) { - ret = CURLE_FAILED_INIT; - goto fail; - } - content_type++; - /* Skip whitespace now */ - while(*content_type == ' ' || *content_type == '\t') - ++content_type; - - canonical_headers = curl_maprintf("content-type:%s\n" - "host:%s\n" - "x-%s-date:%s\n", - content_type, - hostname, - provider1_low, timestamp); - signed_headers = curl_maprintf("content-type;host;x-%s-date", - provider1_low); - } - else { - canonical_headers = curl_maprintf("host:%s\n" - "x-%s-date:%s\n", - hostname, - provider1_low, timestamp); - signed_headers = curl_maprintf("host;x-%s-date", provider1_low); - } - - if(!canonical_headers || !signed_headers) { - goto fail; + if(post_data) { + if(data->set.postfieldsize < 0) + post_data_len = strlen(post_data); + else + post_data_len = (size_t)data->set.postfieldsize; } - - if(data->set.postfieldsize < 0) - post_data_len = strlen(post_data); - else - post_data_len = (size_t)data->set.postfieldsize; if(Curl_sha256it(sha_hash, (const unsigned char *) post_data, - post_data_len)) { + post_data_len)) goto fail; - } sha256_to_hex(sha_hex, sha_hash, sizeof(sha_hex)); - Curl_http_method(data, conn, &method, &httpreq); - - canonical_request = - curl_maprintf("%s\n" /* HTTPRequestMethod */ - "%s\n" /* CanonicalURI */ - "%s\n" /* CanonicalQueryString */ - "%s\n" /* CanonicalHeaders */ - "%s\n" /* SignedHeaders */ - "%s", /* HashedRequestPayload in hex */ - method, - data->state.up.path, - data->state.up.query ? data->state.up.query : "", - canonical_headers, - signed_headers, - sha_hex); - if(!canonical_request) { - goto fail; + { + Curl_HttpReq httpreq; + const char *method; + + Curl_http_method(data, conn, &method, &httpreq); + + canonical_request = + curl_maprintf("%s\n" /* HTTPRequestMethod */ + "%s\n" /* CanonicalURI */ + "%s\n" /* CanonicalQueryString */ + "%s\n" /* CanonicalHeaders */ + "%s\n" /* SignedHeaders */ + "%s", /* HashedRequestPayload in hex */ + method, + data->state.up.path, + data->state.up.query ? data->state.up.query : "", + Curl_dyn_ptr(&canonical_headers), + Curl_dyn_ptr(&signed_headers), + sha_hex); + if(!canonical_request) + goto fail; } - request_type = curl_maprintf("%s4_request", provider0_low); - if(!request_type) { + /* provider 0 lowercase */ + Curl_strntolower(provider0, provider0, strlen(provider0)); + request_type = curl_maprintf("%s4_request", provider0); + if(!request_type) goto fail; - } credential_scope = curl_maprintf("%s/%s/%s/%s", date, region, service, request_type); - if(!credential_scope) { + if(!credential_scope) goto fail; - } if(Curl_sha256it(sha_hash, (unsigned char *) canonical_request, - strlen(canonical_request))) { + strlen(canonical_request))) goto fail; - } sha256_to_hex(sha_hex, sha_hash, sizeof(sha_hex)); + /* provider 0 uppercase */ + Curl_strntoupper(provider0, provider0, strlen(provider0)); + /* - * Google allow to use rsa key instead of HMAC, so this code might change - * In the future, but for now we support only HMAC version + * Google allows using RSA key instead of HMAC, so this code might change + * in the future. For now we ony support HMAC. */ str_to_sign = curl_maprintf("%s4-HMAC-SHA256\n" /* Algorithm */ "%s\n" /* RequestDateTime */ "%s\n" /* CredentialScope */ "%s", /* HashedCanonicalRequest in hex */ - provider0_up, + provider0, timestamp, credential_scope, sha_hex); @@ -346,36 +474,33 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) goto fail; } - secret = curl_maprintf("%s4%s", provider0_up, passwd); - if(!secret) { + /* provider 0 uppercase */ + secret = curl_maprintf("%s4%s", provider0, + data->state.aptr.passwd ? + data->state.aptr.passwd : ""); + if(!secret) goto fail; - } - HMAC_SHA256(secret, strlen(secret), - date, strlen(date), tmp_sign0); - HMAC_SHA256(tmp_sign0, sizeof(tmp_sign0), - region, strlen(region), tmp_sign1); - HMAC_SHA256(tmp_sign1, sizeof(tmp_sign1), - service, strlen(service), tmp_sign0); - HMAC_SHA256(tmp_sign0, sizeof(tmp_sign0), - request_type, strlen(request_type), tmp_sign1); - HMAC_SHA256(tmp_sign1, sizeof(tmp_sign1), - str_to_sign, strlen(str_to_sign), tmp_sign0); + HMAC_SHA256(secret, strlen(secret), date, strlen(date), sign0); + HMAC_SHA256(sign0, sizeof(sign0), region, strlen(region), sign1); + HMAC_SHA256(sign1, sizeof(sign1), service, strlen(service), sign0); + HMAC_SHA256(sign0, sizeof(sign0), request_type, strlen(request_type), sign1); + HMAC_SHA256(sign1, sizeof(sign1), str_to_sign, strlen(str_to_sign), sign0); - sha256_to_hex(sha_hex, tmp_sign0, sizeof(sha_hex)); + sha256_to_hex(sha_hex, sign0, sizeof(sha_hex)); + /* provider 0 uppercase */ auth_headers = curl_maprintf("Authorization: %s4-HMAC-SHA256 " "Credential=%s/%s, " "SignedHeaders=%s, " "Signature=%s\r\n" - "X-%s-Date: %s\r\n", - provider0_up, + "%s\r\n", + provider0, user, credential_scope, - signed_headers, + Curl_dyn_ptr(&signed_headers), sha_hex, - provider1_mid, - timestamp); + date_header); if(!auth_headers) { goto fail; } @@ -386,19 +511,14 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy) ret = CURLE_OK; fail: - free(provider0_low); - free(provider0_up); - free(provider1_low); - free(provider1_mid); - free(region); - free(service); - free(canonical_headers); - free(signed_headers); + Curl_dyn_free(&canonical_headers); + Curl_dyn_free(&signed_headers); free(canonical_request); free(request_type); free(credential_scope); free(str_to_sign); free(secret); + free(date_header); return ret; } |