| /*************************************************************************** | |
| * _ _ ____ _ | |
| * 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.http_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.http_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->origin->scheme->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 = CURL_EASY_STR(data, 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 = CURL_EASY_STR(data, STRING_HTTPSIG_KEY); | |
| const char *keyid = CURL_EASY_STR(data, STRING_HTTPSIG_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; | |
| } | |
| 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 */ |