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
This commit is contained in:
parent
e92edfbef6
commit
fc76a24c53
@ -46,12 +46,12 @@
|
|||||||
|
|
||||||
#define HMAC_SHA256(k, kl, d, dl, o) \
|
#define HMAC_SHA256(k, kl, d, dl, o) \
|
||||||
do { \
|
do { \
|
||||||
ret = Curl_hmacit(Curl_HMAC_SHA256, \
|
result = Curl_hmacit(Curl_HMAC_SHA256, \
|
||||||
(unsigned char *)k, \
|
(unsigned char *)k, \
|
||||||
kl, \
|
kl, \
|
||||||
(unsigned char *)d, \
|
(unsigned char *)d, \
|
||||||
dl, o); \
|
dl, o); \
|
||||||
if(ret) { \
|
if(result) { \
|
||||||
goto fail; \
|
goto fail; \
|
||||||
} \
|
} \
|
||||||
} while(0)
|
} while(0)
|
||||||
@ -369,9 +369,109 @@ fail:
|
|||||||
return ret;
|
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 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;
|
struct connectdata *conn = data->conn;
|
||||||
size_t len;
|
size_t len;
|
||||||
const char *arg;
|
const char *arg;
|
||||||
@ -387,6 +487,7 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
|
|||||||
char date[9];
|
char date[9];
|
||||||
struct dynbuf canonical_headers;
|
struct dynbuf canonical_headers;
|
||||||
struct dynbuf signed_headers;
|
struct dynbuf signed_headers;
|
||||||
|
struct dynbuf canonical_query;
|
||||||
char *date_header = NULL;
|
char *date_header = NULL;
|
||||||
Curl_HttpReq httpreq;
|
Curl_HttpReq httpreq;
|
||||||
const char *method = NULL;
|
const char *method = NULL;
|
||||||
@ -415,6 +516,7 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
|
|||||||
|
|
||||||
/* we init those buffers here, so goto fail will free initialized dynbuf */
|
/* 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_headers, CURL_MAX_HTTP_HEADER);
|
||||||
|
Curl_dyn_init(&canonical_query, CURL_MAX_HTTP_HEADER);
|
||||||
Curl_dyn_init(&signed_headers, CURL_MAX_HTTP_HEADER);
|
Curl_dyn_init(&signed_headers, CURL_MAX_HTTP_HEADER);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -437,8 +539,8 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
|
|||||||
":%" MAX_SIGV4_LEN_TXT "s",
|
":%" MAX_SIGV4_LEN_TXT "s",
|
||||||
provider0, provider1, region, service);
|
provider0, provider1, region, service);
|
||||||
if(!provider0[0]) {
|
if(!provider0[0]) {
|
||||||
failf(data, "first provider can't be empty");
|
failf(data, "first aws-sigv4 provider can't be empty");
|
||||||
ret = CURLE_BAD_FUNCTION_ARGUMENT;
|
result = CURLE_BAD_FUNCTION_ARGUMENT;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
else if(!provider1[0])
|
else if(!provider1[0])
|
||||||
@ -447,35 +549,38 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
|
|||||||
if(!service[0]) {
|
if(!service[0]) {
|
||||||
char *hostdot = strchr(hostname, '.');
|
char *hostdot = strchr(hostname, '.');
|
||||||
if(!hostdot) {
|
if(!hostdot) {
|
||||||
failf(data, "service missing in parameters and hostname");
|
failf(data, "aws-sigv4: service missing in parameters and hostname");
|
||||||
ret = CURLE_URL_MALFORMAT;
|
result = CURLE_URL_MALFORMAT;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
len = hostdot - hostname;
|
len = hostdot - hostname;
|
||||||
if(len > MAX_SIGV4_LEN) {
|
if(len > MAX_SIGV4_LEN) {
|
||||||
failf(data, "service too long in hostname");
|
failf(data, "aws-sigv4: service too long in hostname");
|
||||||
ret = CURLE_URL_MALFORMAT;
|
result = CURLE_URL_MALFORMAT;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
strncpy(service, hostname, len);
|
strncpy(service, hostname, len);
|
||||||
service[len] = '\0';
|
service[len] = '\0';
|
||||||
|
|
||||||
|
infof(data, "aws_sigv4: picked service %s from host", service);
|
||||||
|
|
||||||
if(!region[0]) {
|
if(!region[0]) {
|
||||||
const char *reg = hostdot + 1;
|
const char *reg = hostdot + 1;
|
||||||
const char *hostreg = strchr(reg, '.');
|
const char *hostreg = strchr(reg, '.');
|
||||||
if(!hostreg) {
|
if(!hostreg) {
|
||||||
failf(data, "region missing in parameters and hostname");
|
failf(data, "aws-sigv4: region missing in parameters and hostname");
|
||||||
ret = CURLE_URL_MALFORMAT;
|
result = CURLE_URL_MALFORMAT;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
len = hostreg - reg;
|
len = hostreg - reg;
|
||||||
if(len > MAX_SIGV4_LEN) {
|
if(len > MAX_SIGV4_LEN) {
|
||||||
failf(data, "region too long in hostname");
|
failf(data, "aws-sigv4: region too long in hostname");
|
||||||
ret = CURLE_URL_MALFORMAT;
|
result = CURLE_URL_MALFORMAT;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
strncpy(region, reg, len);
|
strncpy(region, reg, len);
|
||||||
region[len] = '\0';
|
region[len] = '\0';
|
||||||
|
infof(data, "aws_sigv4: picked region %s from host", region);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -490,11 +595,11 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
|
|||||||
|
|
||||||
if(!payload_hash) {
|
if(!payload_hash) {
|
||||||
if(sign_as_s3)
|
if(sign_as_s3)
|
||||||
ret = calc_s3_payload_hash(data, httpreq, provider1, sha_hash,
|
result = calc_s3_payload_hash(data, httpreq, provider1, sha_hash,
|
||||||
sha_hex, content_sha256_hdr);
|
sha_hex, content_sha256_hdr);
|
||||||
else
|
else
|
||||||
ret = calc_payload_hash(data, sha_hash, sha_hex);
|
result = calc_payload_hash(data, sha_hash, sha_hex);
|
||||||
if(ret)
|
if(result)
|
||||||
goto fail;
|
goto fail;
|
||||||
|
|
||||||
payload_hash = sha_hex;
|
payload_hash = sha_hex;
|
||||||
@ -513,21 +618,20 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
|
|||||||
#else
|
#else
|
||||||
time(&clock);
|
time(&clock);
|
||||||
#endif
|
#endif
|
||||||
ret = Curl_gmtime(clock, &tm);
|
result = Curl_gmtime(clock, &tm);
|
||||||
if(ret) {
|
if(result) {
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
if(!strftime(timestamp, sizeof(timestamp), "%Y%m%dT%H%M%SZ", &tm)) {
|
if(!strftime(timestamp, sizeof(timestamp), "%Y%m%dT%H%M%SZ", &tm)) {
|
||||||
ret = CURLE_OUT_OF_MEMORY;
|
result = CURLE_OUT_OF_MEMORY;
|
||||||
goto fail;
|
goto fail;
|
||||||
}
|
}
|
||||||
|
|
||||||
ret = make_headers(data, hostname, timestamp, provider1,
|
result = make_headers(data, hostname, timestamp, provider1,
|
||||||
&date_header, content_sha256_hdr,
|
&date_header, content_sha256_hdr,
|
||||||
&canonical_headers, &signed_headers);
|
&canonical_headers, &signed_headers);
|
||||||
if(ret)
|
if(result)
|
||||||
goto fail;
|
goto fail;
|
||||||
ret = CURLE_OUT_OF_MEMORY;
|
|
||||||
|
|
||||||
if(*content_sha256_hdr) {
|
if(*content_sha256_hdr) {
|
||||||
/* make_headers() needed this without the \r\n for canonicalization */
|
/* make_headers() needed this without the \r\n for canonicalization */
|
||||||
@ -539,6 +643,11 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
|
|||||||
memcpy(date, timestamp, sizeof(date));
|
memcpy(date, timestamp, sizeof(date));
|
||||||
date[sizeof(date) - 1] = 0;
|
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 =
|
canonical_request =
|
||||||
curl_maprintf("%s\n" /* HTTPRequestMethod */
|
curl_maprintf("%s\n" /* HTTPRequestMethod */
|
||||||
"%s\n" /* CanonicalURI */
|
"%s\n" /* CanonicalURI */
|
||||||
@ -548,7 +657,8 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
|
|||||||
"%.*s", /* HashedRequestPayload in hex */
|
"%.*s", /* HashedRequestPayload in hex */
|
||||||
method,
|
method,
|
||||||
data->state.up.path,
|
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(&canonical_headers),
|
||||||
Curl_dyn_ptr(&signed_headers),
|
Curl_dyn_ptr(&signed_headers),
|
||||||
(int)payload_hash_len, payload_hash);
|
(int)payload_hash_len, payload_hash);
|
||||||
@ -632,9 +742,10 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
|
|||||||
Curl_safefree(data->state.aptr.userpwd);
|
Curl_safefree(data->state.aptr.userpwd);
|
||||||
data->state.aptr.userpwd = auth_headers;
|
data->state.aptr.userpwd = auth_headers;
|
||||||
data->state.authhost.done = TRUE;
|
data->state.authhost.done = TRUE;
|
||||||
ret = CURLE_OK;
|
result = CURLE_OK;
|
||||||
|
|
||||||
fail:
|
fail:
|
||||||
|
Curl_dyn_free(&canonical_query);
|
||||||
Curl_dyn_free(&canonical_headers);
|
Curl_dyn_free(&canonical_headers);
|
||||||
Curl_dyn_free(&signed_headers);
|
Curl_dyn_free(&signed_headers);
|
||||||
free(canonical_request);
|
free(canonical_request);
|
||||||
@ -643,7 +754,7 @@ fail:
|
|||||||
free(str_to_sign);
|
free(str_to_sign);
|
||||||
free(secret);
|
free(secret);
|
||||||
free(date_header);
|
free(date_header);
|
||||||
return ret;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
#endif /* !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_AWS) */
|
#endif /* !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_AWS) */
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user