summaryrefslogtreecommitdiff
path: root/Utilities/cmcurl/lib/http_aws_sigv4.c
diff options
context:
space:
mode:
Diffstat (limited to 'Utilities/cmcurl/lib/http_aws_sigv4.c')
-rw-r--r--Utilities/cmcurl/lib/http_aws_sigv4.c542
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 210c3dbe56..440eb385f8 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;
}