aws_sigv4: fall back to UNSIGNED-PAYLOAD for sign_as_s3
Resolves: RHEL-156838
This commit is contained in:
parent
7459c58dc7
commit
b888f76c77
214
0044-curl-7.76.1-aws-sigv4-s3-unsigned-payload.patch
Normal file
214
0044-curl-7.76.1-aws-sigv4-s3-unsigned-payload.patch
Normal file
@ -0,0 +1,214 @@
|
||||
From 0000000000000000000000000000000000000000 Mon Sep 17 00:00:00 2001
|
||||
From: Casey Bodley <cbodley@redhat.com>
|
||||
Date: Wed, 1 Mar 2023 00:00:00 +0000
|
||||
Subject: [PATCH] aws_sigv4: fall back to UNSIGNED-PAYLOAD for sign_as_s3
|
||||
|
||||
Backport of upstream PR #9995 to curl 7.76.1.
|
||||
|
||||
AWS S3 requires special handling for request signing:
|
||||
- Adds x-amz-content-sha256 header to all S3 requests
|
||||
- Uses UNSIGNED-PAYLOAD for requests with unknown payload size
|
||||
- Calculates real checksums for known payloads (GET, HEAD, POST with
|
||||
postfields)
|
||||
|
||||
This fixes S3 signing issues where curl would fail to properly sign
|
||||
requests when using CURLAUTH_AWS_SIGV4 with aws:s3.
|
||||
|
||||
Original commits by Casey Bodley:
|
||||
- aws_sigv4: add service detection for sign_as_s3
|
||||
- aws_sigv4: add required x-amz-content-sha256 header
|
||||
- aws_sigv4: use UNSIGNED-PAYLOAD for s3 requests with unknown payload
|
||||
|
||||
Signed-off-by: Casey Bodley <cbodley@redhat.com>
|
||||
---
|
||||
lib/http_aws_sigv4.c | 133 ++++++++++++++++++++++++++++++++++++++-----
|
||||
1 file changed, 119 insertions(+), 14 deletions(-)
|
||||
|
||||
--- a/lib/http_aws_sigv4.c
|
||||
+++ b/lib/http_aws_sigv4.c
|
||||
@@ -66,6 +66,60 @@ static void sha256_to_hex(char *dst, un
|
||||
}
|
||||
}
|
||||
|
||||
+/* S3's UNSIGNED-PAYLOAD magic string for requests with unknown payload */
|
||||
+#define S3_UNSIGNED_PAYLOAD "UNSIGNED-PAYLOAD"
|
||||
+
|
||||
+/*
|
||||
+ * calc_s3_payload_hash: Calculate payload hash for S3 requests
|
||||
+ *
|
||||
+ * For S3 (AWS), we need to handle payload hashing specially:
|
||||
+ * - GET/HEAD requests: hash of empty payload
|
||||
+ * - POST with known data: hash of the post data
|
||||
+ * - Other requests (uploads with unknown size): UNSIGNED-PAYLOAD
|
||||
+ *
|
||||
+ * This also formats the required x-amz-content-sha256 header.
|
||||
+ */
|
||||
+static CURLcode calc_s3_payload_hash(struct Curl_easy *data,
|
||||
+ Curl_HttpReq httpreq,
|
||||
+ const char *post_data,
|
||||
+ unsigned char *sha_hash,
|
||||
+ char *sha_hex,
|
||||
+ size_t sha_hex_len,
|
||||
+ char **content_sha256_hdr)
|
||||
+{
|
||||
+ bool empty_method = (httpreq == HTTPREQ_GET || httpreq == HTTPREQ_HEAD);
|
||||
+ bool empty_payload = (empty_method || data->set.filesize == 0);
|
||||
+ bool post_payload = (httpreq == HTTPREQ_POST && data->set.postfields);
|
||||
+ const char *payload_hash;
|
||||
+
|
||||
+ if(empty_payload || post_payload) {
|
||||
+ /* Calculate a real hash when we know the request payload */
|
||||
+ size_t post_data_len = 0;
|
||||
+ if(post_data) {
|
||||
+ if(data->set.postfieldsize < 0)
|
||||
+ post_data_len = strlen(post_data);
|
||||
+ else
|
||||
+ post_data_len = (size_t)data->set.postfieldsize;
|
||||
+ }
|
||||
+ /* Curl_sha256it is void in curl 7.76.1 */
|
||||
+ Curl_sha256it(sha_hash, (const unsigned char *)post_data,
|
||||
+ post_data_len);
|
||||
+ sha256_to_hex(sha_hex, sha_hash, sha_hex_len);
|
||||
+ payload_hash = sha_hex;
|
||||
+ }
|
||||
+ else {
|
||||
+ /* Fall back to S3's UNSIGNED-PAYLOAD for unknown payloads */
|
||||
+ payload_hash = S3_UNSIGNED_PAYLOAD;
|
||||
+ curl_msnprintf(sha_hex, sha_hex_len, "%s", S3_UNSIGNED_PAYLOAD);
|
||||
+ }
|
||||
+
|
||||
+ /* Format the required x-amz-content-sha256 header */
|
||||
+ *content_sha256_hdr = curl_maprintf("x-amz-content-sha256:%s",
|
||||
+ payload_hash);
|
||||
+
|
||||
+ return *content_sha256_hdr ? CURLE_OK : CURLE_OUT_OF_MEMORY;
|
||||
+}
|
||||
+
|
||||
CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
|
||||
{
|
||||
CURLcode ret = CURLE_OUT_OF_MEMORY;
|
||||
@@ -79,6 +133,8 @@ CURLcode Curl_output_aws_sigv4(struct C
|
||||
char *provider1_mid = NULL;
|
||||
char *region = NULL;
|
||||
char *service = NULL;
|
||||
+ bool sign_as_s3 = false;
|
||||
+ char *content_sha256_hdr = NULL;
|
||||
const char *hostname = conn->host.name;
|
||||
#ifdef DEBUGBUILD
|
||||
char *force_timestamp;
|
||||
@@ -230,6 +286,13 @@ CURLcode Curl_output_aws_sigv4(struct C
|
||||
}
|
||||
}
|
||||
|
||||
+ /* Get HTTP method early - needed for S3 payload hash calculation */
|
||||
+ Curl_http_method(data, conn, &method, &httpreq);
|
||||
+
|
||||
+ /* AWS S3 requires special handling with x-amz-content-sha256 header */
|
||||
+ sign_as_s3 = (Curl_strcasecompare(provider0_low, "aws") &&
|
||||
+ Curl_strcasecompare(service, "s3"));
|
||||
+
|
||||
#ifdef DEBUGBUILD
|
||||
force_timestamp = getenv("CURL_FORCETIME");
|
||||
if(force_timestamp)
|
||||
@@ -249,6 +312,20 @@ CURLcode Curl_output_aws_sigv4(struct C
|
||||
memcpy(date, timestamp, sizeof(date));
|
||||
date[sizeof(date) - 1] = 0;
|
||||
|
||||
+ /* Calculate payload hash - special handling for S3 */
|
||||
+ if(sign_as_s3) {
|
||||
+ ret = calc_s3_payload_hash(data, httpreq, post_data, sha_hash, sha_hex,
|
||||
+ sizeof(sha_hex), &content_sha256_hdr);
|
||||
+ if(ret != CURLE_OK)
|
||||
+ goto fail;
|
||||
+ }
|
||||
+ else {
|
||||
+ /* Non-S3: just hash the post data */
|
||||
+ Curl_sha256it(sha_hash,
|
||||
+ (const unsigned char *) post_data, strlen(post_data));
|
||||
+ sha256_to_hex(sha_hex, sha_hash, sizeof(sha_hex));
|
||||
+ }
|
||||
+
|
||||
if(content_type) {
|
||||
content_type = strchr(content_type, ':');
|
||||
if(!content_type) {
|
||||
@@ -260,33 +337,53 @@ CURLcode Curl_output_aws_sigv4(struct C
|
||||
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);
|
||||
+ if(sign_as_s3) {
|
||||
+ canonical_headers = curl_maprintf("content-type:%s\n"
|
||||
+ "host:%s\n"
|
||||
+ "%s\n"
|
||||
+ "x-%s-date:%s\n",
|
||||
+ content_type,
|
||||
+ hostname,
|
||||
+ content_sha256_hdr,
|
||||
+ provider1_low, timestamp);
|
||||
+ signed_headers = curl_maprintf("content-type;host;x-amz-content-sha256;"
|
||||
+ "x-%s-date", provider1_low);
|
||||
+ }
|
||||
+ else {
|
||||
+ 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(sign_as_s3) {
|
||||
+ canonical_headers = curl_maprintf("host:%s\n"
|
||||
+ "%s\n"
|
||||
+ "x-%s-date:%s\n",
|
||||
+ hostname,
|
||||
+ content_sha256_hdr,
|
||||
+ provider1_low, timestamp);
|
||||
+ signed_headers = curl_maprintf("host;x-amz-content-sha256;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;
|
||||
}
|
||||
|
||||
- Curl_sha256it(sha_hash,
|
||||
- (const unsigned char *) post_data, strlen(post_data));
|
||||
- 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 */
|
||||
@@ -388,6 +485,7 @@ fail:
|
||||
free(credential_scope);
|
||||
free(str_to_sign);
|
||||
free(secret);
|
||||
+ free(content_sha256_hdr);
|
||||
return ret;
|
||||
}
|
||||
|
||||
@ -1,7 +1,7 @@
|
||||
Summary: A utility for getting files from remote servers (FTP, HTTP, and others)
|
||||
Name: curl
|
||||
Version: 7.76.1
|
||||
Release: 40%{?dist}
|
||||
Release: 41%{?dist}
|
||||
License: MIT
|
||||
Source: https://curl.se/download/%{name}-%{version}.tar.xz
|
||||
|
||||
@ -131,6 +131,9 @@ Patch042: 0042-curl-7.76.1-respect-system-crypto-policy.patch
|
||||
# http: fix crash in rate-limited upload
|
||||
Patch043: 0043-curl-7.76.1-http-fix-crash-in-rate-limited-upload.patch
|
||||
|
||||
# aws_sigv4: fall back to UNSIGNED-PAYLOAD for sign_as_s3
|
||||
Patch044: 0044-curl-7.76.1-aws-sigv4-s3-unsigned-payload.patch
|
||||
|
||||
# patch making libcurl multilib ready
|
||||
Patch101: 0101-curl-7.32.0-multilib.patch
|
||||
|
||||
@ -348,6 +351,7 @@ be installed.
|
||||
%patch -P 41 -p1
|
||||
%patch -P 42 -p1
|
||||
%patch -P 43 -p1
|
||||
%patch -P 44 -p1
|
||||
|
||||
# Fedora patches
|
||||
%patch -P 101 -p1
|
||||
@ -573,6 +577,9 @@ rm -f ${RPM_BUILD_ROOT}%{_libdir}/libcurl.la
|
||||
%{_libdir}/libcurl.so.4.[0-9].[0-9].minimal
|
||||
|
||||
%changelog
|
||||
* Mon Mar 23 2026 Jacek Migacz <jmigacz@redhat.com> - 7.76.1-41
|
||||
- aws_sigv4: fall back to UNSIGNED-PAYLOAD for sign_as_s3
|
||||
|
||||
* Wed Jan 21 2026 Jacek Migacz <jmigacz@redhat.com> - 7.76.1-40
|
||||
- openssl: fix libssh compatibility by preserving original SSL_CTX behavior (RHEL-134721)
|
||||
|
||||
|
||||
Loading…
Reference in New Issue
Block a user