sigv4: URL encode the user name in the header

- split into sub functions
- add 'aws-sigv4' as keyword for many tests

Verify with test 3222

Reported-by: Trail of Bits
Closes #21923
This commit is contained in:
Daniel Stenberg 2026-06-09 11:40:41 +02:00
parent cb4b3e75e8
commit c7cba2fd2d
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
21 changed files with 349 additions and 159 deletions

View file

@ -817,67 +817,14 @@ fail:
return result;
}
CURLcode Curl_output_aws_sigv4(struct Curl_easy *data)
static CURLcode parse_sigv4_params(struct Curl_easy *data,
const char *hostname,
struct Curl_str *provider0,
struct Curl_str *provider1,
struct Curl_str *region,
struct Curl_str *service)
{
CURLcode result = CURLE_OUT_OF_MEMORY;
struct connectdata *conn = data->conn;
const char *line;
struct Curl_str provider0;
struct Curl_str provider1;
struct Curl_str region = { NULL, 0 };
struct Curl_str service = { NULL, 0 };
const char *hostname = conn->origin->hostname;
time_t clock;
struct tm tm;
char timestamp[TIMESTAMP_SIZE];
char date[9];
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;
const char *payload_hash = NULL;
size_t payload_hash_len = 0;
unsigned char sha_hash[CURL_SHA256_DIGEST_LENGTH];
char sha_hex[SHA256_HEX_LENGTH];
char content_sha256_hdr[CONTENT_SHA256_HDR_LEN + 2] = ""; /* add \r\n */
char *canonical_request = NULL;
char *request_type = NULL;
char *credential_scope = NULL;
char *str_to_sign = NULL;
const char *user = Curl_creds_user(data->state.creds);
const char *passwd = Curl_creds_passwd(data->state.creds);
char *secret = NULL;
unsigned char sign0[CURL_SHA256_DIGEST_LENGTH] = { 0 };
unsigned char sign1[CURL_SHA256_DIGEST_LENGTH] = { 0 };
char *auth_headers = NULL;
if(data->set.path_as_is) {
failf(data, "Cannot use sigv4 authentication with path-as-is flag");
return CURLE_BAD_FUNCTION_ARGUMENT;
}
if(Curl_checkheaders(data, STRCONST("Authorization"))) {
/* Authorization already present, Bailing out */
return CURLE_OK;
}
/* we init those buffers here, so goto fail will free initialized dynbuf */
curlx_dyn_init(&canonical_headers, CURL_MAX_HTTP_HEADER);
curlx_dyn_init(&canonical_query, CURL_MAX_HTTP_HEADER);
curlx_dyn_init(&signed_headers, CURL_MAX_HTTP_HEADER);
curlx_dyn_init(&canonical_path, CURL_MAX_HTTP_HEADER);
/*
* 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.
*/
line = data->set.str[STRING_AWS_SIGV4];
const char *line = data->set.str[STRING_AWS_SIGV4];
if(!line || !*line)
line = "aws:amz";
@ -885,71 +832,89 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data)
No string can be longer than N bytes of non-whitespace
*/
if(curlx_str_until(&line, &provider0, MAX_SIGV4_LEN, ':')) {
if(curlx_str_until(&line, provider0, MAX_SIGV4_LEN, ':')) {
failf(data, "first aws-sigv4 provider cannot be empty");
result = CURLE_BAD_FUNCTION_ARGUMENT;
goto fail;
return CURLE_BAD_FUNCTION_ARGUMENT;
}
if(curlx_str_single(&line, ':') ||
curlx_str_until(&line, &provider1, MAX_SIGV4_LEN, ':')) {
provider1 = provider0;
curlx_str_until(&line, provider1, MAX_SIGV4_LEN, ':')) {
*provider1 = *provider0;
}
else if(curlx_str_single(&line, ':') ||
curlx_str_until(&line, &region, MAX_SIGV4_LEN, ':') ||
curlx_str_until(&line, region, MAX_SIGV4_LEN, ':') ||
curlx_str_single(&line, ':') ||
curlx_str_until(&line, &service, MAX_SIGV4_LEN, ':')) {
curlx_str_until(&line, service, MAX_SIGV4_LEN, ':')) {
/* nothing to do */
}
if(!curlx_strlen(&service)) {
if(!curlx_strlen(service)) {
const char *p = hostname;
if(curlx_str_until(&p, &service, MAX_SIGV4_LEN, '.') ||
if(curlx_str_until(&p, service, MAX_SIGV4_LEN, '.') ||
curlx_str_single(&p, '.')) {
failf(data, "aws-sigv4: service missing in parameters and hostname");
result = CURLE_URL_MALFORMAT;
goto fail;
return CURLE_URL_MALFORMAT;
}
infof(data, "aws_sigv4: picked service %.*s from host",
(int)curlx_strlen(&service), curlx_str(&service));
(int)curlx_strlen(service), curlx_str(service));
if(!curlx_strlen(&region)) {
if(curlx_str_until(&p, &region, MAX_SIGV4_LEN, '.') ||
if(!curlx_strlen(region)) {
if(curlx_str_until(&p, region, MAX_SIGV4_LEN, '.') ||
curlx_str_single(&p, '.')) {
failf(data, "aws-sigv4: region missing in parameters and hostname");
result = CURLE_URL_MALFORMAT;
goto fail;
return CURLE_URL_MALFORMAT;
}
infof(data, "aws_sigv4: picked region %.*s from host",
(int)curlx_strlen(&region), curlx_str(&region));
(int)curlx_strlen(region), curlx_str(region));
}
}
Curl_http_method(data, &method, &httpreq);
return CURLE_OK;
}
payload_hash =
parse_content_sha_hdr(data, curlx_str(&provider1),
curlx_strlen(&provider1), &payload_hash_len);
static CURLcode get_payload_hash(struct Curl_easy *data,
Curl_HttpReq httpreq,
struct Curl_str *provider0,
struct Curl_str *provider1,
struct Curl_str *service,
unsigned char *sha_hash,
char *sha_hex,
char *content_sha256_hdr,
const char **payload_hash_out,
size_t *payload_hash_len_out)
{
*payload_hash_out =
parse_content_sha_hdr(data, curlx_str(provider1),
curlx_strlen(provider1), payload_hash_len_out);
if(!payload_hash) {
if(!*payload_hash_out) {
CURLcode result;
/* AWS S3 requires a x-amz-content-sha256 header, and supports special
* values like UNSIGNED-PAYLOAD */
bool sign_as_s3 = curlx_str_casecompare(&provider0, "aws") &&
curlx_str_casecompare(&service, "s3");
bool sign_as_s3 = curlx_str_casecompare(provider0, "aws") &&
curlx_str_casecompare(service, "s3");
if(sign_as_s3)
result = calc_s3_payload_hash(data, httpreq, curlx_str(&provider1),
curlx_strlen(&provider1), sha_hash,
result = calc_s3_payload_hash(data, httpreq, curlx_str(provider1),
curlx_strlen(provider1), sha_hash,
sha_hex, content_sha256_hdr);
else
result = calc_payload_hash(data, sha_hash, sha_hex);
if(result)
goto fail;
return result;
payload_hash = sha_hex;
*payload_hash_out = sha_hex;
/* may be shorter than SHA256_HEX_LENGTH, like S3_UNSIGNED_PAYLOAD */
payload_hash_len = strlen(sha_hex);
*payload_hash_len_out = strlen(sha_hex);
}
return CURLE_OK;
}
static CURLcode get_timestamp(char *timestamp, size_t stampsize)
{
time_t clock;
struct tm tm;
CURLcode result;
#ifdef DEBUGBUILD
{
@ -963,43 +928,54 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data)
clock = time(NULL);
#endif
result = curlx_gmtime(clock, &tm);
if(result) {
goto fail;
}
if(!strftime(timestamp, sizeof(timestamp), "%Y%m%dT%H%M%SZ", &tm)) {
result = CURLE_OUT_OF_MEMORY;
goto fail;
}
if(result)
return result;
if(!strftime(timestamp, stampsize, "%Y%m%dT%H%M%SZ", &tm))
return CURLE_OUT_OF_MEMORY;
return CURLE_OK;
}
static CURLcode make_canonical_request(struct Curl_easy *data,
const char *hostname,
char *timestamp,
struct Curl_str *provider1,
struct Curl_str *service,
const char *method,
const char *payload_hash,
size_t payload_hash_len,
char **date_header_out,
char *content_sha256_hdr,
struct dynbuf *canonical_headers,
struct dynbuf *signed_headers,
char **canonical_request_out)
{
struct dynbuf canonical_query;
struct dynbuf canonical_path;
CURLcode result;
curlx_dyn_init(&canonical_query, CURL_MAX_HTTP_HEADER);
curlx_dyn_init(&canonical_path, CURL_MAX_HTTP_HEADER);
result = make_headers(data, hostname, timestamp,
curlx_str(&provider1), curlx_strlen(&provider1),
&date_header, content_sha256_hdr,
&canonical_headers, &signed_headers);
curlx_str(provider1), curlx_strlen(provider1),
date_header_out, content_sha256_hdr,
canonical_headers, signed_headers);
if(result)
goto fail;
if(*content_sha256_hdr) {
/* make_headers() needed this without the \r\n for canonicalization */
size_t hdrlen = strlen(content_sha256_hdr);
DEBUGASSERT(hdrlen + 3 < sizeof(content_sha256_hdr));
memcpy(content_sha256_hdr + hdrlen, "\r\n", 3);
}
memcpy(date, timestamp, sizeof(date));
date[sizeof(date) - 1] = 0;
result = canon_query(data->state.up.query, &canonical_query);
if(result)
goto fail;
result = canon_path(data->state.up.path, strlen(data->state.up.path),
&canonical_path,
should_urlencode(&service));
should_urlencode(service));
if(result)
goto fail;
result = CURLE_OUT_OF_MEMORY;
canonical_request =
*canonical_request_out =
curl_maprintf("%s\n" /* HTTPRequestMethod */
"%s\n" /* CanonicalURI */
"%s\n" /* CanonicalQueryString */
@ -1010,37 +986,65 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data)
curlx_dyn_ptr(&canonical_path),
curlx_dyn_ptr(&canonical_query) ?
curlx_dyn_ptr(&canonical_query) : "",
curlx_dyn_ptr(&canonical_headers),
curlx_dyn_ptr(&signed_headers),
curlx_dyn_ptr(canonical_headers),
curlx_dyn_ptr(signed_headers),
(int)payload_hash_len, payload_hash);
if(!canonical_request)
if(!*canonical_request_out) {
result = CURLE_OUT_OF_MEMORY;
goto fail;
}
infof(data, "aws_sigv4: Canonical request (enclosed in []) - [%s]",
canonical_request);
result = CURLE_OK;
fail:
curlx_dyn_free(&canonical_query);
curlx_dyn_free(&canonical_path);
return result;
}
static CURLcode make_string_to_sign(struct Curl_easy *data,
struct Curl_str *provider0,
struct Curl_str *region,
struct Curl_str *service,
const char *date,
const char *timestamp,
const char *canonical_request,
char **request_type_out,
char **credential_scope_out,
char **str_to_sign_out)
{
char *request_type;
char *credential_scope;
char *str_to_sign;
unsigned char sha_hash[CURL_SHA256_DIGEST_LENGTH];
char sha_hex[SHA256_HEX_LENGTH];
request_type = curl_maprintf("%.*s4_request",
(int)curlx_strlen(&provider0),
curlx_str(&provider0));
(int)curlx_strlen(provider0),
curlx_str(provider0));
if(!request_type)
goto fail;
return CURLE_OUT_OF_MEMORY;
/* provider0 is lowercased *after* curl_maprintf() so that the buffer
can be written to */
Curl_strntolower(request_type, request_type, curlx_strlen(&provider0));
Curl_strntolower(request_type, request_type, curlx_strlen(provider0));
credential_scope = curl_maprintf("%s/%.*s/%.*s/%s", date,
(int)curlx_strlen(&region),
curlx_str(&region),
(int)curlx_strlen(&service),
curlx_str(&service),
(int)curlx_strlen(region),
curlx_str(region),
(int)curlx_strlen(service),
curlx_str(service),
request_type);
if(!credential_scope)
goto fail;
if(!credential_scope) {
curlx_free(request_type);
return CURLE_OUT_OF_MEMORY;
}
if(Curl_sha256it(sha_hash, (unsigned char *)canonical_request,
strlen(canonical_request)))
goto fail;
if(Curl_sha256it(sha_hash, (const unsigned char *)canonical_request,
strlen(canonical_request))) {
curlx_free(request_type);
curlx_free(credential_scope);
return CURLE_OUT_OF_MEMORY;
}
sha256_to_hex(sha_hex, sha_hash);
@ -1052,35 +1056,69 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data)
"%s\n" /* RequestDateTime */
"%s\n" /* CredentialScope */
"%s", /* HashedCanonicalRequest in hex */
(int)curlx_strlen(&provider0),
curlx_str(&provider0),
(int)curlx_strlen(provider0),
curlx_str(provider0),
timestamp,
credential_scope,
sha_hex);
if(!str_to_sign)
goto fail;
if(!str_to_sign) {
curlx_free(request_type);
curlx_free(credential_scope);
return CURLE_OUT_OF_MEMORY;
}
/* make provider0 part done uppercase */
Curl_strntoupper(str_to_sign, curlx_str(&provider0),
curlx_strlen(&provider0));
Curl_strntoupper(str_to_sign, curlx_str(provider0),
curlx_strlen(provider0));
infof(data, "aws_sigv4: String to sign (enclosed in []) - [%s]",
str_to_sign);
secret = curl_maprintf("%.*s4%s", (int)curlx_strlen(&provider0),
curlx_str(&provider0), passwd);
*request_type_out = request_type;
*credential_scope_out = credential_scope;
*str_to_sign_out = str_to_sign;
return CURLE_OK;
}
static CURLcode sign_and_set_auth_headers(struct Curl_easy *data,
struct Curl_str *provider0,
struct Curl_str *region,
struct Curl_str *service,
const char *request_type,
const char *credential_scope,
const char *date,
const char *str_to_sign,
const char *date_header,
const char *content_sha256_hdr,
struct dynbuf *signed_headers)
{
CURLcode result = CURLE_OUT_OF_MEMORY;
const char *passwd = Curl_creds_passwd(data->state.creds);
char *secret = NULL;
unsigned char sign0[CURL_SHA256_DIGEST_LENGTH] = { 0 };
unsigned char sign1[CURL_SHA256_DIGEST_LENGTH] = { 0 };
char sha_hex[SHA256_HEX_LENGTH];
char *auth_headers = NULL;
char *user = curl_escape(Curl_creds_user(data->state.creds), 0);
if(!user)
return CURLE_OUT_OF_MEMORY;
secret = curl_maprintf("%.*s4%s", (int)curlx_strlen(provider0),
curlx_str(provider0), passwd);
if(!secret)
goto fail;
/* make provider0 part done uppercase */
Curl_strntoupper(secret, curlx_str(&provider0), curlx_strlen(&provider0));
Curl_strntoupper(secret, curlx_str(provider0), curlx_strlen(provider0));
HMAC_SHA256(secret, strlen(secret), date, strlen(date), sign0);
HMAC_SHA256(sign0, sizeof(sign0),
curlx_str(&region), curlx_strlen(&region), sign1);
curlx_str(region), curlx_strlen(region), sign1);
HMAC_SHA256(sign1, sizeof(sign1),
curlx_str(&service), curlx_strlen(&service), sign0);
HMAC_SHA256(sign0, sizeof(sign0), request_type, strlen(request_type), sign1);
HMAC_SHA256(sign1, sizeof(sign1), str_to_sign, strlen(str_to_sign), sign0);
curlx_str(service), curlx_strlen(service), sign0);
HMAC_SHA256(sign0, sizeof(sign0),
request_type, strlen(request_type), sign1);
HMAC_SHA256(sign1, sizeof(sign1),
str_to_sign, strlen(str_to_sign), sign0);
sha256_to_hex(sha_hex, sign0);
@ -1090,27 +1128,28 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data)
"Credential=%s/%s, "
"SignedHeaders=%s, "
"Signature=%s\r\n"
"%s"
"%s%s",
(int)curlx_strlen(provider0),
curlx_str(provider0),
user,
credential_scope,
curlx_dyn_ptr(signed_headers),
sha_hex,
/*
* date_header is added here, only if it was not
* user-specified (using CURLOPT_HTTPHEADER).
* date_header includes \r\n
*/
"%s"
"%s", /* optional sha256 header includes \r\n */
(int)curlx_strlen(&provider0),
curlx_str(&provider0),
user,
credential_scope,
curlx_dyn_ptr(&signed_headers),
sha_hex,
date_header ? date_header : "",
content_sha256_hdr);
if(!auth_headers) {
content_sha256_hdr,
content_sha256_hdr[0] ? "\r\n": "");
if(!auth_headers)
goto fail;
}
/* provider 0 uppercase */
Curl_strntoupper(&auth_headers[sizeof("Authorization: ") - 1],
curlx_str(&provider0), curlx_strlen(&provider0));
curlx_str(provider0), curlx_strlen(provider0));
curlx_free(data->req.hd_auth);
data->req.hd_auth = auth_headers;
@ -1118,15 +1157,91 @@ CURLcode Curl_output_aws_sigv4(struct Curl_easy *data)
result = CURLE_OK;
fail:
curlx_dyn_free(&canonical_query);
curlx_dyn_free(&canonical_path);
curlx_free(user);
curlx_free(secret);
return result;
}
CURLcode Curl_output_aws_sigv4(struct Curl_easy *data)
{
CURLcode result = CURLE_OUT_OF_MEMORY;
struct connectdata *conn = data->conn;
struct Curl_str provider0 = { NULL, 0 };
struct Curl_str provider1 = { NULL, 0 };
struct Curl_str region = { NULL, 0 };
struct Curl_str service = { NULL, 0 };
const char *hostname = conn->origin->hostname;
char timestamp[TIMESTAMP_SIZE];
char date[9];
struct dynbuf canonical_headers;
struct dynbuf signed_headers;
char *date_header = NULL;
Curl_HttpReq httpreq;
const char *method = NULL;
const char *payload_hash = NULL;
size_t payload_hash_len = 0;
unsigned char sha_hash[CURL_SHA256_DIGEST_LENGTH];
char sha_hex[SHA256_HEX_LENGTH];
char content_sha256_hdr[CONTENT_SHA256_HDR_LEN + 2] = ""; /* add \r\n */
char *canonical_request = NULL;
char *request_type = NULL;
char *credential_scope = NULL;
char *str_to_sign = NULL;
if(data->set.path_as_is) {
failf(data, "Cannot use sigv4 authentication with path-as-is flag");
return CURLE_BAD_FUNCTION_ARGUMENT;
}
if(Curl_checkheaders(data, STRCONST("Authorization")))
/* Authorization already present, Bailing out */
return CURLE_OK;
/* we init those buffers here, so goto fail will free initialized dynbuf */
curlx_dyn_init(&canonical_headers, CURL_MAX_HTTP_HEADER);
curlx_dyn_init(&signed_headers, CURL_MAX_HTTP_HEADER);
result = parse_sigv4_params(data, hostname, &provider0, &provider1,
&region, &service);
if(!result) {
Curl_http_method(data, &method, &httpreq);
result = get_payload_hash(data, httpreq, &provider0, &provider1, &service,
sha_hash, sha_hex, content_sha256_hdr,
&payload_hash, &payload_hash_len);
}
if(!result)
result = get_timestamp(timestamp, sizeof(timestamp));
if(!result)
result = make_canonical_request(data, hostname, timestamp,
&provider1, &service,
method, payload_hash, payload_hash_len,
&date_header, content_sha256_hdr,
&canonical_headers, &signed_headers,
&canonical_request);
if(!result) {
/* the timestamp might have been updated in make_canonical_request */
memcpy(date, timestamp, sizeof(date) - 1);
date[sizeof(date) - 1] = 0;
result = make_string_to_sign(data, &provider0, &region, &service,
date, timestamp, canonical_request,
&request_type, &credential_scope,
&str_to_sign);
}
if(!result)
result = sign_and_set_auth_headers(data, &provider0, &region, &service,
request_type, credential_scope,
date, str_to_sign, date_header,
content_sha256_hdr, &signed_headers);
curlx_dyn_free(&canonical_headers);
curlx_dyn_free(&signed_headers);
curlx_free(canonical_request);
curlx_free(request_type);
curlx_free(credential_scope);
curlx_free(str_to_sign);
curlx_free(secret);
curlx_free(date_header);
return result;
}

