File curl-aws_sigv4-canonicalize-the-query.patch of Package curl.36707
From fc76a24c53b08cdf6eec8ba787d8eac64651d56e Mon Sep 17 00:00:00 2001
From: Daniel Stenberg <daniel@haxx.se>
Date: Wed, 6 Sep 2023 10:14:44 +0200
Subject: [PATCH] http_aws_sigv4: canonicalize the query
Percent encoding needs to be done using uppercase, and most
non-alphanumerical must be percent-encoded.
Fixes #11794
Reported-by: John Walker
Closes #11806
---
lib/http_aws_sigv4.c | 185 ++++++++++++++++++++++++++++++++++++++++-----------
1 file changed, 148 insertions(+), 37 deletions(-)
--- curl-8.0.1.orig/lib/http_aws_sigv4.c
+++ curl-8.0.1/lib/http_aws_sigv4.c
@@ -44,16 +44,16 @@
#include "slist.h"
-#define HMAC_SHA256(k, kl, d, dl, o) \
- do { \
- ret = Curl_hmacit(Curl_HMAC_SHA256, \
- (unsigned char *)k, \
- kl, \
- (unsigned char *)d, \
- dl, o); \
- if(ret) { \
- goto fail; \
- } \
+#define HMAC_SHA256(k, kl, d, dl, o) \
+ do { \
+ result = Curl_hmacit(Curl_HMAC_SHA256, \
+ (unsigned char *)k, \
+ kl, \
+ (unsigned char *)d, \
+ dl, o); \
+ if(result) { \
+ goto fail; \
+ } \
} while(0)
#define TIMESTAMP_SIZE 17
@@ -370,9 +370,109 @@ fail:
return ret;
}
+struct pair {
+ const char *p;
+ size_t len;
+};
+
+static int compare_func(const void *a, const void *b)
+{
+ const struct pair *aa = a;
+ const struct pair *bb = b;
+ return strncmp(aa->p, bb->p, aa->len < bb->len ? aa->len : bb->len);
+}
+
+#define MAX_QUERYPAIRS 64
+
+static CURLcode canon_query(struct Curl_easy *data,
+ const char *query, struct dynbuf *dq)
+{
+ CURLcode result = CURLE_OK;
+ int entry = 0;
+ int i;
+ const char *p = query;
+ struct pair array[MAX_QUERYPAIRS];
+ struct pair *ap = &array[0];
+ if(!query)
+ return result;
+
+ /* sort the name=value pairs first */
+ do {
+ char *amp;
+ entry++;
+ ap->p = p;
+ amp = strchr(p, '&');
+ if(amp)
+ ap->len = amp - p; /* excluding the ampersand */
+ else {
+ ap->len = strlen(p);
+ break;
+ }
+ ap++;
+ p = amp + 1;
+ } while(entry < MAX_QUERYPAIRS);
+ if(entry == MAX_QUERYPAIRS) {
+ /* too many query pairs for us */
+ failf(data, "aws-sigv4: too many query pairs in URL");
+ return CURLE_URL_MALFORMAT;
+ }
+
+ qsort(&array[0], entry, sizeof(struct pair), compare_func);
+
+ ap = &array[0];
+ for(i = 0; !result && (i < entry); i++, ap++) {
+ size_t len;
+ const char *q = ap->p;
+ for(len = ap->len; len && !result; q++, len--) {
+ if(ISALNUM(*q))
+ result = Curl_dyn_addn(dq, q, 1);
+ else {
+ switch(*q) {
+ case '-':
+ case '.':
+ case '_':
+ case '~':
+ case '=':
+ /* allowed as-is */
+ result = Curl_dyn_addn(dq, q, 1);
+ break;
+ case '%':
+ /* uppercase the following if hexadecimal */
+ if(ISXDIGIT(q[1]) && ISXDIGIT(q[2])) {
+ char tmp[3]="%";
+ tmp[1] = Curl_raw_toupper(q[1]);
+ tmp[2] = Curl_raw_toupper(q[2]);
+ result = Curl_dyn_addn(dq, tmp, 3);
+ q += 2;
+ }
+ else
+ /* '%' without a following two-digit hex, encode it */
+ result = Curl_dyn_addn(dq, "%25", 3);
+ break;
+ default: {
+ /* URL encode */
+ const char hex[] = "0123456789ABCDEF";
+ char out[3]={'%'};
+ out[1] = hex[((unsigned char)*q)>>4];
+ out[2] = hex[*q & 0xf];
+ result = Curl_dyn_addn(dq, out, 3);
+ break;
+ }
+ }
+ }
+ }
+ if(i < entry - 1) {
+ /* insert ampersands between query pairs */
+ result = Curl_dyn_addn(dq, "&", 1);
+ }
+ }
+ return result;
+}
+
+
CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
{
- CURLcode ret = CURLE_OUT_OF_MEMORY;
+ CURLcode result = CURLE_OUT_OF_MEMORY;
struct connectdata *conn = data->conn;
size_t len;
const char *arg;
@@ -388,6 +488,7 @@ CURLcode Curl_output_aws_sigv4(struct Cu
char date[9];
struct dynbuf canonical_headers;
struct dynbuf signed_headers;
+ struct dynbuf canonical_query;
char *date_header = NULL;
Curl_HttpReq httpreq;
const char *method = NULL;
@@ -416,6 +517,7 @@ CURLcode Curl_output_aws_sigv4(struct Cu
/* we init those buffers here, so goto fail will free initialized dynbuf */
Curl_dyn_init(&canonical_headers, CURL_MAX_HTTP_HEADER);
+ Curl_dyn_init(&canonical_query, CURL_MAX_HTTP_HEADER);
Curl_dyn_init(&signed_headers, CURL_MAX_HTTP_HEADER);
/*
@@ -431,15 +533,15 @@ CURLcode Curl_output_aws_sigv4(struct Cu
/* 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;
+ failf(data, "first aws-sigv4 provider can't be empty");
+ result = CURLE_BAD_FUNCTION_ARGUMENT;
goto fail;
}
else if(!provider1[0])
@@ -448,35 +550,38 @@ CURLcode Curl_output_aws_sigv4(struct Cu
if(!service[0]) {
char *hostdot = strchr(hostname, '.');
if(!hostdot) {
- failf(data, "service missing in parameters and hostname");
- ret = CURLE_URL_MALFORMAT;
+ failf(data, "aws-sigv4: service missing in parameters and hostname");
+ result = CURLE_URL_MALFORMAT;
goto fail;
}
len = hostdot - hostname;
if(len > MAX_SIGV4_LEN) {
- failf(data, "service too long in hostname");
- ret = CURLE_URL_MALFORMAT;
+ failf(data, "aws-sigv4: service too long in hostname");
+ result = CURLE_URL_MALFORMAT;
goto fail;
}
strncpy(service, hostname, len);
service[len] = '\0';
+ infof(data, "aws_sigv4: picked service %s from host", service);
+
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;
+ failf(data, "aws-sigv4: region missing in parameters and hostname");
+ result = CURLE_URL_MALFORMAT;
goto fail;
}
len = hostreg - reg;
if(len > MAX_SIGV4_LEN) {
- failf(data, "region too long in hostname");
- ret = CURLE_URL_MALFORMAT;
+ failf(data, "aws-sigv4: region too long in hostname");
+ result = CURLE_URL_MALFORMAT;
goto fail;
}
strncpy(region, reg, len);
region[len] = '\0';
+ infof(data, "aws_sigv4: picked region %s from host", region);
}
}
@@ -491,11 +596,11 @@ CURLcode Curl_output_aws_sigv4(struct Cu
if(!payload_hash) {
if(sign_as_s3)
- ret = calc_s3_payload_hash(data, httpreq, provider1, sha_hash,
- sha_hex, content_sha256_hdr);
+ result = calc_s3_payload_hash(data, httpreq, provider1, sha_hash,
+ sha_hex, content_sha256_hdr);
else
- ret = calc_payload_hash(data, sha_hash, sha_hex);
- if(ret)
+ result = calc_payload_hash(data, sha_hash, sha_hex);
+ if(result)
goto fail;
payload_hash = sha_hex;
@@ -514,21 +619,20 @@ CURLcode Curl_output_aws_sigv4(struct Cu
#else
time(&clock);
#endif
- ret = Curl_gmtime(clock, &tm);
- if(ret) {
+ result = Curl_gmtime(clock, &tm);
+ if(result) {
goto fail;
}
if(!strftime(timestamp, sizeof(timestamp), "%Y%m%dT%H%M%SZ", &tm)) {
- ret = CURLE_OUT_OF_MEMORY;
+ result = CURLE_OUT_OF_MEMORY;
goto fail;
}
- ret = make_headers(data, hostname, timestamp, provider1,
- &date_header, content_sha256_hdr,
- &canonical_headers, &signed_headers);
- if(ret)
+ result = make_headers(data, hostname, timestamp, provider1,
+ &date_header, content_sha256_hdr,
+ &canonical_headers, &signed_headers);
+ if(result)
goto fail;
- ret = CURLE_OUT_OF_MEMORY;
if(*content_sha256_hdr) {
/* make_headers() needed this without the \r\n for canonicalization */
@@ -540,6 +644,11 @@ CURLcode Curl_output_aws_sigv4(struct Cu
memcpy(date, timestamp, sizeof(date));
date[sizeof(date) - 1] = 0;
+ result = canon_query(data, data->state.up.query, &canonical_query);
+ if(result)
+ goto fail;
+ result = CURLE_OUT_OF_MEMORY;
+
canonical_request =
curl_maprintf("%s\n" /* HTTPRequestMethod */
"%s\n" /* CanonicalURI */
@@ -549,7 +658,8 @@ CURLcode Curl_output_aws_sigv4(struct Cu
"%.*s", /* HashedRequestPayload in hex */
method,
data->state.up.path,
- data->state.up.query ? data->state.up.query : "",
+ Curl_dyn_ptr(&canonical_query) ?
+ Curl_dyn_ptr(&canonical_query) : "",
Curl_dyn_ptr(&canonical_headers),
Curl_dyn_ptr(&signed_headers),
(int)payload_hash_len, payload_hash);
@@ -628,9 +738,10 @@ CURLcode Curl_output_aws_sigv4(struct Cu
Curl_safefree(data->state.aptr.userpwd);
data->state.aptr.userpwd = auth_headers;
data->state.authhost.done = TRUE;
- ret = CURLE_OK;
+ result = CURLE_OK;
fail:
+ Curl_dyn_free(&canonical_query);
Curl_dyn_free(&canonical_headers);
Curl_dyn_free(&signed_headers);
free(canonical_request);
@@ -639,7 +750,7 @@ fail:
free(str_to_sign);
free(secret);
free(date_header);
- return ret;
+ return result;
}
#endif /* !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH) */