http: improve AWS HTTP v4 Signature auth

- Add support services without region and service prefixes in
the URL endpoint (ex. Min.IO, GCP, Yandex Cloud, Mail.Ru Cloud Solutions, etc)
by providing region and service parameters via aws-sigv4 option.
- Add [:region[:service]] suffix to aws-sigv4 option;
- Fix memory allocation errors.
- Refactor memory management.
- Use Curl_http_method instead() STRING_CUSTOMREQUEST.
- Refactor canonical headers generating.
- Remove repeated sha256_to_hex() usage.
- Add some docs fixes.
- Add some codestyle fixes.
- Add overloaded strndup() for debug - curl_dbg_strndup().
- Update tests.

Closes #6524
This commit is contained in:
Dmitry Wagin 2021-01-25 17:02:09 +03:00 committed by Daniel Stenberg
parent 3c2210713e
commit 796ce293de
No known key found for this signature in database
GPG Key ID: 5CC908FDB71E12C2
21 changed files with 740 additions and 255 deletions

View File

@ -1,5 +1,5 @@
Long: aws-sigv4
Arg: <provider1[:provider2]>
Arg: <provider1[:provider2[:region[:service]]]>
Help: Use AWS V4 signature authentication
Category: auth http
Added: 7.75.0
@ -8,3 +8,10 @@ Use AWS V4 signature authentication in the transfer.
The provider argument is a string that is used by the algorithm when creating
outgoing authentication headers.
The region argument is a string that points to a geographic area of
a resources collection (region-code) when the region name is omitted from
the endpoint.
The service argument is a string that points to a function provided by a cloud
(service-code) when the service name is omitted from the endpoint.

View File

@ -20,37 +20,47 @@
.\" *
.\" **************************************************************************
.\"
.TH CURLOPT_AWS_SIGV4 3 "03 Jun 2020" "libcurl 7.72.0" "curl_easy_setopt options"
.TH CURLOPT_AWS_SIGV4 3 "03 Jun 2020" "libcurl 7.75.0" "curl_easy_setopt options"
.SH NAME
CURLOPT_AWS_SIGV4 \- V4 signature
.SH SYNOPSIS
.nf
#include <curl/curl.h>
CURLcode curl_easy_setopt(CURL *handle, CURLOPT_AWS_SIGV4,
char *providers_infos);
CURLcode curl_easy_setopt(CURL *handle, CURLOPT_AWS_SIGV4, char *param);
.fi
.SH DESCRIPTION
provides AWS V4 signature authentication on HTTPS header
The provider argument is a string that is merged to some authentication
parameters use by the algorithm.
It's used by "Algorithm", "date", "request type", "signed headers" arguments,
NOTE: This call set CURLOPT_HTTPAUTH to CURLAUTH_AWS_SIGV4.
Calling CURLOPT_HTTPAUTH with CURLAUTH_AWS_SIGV4 is the same as calling
this with "aws:amz" in paramater.
Example with "Test:Try", when curl will do the algorithm, it will Generate:
"TEST-HMAC-SHA256" for "Algorithm"
"x-try-date" and "X-Try-Date" for "date"
"test4_request" for "request type"
Provides AWS V4 signature authentication on HTTP(S) header.
.PP
Pass a char * that is the collection of specific arguments are used for
creating outgoing authentication headers.
The format of the param option is:
.IP provider1[:provider2[:region[:service]]]
.IP provider1,\ provider2
The providers arguments are used for generating some authentication parameters
such as "Algorithm", "date", "request type" and "signed headers".
.IP region
The argument is a geographic area of a resources collection.
It is extracted from the host name specified in the URL if omitted.
.IP service
The argument is a function provided by a cloud.
It is extracted from the host name specified in the URL if omitted.
.PP
NOTE: This call set \fICURLOPT_HTTPAUTH(3)\fP to CURLAUTH_AWS_SIGV4.
Calling \fICURLOPT_HTTPAUTH(3)\fP with CURLAUTH_AWS_SIGV4 is the same
as calling this with "aws:amz" in parameter.
.PP
Example with "Test:Try", when curl will do the algorithm, it will generate
"TEST-HMAC-SHA256" for "Algorithm", "x-try-date" and "X-Try-Date" for "date",
"test4_request" for "request type",
"SignedHeaders=content-type;host;x-try-date" for "signed headers"
.PP
If you use just "test", instead of "test:try",
test will be use for every strings generated
.SH DEFAULT
NULL
By default, the value of this parameter is NULL.
Calling \fICURLOPT_HTTPAUTH(3)\fP with CURLAUTH_AWS_SIGV4 is the same
as calling this with "aws:amz" in parameter.
.SH PROTOCOLS
HTTP
.SH EXAMPLE
@ -61,22 +71,27 @@ struct curl_slist *list = NULL;
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL,
"https://api_type.region.example.com/uri");
"https://service.region.example.com/uri");
curl_easy_setopt(c, CURLOPT_AWS_SIGV4, "provider1:provider2");
/* service and region also could be set in CURLOPT_AWS_SIGV4 */
/*
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/uri");
curl_easy_setopt(c, CURLOPT_AWS_SIGV4,
"provider1:provider2:region:service");
*/
curl_easy_setopt(c, CURLOPT_AWS_SIGV4, "xxx:yyy");
curl_easy_setopt(c, CURLOPT_USERPWD, "MY_ACCESS_KEY:MY_SECRET_KEY");
curl_easy_perform(curl);
}
.fi
.SH AVAILABILITY
Added in 7.75.0
.SH RETURN VALUE
Returns CURLE_OK if the option is supported, and CURLE_UNKNOWN_OPTION if not.
.SH NOTES
this option overrides the other auth types you might have set in CURL_HTTPAUTH which should be highlighted as this makes this auth method special. It could probably also be mentioned that this method can't be combined with other auth types.
This option overrides the other auth types you might have set in CURL_HTTPAUTH
which should be highlighted as this makes this auth method special.
This method can't be combined with other auth types.
.SH "SEE ALSO"
.BR CURLOPT_HEADEROPT "(3), " CURLOPT_HTTPHEADER "(3), "