View file

@ -281,7 +281,7 @@ test3100 test3101 test3102 test3103 test3104 test3105 test3106 \
\
test3200 test3201 test3202 test3203 test3204 test3205 test3206 test3207 \
test3208 test3209 test3210 test3211 test3212 test3213 test3214 test3215 \
test3216 test3217 test3218 test3219 test3220 test3221 \
test3216 test3217 test3218 test3219 test3220 test3221 test3222 \
\
test3300 test3301 test3302 test3303 test3304 test3305 \
\

View file

@ -4,6 +4,7 @@
<keywords>
HTTP
CURLOPT_AWS_SIGV4
aws-sigv4
</keywords>
</info>

View file

@ -4,6 +4,7 @@
<keywords>
HTTP
CURLOPT_AWS_SIGV4
aws-sigv4
</keywords>
</info>

View file

@ -4,6 +4,7 @@
<keywords>
HTTP
CURLOPT_AWS_SIGV4
aws-sigv4
</keywords>
</info>

View file

@ -4,6 +4,7 @@
<keywords>
HTTP
CURLOPT_AWS_SIGV4
aws-sigv4
</keywords>
</info>

View file

@ -5,6 +5,7 @@
HTTP
HTTP POST
CURLOPT_AWS_SIGV4
aws-sigv4
</keywords>
</info>

