mirror of
https://github.com/curl/curl
synced 2026-08-25 12:32:35 -04:00
Add support for signing outgoing HTTP requests per RFC 9421 using
Ed25519 or HMAC-SHA256 algorithms.
New libcurl options:
- CURLOPT_HTTPSIG: signing algorithm ("ed25519" or "hmac-sha256")
- CURLOPT_HTTPSIG_KEY: path to hex-encoded key file
- CURLOPT_HTTPSIG_KEYID: key identifier for Signature-Input
- CURLOPT_HTTPSIG_HEADERS: space-separated components to sign
New CLI flags: --httpsig, --httpsig-key, --httpsig-keyid,
--httpsig-headers
The crypto layer follows the sha256.c multi-backend pattern with
implementations for OpenSSL (EVP_DigestSign) and wolfSSL
(wc_ed25519_sign_msg). HMAC-SHA256 uses the existing Curl_hmacit()
infrastructure which works on all backends.
Verified by test 5000 to 5021
Assisted-by: Daniel Stenberg
Signed-off-by: Sameeh Jubran <sameeh@wolfssl.com>
Closes #22386
Closes #21239
692 lines
20 KiB
C
692 lines
20 KiB
C
/***************************************************************************
|
|
* _ _ ____ _
|
|
* Project ___| | | | _ \| |
|
|
* / __| | | | |_) | |
|
|
* | (__| |_| | _ <| |___
|
|
* \___|\___/|_| \_\_____|
|
|
*
|
|
* Copyright (C) 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.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.
|
|
*
|
|
* SPDX-License-Identifier: curl
|
|
*
|
|
***************************************************************************/
|
|
#include "curl_setup.h"
|
|
|
|
#if !defined(CURL_DISABLE_HTTP) && !defined(CURL_DISABLE_HTTPSIG)
|
|
|
|
#include "urldata.h"
|
|
#include "http_httpsig.h"
|
|
#include "curl_ed25519.h"
|
|
#include "curl_hmac.h"
|
|
#include "curl_sha256.h"
|
|
#include "http.h"
|
|
#include "transfer.h"
|
|
#include "curl_trc.h"
|
|
#include "slist.h"
|
|
#include "curlx/dynbuf.h"
|
|
#include "curlx/base64.h"
|
|
#include "curlx/strdup.h"
|
|
#include "curlx/strparse.h"
|
|
#include "strcase.h"
|
|
|
|
#include <time.h>
|
|
|
|
#define HTTPSIG_MAX_SIG_BASE CURL_MAX_HTTP_HEADER
|
|
#define HTTPSIG_MAX_COMPONENTS 16
|
|
#define HTTPSIG_MAX_RAW_SIG CURL_ED25519_SIGLEN
|
|
#define HTTPSIG_DEFAULT_LABEL "sig1"
|
|
|
|
enum httpsig_alg {
|
|
HTTPSIG_ALG_ED25519,
|
|
HTTPSIG_ALG_HMAC_SHA256,
|
|
HTTPSIG_ALG_UNKNOWN
|
|
};
|
|
|
|
static const char *alg_to_str(enum httpsig_alg alg)
|
|
{
|
|
switch(alg) {
|
|
case HTTPSIG_ALG_ED25519:
|
|
return "ed25519";
|
|
case HTTPSIG_ALG_HMAC_SHA256:
|
|
return "hmac-sha256";
|
|
default:
|
|
break;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
static enum httpsig_alg id_to_alg(uint8_t val)
|
|
{
|
|
switch(val) {
|
|
case CURLHTTPSIG_ED25519:
|
|
return HTTPSIG_ALG_ED25519;
|
|
case CURLHTTPSIG_HMAC_SHA256:
|
|
return HTTPSIG_ALG_HMAC_SHA256;
|
|
default:
|
|
break;
|
|
}
|
|
return HTTPSIG_ALG_UNKNOWN;
|
|
}
|
|
|
|
static CURLcode decode_hex_key(struct Curl_easy *data,
|
|
const char *hexstr,
|
|
unsigned char **keyout,
|
|
size_t *keylen)
|
|
{
|
|
size_t len, i;
|
|
unsigned char *keybuf;
|
|
|
|
*keyout = NULL;
|
|
*keylen = 0;
|
|
|
|
len = strlen(hexstr);
|
|
while(len > 0 && ISNEWLINE(hexstr[len - 1]))
|
|
len--;
|
|
|
|
if(len == 0 || (len & 1) != 0) {
|
|
failf(data, "httpsig: invalid hex key (length %zu)", len);
|
|
return CURLE_BAD_FUNCTION_ARGUMENT;
|
|
}
|
|
|
|
if(len > CURL_MAX_INPUT_LENGTH) {
|
|
failf(data, "httpsig: hex key too long");
|
|
return CURLE_BAD_FUNCTION_ARGUMENT;
|
|
}
|
|
|
|
keybuf = curlx_malloc(len / 2);
|
|
if(!keybuf)
|
|
return CURLE_OUT_OF_MEMORY;
|
|
|
|
for(i = 0; i < len; i += 2) {
|
|
if(!ISXDIGIT(hexstr[i]) || !ISXDIGIT(hexstr[i + 1])) {
|
|
failf(data, "httpsig: invalid hex at position %zu ('%c%c')",
|
|
i, hexstr[i], hexstr[i + 1]);
|
|
curlx_free(keybuf);
|
|
return CURLE_BAD_FUNCTION_ARGUMENT;
|
|
}
|
|
keybuf[i / 2] = (unsigned char)((curlx_hexval(hexstr[i]) << 4) |
|
|
curlx_hexval(hexstr[i + 1]));
|
|
}
|
|
|
|
*keyout = keybuf;
|
|
*keylen = len / 2;
|
|
return CURLE_OK;
|
|
}
|
|
|
|
/* @authority matches the Host header field-value when available (RFC 9421).
|
|
data->state.aptr.host is produced by http_set_aptr_host() before auth. */
|
|
static CURLcode httpsig_authority(struct Curl_easy *data,
|
|
struct connectdata *conn,
|
|
struct dynbuf *authority_buf)
|
|
{
|
|
const char *h = data->state.aptr.host;
|
|
|
|
if(h && curl_strnequal(h, "host:", 5)) {
|
|
const char *value = h + 5;
|
|
const char *end;
|
|
|
|
while(ISBLANK(*value))
|
|
value++;
|
|
if(*value) {
|
|
CURLcode result;
|
|
|
|
end = value;
|
|
while(*end && !ISNEWLINE(*end))
|
|
end++;
|
|
while(end > value && ISBLANK(end[-1]))
|
|
end--;
|
|
result = curlx_dyn_addn(authority_buf, value, (size_t)(end - value));
|
|
if(result)
|
|
return result;
|
|
return CURLE_OK;
|
|
}
|
|
}
|
|
|
|
{
|
|
const char *hostname = conn->origin->hostname;
|
|
uint16_t port = conn->origin->port;
|
|
|
|
if((conn->given->defport != port) && port)
|
|
return curlx_dyn_addf(authority_buf, "%s:%u", hostname, port);
|
|
return curlx_dyn_add(authority_buf, hostname);
|
|
}
|
|
}
|
|
|
|
static CURLcode sf_append_quoted(struct dynbuf *buf, const char *str)
|
|
{
|
|
CURLcode result = curlx_dyn_addn(buf, "\"", 1);
|
|
if(result)
|
|
return result;
|
|
while(*str) {
|
|
if(ISCNTRL(*str))
|
|
return CURLE_BAD_FUNCTION_ARGUMENT;
|
|
if(*str == '\\' || *str == '"') {
|
|
result = curlx_dyn_addn(buf, "\\", 1);
|
|
if(result)
|
|
return result;
|
|
}
|
|
result = curlx_dyn_addn(buf, str, 1);
|
|
if(result)
|
|
return result;
|
|
str++;
|
|
}
|
|
return curlx_dyn_addn(buf, "\"", 1);
|
|
}
|
|
|
|
/* base64-encode raw bytes into an RFC 8941 byte sequence (:base64:) */
|
|
static CURLcode sf_encode_byte_seq(const unsigned char *raw, size_t rawlen,
|
|
struct dynbuf *out)
|
|
{
|
|
CURLcode result;
|
|
size_t b64len;
|
|
char *b64;
|
|
|
|
result = curlx_base64_encode(raw, rawlen, &b64, &b64len);
|
|
if(result)
|
|
return result;
|
|
|
|
result = curlx_dyn_addn(out, ":", 1);
|
|
if(!result)
|
|
result = curlx_dyn_addn(out, b64, b64len);
|
|
if(!result)
|
|
result = curlx_dyn_addn(out, ":", 1);
|
|
|
|
curlx_free(b64);
|
|
return result;
|
|
}
|
|
|
|
static CURLcode build_sig_params(struct dynbuf *params,
|
|
const char **components, size_t count,
|
|
time_t created, const char *keyid,
|
|
enum httpsig_alg alg)
|
|
{
|
|
CURLcode result;
|
|
size_t i;
|
|
|
|
result = curlx_dyn_addn(params, "(", 1);
|
|
if(result)
|
|
return result;
|
|
|
|
for(i = 0; i < count; i++) {
|
|
if(i > 0) {
|
|
result = curlx_dyn_addn(params, " ", 1);
|
|
if(result)
|
|
return result;
|
|
}
|
|
result = sf_append_quoted(params, components[i]);
|
|
if(result)
|
|
return result;
|
|
}
|
|
|
|
result = curlx_dyn_addn(params, ")", 1);
|
|
if(result)
|
|
return result;
|
|
|
|
result = curlx_dyn_addf(params, ";created=%lld", (long long)created);
|
|
if(result)
|
|
return result;
|
|
|
|
if(keyid && *keyid) {
|
|
result = curlx_dyn_add(params, ";keyid=");
|
|
if(result)
|
|
return result;
|
|
result = sf_append_quoted(params, keyid);
|
|
if(result)
|
|
return result;
|
|
}
|
|
|
|
result = curlx_dyn_addf(params, ";alg=\"%s\"", alg_to_str(alg));
|
|
return result;
|
|
}
|
|
|
|
/* strings defined by RFC 9421 */
|
|
#define SIG_METHOD "@method"
|
|
#define SIG_AUTHORITY "@authority"
|
|
#define SIG_PATH "@path"
|
|
#define SIG_QUERY "@query"
|
|
|
|
/* Resolve a component identifier to its value.
|
|
* For headers, we walk the full user-supplied header list to combine
|
|
* duplicate field values with ", " per RFC 9421 Section 2.1. Each
|
|
* individual value is trimmed of leading/trailing OWS and the trailing
|
|
* \r\n. The combined result is written into the caller-provided buffer. */
|
|
static CURLcode resolve_component(const char *name,
|
|
const char *method,
|
|
const char *authority,
|
|
const char *path,
|
|
const char *query,
|
|
struct Curl_easy *data,
|
|
struct dynbuf *valbuf,
|
|
const char **out)
|
|
{
|
|
*out = NULL;
|
|
|
|
if(name[0] == '@') {
|
|
if(curl_strequal(name, SIG_METHOD))
|
|
*out = method;
|
|
else if(curl_strequal(name, SIG_AUTHORITY))
|
|
*out = authority;
|
|
else if(curl_strequal(name, SIG_PATH))
|
|
*out = path;
|
|
else if(curl_strequal(name, SIG_QUERY))
|
|
*out = query;
|
|
else {
|
|
failf(data, "httpsig: unsupported derived component '%s'", name);
|
|
return CURLE_BAD_FUNCTION_ARGUMENT;
|
|
}
|
|
if(!*out) {
|
|
failf(data, "httpsig: derived component '%s' has no value", name);
|
|
return CURLE_BAD_FUNCTION_ARGUMENT;
|
|
}
|
|
return CURLE_OK;
|
|
}
|
|
else {
|
|
/* RFC 9421 Section 2.1: walk all user-supplied headers and combine
|
|
duplicate field values with ", " per HTTP field combination rules. */
|
|
struct curl_slist *head;
|
|
size_t namelen = strlen(name);
|
|
bool found = FALSE;
|
|
|
|
curlx_dyn_reset(valbuf);
|
|
|
|
for(head = data->set.headers; head; head = head->next) {
|
|
if(curl_strnequal(head->data, name, namelen) &&
|
|
Curl_headersep(head->data[namelen])) {
|
|
const char *p = strchr(head->data, ':');
|
|
if(p) {
|
|
CURLcode result;
|
|
struct Curl_str content;
|
|
curlx_str_assign(&content, p + 1, strlen(p + 1));
|
|
curlx_str_trimblanks(&content);
|
|
if(found) {
|
|
result = curlx_dyn_addn(valbuf, ", ", 2);
|
|
if(result)
|
|
return result;
|
|
}
|
|
result = curlx_dyn_addn(valbuf, curlx_str(&content),
|
|
curlx_strlen(&content));
|
|
if(result)
|
|
return result;
|
|
found = TRUE;
|
|
}
|
|
}
|
|
}
|
|
|
|
if(found) {
|
|
*out = curlx_dyn_ptr(valbuf);
|
|
return CURLE_OK;
|
|
}
|
|
failf(data, "httpsig: header '%s' not found in request", name);
|
|
return CURLE_BAD_FUNCTION_ARGUMENT;
|
|
}
|
|
}
|
|
|
|
static CURLcode build_sig_base(struct dynbuf *base,
|
|
const char **components, size_t count,
|
|
const char *method,
|
|
const char *authority,
|
|
const char *path,
|
|
const char *query,
|
|
struct Curl_easy *data,
|
|
const char *sig_params)
|
|
{
|
|
CURLcode result;
|
|
size_t i;
|
|
struct dynbuf hdrvalbuf;
|
|
|
|
curlx_dyn_init(&hdrvalbuf, CURL_MAX_HTTP_HEADER);
|
|
|
|
for(i = 0; i < count; i++) {
|
|
const char *val = NULL;
|
|
result = resolve_component(components[i], method,
|
|
authority, path, query, data,
|
|
&hdrvalbuf, &val);
|
|
if(result || !val) {
|
|
failf(data, "httpsig: cannot resolve component '%s'", components[i]);
|
|
curlx_dyn_free(&hdrvalbuf);
|
|
return result ? result : CURLE_BAD_FUNCTION_ARGUMENT;
|
|
}
|
|
|
|
result = curlx_dyn_addf(base, "\"%s\": %s\n", components[i], val);
|
|
if(result) {
|
|
curlx_dyn_free(&hdrvalbuf);
|
|
return result;
|
|
}
|
|
}
|
|
|
|
curlx_dyn_free(&hdrvalbuf);
|
|
result = curlx_dyn_addf(base, "\"@signature-params\": %s", sig_params);
|
|
return result;
|
|
}
|
|
|
|
static CURLcode parse_components(struct Curl_easy *data,
|
|
const char *query,
|
|
const char **components,
|
|
size_t *ncomp_out,
|
|
char **hdrs_copy_out)
|
|
{
|
|
const char *hdrs = data->set.str[STRING_HTTPSIG_HEADERS];
|
|
size_t ncomp = 0;
|
|
|
|
*hdrs_copy_out = NULL;
|
|
if(hdrs && *hdrs) {
|
|
char *p;
|
|
char *hdrs_copy = curlx_strdup(hdrs);
|
|
if(!hdrs_copy)
|
|
return CURLE_OUT_OF_MEMORY;
|
|
*hdrs_copy_out = hdrs_copy;
|
|
p = hdrs_copy;
|
|
while(*p && ncomp < HTTPSIG_MAX_COMPONENTS) {
|
|
char *start;
|
|
size_t tlen = 0;
|
|
const char *p2 = p;
|
|
|
|
curlx_str_passblanks(&p2);
|
|
if(!*p2)
|
|
break;
|
|
p = start = CURL_UNCONST(p2);
|
|
while(*p && !ISBLANK(*p)) {
|
|
if((*p == '\"') || (*p == '\\'))
|
|
return CURLE_BAD_FUNCTION_ARGUMENT;
|
|
p++;
|
|
tlen++;
|
|
}
|
|
if(*p)
|
|
*p++ = '\0';
|
|
|
|
if(tlen && start[tlen - 1] == ':') {
|
|
/* Header field: drop the trailing ':' marker. RFC 9421 field
|
|
names are canonically lowercase (Section 2.1). */
|
|
start[--tlen] = '\0';
|
|
if(!tlen) {
|
|
failf(data, "httpsig: empty header component name");
|
|
return CURLE_BAD_FUNCTION_ARGUMENT;
|
|
}
|
|
Curl_strntolower(start, start, tlen);
|
|
components[ncomp++] = start;
|
|
}
|
|
else {
|
|
/* Derived component: map the bare name to its canonical RFC 9421
|
|
'@'-prefixed identifier (Section 2.2). */
|
|
Curl_strntolower(start, start, tlen);
|
|
if(!strcmp(start, "method"))
|
|
components[ncomp++] = SIG_METHOD;
|
|
else if(!strcmp(start, "authority"))
|
|
components[ncomp++] = SIG_AUTHORITY;
|
|
else if(!strcmp(start, "path"))
|
|
components[ncomp++] = SIG_PATH;
|
|
else if(!strcmp(start, "query"))
|
|
components[ncomp++] = SIG_QUERY;
|
|
else {
|
|
failf(data, "httpsig: unknown component '%s'", start);
|
|
return CURLE_BAD_FUNCTION_ARGUMENT;
|
|
}
|
|
}
|
|
}
|
|
if(!ncomp) {
|
|
failf(data, "httpsig: no signature components specified");
|
|
return CURLE_BAD_FUNCTION_ARGUMENT;
|
|
}
|
|
if(*p) {
|
|
failf(data, "httpsig: too many signature components (max %u)",
|
|
(unsigned int)HTTPSIG_MAX_COMPONENTS);
|
|
return CURLE_BAD_FUNCTION_ARGUMENT;
|
|
}
|
|
|
|
/* RFC 9421 Section 2: each covered component MUST occur only once */
|
|
{
|
|
size_t i, j;
|
|
|
|
for(i = 0; i < ncomp; i++) {
|
|
for(j = i + 1; j < ncomp; j++) {
|
|
if(!strcmp(components[i], components[j])) {
|
|
failf(data, "httpsig: duplicate signature component '%s'",
|
|
components[i]);
|
|
return CURLE_BAD_FUNCTION_ARGUMENT;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
else {
|
|
components[ncomp++] = SIG_METHOD;
|
|
components[ncomp++] = SIG_AUTHORITY;
|
|
components[ncomp++] = SIG_PATH;
|
|
if(query)
|
|
components[ncomp++] = SIG_QUERY;
|
|
}
|
|
|
|
*ncomp_out = ncomp;
|
|
return CURLE_OK;
|
|
}
|
|
|
|
static time_t httpsig_get_created(void)
|
|
{
|
|
#ifdef DEBUGBUILD
|
|
char *force = getenv("CURL_FORCETIME");
|
|
if(force && *force) {
|
|
char *sigts = getenv("CURL_HTTPSIG_CREATED");
|
|
if(sigts && *sigts) {
|
|
const char *p = sigts;
|
|
curl_off_t num;
|
|
if(!curlx_str_number(&p, &num, CURL_OFF_T_MAX))
|
|
return (time_t)num;
|
|
}
|
|
return 0;
|
|
}
|
|
#endif
|
|
return time(NULL);
|
|
}
|
|
|
|
static CURLcode httpsig_sign_base(struct Curl_easy *data,
|
|
enum httpsig_alg alg,
|
|
const unsigned char *keybuf,
|
|
size_t keylen,
|
|
const struct dynbuf *sig_base,
|
|
unsigned char *raw_sig,
|
|
size_t *raw_sig_len)
|
|
{
|
|
CURLcode result;
|
|
|
|
switch(alg) {
|
|
case HTTPSIG_ALG_ED25519:
|
|
result = Curl_ed25519_sign(
|
|
keybuf, keylen,
|
|
(const unsigned char *)curlx_dyn_ptr(sig_base),
|
|
curlx_dyn_len(sig_base),
|
|
raw_sig, raw_sig_len);
|
|
break;
|
|
case HTTPSIG_ALG_HMAC_SHA256:
|
|
result = Curl_hmacit(&Curl_HMAC_SHA256, keybuf, keylen,
|
|
(const unsigned char *)curlx_dyn_ptr(sig_base),
|
|
curlx_dyn_len(sig_base), raw_sig);
|
|
if(!result)
|
|
*raw_sig_len = CURL_SHA256_DIGEST_LENGTH;
|
|
break;
|
|
default:
|
|
result = CURLE_BAD_FUNCTION_ARGUMENT;
|
|
break;
|
|
}
|
|
|
|
if(result && result == CURLE_NOT_BUILT_IN) {
|
|
failf(data, "httpsig: algorithm '%s' not supported by TLS backend",
|
|
alg_to_str(alg));
|
|
}
|
|
return result;
|
|
}
|
|
|
|
CURLcode Curl_output_httpsig(struct Curl_easy *data)
|
|
{
|
|
CURLcode result = CURLE_OUT_OF_MEMORY;
|
|
struct connectdata *conn = data->conn;
|
|
const char *path;
|
|
const char *query;
|
|
Curl_HttpReq httpreq;
|
|
const char *method = NULL;
|
|
const char *hexkey;
|
|
const char *keyid;
|
|
enum httpsig_alg alg;
|
|
time_t created;
|
|
struct dynbuf sig_params;
|
|
struct dynbuf sig_base;
|
|
struct dynbuf sig_hdr;
|
|
struct dynbuf input_hdr;
|
|
struct dynbuf authority_buf;
|
|
const char *authority;
|
|
const char *components[HTTPSIG_MAX_COMPONENTS];
|
|
size_t ncomp = 0;
|
|
unsigned char *keybuf = NULL;
|
|
size_t keylen = 0;
|
|
unsigned char raw_sig[HTTPSIG_MAX_RAW_SIG];
|
|
size_t raw_sig_len = 0;
|
|
char *auth_headers = NULL;
|
|
char *hdrs_copy = NULL;
|
|
struct dynbuf query_dyn;
|
|
|
|
alg = id_to_alg(data->set.httpsig_algorithm);
|
|
if(alg == HTTPSIG_ALG_UNKNOWN) {
|
|
failf(data, "httpsig: CURLOPT_HTTPSIG_ALGORITHM is required");
|
|
return CURLE_BAD_FUNCTION_ARGUMENT;
|
|
}
|
|
|
|
hexkey = data->set.str[STRING_HTTPSIG_KEY];
|
|
keyid = data->set.str[STRING_HTTPSIG_KEYID];
|
|
|
|
if(!hexkey || !*hexkey) {
|
|
failf(data, "httpsig: CURLOPT_HTTPSIG_KEY is required");
|
|
return CURLE_BAD_FUNCTION_ARGUMENT;
|
|
}
|
|
if(!keyid || !*keyid) {
|
|
failf(data, "httpsig: CURLOPT_HTTPSIG_KEYID is required");
|
|
return CURLE_BAD_FUNCTION_ARGUMENT;
|
|
}
|
|
|
|
curlx_dyn_init(&sig_params, CURL_MAX_HTTP_HEADER);
|
|
curlx_dyn_init(&sig_base, HTTPSIG_MAX_SIG_BASE);
|
|
curlx_dyn_init(&sig_hdr, CURL_MAX_HTTP_HEADER);
|
|
curlx_dyn_init(&input_hdr, CURL_MAX_HTTP_HEADER);
|
|
curlx_dyn_init(&authority_buf, CURL_MAX_HTTP_HEADER);
|
|
curlx_dyn_init(&query_dyn, CURL_MAX_HTTP_HEADER);
|
|
|
|
if(Curl_checkheaders(data, STRCONST("Signature")) ||
|
|
Curl_checkheaders(data, STRCONST("Signature-Input"))) {
|
|
/* user provides their own Signature / Signature-Input headers, consider
|
|
this done */
|
|
goto done;
|
|
}
|
|
|
|
result = decode_hex_key(data, hexkey, &keybuf, &keylen);
|
|
if(result)
|
|
goto fail;
|
|
|
|
if(alg == HTTPSIG_ALG_ED25519 && keylen != 32) {
|
|
failf(data, "httpsig: ed25519 requires a 32-byte key (got %zu)", keylen);
|
|
result = CURLE_BAD_FUNCTION_ARGUMENT;
|
|
goto fail;
|
|
}
|
|
|
|
Curl_http_method(data, &method, &httpreq);
|
|
|
|
path = data->state.up.path;
|
|
if(!path || !*path)
|
|
path = "/";
|
|
|
|
query = data->state.up.query;
|
|
|
|
result = httpsig_authority(data, conn, &authority_buf);
|
|
if(result)
|
|
goto fail;
|
|
authority = curlx_dyn_ptr(&authority_buf);
|
|
|
|
/* Build @query value: RFC 9421 Section 2.2.7 - always starts with "?" */
|
|
if(query && *query)
|
|
result = curlx_dyn_addf(&query_dyn, "?%s", query);
|
|
else
|
|
result = curlx_dyn_add(&query_dyn, "?");
|
|
if(result)
|
|
goto fail;
|
|
|
|
result = parse_components(data, query, components, &ncomp, &hdrs_copy);
|
|
if(result)
|
|
goto fail;
|
|
|
|
created = httpsig_get_created();
|
|
|
|
result = build_sig_params(&sig_params, components, ncomp,
|
|
created, keyid, alg);
|
|
if(result)
|
|
goto fail;
|
|
|
|
infof(data, "httpsig: Signature-Input params: %s",
|
|
curlx_dyn_ptr(&sig_params));
|
|
|
|
result = build_sig_base(&sig_base, components, ncomp,
|
|
method, authority, path,
|
|
curlx_dyn_ptr(&query_dyn),
|
|
data, curlx_dyn_ptr(&sig_params));
|
|
if(result)
|
|
goto fail;
|
|
|
|
infof(data, "httpsig: Signature base: [%s]",
|
|
curlx_dyn_ptr(&sig_base));
|
|
|
|
result = httpsig_sign_base(data, alg, keybuf, keylen, &sig_base,
|
|
raw_sig, &raw_sig_len);
|
|
if(result)
|
|
goto fail;
|
|
|
|
result = curlx_dyn_add(&sig_hdr, HTTPSIG_DEFAULT_LABEL "=");
|
|
if(result)
|
|
goto fail;
|
|
result = sf_encode_byte_seq(raw_sig, raw_sig_len, &sig_hdr);
|
|
if(result)
|
|
goto fail;
|
|
|
|
result = curlx_dyn_addf(&input_hdr, "%s=%s", HTTPSIG_DEFAULT_LABEL,
|
|
curlx_dyn_ptr(&sig_params));
|
|
if(result)
|
|
goto fail;
|
|
|
|
auth_headers = curl_maprintf("Signature-Input: %s\r\n"
|
|
"Signature: %s\r\n",
|
|
curlx_dyn_ptr(&input_hdr),
|
|
curlx_dyn_ptr(&sig_hdr));
|
|
if(!auth_headers)
|
|
goto fail;
|
|
|
|
infof(data, "httpsig: Signature-Input: %s", curlx_dyn_ptr(&input_hdr));
|
|
infof(data, "httpsig: Signature: %s", curlx_dyn_ptr(&sig_hdr));
|
|
done:
|
|
curlx_free(data->req.hd_auth);
|
|
data->req.hd_auth = auth_headers;
|
|
data->state.authhost.done = TRUE;
|
|
result = CURLE_OK;
|
|
|
|
fail:
|
|
if(keybuf) {
|
|
memset(keybuf, 0, keylen);
|
|
curlx_free(keybuf);
|
|
}
|
|
memset(raw_sig, 0, sizeof(raw_sig));
|
|
curlx_free(hdrs_copy);
|
|
curlx_dyn_free(&sig_params);
|
|
curlx_dyn_free(&sig_base);
|
|
curlx_dyn_free(&sig_hdr);
|
|
curlx_dyn_free(&input_hdr);
|
|
curlx_dyn_free(&authority_buf);
|
|
curlx_dyn_free(&query_dyn);
|
|
return result;
|
|
}
|
|
|
|
#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_HTTPSIG */
|