View File

@ -787,7 +787,7 @@ typedef enum {
#define CURLAUTH_DIGEST_IE (((unsigned long)1)<<4)
#define CURLAUTH_NTLM_WB (((unsigned long)1)<<5)
#define CURLAUTH_BEARER (((unsigned long)1)<<6)
#define CURLAUTH_AWS_SIGV4 (((unsigned long)1)<<7)
#define CURLAUTH_AWS_SIGV4 (((unsigned long)1)<<7)
#define CURLAUTH_ONLY (((unsigned long)1)<<31)
#define CURLAUTH_ANY (~CURLAUTH_DIGEST_IE)
#define CURLAUTH_ANYSAFE (~(CURLAUTH_BASIC|CURLAUTH_DIGEST_IE))
@ -2075,7 +2075,7 @@ typedef enum {
CURLOPT(CURLOPT_HSTSWRITEFUNCTION, CURLOPTTYPE_FUNCTIONPOINT, 303),
CURLOPT(CURLOPT_HSTSWRITEDATA, CURLOPTTYPE_CBPOINT, 304),
/* Provider for V4 signature */
/* Parameters for V4 signature */
CURLOPT(CURLOPT_AWS_SIGV4, CURLOPTTYPE_STRINGPOINT, 305),
CURLOPT_LASTENTRY /* the last unused */

View File

@ -26,6 +26,7 @@
#include "urldata.h"
#include "strcase.h"
#include "strdup.h"
#include "vauth/vauth.h"
#include "vauth/digest.h"
#include "http_aws_sigv4.h"
@ -43,22 +44,18 @@
#include "curl_memory.h"
#include "memdebug.h"
#define HMAC_SHA256(k, kl, d, dl, o) \
do { \
if(Curl_hmacit(Curl_HMAC_SHA256, (unsigned char *)k, \
(unsigned int)kl, \
(unsigned char *)d, \
(unsigned int)dl, o) != CURLE_OK) { \
ret = CURLE_OUT_OF_MEMORY; \
goto free_all; \
} \
#define HMAC_SHA256(k, kl, d, dl, o) \
do { \
ret = Curl_hmacit(Curl_HMAC_SHA256, \
(unsigned char *)k, \
(unsigned int)kl, \
(unsigned char *)d, \
(unsigned int)dl, o); \
if(ret != CURLE_OK) { \
goto fail; \
} \
} while(0)
#define PROVIDER_MAX_L 16
#define REQUEST_TYPE_L (PROVIDER_MAX_L + sizeof("4_request"))
/* secret key is 40 bytes long + PROVIDER_MAX_L + \0 */
#define FULL_SK_L (PROVIDER_MAX_L + 40 + 1)
static void sha256_to_hex(char *dst, unsigned char *sha, size_t dst_l)
{
int i;
@ -71,46 +68,43 @@ static void sha256_to_hex(char *dst, unsigned char *sha, size_t dst_l)
CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
{
CURLcode ret = CURLE_OK;
char sk[FULL_SK_L] = {0};
const char *customrequest = data->set.str[STRING_CUSTOMREQUEST];
const char *hostname = data->state.up.hostname;
struct tm info;
time_t rawtime;
/* aws is the default because some provider that are not amazone still use
* aws:amz as prefix
*/
const char *provider = data->set.str[STRING_AWS_SIGV4] ?
data->set.str[STRING_AWS_SIGV4] : "aws:amz";
size_t provider_l = strlen(provider);
char low_provider0[PROVIDER_MAX_L + 1] = {0};
char low_provider[PROVIDER_MAX_L + 1] = {0};
char up_provider[PROVIDER_MAX_L + 1] = {0};
char mid_provider[PROVIDER_MAX_L + 1] = {0};
CURLcode ret = CURLE_OUT_OF_MEMORY;
struct connectdata *conn = data->conn;
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 *uri = NULL;
char *api_type = NULL;
char date_iso[17];
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];
char date_str[64];
const char *post_data = data->set.postfields ?
data->set.postfields : "";
const char *content_type = Curl_checkheaders(data, "Content-Type");
unsigned char sha_d[32];
char sha_hex[65];
char *cred_scope = NULL;
char *canonical_headers = NULL;
char *signed_headers = NULL;
char request_type[REQUEST_TYPE_L];
char *canonical_hdr = NULL;
Curl_HttpReq httpreq;
const char *method;
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 = 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};
char *auth = NULL;
char *tmp;
#ifdef DEBUGBUILD
char *force_timestamp;
#endif
char *auth_headers = NULL;
DEBUGASSERT(!proxy);
(void)proxy;
@ -120,214 +114,280 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data, bool proxy)
return CURLE_OK;
}
if(content_type) {
content_type = strchr(content_type, ':');
if(!content_type)
return CURLE_FAILED_INIT;
content_type++;
/* Skip whitespace now */
while(*content_type == ' ' || *content_type == '\t')
++content_type;
/*
* 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';
/* Get Parameter
Google and Outscale use the same OSC or GOOG,
but Amazon use AWS and AMZ for header arguments */
tmp = strchr(provider, ':');
if(tmp) {
provider_l = tmp - provider;
if(provider_l >= PROVIDER_MAX_L) {
infof(data, "v4 signature argument string too long\n");
return CURLE_BAD_FUNCTION_ARGUMENT;
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;
}
Curl_strntolower(low_provider0, provider, provider_l);
Curl_strntoupper(up_provider, provider, provider_l);
provider = tmp + 1;
/* if "xxx:" was pass as parameter, tmp + 1 should point to \0 */
provider_l = strlen(provider);
if(provider_l >= PROVIDER_MAX_L) {
infof(data, "v4 signature argument string too long\n");
return CURLE_BAD_FUNCTION_ARGUMENT;
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;
}
}
}
Curl_strntolower(low_provider, provider, provider_l);
Curl_strntolower(mid_provider, provider, provider_l);
}
else if(provider_l <= PROVIDER_MAX_L) {
Curl_strntolower(low_provider0, provider, provider_l);
Curl_strntolower(low_provider, provider, provider_l);
Curl_strntolower(mid_provider, provider, provider_l);
Curl_strntoupper(up_provider, provider, provider_l);
mid_provider[0] = Curl_raw_toupper(mid_provider[0]);
}
else {
infof(data, "v4 signature argument string too long\n");
return CURLE_BAD_FUNCTION_ARGUMENT;
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)
rawtime = 0;
clock = 0;
else
time(&clock);
#else
time(&clock);
#endif
time(&rawtime);
ret = Curl_gmtime(rawtime, &info);
ret = Curl_gmtime(clock, &tm);
if(ret != CURLE_OK) {
return ret;
goto fail;
}
if(!strftime(date_iso, sizeof(date_iso), "%Y%m%dT%H%M%SZ", &info)) {
return CURLE_OUT_OF_MEMORY;
if(!strftime(timestamp, sizeof(timestamp), "%Y%m%dT%H%M%SZ", &tm)) {
goto fail;
}
memcpy(date, date_iso, sizeof(date));
memcpy(date, timestamp, sizeof(date));
date[sizeof(date) - 1] = 0;
api_type = strdup(hostname);
if(!api_type) {
ret = CURLE_OUT_OF_MEMORY;
goto free_all;
}
tmp = strchr(api_type, '.');
if(!tmp) {
ret = CURLE_URL_MALFORMAT;
goto free_all;
}
*tmp = 0;
/* at worst, *(tmp + 1) is a '\0' */
region = tmp + 1;
tmp = strchr(region, '.');
if(!tmp) {
ret = CURLE_URL_MALFORMAT;
goto free_all;
}
*tmp = 0;
uri = data->state.up.path;
if(!curl_msnprintf(request_type, REQUEST_TYPE_L, "%s4_request",
low_provider0)) {
ret = CURLE_OUT_OF_MEMORY;
goto free_all;
}
cred_scope = curl_maprintf("%s/%s/%s/%s", date, region, api_type,
request_type);
if(!cred_scope) {
ret = CURLE_OUT_OF_MEMORY;
goto free_all;
}
if(content_type) {
canonical_hdr = curl_maprintf(
"content-type:%s\n"
"host:%s\n"
"x-%s-date:%s\n", content_type, hostname, low_provider, date_iso);
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",
low_provider);
}
else if(data->state.up.query) {
canonical_hdr = curl_maprintf(
"host:%s\n"
"x-%s-date:%s\n", hostname, low_provider, date_iso);
signed_headers = curl_maprintf("host;x-%s-date", low_provider);
provider1_low);
}
else {
ret = CURLE_FAILED_INIT;
goto free_all;
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_hdr || !signed_headers) {
ret = CURLE_OUT_OF_MEMORY;
goto free_all;
if(!canonical_headers || !signed_headers) {
goto fail;
}
Curl_sha256it(sha_d, (const unsigned char *)post_data, strlen(post_data));
sha256_to_hex(sha_hex, sha_d, sizeof(sha_hex));
Curl_sha256it(sha_hash,
(const unsigned char *) post_data, strlen(post_data));
sha256_to_hex(sha_hex, sha_hash, sizeof(sha_hex));
canonical_request = curl_maprintf(
"%s\n" /* Method */
"%s\n" /* uri */
"%s\n" /* querystring */
"%s\n" /* canonical_headers */
"%s\n" /* signed header */
"%s" /* SHA ! */,
customrequest, uri,
data->state.up.query ? data->state.up.query : "",
canonical_hdr, signed_headers, sha_hex);
Curl_http_method(data, conn, &method, &httpreq);
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) {
ret = CURLE_OUT_OF_MEMORY;
goto free_all;
goto fail;
}
Curl_sha256it(sha_d, (unsigned char *)canonical_request,
strlen(canonical_request));
sha256_to_hex(sha_hex, sha_d, sizeof(sha_hex));
request_type = curl_maprintf("%s4_request", provider0_low);
if(!request_type) {
goto fail;
}
/* Google allow to use rsa key instead of HMAC, so this code might change
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"
"%s\n%s\n%s",
up_provider, date_iso, cred_scope, sha_hex);
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) {
ret = CURLE_OUT_OF_MEMORY;
goto free_all;
goto fail;
}
curl_msnprintf(sk, sizeof(sk) - 1, "%s4%s", up_provider,
data->set.str[STRING_PASSWORD]);
secret = curl_maprintf("%s4%s", provider0_up, passwd);
if(!secret) {
goto fail;
}
HMAC_SHA256(sk, strlen(sk), date,
strlen(date), tmp_sign0);
sha256_to_hex(sha_hex, tmp_sign0, sizeof(sha_hex));
HMAC_SHA256(tmp_sign0, sizeof(tmp_sign0), region,
strlen(region), tmp_sign1);
HMAC_SHA256(tmp_sign1, sizeof(tmp_sign1), api_type,
strlen(api_type), 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);
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 = curl_maprintf("Authorization: %s4-HMAC-SHA256 Credential=%s/%s, "
"SignedHeaders=%s, Signature=%s",
up_provider, data->set.str[STRING_USERNAME], cred_scope,
signed_headers, sha_hex);
if(!auth) {
ret = CURLE_OUT_OF_MEMORY;
goto free_all;
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_msnprintf(date_str, sizeof(date_str), "X-%s-Date: %s",
mid_provider, date_iso);
data->set.headers = curl_slist_append(data->set.headers, date_str);
if(!data->set.headers) {
ret = CURLE_FAILED_INIT;
goto free_all;
}
data->set.headers = curl_slist_append(data->set.headers, auth);
if(!data->set.headers) {
ret = CURLE_FAILED_INIT;
goto free_all;
}
data->state.authhost.done = 1;
Curl_safefree(data->state.aptr.userpwd);
data->state.aptr.userpwd = auth_headers;
data->state.authhost.done = TRUE;
ret = CURLE_OK;
free_all:
free(canonical_request);
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(canonical_hdr);
free(auth);
free(cred_scope);
free(api_type);
free(secret);
return ret;
}

View File

@ -641,7 +641,8 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
case CURLOPT_AWS_SIGV4:
/*
* String that holds file type of the SSL certificate to use
* 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 *));

View File

@ -1590,7 +1590,7 @@ enum dupstring {
STRING_COPYPOSTFIELDS, /* if POST, set the fields' values here */
STRING_AWS_SIGV4, /* Provider for V4 signature */
STRING_AWS_SIGV4, /* Parameters for V4 signature */
STRING_LAST /* not used, just an end-of-list marker */
};