View file

@ -5,6 +5,7 @@
HTTP
HTTP POST
CURLOPT_AWS_SIGV4
aws-sigv4
</keywords>
</info>

View file

@ -4,6 +4,7 @@
<keywords>
HTTP
CURLOPT_AWS_SIGV4
aws-sigv4
</keywords>
</info>

View file

@ -4,6 +4,7 @@
<keywords>
HTTP
CURLOPT_AWS_SIGV4
aws-sigv4
</keywords>
</info>

View file

@ -4,6 +4,7 @@
<keywords>
HTTP
CURLOPT_AWS_SIGV4
aws-sigv4
</keywords>
</info>

View file

@ -4,6 +4,7 @@
<keywords>
HTTP
CURLOPT_AWS_SIGV4
aws-sigv4
</keywords>
</info>

View file

@ -4,6 +4,7 @@
<keywords>
HTTP
CURLOPT_AWS_SIGV4
aws-sigv4
</keywords>
</info>

View file

@ -4,6 +4,7 @@
<keywords>
HTTP
CURLOPT_AWS_SIGV4
aws-sigv4
</keywords>
</info>

View file

@ -4,6 +4,7 @@
<keywords>
HTTP
CURLOPT_AWS_SIGV4
aws-sigv4
</keywords>
</info>

