Add AWS Signature Version 4 authentication support

Resolves: RHEL-116183
This commit is contained in:
Jacek Migacz 2025-12-03 13:22:19 +01:00
parent d5448f07fe
commit 66cf34993a
2 changed files with 996 additions and 1 deletions

View File

@ -0,0 +1,988 @@
diff -Naur curl-7.61.1.orig/lib/http_aws_sigv4.c curl-7.61.1/lib/http_aws_sigv4.c
--- curl-7.61.1.orig/lib/http_aws_sigv4.c 1970-01-01 01:00:00.000000000 +0100
+++ curl-7.61.1/lib/http_aws_sigv4.c 2025-12-02 13:31:38.991776478 +0100
@@ -0,0 +1,511 @@
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2021, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * This software is licensed as described in the file COPYING, which
+ * you should have received as part of this distribution. The terms
+ * are also available at https://curl.haxx.se/docs/copyright.html.
+ *
+ * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+ * copies of the Software, and permit persons to whom the Software is
+ * furnished to do so, under the terms of the COPYING file.
+ *
+ * This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
+ * KIND, either express or implied.
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH)
+
+#define USE_AWS_SIGV4
+
+#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 "curl_hmac.h"
+#include "warnless.h"
+#include "transfer.h"
+
+#include "strcase.h"
+#include "parsedate.h"
+#include "sendf.h"
+
+#include <time.h>
+
+/* The last 3 #include files should be in this order */
+#include "curl_printf.h"
+#include "curl_memory.h"
+#include "memdebug.h"
+
+/* SHA256 Init/Update/Final function pointers for HMAC */
+#if defined(USE_OPENSSL)
+#include <openssl/sha.h>
+#include <openssl/opensslv.h>
+#if (OPENSSL_VERSION_NUMBER >= 0x0090800fL)
+#define USE_OPENSSL_SHA256
+#endif
+#endif
+
+#ifdef USE_OPENSSL_SHA256
+static void SHA256_Init_Wrapper(void *context)
+{
+ SHA256_Init((SHA256_CTX *)context);
+}
+
+static void SHA256_Update_Wrapper(void *context,
+ const unsigned char *data,
+ unsigned int len)
+{
+ SHA256_Update((SHA256_CTX *)context, data, len);
+}
+
+static void SHA256_Final_Wrapper(unsigned char *result, void *context)
+{
+ SHA256_Final(result, (SHA256_CTX *)context);
+}
+#else
+/* Use internal SHA256 implementation */
+typedef struct sha256_state SHA256_CTX;
+
+static void SHA256_Init_Wrapper(void *context);
+static int SHA256_Update_Wrapper_Internal(void *context,
+ const unsigned char *data,
+ unsigned int len);
+static int SHA256_Final_Wrapper_Internal(unsigned char *result,
+ void *context);
+
+static void SHA256_Update_Wrapper(void *context,
+ const unsigned char *data,
+ unsigned int len)
+{
+ SHA256_Update_Wrapper_Internal(context, data, len);
+}
+
+static void SHA256_Final_Wrapper(unsigned char *result, void *context)
+{
+ SHA256_Final_Wrapper_Internal(result, context);
+}
+
+/* Forward declarations for internal SHA256 functions */
+extern void SHA256_Init(SHA256_CTX *ctx);
+extern int SHA256_Update(SHA256_CTX *ctx, const unsigned char *data,
+ unsigned int len);
+extern int SHA256_Final(unsigned char *out, SHA256_CTX *ctx);
+
+static void SHA256_Init_Wrapper(void *context)
+{
+ SHA256_Init((SHA256_CTX *)context);
+}
+
+static int SHA256_Update_Wrapper_Internal(void *context,
+ const unsigned char *data,
+ unsigned int len)
+{
+ return SHA256_Update((SHA256_CTX *)context, data, len);
+}
+
+static int SHA256_Final_Wrapper_Internal(unsigned char *result,
+ void *context)
+{
+ return SHA256_Final(result, (SHA256_CTX *)context);
+}
+#endif
+
+/* HMAC-SHA256 parameters for curl 7.61.1 */
+static const HMAC_params Curl_HMAC_SHA256[] = {
+ {
+ CURLX_FUNCTION_CAST(HMAC_hinit_func, SHA256_Init_Wrapper),
+ CURLX_FUNCTION_CAST(HMAC_hupdate_func, SHA256_Update_Wrapper),
+ CURLX_FUNCTION_CAST(HMAC_hfinal_func, SHA256_Final_Wrapper),
+ sizeof(SHA256_CTX),
+ 64, /* Maximum key length (block size) */
+ 32 /* Result length (SHA256 produces 32 bytes) */
+ }
+};
+
+#define HMAC_SHA256(k, kl, d, dl, o) \
+ do { \
+ HMAC_context *hctx = Curl_HMAC_init(Curl_HMAC_SHA256, \
+ (unsigned char *)k, \
+ (unsigned int)kl); \
+ if(!hctx) { \
+ ret = CURLE_OUT_OF_MEMORY; \
+ goto fail; \
+ } \
+ Curl_HMAC_update(hctx, \
+ (unsigned char *)d, \
+ (unsigned int)dl); \
+ Curl_HMAC_final(hctx, o); \
+ } while(0)
+
+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]);
+ }
+}
+
+CURLcode Curl_output_aws_sigv4(struct connectdata *conn, bool proxy)
+{
+ CURLcode ret = CURLE_OUT_OF_MEMORY;
+ struct Curl_easy *data = conn->data;
+ 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 *hostname = conn->host.name;
+#ifdef DEBUGBUILD
+ char *force_timestamp;
+#endif
+ time_t clock;
+ struct tm tm;
+ char timestamp[17];
+ char date[9];
+ const char *content_type = Curl_checkheaders(conn, "Content-Type");
+ char *canonical_headers = NULL;
+ char *signed_headers = NULL;
+ Curl_HttpReq httpreq;
+ const char *method = NULL;
+ const char *post_data = data->set.postfields ? data->set.postfields : "";
+ unsigned char sha_hash[32];
+ char sha_hex[65];
+ char *canonical_request = NULL;
+ char *request_type = NULL;
+ 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};
+ char *auth_headers = NULL;
+
+ DEBUGASSERT(!proxy);
+ (void)proxy;
+
+ if(Curl_checkheaders(conn, "Authorization")) {
+ /* Authorization already present, Bailing out */
+ return CURLE_OK;
+ }
+
+ /*
+ * Parameters parsing
+ * Google and Outscale use the same OSC or GOOG,
+ * but Amazon uses AWS and AMZ for header arguments.
+ * 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] ?
+ 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\n");
+ 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\n");
+ 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\n");
+ ret = CURLE_BAD_FUNCTION_ARGUMENT;
+ goto fail;
+ }
+ region = Curl_memdup(tmp0, len + 1);
+ if(!region) {
+ goto fail;
+ }
+ region[len] = '\0';
+
+ if(tmp1) {
+ tmp0 = tmp1 + 1;
+ service = strdup(tmp0);
+ if(!service) {
+ goto fail;
+ }
+ if(strlen(service) < 1) {
+ infof(data, "service can't be empty\n");
+ 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\n");
+ ret = CURLE_URL_MALFORMAT;
+ goto fail;
+ }
+ service = Curl_memdup(tmp0, len + 1);
+ if(!service) {
+ goto fail;
+ }
+ 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\n");
+ ret = CURLE_URL_MALFORMAT;
+ goto fail;
+ }
+ region = Curl_memdup(tmp0, len + 1);
+ if(!region) {
+ goto fail;
+ }
+ region[len] = '\0';
+ }
+ }
+
+#ifdef DEBUGBUILD
+ 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) {
+ goto fail;
+ }
+ if(!strftime(timestamp, sizeof(timestamp), "%Y%m%dT%H%M%SZ", &tm)) {
+ goto fail;
+ }
+ 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;
+ }
+
+ Curl_sha256it(sha_hash,
+ (const unsigned char *) post_data, strlen(post_data));
+ sha256_to_hex(sha_hex, sha_hash, sizeof(sha_hex));
+
+ /* Determine HTTP method - curl 7.61.1 style */
+ httpreq = data->set.httpreq;
+ switch(httpreq) {
+ case HTTPREQ_GET:
+ method = "GET";
+ break;
+ case HTTPREQ_POST:
+ case HTTPREQ_POST_FORM:
+ case HTTPREQ_POST_MIME:
+ method = "POST";
+ break;
+ case HTTPREQ_PUT:
+ method = "PUT";
+ break;
+ case HTTPREQ_HEAD:
+ method = "HEAD";
+ break;
+ case HTTPREQ_OPTIONS:
+ method = "OPTIONS";
+ break;
+ case HTTPREQ_CUSTOM:
+ method = data->set.customrequest;
+ break;
+ default:
+ method = "GET";
+ break;
+ }
+
+ 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;
+ }
+
+ request_type = curl_maprintf("%s4_request", provider0_low);
+ if(!request_type) {
+ goto fail;
+ }
+
+ credential_scope = curl_maprintf("%s/%s/%s/%s",
+ date, region, service, request_type);
+ if(!credential_scope) {
+ goto fail;
+ }
+
+ Curl_sha256it(sha_hash, (unsigned char *) canonical_request,
+ strlen(canonical_request));
+ sha256_to_hex(sha_hex, sha_hash, sizeof(sha_hex));
+
+ /*
+ * Google allow to use rsa key instead of HMAC, so this code might change
+ * In the furure, but for now we support only HMAC version
+ */
+ str_to_sign = curl_maprintf("%s4-HMAC-SHA256\n" /* Algorithm */
+ "%s\n" /* RequestDateTime */
+ "%s\n" /* CredentialScope */
+ "%s", /* HashedCanonicalRequest in hex */
+ provider0_up,
+ timestamp,
+ credential_scope,
+ sha_hex);
+ if(!str_to_sign) {
+ goto fail;
+ }
+
+ secret = curl_maprintf("%s4%s", provider0_up, 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);
+
+ sha256_to_hex(sha_hex, tmp_sign0, sizeof(sha_hex));
+
+ 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,
+ user,
+ credential_scope,
+ signed_headers,
+ sha_hex,
+ provider1_mid,
+ timestamp);
+ if(!auth_headers) {
+ goto fail;
+ }
+
+ Curl_safefree(data->state.aptr.userpwd);
+ data->state.aptr.userpwd = auth_headers;
+ data->state.authhost.done = TRUE;
+ 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);
+ free(canonical_request);
+ free(request_type);
+ free(credential_scope);
+ free(str_to_sign);
+ free(secret);
+ return ret;
+}
+
+#endif /* !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH) */
diff -Naur curl-7.61.1.orig/lib/http_aws_sigv4.h curl-7.61.1/lib/http_aws_sigv4.h
--- curl-7.61.1.orig/lib/http_aws_sigv4.h 1970-01-01 01:00:00.000000000 +0100
+++ curl-7.61.1/lib/http_aws_sigv4.h 2025-12-02 13:31:38.992776500 +0100
@@ -0,0 +1,29 @@
+#ifndef HEADER_CURL_HTTP_AWS_SIGV4_H
+#define HEADER_CURL_HTTP_AWS_SIGV4_H
+/***************************************************************************
+ * _ _ ____ _
+ * Project ___| | | | _ \| |
+ * / __| | | | |_) | |
+ * | (__| |_| | _ <| |___
+ * \___|\___/|_| \_\_____|
+ *
+ * Copyright (C) 1998 - 2024, Daniel Stenberg, <daniel@haxx.se>, et al.
+ *
+ * AWS SigV4 Support - Backport to RHEL 8
+ *
+ ***************************************************************************/
+
+#include "curl_setup.h"
+
+#ifdef USE_AWS_SIGV4
+
+/* AWS SigV4 authentication function */
+CURLcode Curl_output_aws_sigv4(struct connectdata *conn, bool proxy);
+
+#else
+
+#define Curl_output_aws_sigv4(x,y) CURLE_NOT_BUILT_IN
+
+#endif /* USE_AWS_SIGV4 */
+
+#endif /* HEADER_CURL_HTTP_AWS_SIGV4_H */
diff -Naur curl-7.61.1.orig/lib/Makefile.inc curl-7.61.1/lib/Makefile.inc
--- curl-7.61.1.orig/lib/Makefile.inc 2025-12-02 13:31:37.675747831 +0100
+++ curl-7.61.1/lib/Makefile.inc 2025-12-02 13:31:40.198802751 +0100
@@ -54,7 +54,7 @@
curl_multibyte.c hostcheck.c conncache.c pipeline.c dotdot.c \
x509asn1.c http2.c smb.c curl_endian.c curl_des.c system_win32.c \
mime.c sha256.c setopt.c curl_path.c curl_ctype.c curl_range.c psl.c \
- urlapi.c
+ urlapi.c http_aws_sigv4.c
LIB_HFILES = arpa_telnet.h netrc.h file.h timeval.h hostip.h progress.h \
formdata.h cookie.h http.h sendf.h ftp.h url.h dict.h if2ip.h \
@@ -74,7 +74,8 @@
curl_setup_once.h multihandle.h setup-vms.h pipeline.h dotdot.h \
x509asn1.h http2.h sigpipe.h smb.h curl_endian.h curl_des.h \
curl_printf.h system_win32.h rand.h mime.h curl_sha256.h setopt.h \
- curl_path.h curl_ctype.h curl_range.h psl.h urlapi-int.h
+ curl_path.h curl_ctype.h curl_range.h psl.h urlapi-int.h \
+ http_aws_sigv4.h
LIB_RCFILES = libcurl.rc
diff -Naur curl-7.61.1.orig/lib/openldap.c curl-7.61.1/lib/openldap.c
--- curl-7.61.1.orig/lib/openldap.c 2025-12-02 13:31:37.681747962 +0100
+++ curl-7.61.1/lib/openldap.c 2025-12-02 13:31:41.175824017 +0100
@@ -79,8 +79,8 @@
static CURLcode ldap_setup_connection(struct connectdata *conn);
static CURLcode ldap_do(struct connectdata *conn, bool *done);
static CURLcode ldap_done(struct connectdata *conn, CURLcode, bool);
-static CURLcode ldap_connect(struct connectdata *conn, bool *done);
-static CURLcode ldap_connecting(struct connectdata *conn, bool *done);
+static CURLcode oldap_connect(struct connectdata *conn, bool *done);
+static CURLcode ooldap_connecting(struct connectdata *conn, bool *done);
static CURLcode ldap_disconnect(struct connectdata *conn, bool dead);
static Curl_recv ldap_recv;
@@ -95,8 +95,8 @@
ldap_do, /* do_it */
ldap_done, /* done */
ZERO_NULL, /* do_more */
- ldap_connect, /* connect_it */
- ldap_connecting, /* connecting */
+ oldap_connect, /* connect_it */
+ ooldap_connecting, /* connecting */
ZERO_NULL, /* doing */
ZERO_NULL, /* proto_getsock */
ZERO_NULL, /* doing_getsock */
@@ -121,8 +121,8 @@
ldap_do, /* do_it */
ldap_done, /* done */
ZERO_NULL, /* do_more */
- ldap_connect, /* connect_it */
- ldap_connecting, /* connecting */
+ oldap_connect, /* connect_it */
+ ooldap_connecting, /* connecting */
ZERO_NULL, /* doing */
ZERO_NULL, /* proto_getsock */
ZERO_NULL, /* doing_getsock */
@@ -206,7 +206,7 @@
static Sockbuf_IO ldapsb_tls;
#endif
-static CURLcode ldap_connect(struct connectdata *conn, bool *done)
+static CURLcode oldap_connect(struct connectdata *conn, bool *done)
{
ldapconninfo *li = conn->proto.generic;
struct Curl_easy *data = conn->data;
@@ -253,7 +253,7 @@
return CURLE_OK;
}
-static CURLcode ldap_connecting(struct connectdata *conn, bool *done)
+static CURLcode ooldap_connecting(struct connectdata *conn, bool *done)
{
ldapconninfo *li = conn->proto.generic;
struct Curl_easy *data = conn->data;
diff -Naur curl-7.61.1.orig/lib/http.c curl-7.61.1/lib/http.c
--- curl-7.61.1.orig/lib/http.c 2025-12-05 12:00:00.000000000 +0100
+++ curl-7.61.1/lib/http.c 2025-12-05 12:30:00.000000000 +0100
@@ -60,6 +60,7 @@
#include "http_ntlm.h"
#include "curl_ntlm_wb.h"
#include "http_negotiate.h"
+#include "http_aws_sigv4.h"
#include "url.h"
#include "share.h"
#include "hostip.h"
@@ -362,6 +363,8 @@
pick->picked = CURLAUTH_NTLM_WB;
else if(avail & CURLAUTH_BASIC)
pick->picked = CURLAUTH_BASIC;
+ else if(avail & CURLAUTH_AWS_SIGV4)
+ pick->picked = CURLAUTH_AWS_SIGV4;
else {
pick->picked = CURLAUTH_PICKNONE; /* we select to use nothing */
picked = FALSE;
@@ -682,6 +685,21 @@
functions work that way */
authstatus->done = TRUE;
}
+ if(authstatus->picked == CURLAUTH_AWS_SIGV4) {
+ /* AWS SigV4 */
+ if((!proxy && data->set.str[STRING_AWS_SIGV4] &&
+ !Curl_checkheaders(conn, "Authorization:"))) {
+ auth = "AWS_SIGV4";
+ result = Curl_output_aws_sigv4(conn, FALSE);
+ if(result)
+ return result;
+ }
+
+ /* NOTE: this function should set 'done' TRUE, as the other auth
+ functions work that way */
+ authstatus->done = TRUE;
+ }
+
if(auth) {
infof(data, "%s auth using %s with user '%s'\n",
diff -Naur curl-7.61.1.orig/include/curl/curl.h curl-7.61.1/include/curl/curl.h
--- curl-7.61.1.orig/include/curl/curl.h 2018-07-11 07:17:00.000000000 +0200
+++ curl-7.61.1/include/curl/curl.h 2025-12-10 14:00:00.000000000 +0100
@@ -710,6 +710,7 @@
#define CURLAUTH_NTLM (((unsigned long)1)<<3)
#define CURLAUTH_DIGEST_IE (((unsigned long)1)<<4)
#define CURLAUTH_NTLM_WB (((unsigned long)1)<<5)
+#define CURLAUTH_AWS_SIGV4 (((unsigned long)1)<<7)
#define CURLAUTH_BEARER (((unsigned long)1)<<6)
#define CURLAUTH_ONLY (((unsigned long)1)<<31)
#define CURLAUTH_ANY (~CURLAUTH_DIGEST_IE)
@@ -1856,6 +1857,9 @@
/* Disallow specifying username/login in URL. */
CINIT(DISALLOW_USERNAME_IN_URL, LONG, 278),
+ /* AWS HTTP V4 Signature */
+ CINIT(AWS_SIGV4, STRINGPOINT, 279),
+
CURLOPT_LASTENTRY /* the last unused */
} CURLoption;
--- curl-7.61.1.orig/lib/urldata.h 2018-07-11 07:17:00.000000000 +0200
+++ curl-7.61.1/lib/urldata.h 2025-12-09 00:00:00.000000000 +0100
@@ -1414,6 +1414,7 @@
STRING_TLSAUTH_PASSWORD_PROXY, /* TLS auth <password> */
#endif
STRING_BEARER, /* <bearer>, if used */
+ STRING_AWS_SIGV4, /* <aws-sigv4>, if used */
#ifdef USE_UNIX_SOCKETS
STRING_UNIX_SOCKET_PATH, /* path to Unix socket, if used */
#endif
diff -Naur curl-7.61.1.orig/lib/http_aws_sigv4.c curl-7.61.1/lib/http_aws_sigv4.c
--- curl-7.61.1.orig/lib/http_aws_sigv4.c 2025-12-09 07:00:00.000000000 +0100
+++ curl-7.61.1/lib/http_aws_sigv4.c 2025-12-09 14:30:00.000000000 +0100
@@ -192,8 +192,8 @@
char *request_type = NULL;
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 : "";
+ const char *user = conn->user ? conn->user : "";
+ const char *passwd = conn->passwd ? conn->passwd : "";
char *secret = NULL;
unsigned char tmp_sign0[32] = {0};
unsigned char tmp_sign1[32] = {0};
@@ -399,7 +399,7 @@
method = "OPTIONS";
break;
case HTTPREQ_CUSTOM:
- method = data->set.customrequest;
+ method = data->set.str[STRING_CUSTOMREQUEST];
break;
default:
method = "GET";
@@ -406,6 +406,16 @@
break;
}
+ /* Extract query string from path if present */
+ const char *query_str = NULL;
+ char *question_mark = strchr(data->state.path, '?');
+ if(question_mark) {
+ query_str = question_mark + 1;
+ }
+ else {
+ query_str = "";
+ }
+
canonical_request =
curl_maprintf("%s\n" /* HTTPRequestMethod */
"%s\n" /* CanonicalURI */
@@ -414,8 +424,8 @@
"%s\n" /* SignedHeaders */
"%s", /* HashedRequestPayload in hex */
method,
- data->state.up.path,
- data->state.up.query ? data->state.up.query : "",
+ data->state.path,
+ query_str,
canonical_headers,
signed_headers,
sha_hex);
@@ -488,8 +498,8 @@
goto fail;
}
- Curl_safefree(data->state.aptr.userpwd);
- data->state.aptr.userpwd = auth_headers;
+ Curl_safefree(conn->allocptr.userpwd);
+ conn->allocptr.userpwd = auth_headers;
data->state.authhost.done = TRUE;
ret = CURLE_OK;
@@ -509,3 +519,5 @@
free(secret);
return ret;
}
+
+#endif /* !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_CRYPTO_AUTH) */
diff -Naur a/docs/libcurl/symbols-in-versions b/docs/libcurl/symbols-in-versions
--- a/docs/libcurl/symbols-in-versions 2018-09-04 22:48:37.000000000 +0200
+++ b/docs/libcurl/symbols-in-versions 2025-12-10 14:23:34.178991689 +0100
@@ -15,6 +15,7 @@
CURLAUTH_ANY 7.10.6
CURLAUTH_ANYSAFE 7.10.6
CURLAUTH_BASIC 7.10.6
+CURLAUTH_AWS_SIGV4 7.61.1
CURLAUTH_BEARER 7.61.0
CURLAUTH_DIGEST 7.10.6
CURLAUTH_DIGEST_IE 7.19.3
@@ -344,6 +345,7 @@
CURLOPT_ACCEPT_ENCODING 7.21.6
CURLOPT_ADDRESS_SCOPE 7.19.0
CURLOPT_APPEND 7.17.0
+CURLOPT_AWS_SIGV4 7.61.1
CURLOPT_AUTOREFERER 7.1
CURLOPT_BUFFERSIZE 7.10
CURLOPT_CAINFO 7.4.2
diff -Naur a/lib/setopt.c b/lib/setopt.c
--- a/lib/setopt.c 2025-12-02 13:31:37.682747984 +0100
+++ b/lib/setopt.c 2025-12-10 12:46:02.532544111 +0100
@@ -618,6 +618,20 @@
data->set.httpreq = HTTPREQ_POST_FORM;
data->set.opt_no_body = FALSE; /* this is implied */
break;
+
+ case CURLOPT_AWS_SIGV4:
+ /*
+ * String that is merged to some authentication
+ * parameters are used by the algorithm.
+ */
+ result = Curl_setstropt(&data->set.str[STRING_AWS_SIGV4],
+ va_arg(param, char *));
+ /*
+ * Basic been set by default it need to be unset here
+ */
+ if(data->set.str[STRING_AWS_SIGV4])
+ data->set.httpauth = CURLAUTH_AWS_SIGV4;
+ break;
#endif /* CURL_DISABLE_HTTP */
case CURLOPT_MIMEPOST:
diff -Naur a/src/tool_cfgable.c b/src/tool_cfgable.c
--- a/src/tool_cfgable.c 2025-12-02 13:31:37.707011250 +0100
+++ b/src/tool_cfgable.c 2025-12-10 12:33:54.247212425 +0100
@@ -130,6 +130,7 @@
Curl_safefree(config->krblevel);
Curl_safefree(config->oauth_bearer);
+ Curl_safefree(config->aws_sigv4);
Curl_safefree(config->unix_socket_path);
Curl_safefree(config->writeout);
diff -Naur a/src/tool_cfgable.h b/src/tool_cfgable.h
--- a/src/tool_cfgable.h 2025-12-02 13:31:37.707011250 +0100
+++ b/src/tool_cfgable.h 2025-12-10 12:33:38.542315604 +0100
@@ -240,6 +240,7 @@
bool test_event_based;
#endif
char *oauth_bearer; /* OAuth 2.0 bearer token */
+ char *aws_sigv4; /* AWS Signature Version 4 */
bool nonpn; /* enable/disable TLS NPN extension */
bool noalpn; /* enable/disable TLS ALPN extension */
char *unix_socket_path; /* path to Unix domain socket */
diff -Naur a/src/tool_getparam.c b/src/tool_getparam.c
--- a/src/tool_getparam.c 2025-12-02 13:31:37.711748615 +0100
+++ b/src/tool_getparam.c 2025-12-10 12:36:24.374195746 +0100
@@ -79,6 +79,7 @@
{"*a", "random-file", ARG_FILENAME},
{"*b", "egd-file", ARG_STRING},
{"*B", "oauth2-bearer", ARG_STRING},
+ {"*V", "aws-sigv4", ARG_STRING},
{"*c", "connect-timeout", ARG_STRING},
{"*d", "ciphers", ARG_STRING},
{"*D", "dns-interface", ARG_STRING},
@@ -813,6 +814,10 @@
config->disable_eprt = toggle;
break;
case 'Z': /* --eprt */
+ case 'V': /* --aws-sigv4 */
+ GetStr(&config->aws_sigv4, nextarg);
+ config->authtype |= CURLAUTH_AWS_SIGV4;
+ break;
config->disable_eprt = (!toggle)?TRUE:FALSE;
break;
case '~': /* --xattr */
diff -Naur a/src/tool_help.c b/src/tool_help.c
--- a/src/tool_help.c 2025-12-02 13:31:37.709748572 +0100
+++ b/src/tool_help.c 2025-12-10 12:39:33.200888878 +0100
@@ -52,6 +52,8 @@
"Pick any authentication method"},
{"-a, --append",
"Append to target file when uploading"},
+ {" --aws-sigv4 <provider1[:provider2[:region[:service]]]>",
+ "Use AWS V4 signature authentication"},
{" --basic",
"Use HTTP Basic Authentication"},
{" --cacert <file>",
diff -Naur a/src/tool_operate.c b/src/tool_operate.c
--- a/src/tool_operate.c 2025-12-02 13:31:37.711748615 +0100
+++ b/src/tool_operate.c 2025-12-10 12:40:10.466140413 +0100
@@ -1136,6 +1136,8 @@
my_setopt_str(curl, CURLOPT_SSLKEYTYPE, config->key_type);
my_setopt_str(curl, CURLOPT_PROXY_SSLKEYTYPE,
config->proxy_key_type);
+ my_setopt_str(curl, CURLOPT_AWS_SIGV4,
+ config->aws_sigv4);
if(config->insecure_ok) {
my_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);
diff -Naur a/docs/curl.1 b/docs/curl.1
--- a/docs/curl.1 2018-09-05 08:15:48.000000000 +0200
+++ b/docs/curl.1 2025-12-10 12:00:00.000000000 +0100
@@ -163,6 +163,12 @@
See also \fI--proxy-anyauth\fP and \fI--basic\fP and \fI--digest\fP.
.IP "-a, --append"
(FTP SFTP) When used in an upload, this makes curl append to the target file instead of
+.IP "--aws-sigv4 <service:region>"
+(HTTP) Use AWS Signature Version 4 authentication for the specified service and region.
+The service is the AWS service name (such as s3) and the region is the geographical
+AWS region (such as us-east-1). The credentials must be provided via \fI-u, --user\fP.
+
+If this option is used several times, the last one will be used.
overwriting it. If the remote file doesn't exist, it will be created. Note
that this flag is ignored by some SFTP servers (including OpenSSH).
.IP "--basic"
diff -Naur a/docs/libcurl/curl_easy_setopt.3 b/docs/libcurl/curl_easy_setopt.3
--- a/docs/libcurl/curl_easy_setopt.3 2018-09-05 08:15:48.000000000 +0200
+++ b/docs/libcurl/curl_easy_setopt.3 2025-12-10 12:00:00.000000000 +0100
@@ -262,6 +262,8 @@
.IP CURLOPT_DISALLOW_USERNAME_IN_URL
Don't allow username in URL. See \fICURLOPT_DISALLOW_USERNAME_IN_URL(3)\fP
.SH HTTP OPTIONS
+.IP CURLOPT_AWS_SIGV4
+Set AWS Signature Version 4 authentication. See \fICURLOPT_AWS_SIGV4(3)\fP
.IP CURLOPT_AUTOREFERER
Automatically set Referer: header. See \fICURLOPT_AUTOREFERER(3)\fP
.IP CURLOPT_ACCEPT_ENCODING
diff -Naur a/docs/libcurl/opts/CURLOPT_AWS_SIGV4.3 b/docs/libcurl/opts/CURLOPT_AWS_SIGV4.3
--- a/docs/libcurl/opts/CURLOPT_AWS_SIGV4.3 1970-01-01 00:00:00.000000000 +0000
+++ b/docs/libcurl/opts/CURLOPT_AWS_SIGV4.3 2025-12-10 12:00:00.000000000 +0100
@@ -0,0 +1,78 @@
+.\" **************************************************************************
+.\" * _ _ ____ _
+.\" * Project ___| | | | _ \| |
+.\" * / __| | | | |_) | |
+.\" * | (__| |_| | _ <| |___
+.\" * \___\|\___/|_| \_\_____|
+.\" *
+.\" * Copyright (C) 1998 - 2018, Daniel Stenberg, <daniel@haxx.se>, et al.
+.\" *
+.\" * This software is licensed as described in the file COPYING, which
+.\" * you should have received as part of this distribution. The terms
+.\" * are also available at https://curl.haxx.se/docs/copyright.html.
+.\" *
+.\" * You may opt to use, copy, modify, merge, publish, distribute and/or sell
+.\" * copies of the Software, and permit persons to whom the Software is
+.\" * furnished to do so, under the terms of the COPYING file.
+.\" *
+.\" * This software is distributed on an AS IS basis, WITHOUT WARRANTY OF ANY
+.\" * KIND, either express or implied.
+.\" *
+.\" **************************************************************************
+.\"
+.TH CURLOPT_AWS_SIGV4 3 "December 10, 2025" "libcurl 7.61.1" "curl_easy_setopt options"
+
+.SH NAME
+CURLOPT_AWS_SIGV4 \- set AWS Signature Version 4 authentication
+.SH SYNOPSIS
+.nf
+#include <curl/curl.h>
+
+CURLcode curl_easy_setopt(CURL *handle, CURLOPT_AWS_SIGV4, char *param);
+.SH DESCRIPTION
+Pass a char * that is the collection of service and region parameters that
+are used to authorize and sign requests.
+
+The format is:
+.B service:region
+where the
+.B service
+is the AWS service (for example,
+.B s3
+) and the
+.B region
+is the geographical AWS region (for example,
+.B us-east-1
+).
+
+When this option is set, the HTTP request will be signed using AWS Signature
+Version 4. The request is signed using the credentials provided in the
+\fICURLOPT_USERNAME(3)\fP and \fICURLOPT_PASSWORD(3)\fP options, which
+correspond to the AWS Access Key ID and Secret Access Key, respectively.
+
+The application does not have to keep the string around after setting this
+option.
+.SH DEFAULT
+NULL
+.SH PROTOCOLS
+HTTP
+.SH EXAMPLE
+.nf
+CURL *curl = curl_easy_init();
+if(curl) {
+ curl_easy_setopt(curl, CURLOPT_URL,
+ "https://s3.us-east-1.amazonaws.com/bucket/file");
+ curl_easy_setopt(curl, CURLOPT_AWS_SIGV4, "s3:us-east-1");
+ curl_easy_setopt(curl, CURLOPT_USERNAME, "AKIAIOSFODNN7EXAMPLE");
+ curl_easy_setopt(curl, CURLOPT_PASSWORD,
+ "wJalrXUtnFEMI/K7MDENG/bPxRfiCYEXAMPLEKEY");
+ curl_easy_perform(curl);
+}
+.SH AVAILABILITY
+Added in 7.61.1
+.SH RETURN VALUE
+Returns CURLE_OK if the option is supported, and CURLE_UNKNOWN_OPTION if not.
+.SH "SEE ALSO"
+.BR CURLOPT_HTTPAUTH "(3), "
+.BR CURLOPT_USERNAME "(3), "
+.BR CURLOPT_PASSWORD "(3), "

View File

@ -1,7 +1,7 @@
Summary: A utility for getting files from remote servers (FTP, HTTP, and others)
Name: curl
Version: 7.61.1
Release: 34%{?dist}.9
Release: 34%{?dist}.10
License: MIT
Source: https://curl.haxx.se/download/%{name}-%{version}.tar.xz
@ -199,6 +199,9 @@ Patch67: 0067-curl-7.61.1-ntlm-force-http-1-1.patch
# cookie: don't treat the leading slash as trailing (CVE-2025-9086)
Patch68: 0068-curl-7.61.1-CVE-2025-9086.patch
# AWS Signature Version 4 authentication support
Patch69: 0069-curl-7.61.1-aws-sigv4.patch
# patch making libcurl multilib ready
Patch101: 0101-curl-7.32.0-multilib.patch
@ -440,6 +443,7 @@ git apply %{PATCH52}
%patch -P 66 -p1
%patch -P 67 -p1
%patch -P 68 -p1
%patch -P 69 -p1
# make tests/*.py use Python 3
sed -e '1 s|^#!/.*python|#!%{__python3}|' -i tests/*.py
@ -602,6 +606,9 @@ rm -f ${RPM_BUILD_ROOT}%{_libdir}/libcurl.la
%{_libdir}/libcurl.so.4.[0-9].[0-9].minimal
%changelog
* Wed Dec 03 2025 Jacek Migacz <jmigacz@redhat.com> - 7.61.1-34.el8_10.10
- AWS Signature Version 4 authentication support (RHEL-116183)
* Fri Oct 24 2025 Jacek Migacz <jmigacz@redhat.com> - 7.61.1-34.el8_10.9
- cookie: don't treat the leading slash as trailing (CVE-2025-9086)
Resolves: RHEL-121655