diff --git a/curl-aws_sigv4-url-encode-the-canonical-path.patch b/curl-aws_sigv4-url-encode-the-canonical-path.patch new file mode 100644 index 0000000..981b8e5 --- /dev/null +++ b/curl-aws_sigv4-url-encode-the-canonical-path.patch @@ -0,0 +1,295 @@ +From 768909d89cb3089f96fb495b13e636ecf0742e3d Mon Sep 17 00:00:00 2001 +From: Matthias Gatto +Date: Mon, 27 May 2024 14:58:11 +0200 +Subject: [PATCH] aws-sigv4: url encode the canonical path + +Refactors canon_query, so it could use the encoding part of the function +to use it in the path. + +As the path doesn't encode '/', but encode '=', I had to add some +conditions to know If I was doing the query or path encoding. + +Also, instead of adding a `bool in_path` variable, I use `bool +*found_equals` to know if the function was called for the query or path, +as found_equals is used only in query_encoding. + +Test 472 verifies. + +Reported-by: Alexander Shtuchkin +Fixes #13754 +Closes #13814 + +Signed-off-by: Matthias Gatto + +Index: curl-8.6.0/lib/http_aws_sigv4.c +=================================================================== +--- curl-8.6.0.orig/lib/http_aws_sigv4.c ++++ curl-8.6.0/lib/http_aws_sigv4.c +@@ -426,6 +426,76 @@ static int compare_func(const void *a, c + + #define MAX_QUERYPAIRS 64 + ++/** ++ * found_equals have a double meaning, ++ * detect if an equal have been found when called from canon_query, ++ * and mark that this function is called to compute the path, ++ * if found_equals is NULL. ++ */ ++static CURLcode canon_string(const char *q, size_t len, ++ struct dynbuf *dq, bool *found_equals) ++{ ++ CURLcode result = CURLE_OK; ++ ++ for(; len && !result; q++, len--) { ++ if(ISALNUM(*q)) ++ result = Curl_dyn_addn(dq, q, 1); ++ else { ++ switch(*q) { ++ 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; ++ len -= 2; ++ } ++ else ++ /* '%' without a following two-digit hex, encode it */ ++ result = Curl_dyn_addn(dq, "%25", 3); ++ break; ++ default: { ++ const char hex[] = "0123456789ABCDEF"; ++ char out[3]={'%'}; ++ ++ if(!found_equals) { ++ /* if found_equals is NULL assuming, been in path */ ++ if(*q == '/') { ++ /* allowed as if */ ++ result = Curl_dyn_addn(dq, q, 1); ++ break; ++ } ++ } ++ else { ++ /* allowed as-is */ ++ if(*q == '=') { ++ result = Curl_dyn_addn(dq, q, 1); ++ *found_equals = true; ++ break; ++ } ++ } ++ /* URL encode */ ++ out[1] = hex[((unsigned char)*q)>>4]; ++ out[2] = hex[*q & 0xf]; ++ result = Curl_dyn_addn(dq, out, 3); ++ break; ++ } ++ } ++ } ++ } ++ return result; ++} ++ ++ + static CURLcode canon_query(struct Curl_easy *data, + const char *query, struct dynbuf *dq) + { +@@ -463,54 +533,11 @@ static CURLcode canon_query(struct Curl_ + + ap = &array[0]; + for(i = 0; !result && (i < entry); i++, ap++) { +- size_t len; + const char *q = ap->p; + bool found_equals = false; + if(!ap->len) + continue; +- 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 '~': +- /* allowed as-is */ +- result = Curl_dyn_addn(dq, q, 1); +- break; +- case '=': +- /* allowed as-is */ +- result = Curl_dyn_addn(dq, q, 1); +- found_equals = true; +- 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; +- len -= 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; +- } +- } +- } +- } ++ result = canon_string(q, ap->len, dq, &found_equals); + if(!result && !found_equals) { + /* queries without value still need an equals */ + result = Curl_dyn_addn(dq, "=", 1); +@@ -543,6 +570,7 @@ CURLcode Curl_output_aws_sigv4(struct Cu + struct dynbuf canonical_headers; + struct dynbuf signed_headers; + struct dynbuf canonical_query; ++ struct dynbuf canonical_path; + char *date_header = NULL; + Curl_HttpReq httpreq; + const char *method = NULL; +@@ -573,6 +601,7 @@ CURLcode Curl_output_aws_sigv4(struct Cu + 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(&canonical_path, CURL_MAX_HTTP_HEADER); + + /* + * Parameters parsing +@@ -701,6 +730,11 @@ CURLcode Curl_output_aws_sigv4(struct Cu + result = canon_query(data, data->state.up.query, &canonical_query); + if(result) + goto fail; ++ ++ result = canon_string(data->state.up.path, strlen(data->state.up.path), ++ &canonical_path, NULL); ++ if(result) ++ goto fail; + result = CURLE_OUT_OF_MEMORY; + + canonical_request = +@@ -711,7 +745,7 @@ CURLcode Curl_output_aws_sigv4(struct Cu + "%s\n" /* SignedHeaders */ + "%.*s", /* HashedRequestPayload in hex */ + method, +- data->state.up.path, ++ Curl_dyn_ptr(&canonical_path), + Curl_dyn_ptr(&canonical_query) ? + Curl_dyn_ptr(&canonical_query) : "", + Curl_dyn_ptr(&canonical_headers), +@@ -803,6 +837,7 @@ CURLcode Curl_output_aws_sigv4(struct Cu + + fail: + Curl_dyn_free(&canonical_query); ++ Curl_dyn_free(&canonical_path); + Curl_dyn_free(&canonical_headers); + Curl_dyn_free(&signed_headers); + free(canonical_request); +Index: curl-8.6.0/tests/data/Makefile.inc +=================================================================== +--- curl-8.6.0.orig/tests/data/Makefile.inc ++++ curl-8.6.0/tests/data/Makefile.inc +@@ -73,7 +73,7 @@ test426 test427 test428 test429 test430 + test435 test436 test437 test438 test439 test440 test441 test442 test443 \ + test444 test445 test446 test447 test448 test449 test450 test451 test452 \ + test453 test454 test455 test456 test457 test458 test459 test460 test461 \ +-\ ++test472 \ + test490 test491 test492 test493 test494 test495 test496 test497 test498 \ + \ + test500 test501 test502 test503 test504 test505 test506 test507 test508 \ +Index: curl-8.6.0/tests/data/test472 +=================================================================== +--- /dev/null ++++ curl-8.6.0/tests/data/test472 +@@ -0,0 +1,59 @@ ++ ++ ++ ++HTTP ++aws-sigv4 ++ ++ ++ ++# ++# Server-side ++ ++ ++HTTP/1.1 200 OK ++Date: Tue, 09 Nov 2010 14:49:00 GMT ++Server: test-server/fake ++Last-Modified: Tue, 13 Jun 2000 12:10:00 GMT ++ETag: "21025-dc7-39462498" ++Accept-Ranges: bytes ++Content-Length: 6 ++Connection: close ++Content-Type: text/html ++Funny-head: yesyes ++ ++-foo- ++ ++ ++ ++# ++# Client-side ++ ++ ++http ++ ++ ++debug ++Unicode ++ ++ ++aws-sigv4 with query ++ ++ ++"http://fake.fake.fake:8000/%TESTNUMBER/a=あ" -u user:secret --aws-sigv4 "aws:amz:us-east-2:es" --connect-to fake.fake.fake:8000:%HOSTIP:%HTTPPORT ++ ++ ++ ++# ++# Verify data after the test has been "shot" ++ ++ ++GET /472/a=%e3%81%82 HTTP/1.1 ++Host: fake.fake.fake:8000 ++Authorization: AWS4-HMAC-SHA256 Credential=user/19700101/us-east-2/es/aws4_request, SignedHeaders=host;x-amz-date, Signature=c63315c199922f7ee00141869a250389405d19e205057249fb74726d940b1fc3 ++X-Amz-Date: 19700101T000000Z ++User-Agent: curl/%VERSION ++Accept: */* ++ ++ ++ ++ +Index: curl-8.6.0/tests/data/Makefile.in +=================================================================== +--- curl-8.6.0.orig/tests/data/Makefile.in ++++ curl-8.6.0/tests/data/Makefile.in +@@ -445,7 +445,7 @@ test426 test427 test428 test429 test430 + test435 test436 test437 test438 test439 test440 test441 test442 test443 \ + test444 test445 test446 test447 test448 test449 test450 test451 test452 \ + test453 test454 test455 test456 test457 test458 test459 test460 test461 \ +-\ ++test472 \ + test490 test491 test492 test493 test494 test495 test496 test497 test498 \ + \ + test500 test501 test502 test503 test504 test505 test506 test507 test508 \ diff --git a/curl.changes b/curl.changes index 1ae0db2..0b89685 100644 --- a/curl.changes +++ b/curl.changes @@ -1,3 +1,11 @@ +------------------------------------------------------------------- +Tue Sep 17 10:15:48 UTC 2024 - Pedro Monreal + +- Make special characters in URL work with aws-sigv4 [bsc#1230516] + * aws-sigv4: url encode the canonical path [768909d8] + * Add upstream patch: + - curl-aws_sigv4-url-encode-the-canonical-path.patch + ------------------------------------------------------------------- Wed Sep 4 09:04:55 UTC 2024 - Pedro Monreal diff --git a/curl.spec b/curl.spec index 535a9fd..58d0556 100644 --- a/curl.spec +++ b/curl.spec @@ -51,6 +51,8 @@ Patch9: curl-CVE-2024-6197.patch Patch10: curl-CVE-2024-7264.patch #PATCH-FIX-UPSTREAM bsc#1230093 CVE-2024-8096 OCSP stapling bypass with GnuTLS Patch11: curl-CVE-2024-8096.patch +#PATCH-FIX-UPSTREAM bsc#1230516 Make special characters in URL work with aws-sigv4 +Patch12: curl-aws_sigv4-url-encode-the-canonical-path.patch BuildRequires: libtool BuildRequires: pkgconfig Requires: libcurl4 = %{version}