View file

@ -4,6 +4,7 @@
<keywords>
HTTP
CURLOPT_AWS_SIGV4
aws-sigv4
</keywords>
</info>

View file

@ -4,6 +4,7 @@
<keywords>
HTTP
CURLOPT_AWS_SIGV4
aws-sigv4
</keywords>
</info>

View file

@ -4,6 +4,7 @@
<keywords>
HTTP
CURLOPT_AWS_SIGV4
aws-sigv4
</keywords>
</info>

View file

@ -4,6 +4,7 @@
<keywords>
HTTP
CURLOPT_AWS_SIGV4
aws-sigv4
</keywords>
</info>

View file

@ -4,6 +4,7 @@
<keywords>
HTTP
CURLOPT_AWS_SIGV4
aws-sigv4
</keywords>
</info>

57
tests/data/test3222 Normal file
View file

@ -0,0 +1,57 @@
<?xml version="1.0" encoding="US-ASCII"?>
<testcase>
<info>
<keywords>
HTTP
aws-sigv4
</keywords>
</info>
# Server-side
<reply>
<data crlf="headers">
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-
</data>
</reply>
# Client-side
<client>
<server>
http
</server>
<features>
Debug
aws
</features>
<name>
aws-sigv4 with CRLF in username
</name>
<command>
"http://user%0d%0a:secret@fake.fake.fake:8000/" --aws-sigv4 "aws:amz:us-east-2:es" --connect-to fake.fake.fake:8000:%HOSTIP:%HTTPPORT
</command>
</client>
# Verify data after the test has been "shot"
<verify>
<protocol crlf="headers">
GET / HTTP/1.1
Host: fake.fake.fake:8000
Authorization: AWS4-HMAC-SHA256 Credential=user%0D%0A/19700101/us-east-2/es/aws4_request, SignedHeaders=host;x-amz-date, Signature=e5747e9555c0e96f1067cc4bf9f6055e72a185178e5dd0c2909279ec1d66360b
X-Amz-Date: 19700101T000000Z
User-Agent: curl/%VERSION
Accept: */*
</protocol>
</verify>
</testcase>