View File

@ -169,7 +169,7 @@ static void free_config_fields(struct OperationConfig *config)
Curl_safefree(config->ftp_account);
Curl_safefree(config->ftp_alternative_to_user);
Curl_safefree(config->aws_sigv4_provider);
Curl_safefree(config->aws_sigv4);
}
void config_free(struct OperationConfig *config)

View File

@ -284,7 +284,7 @@ struct OperationConfig {
0 is valid. default: CURL_HET_DEFAULT. */
bool haproxy_protocol; /* whether to send HAProxy protocol v1 */
bool disallow_username_in_url; /* disallow usernames in URLs */
char *aws_sigv4_provider;
char *aws_sigv4;
struct GlobalConfig *global;
struct OperationConfig *prev;
struct OperationConfig *next; /* Always last in the struct */

View File

@ -113,7 +113,7 @@ static const struct LongShort aliases[]= {
{"*t", "proxy-ntlm", ARG_BOOL},
{"*u", "crlf", ARG_BOOL},
{"*v", "stderr", ARG_FILENAME},
{"*V", "aws-sigv4", ARG_STRING},
{"*V", "aws-sigv4", ARG_STRING},
{"*w", "interface", ARG_STRING},
{"*x", "krb", ARG_STRING},
{"*x", "krb4", ARG_STRING},
@ -806,8 +806,9 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
case 'V': /* --aws-sigv4 */
config->authtype |= CURLAUTH_AWS_SIGV4;
GetStr(&config->aws_sigv4_provider, nextarg);
GetStr(&config->aws_sigv4, nextarg);
break;
case 'v': /* --stderr */
if(strcmp(nextarg, "-")) {
FILE *newfile = fopen(nextarg, FOPEN_WRITETEXT);

View File

@ -133,7 +133,7 @@ static const struct helptxt helptext[] = {
{"-a, --append",
"Append to target file when uploading",
CURLHELP_FTP | CURLHELP_SFTP},
{" --aws-sigv4 <provider1[:provider2]>",
{" --aws-sigv4 <provider1[:provider2[:region[:service]]]>",
"Use AWS V4 signature authentication",
CURLHELP_AUTH | CURLHELP_HTTP},
{" --basic",

View File

@ -1662,7 +1662,7 @@ static CURLcode single_transfer(struct GlobalConfig *global,
my_setopt_str(curl, CURLOPT_PROXY_SSLKEYTYPE,
config->proxy_key_type);
my_setopt_str(curl, CURLOPT_AWS_SIGV4,
config->aws_sigv4_provider);
config->aws_sigv4);
if(config->insecure_ok) {
my_setopt(curl, CURLOPT_SSL_VERIFYPEER, 0L);

View File

@ -209,7 +209,7 @@ test1800 test1801 \
test1908 test1909 test1910 test1911 test1912 test1913 test1914 test1915 \
test1916 test1917 test1918 \
\
test1933 \
test1933 test1934 test1935 test1936 \
\
test2000 test2001 test2002 test2003 test2004 test2005 test2006 test2007 \
test2008 test2009 test2010 test2011 test2012 test2013 test2014 test2015 \

View File

@ -40,14 +40,14 @@ crypto
</features>
<name>
HTTP AWS_SIGV4
HTTP AWS_SIGV4 with one provider and auth cred via URL
</name>
<tool>
lib1933
</tool>
<command>
http://%HOSTIP:%HTTPPORT/1933/testapi/test
http://xxx:yyy@%HOSTIP:%HTTPPORT/1933/testapi/test
</command>
</client>
@ -61,8 +61,8 @@ http://%HOSTIP:%HTTPPORT/1933/testapi/test
<protocol>
GET /1933/testapi/test HTTP/1.1
Host: %HOSTIP:%HTTPPORT
X-yyy-Date: 19700101T000000Z
Authorization: XXX4-HMAC-SHA256 Credential=xxx/19700101/0/127/xxx4_request, SignedHeaders=content-type;host;x-yyy-date, Signature=b125a904e7f5cc1f553d8f3682947eb003c4ca6c504097c0dc2d9323289bfcdd
Authorization: XXX4-HMAC-SHA256 Credential=xxx/19700101/0/127/xxx4_request, SignedHeaders=content-type;host;x-xxx-date, Signature=d2c2dff48c59ec49dc31ef94f18c5dc1ac3eae2a70d51633a4342dadc0683664
X-Xxx-Date: 19700101T000000Z
</protocol>
</verify>

69
tests/data/test1934 Normal file
View File

@ -0,0 +1,69 @@
<testcase>
<info>
<keywords>
HTTP
CURLOPT_AWS_SIGV4
</keywords>
</info>
# Server-side
<reply>
<data nocheck="yes">
HTTP/1.1 302 OK
Date: Thu, 09 Nov 2010 14:49:00 GMT
Server: test-server/fake
Content-Type: text/html
Content-Length: 0
Location: /19340002
</data>
<data2>
HTTP/1.1 200 OK
Date: Thu, 09 Nov 2010 14:49:00 GMT
Server: test-server/fake
Content-Type: text/html
Content-Length: 0
</data>
</reply>
# Client-side
<client>
<server>
http
</server>
# this relies on the debug feature which allow to set the time
<features>
SSL
debug
crypto
</features>
<name>
HTTP AWS_SIGV4 with two providers
</name>
<tool>
lib1934
</tool>
<command>
http://%HOSTIP:%HTTPPORT/1934/testapi/test
</command>
</client>
# Verify data after the test has been "shot"
<verify>
<strip>
^User-Agent:.*
^Content-Type:.*
^Accept:.*
</strip>
<protocol>
GET /1934/testapi/test HTTP/1.1
Host: %HOSTIP:%HTTPPORT
Authorization: XXX4-HMAC-SHA256 Credential=xxx/19700101/0/127/xxx4_request, SignedHeaders=content-type;host;x-yyy-date, Signature=938937ca7da6bb3dbf15e30928265ec6f061532d035d2afda92fa7cb10feb196
X-Yyy-Date: 19700101T000000Z
</protocol>
</verify>
</testcase>

69
tests/data/test1935 Normal file
View File

@ -0,0 +1,69 @@
<testcase>
<info>
<keywords>
HTTP
CURLOPT_AWS_SIGV4
</keywords>
</info>
# Server-side
<reply>
<data nocheck="yes">
HTTP/1.1 302 OK
Date: Thu, 09 Nov 2010 14:49:00 GMT
Server: test-server/fake
Content-Type: text/html
Content-Length: 0
Location: /19350002
</data>
<data2>
HTTP/1.1 200 OK
Date: Thu, 09 Nov 2010 14:49:00 GMT
Server: test-server/fake
Content-Type: text/html
Content-Length: 0
</data>
</reply>
# Client-side
<client>
<server>
http
</server>
# this relies on the debug feature which allow to set the time
<features>
SSL
debug
crypto
</features>
<name>
HTTP AWS_SIGV4 with two providers and region
</name>
<tool>
lib1935
</tool>
<command>
http://%HOSTIP:%HTTPPORT/1935/testapi/test
</command>
</client>
# Verify data after the test has been "shot"
<verify>
<strip>
^User-Agent:.*
^Content-Type:.*
^Accept:.*
</strip>
<protocol>
GET /1935/testapi/test HTTP/1.1
Host: %HOSTIP:%HTTPPORT
Authorization: XXX4-HMAC-SHA256 Credential=xxx/19700101/rrr/127/xxx4_request, SignedHeaders=content-type;host;x-yyy-date, Signature=240750deb9263d4c8ece71c016f3919b56e518249390ef075740f94ef8df846f
X-Yyy-Date: 19700101T000000Z
</protocol>
</verify>
</testcase>

69
tests/data/test1936 Normal file
View File

@ -0,0 +1,69 @@
<testcase>
<info>
<keywords>
HTTP
CURLOPT_AWS_SIGV4
</keywords>
</info>
# Server-side
<reply>
<data nocheck="yes">
HTTP/1.1 302 OK
Date: Thu, 09 Nov 2010 14:49:00 GMT
Server: test-server/fake
Content-Type: text/html
Content-Length: 0
Location: /19360002
</data>
<data2>
HTTP/1.1 200 OK
Date: Thu, 09 Nov 2010 14:49:00 GMT
Server: test-server/fake
Content-Type: text/html
Content-Length: 0
</data>
</reply>
# Client-side
<client>
<server>
http
</server>
# this relies on the debug feature which allow to set the time
<features>
SSL
debug
crypto
</features>
<name>
HTTP AWS_SIGV4 with two providers, region and service
</name>
<tool>
lib1936
</tool>
<command>
http://%HOSTIP:%HTTPPORT/1936/testapi/test
</command>
</client>
# Verify data after the test has been "shot"
<verify>
<strip>
^User-Agent:.*
^Content-Type:.*
^Accept:.*
</strip>
<protocol>
GET /1936/testapi/test HTTP/1.1
Host: %HOSTIP:%HTTPPORT
Authorization: XXX4-HMAC-SHA256 Credential=xxx/19700101/rrr/sss/xxx4_request, SignedHeaders=content-type;host;x-yyy-date, Signature=f32cf87977cea5d3274b524b53e5d28f4aac54c372f710ae0cc3a9ececaf169f
X-Yyy-Date: 19700101T000000Z
</protocol>
</verify>
</testcase>

View File

@ -60,7 +60,7 @@ noinst_PROGRAMS = chkhostname libauthretry libntlmconnect \
lib1558 lib1559 lib1560 lib1564 lib1565 lib1567 lib1568 \
lib1591 lib1592 lib1593 lib1594 lib1596 \
lib1905 lib1906 lib1907 lib1908 lib1910 lib1911 lib1912 lib1913 \
lib1915 lib1916 lib1917 lib1918 lib1933 \
lib1915 lib1916 lib1917 lib1918 lib1933 lib1934 lib1935 lib1936 \
lib3010
chkdecimalpoint_SOURCES = chkdecimalpoint.c ../../lib/mprintf.c \
@ -675,6 +675,18 @@ lib1933_SOURCES = lib1933.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
lib1933_LDADD = $(TESTUTIL_LIBS)
lib1933_CPPFLAGS = $(AM_CPPFLAGS)
lib1934_SOURCES = lib1934.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
lib1934_LDADD = $(TESTUTIL_LIBS)
lib1934_CPPFLAGS = $(AM_CPPFLAGS)
lib1935_SOURCES = lib1935.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
lib1935_LDADD = $(TESTUTIL_LIBS)
lib1935_CPPFLAGS = $(AM_CPPFLAGS)
lib1936_SOURCES = lib1936.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
lib1936_LDADD = $(TESTUTIL_LIBS)
lib1936_CPPFLAGS = $(AM_CPPFLAGS)
lib3010_SOURCES = lib3010.c $(SUPPORTFILES) $(TESTUTIL) $(WARNLESS)
lib3010_LDADD = $(TESTUTIL_LIBS)
lib3010_CPPFLAGS = $(AM_CPPFLAGS)

View File

@ -42,8 +42,7 @@ int test(char *URL)
}
test_setopt(curl, CURLOPT_VERBOSE, 1L);
test_setopt(curl, CURLOPT_AWS_SIGV4, "xxx:yyy");
test_setopt(curl, CURLOPT_USERPWD, "xxx:yyy");
test_setopt(curl, CURLOPT_AWS_SIGV4, "xxx");
test_setopt(curl, CURLOPT_HEADER, 0L);
test_setopt(curl, CURLOPT_URL, URL);
list = curl_slist_append(list, "Content-Type: application/json");

61
tests/libtest/lib1934.c Normal file
View File

@ -0,0 +1,61 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2020, 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 "test.h"
#include "memdebug.h"
int test(char *URL)
{
CURL *curl;
CURLcode res = TEST_ERR_MAJOR_BAD;
struct curl_slist *list = NULL;
if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
fprintf(stderr, "curl_global_init() failed\n");
return TEST_ERR_MAJOR_BAD;
}
curl = curl_easy_init();
if(!curl) {
fprintf(stderr, "curl_easy_init() failed\n");
curl_global_cleanup();
return TEST_ERR_MAJOR_BAD;
}
test_setopt(curl, CURLOPT_VERBOSE, 1L);
test_setopt(curl, CURLOPT_AWS_SIGV4, "xxx:yyy");
test_setopt(curl, CURLOPT_USERPWD, "xxx:yyy");
test_setopt(curl, CURLOPT_HEADER, 0L);
test_setopt(curl, CURLOPT_URL, URL);
list = curl_slist_append(list, "Content-Type: application/json");
test_setopt(curl, CURLOPT_HTTPHEADER, list);
res = curl_easy_perform(curl);
test_cleanup:
curl_slist_free_all(list);
curl_easy_cleanup(curl);
curl_global_cleanup();
return res;
}

61
tests/libtest/lib1935.c Normal file
View File

@ -0,0 +1,61 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2020, 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 "test.h"
#include "memdebug.h"
int test(char *URL)
{
CURL *curl;
CURLcode res = TEST_ERR_MAJOR_BAD;
struct curl_slist *list = NULL;
if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
fprintf(stderr, "curl_global_init() failed\n");
return TEST_ERR_MAJOR_BAD;
}
curl = curl_easy_init();
if(!curl) {
fprintf(stderr, "curl_easy_init() failed\n");
curl_global_cleanup();
return TEST_ERR_MAJOR_BAD;
}
test_setopt(curl, CURLOPT_VERBOSE, 1L);
test_setopt(curl, CURLOPT_AWS_SIGV4, "xxx:yyy:rrr");
test_setopt(curl, CURLOPT_USERPWD, "xxx:yyy");
test_setopt(curl, CURLOPT_HEADER, 0L);
test_setopt(curl, CURLOPT_URL, URL);
list = curl_slist_append(list, "Content-Type: application/json");
test_setopt(curl, CURLOPT_HTTPHEADER, list);
res = curl_easy_perform(curl);
test_cleanup:
curl_slist_free_all(list);
curl_easy_cleanup(curl);
curl_global_cleanup();
return res;
}

61
tests/libtest/lib1936.c Normal file
View File

@ -0,0 +1,61 @@
/***************************************************************************
* _ _ ____ _
* Project ___| | | | _ \| |
* / __| | | | |_) | |
* | (__| |_| | _ <| |___
* \___|\___/|_| \_\_____|
*
* Copyright (C) 1998 - 2020, 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 "test.h"
#include "memdebug.h"
int test(char *URL)
{
CURL *curl;
CURLcode res = TEST_ERR_MAJOR_BAD;
struct curl_slist *list = NULL;
if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
fprintf(stderr, "curl_global_init() failed\n");
return TEST_ERR_MAJOR_BAD;
}
curl = curl_easy_init();
if(!curl) {
fprintf(stderr, "curl_easy_init() failed\n");
curl_global_cleanup();
return TEST_ERR_MAJOR_BAD;
}
test_setopt(curl, CURLOPT_VERBOSE, 1L);
test_setopt(curl, CURLOPT_AWS_SIGV4, "xxx:yyy:rrr:sss");
test_setopt(curl, CURLOPT_USERPWD, "xxx:yyy");
test_setopt(curl, CURLOPT_HEADER, 0L);
test_setopt(curl, CURLOPT_URL, URL);
list = curl_slist_append(list, "Content-Type: application/json");
test_setopt(curl, CURLOPT_HTTPHEADER, list);
res = curl_easy_perform(curl);
test_cleanup:
curl_slist_free_all(list);
curl_easy_cleanup(curl);
curl_global_cleanup();
return res;
}