httpsig: add RFC 9421 HTTP Message Signatures support

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
This commit is contained in:
Sameeh Jubran 2026-07-24 22:49:52 +02:00 committed by Daniel Stenberg
parent ebc5212dac
commit a55731050e
No known key found for this signature in database
GPG key ID: 5CC908FDB71E12C2
78 changed files with 3192 additions and 10 deletions

View file

@ -42,6 +42,10 @@ Disable support for the negotiate authentication methods.
Disable **aws-sigv4** support.
## `CURL_DISABLE_HTTPSIG`
Disable RFC 9421 HTTP Message Signatures support.
## `CURL_DISABLE_CA_SEARCH`
Disable unsafe CA bundle search in PATH on Windows.

View file

@ -105,3 +105,22 @@ Graduation requirements:
- HTTPS records can control ALPN and port number, at least
- There are options to control HTTPS use
## HTTP Message Signatures (RFC 9421)
Sign outgoing HTTP requests according to RFC 9421 using the
`--httpsig-algo`, `--httpsig-key`, `--httpsig-keyid` and
`--httpsig-headers` command line options, or the corresponding
`CURLOPT_HTTPSIG_*` libcurl options. Built only when configured with
`--enable-httpsig`.
Graduation requirements:
- the option set (names, arguments, defaults) is settled
- interoperability has been verified against at least two independent
RFC 9421 implementations
- no test cases are disabled for the feature
- feedback from users saying the API works for their use cases

View file

@ -3,3 +3,4 @@
# SPDX-License-Identifier: curl
curl.txt
asciipage.tmp.*

View file

@ -134,6 +134,10 @@ DPAGES = \
http2.md \
http3.md \
http3-only.md \
httpsig-algo.md \
httpsig-headers.md \
httpsig-key.md \
httpsig-keyid.md \
ignore-content-length.md \
insecure.md \
interface.md \

View file

@ -0,0 +1,36 @@
---
c: Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
SPDX-License-Identifier: curl
Long: httpsig-algo
Protocols: HTTP
Arg: <algorithm>
Help: Algorithm for HTTP Message Signatures
Category: auth http
Added: 8.22.0
Multi: single
Experimental: yes
See-also:
- httpsig-key
- httpsig-keyid
- httpsig-headers
Example:
- --httpsig-key key.hex --httpsig-keyid "my-key" $URL
- --httpsig-algo hmac-sha256 --httpsig-key secret.hex --httpsig-keyid "shared" $URL
---
# `--httpsig-algo`
Sign outgoing HTTP requests using RFC 9421 HTTP Message Signatures.
This option specifies which signing algorithm to use. Supported values are
**ed25519** and **hmac-sha256**. If not specified, **ed25519** is used. Any
other value causes curl to exit with an error.
HTTP Message Signatures are enabled when any of --httpsig-algo,
--httpsig-key, --httpsig-keyid or --httpsig-headers is given. When enabled,
--httpsig-key and --httpsig-keyid are required. Without any of these options
no signing is performed.
By default, the signed components are `method`, `authority`, `path`, and
`query` (when a query string is present). Use --httpsig-headers to override
the set of components included in the signature.

View file

@ -0,0 +1,45 @@
---
c: Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
SPDX-License-Identifier: curl
Long: httpsig-headers
Protocols: HTTP
Arg: <components>
Help: Components to sign for HTTP Message Signatures
Category: auth http
Added: 8.22.0
Multi: single
Experimental: yes
See-also:
- httpsig-algo
- httpsig-key
- httpsig-keyid
Example:
- --httpsig-algo ed25519 --httpsig-key key.hex --httpsig-keyid "my-key" --httpsig-headers "method authority content-type:" $URL
---
# `--httpsig-headers`
Space-separated list of components to include in the RFC 9421 HTTP Message
Signature. Derived components are given as bare names: `method`, `authority`,
`path`, and `query`. HTTP header fields are given with a trailing colon, for
example `content-type:` and `content-digest:`.
If not specified, the default set is `method authority path` (plus `query`
when a query string is present in the URL).
## Signing request headers
Header components are taken from `-H` / `--header` options only. Headers curl
adds by default (such as `User-Agent`) are not signed unless you set them
explicitly, for example:
curl --httpsig-algo ed25519 \
--httpsig-key k.hex \
--httpsig-keyid mykey \
-H "User-Agent: MyApp/1.0" \
--httpsig-headers \
"method authority path user-agent:" \
$URL
Each component may appear only once. Duplicate identifiers in
`--httpsig-headers` cause curl to exit with an error.

View file

@ -0,0 +1,35 @@
---
c: Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
SPDX-License-Identifier: curl
Long: httpsig-key
Protocols: HTTP
Arg: <file>
Help: Key file for HTTP Message Signatures
Category: auth http
Added: 8.22.0
Multi: single
Experimental: yes
See-also:
- httpsig-algo
- httpsig-keyid
Example:
- --httpsig-algo ed25519 --httpsig-key key.hex --httpsig-keyid "my-key" $URL
---
# `--httpsig-key`
Path to the key file used for RFC 9421 HTTP Message Signatures.
The file must contain a hex-encoded key on its first line. For **ed25519**,
this is the 32-byte private seed (64 hex characters). For **hmac-sha256**,
this is the shared secret. PEM files are not supported.
## Generating Ed25519 keys
With OpenSSL 3:
openssl genpkey -algorithm ED25519 -out k.pem
openssl pkey -in k.pem -outform RAW -out k.raw
xxd -p -c 64 k.raw | tr -d '\n' > k.hex
Use `k.hex` with `--httpsig-key`.

View file

@ -0,0 +1,23 @@
---
c: Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
SPDX-License-Identifier: curl
Long: httpsig-keyid
Protocols: HTTP
Arg: <id>
Help: Key identifier for HTTP Message Signatures
Category: auth http
Added: 8.22.0
Multi: single
Experimental: yes
See-also:
- httpsig-algo
- httpsig-key
Example:
- --httpsig-algo ed25519 --httpsig-key key.hex --httpsig-keyid "my-key" $URL
---
# `--httpsig-keyid`
The key identifier to include in the `Signature-Input` header when using RFC
9421 HTTP Message Signatures. This value appears as the `keyid` parameter and
allows the server to look up the correct verification key.

View file

@ -474,6 +474,22 @@ See CURLOPT_HTTPPOST(3)
Tunnel through the HTTP proxy. CURLOPT_HTTPPROXYTUNNEL(3)
## CURLOPT_HTTPSIG_ALGORITHM
RFC 9421 HTTP Message Signatures algorithm. See CURLOPT_HTTPSIG_ALGORITHM(3)
## CURLOPT_HTTPSIG_HEADERS
Components to sign for HTTP Message Signatures. See CURLOPT_HTTPSIG_HEADERS(3)
## CURLOPT_HTTPSIG_KEY
Hex-encoded key for HTTP Message Signatures. See CURLOPT_HTTPSIG_KEY(3)
## CURLOPT_HTTPSIG_KEYID
Key identifier for HTTP Message Signatures. See CURLOPT_HTTPSIG_KEYID(3)
## CURLOPT_HTTP_CONTENT_DECODING
Disable Content decoding. See CURLOPT_HTTP_CONTENT_DECODING(3)

View file

@ -232,6 +232,13 @@ HTTP/3 and QUIC support are built-in (Added in 7.66.0)
libcurl was built with support for HTTPS-proxy.
## `HTTPSIG`
*features* mask bit: non-existent
libcurl was built with support for RFC 9421 HTTP Message Signatures (Added in
8.22.0)
## `HTTPSRR`
*features* mask bit: non-existent

View file

@ -118,6 +118,11 @@ single auth algorithm is acceptable.
provides AWS V4 signature authentication on HTTPS header
see CURLOPT_AWS_SIGV4(3).
## CURLAUTH_HTTPSIG
provides RFC 9421 HTTP Message Signatures on outgoing requests,
see CURLOPT_HTTPSIG_ALGORITHM(3).
# DEFAULT
CURLAUTH_BASIC
@ -158,6 +163,8 @@ CURLAUTH_AWS_SIGV4 was added in 7.74.0
CURLAUTH_DIGEST_IE does nothing since 8.21.0
CURLAUTH_HTTPSIG was added in 8.22.0
# %AVAILABILITY%
# RETURN VALUE

View file

@ -0,0 +1,89 @@
---
c: Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
SPDX-License-Identifier: curl
Title: CURLOPT_HTTPSIG_ALGORITHM
Section: 3
Source: libcurl
See-also:
- CURLOPT_HTTPSIG_HEADERS (3)
- CURLOPT_HTTPSIG_KEY (3)
- CURLOPT_HTTPSIG_KEYID (3)
- CURLOPT_HTTPAUTH (3)
Protocol:
- HTTP
Added-in: 8.22.0
---
# NAME
CURLOPT_HTTPSIG_ALGORITHM - RFC 9421 HTTP Message Signatures algorithm
# SYNOPSIS
~~~c
#include <curl/curl.h>
CURLcode curl_easy_setopt(CURL *handle, CURLOPT_HTTPSIG_ALGORITHM,
long algorithm);
~~~
# DESCRIPTION
This feature is **experimental** and may change before it is considered
stable. We advise against using it in production.
Enable RFC 9421 HTTP Message Signatures on outgoing requests. Pass a long
set to one of the values below to select the signing algorithm.
## CURLHTTPSIG_NONE (0)
Disable HTTP Message Signatures.
## CURLHTTPSIG_ED25519 (1)
Sign with Ed25519 (RFC 8032). Requires a TLS backend with Ed25519 support.
## CURLHTTPSIG_HMAC_SHA256 (2)
Sign with HMAC-SHA256.
##
Setting this option to a non-zero value also sets CURLOPT_HTTPAUTH(3) to
CURLAUTH_HTTPSIG. The options CURLOPT_HTTPSIG_KEY(3) and
CURLOPT_HTTPSIG_KEYID(3) must also be set.
# DEFAULT
CURLHTTPSIG_NONE (0)
# %PROTOCOLS%
# EXAMPLE
~~~c
int main(void)
{
CURL *curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/api");
curl_easy_setopt(curl, CURLOPT_HTTPSIG_ALGORITHM,
CURLHTTPSIG_ED25519);
curl_easy_setopt(curl, CURLOPT_HTTPSIG_KEY,
"9f8362f87a484a954e6e740c5b4c0e84"
"229139a20aa8ab56ff66586f6a7d29c5");
curl_easy_setopt(curl, CURLOPT_HTTPSIG_KEYID, "my-key-id");
curl_easy_perform(curl);
}
}
~~~
# %AVAILABILITY%
# RETURN VALUE
curl_easy_setopt(3) returns a CURLcode indicating success or error.
CURLE_OK (0) means everything was OK, non-zero means an error occurred, see
libcurl-errors(3).

View file

@ -0,0 +1,108 @@
---
c: Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
SPDX-License-Identifier: curl
Title: CURLOPT_HTTPSIG_HEADERS
Section: 3
Source: libcurl
See-also:
- CURLOPT_HTTPSIG_ALGORITHM (3)
- CURLOPT_HTTPSIG_KEY (3)
- CURLOPT_HTTPSIG_KEYID (3)
Protocol:
- HTTP
Added-in: 8.22.0
---
# NAME
CURLOPT_HTTPSIG_HEADERS - components to sign for HTTP Message Signatures
# SYNOPSIS
~~~c
#include <curl/curl.h>
CURLcode curl_easy_setopt(CURL *handle, CURLOPT_HTTPSIG_HEADERS,
char *components);
~~~
# DESCRIPTION
This feature is **experimental** and may change before it is considered
stable. We advise against using it in production.
Pass a space-separated list of component identifiers to include in the
RFC 9421 HTTP Message Signature.
Derived components are given as bare names:
- **method** - the HTTP method (GET, POST, etc.)
- **authority** - the host and optional port
- **path** - the request path
- **query** - the query string including the leading `?`
HTTP header fields are given with a trailing colon, for example `content-type:`
or `content-digest:`. This mirrors how a header looks and keeps a leading `@`
free for its usual curl meaning (read the value from a file).
If this option is not set, the default components are **method**, **authority**,
**path** (plus **query** when a query string is present).
## Signing request headers
Header components are resolved from the list set with CURLOPT_HTTPHEADER(3)
only. Headers that libcurl adds later (such as the default `User-Agent`) are
**not** visible to the signer unless the application supplies them explicitly.
To sign `User-Agent`, supply it via CURLOPT_HTTPHEADER(3) together with this
option before the transfer; see EXAMPLE.
Each component identifier may appear at most once (RFC 9421 Section 2).
Listing the same component twice returns `CURLE_BAD_FUNCTION_ARGUMENT`.
At most 16 components are accepted; supplying more returns
`CURLE_BAD_FUNCTION_ARGUMENT`.
The application does not have to keep the string around after setting this
option.
# DEFAULT
NULL (uses the default component set)
# %PROTOCOLS%
# EXAMPLE
~~~c
int main(void)
{
CURL *curl = curl_easy_init();
struct curl_slist *headers = NULL;
if(curl) {
headers = curl_slist_append(headers, "User-Agent: MyApp/1.0");
curl_easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/api");
curl_easy_setopt(curl, CURLOPT_HTTPSIG_ALGORITHM,
CURLHTTPSIG_ED25519);
curl_easy_setopt(curl, CURLOPT_HTTPSIG_KEY,
"9f8362f87a484a954e6e740c5b4c0e84"
"229139a20aa8ab56ff66586f6a7d29c5");
curl_easy_setopt(curl, CURLOPT_HTTPSIG_KEYID, "my-key-id");
curl_easy_setopt(curl, CURLOPT_HTTPSIG_HEADERS,
"method authority path content-type: user-agent:");
curl_easy_perform(curl);
curl_slist_free_all(headers);
}
}
~~~
# %AVAILABILITY%
# RETURN VALUE
curl_easy_setopt(3) returns a CURLcode indicating success or error.
CURLE_OK (0) means everything was OK, non-zero means an error occurred, see
libcurl-errors(3).

View file

@ -0,0 +1,90 @@
---
c: Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
SPDX-License-Identifier: curl
Title: CURLOPT_HTTPSIG_KEY
Section: 3
Source: libcurl
See-also:
- CURLOPT_HTTPSIG_ALGORITHM (3)
- CURLOPT_HTTPSIG_KEYID (3)
Protocol:
- HTTP
Added-in: 8.22.0
---
# NAME
CURLOPT_HTTPSIG_KEY - hex-encoded key for HTTP Message Signatures
# SYNOPSIS
~~~c
#include <curl/curl.h>
CURLcode curl_easy_setopt(CURL *handle, CURLOPT_HTTPSIG_KEY, char *key);
~~~
# DESCRIPTION
This feature is **experimental** and may change before it is considered
stable. We advise against using it in production.
Pass a null-terminated string containing the hex-encoded private key or
shared secret used for RFC 9421 HTTP Message Signatures.
For **ed25519**, this is the 32-byte private seed (64 hex characters). For
**hmac-sha256**, this is the shared secret as hex; the decoded length is half
the number of hex digits, up to `CURL_MAX_INPUT_LENGTH / 2` bytes (the same
upper bound as other libcurl string options).
PEM and other encodings are not supported; pass the raw key material as hex.
## Generating Ed25519 keys
With OpenSSL 3:
openssl genpkey -algorithm ED25519 -out ed25519.pem
openssl pkey -in ed25519.pem -outform RAW | xxd -p -c 64 | tr -d '\n' > key.hex
The `key.hex` file is one line of 64 hexadecimal digits.
The application does not have to keep the string around after setting this
option.
Using this option multiple times makes the last set string override the
previous ones. Set it to NULL to disable its use again.
# DEFAULT
NULL
# %PROTOCOLS%
# EXAMPLE
~~~c
int main(void)
{
CURL *curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/api");
curl_easy_setopt(curl, CURLOPT_HTTPSIG_ALGORITHM,
CURLHTTPSIG_ED25519);
curl_easy_setopt(curl, CURLOPT_HTTPSIG_KEY,
"9f8362f87a484a954e6e740c5b4c0e84"
"229139a20aa8ab56ff66586f6a7d29c5");
curl_easy_setopt(curl, CURLOPT_HTTPSIG_KEYID, "my-key-id");
curl_easy_perform(curl);
}
}
~~~
# %AVAILABILITY%
# RETURN VALUE
curl_easy_setopt(3) returns a CURLcode indicating success or error.
CURLE_OK (0) means everything was OK, non-zero means an error occurred, see
libcurl-errors(3).

View file

@ -0,0 +1,76 @@
---
c: Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
SPDX-License-Identifier: curl
Title: CURLOPT_HTTPSIG_KEYID
Section: 3
Source: libcurl
See-also:
- CURLOPT_HTTPSIG_ALGORITHM (3)
- CURLOPT_HTTPSIG_KEY (3)
Protocol:
- HTTP
Added-in: 8.22.0
---
# NAME
CURLOPT_HTTPSIG_KEYID - key identifier for HTTP Message Signatures
# SYNOPSIS
~~~c
#include <curl/curl.h>
CURLcode curl_easy_setopt(CURL *handle, CURLOPT_HTTPSIG_KEYID, char *keyid);
~~~
# DESCRIPTION
This feature is **experimental** and may change before it is considered
stable. We advise against using it in production.
Pass a null-terminated string that identifies the key used for RFC 9421 HTTP
Message Signatures. This value is included as the `keyid` parameter in the
`Signature-Input` header, allowing the server to look up the corresponding
verification key.
The application does not have to keep the string around after setting this
option.
Using this option multiple times makes the last set string override the
previous ones. Set it to NULL to disable its use again.
# DEFAULT
NULL
# %PROTOCOLS%
# EXAMPLE
~~~c
int main(void)
{
CURL *curl = curl_easy_init();
if(curl) {
curl_easy_setopt(curl, CURLOPT_URL, "https://example.com/api");
curl_easy_setopt(curl, CURLOPT_HTTPSIG_ALGORITHM,
CURLHTTPSIG_ED25519);
curl_easy_setopt(curl, CURLOPT_HTTPSIG_KEY,
"9f8362f87a484a954e6e740c5b4c0e84"
"229139a20aa8ab56ff66586f6a7d29c5");
curl_easy_setopt(curl, CURLOPT_HTTPSIG_KEYID, "my-key-id");
curl_easy_perform(curl);
}
}
~~~
# %AVAILABILITY%
# RETURN VALUE
curl_easy_setopt(3) returns a CURLcode indicating success or error.
CURLE_OK (0) means everything was OK, non-zero means an error occurred, see
libcurl-errors(3).

View file

@ -227,6 +227,10 @@ man_MANS = \
CURLOPT_HTTPHEADER.3 \
CURLOPT_HTTPPOST.3 \
CURLOPT_HTTPPROXYTUNNEL.3 \
CURLOPT_HTTPSIG_ALGORITHM.3 \
CURLOPT_HTTPSIG_HEADERS.3 \
CURLOPT_HTTPSIG_KEY.3 \
CURLOPT_HTTPSIG_KEYID.3 \
CURLOPT_IGNORE_CONTENT_LENGTH.3 \
CURLOPT_INFILESIZE.3 \
CURLOPT_INFILESIZE_LARGE.3 \

View file

@ -202,6 +202,7 @@ CURLALTSVC_READONLYFILE 7.64.1
CURLAUTH_ANY 7.10.6
CURLAUTH_ANYSAFE 7.10.6
CURLAUTH_AWS_SIGV4 7.75.0
CURLAUTH_HTTPSIG 8.22.0
CURLAUTH_BASIC 7.10.6
CURLAUTH_BEARER 7.61.0
CURLAUTH_DIGEST 7.10.6
@ -420,6 +421,9 @@ CURLHEADER_SEPARATE 7.37.0
CURLHEADER_UNIFIED 7.37.0
CURLHSTS_ENABLE 7.74.0
CURLHSTS_READONLYFILE 7.74.0
CURLHTTPSIG_ED25519 8.22.0
CURLHTTPSIG_HMAC_SHA256 8.22.0
CURLHTTPSIG_NONE 8.22.0
CURLINFO_ACTIVESOCKET 7.45.0
CURLINFO_APPCONNECT_TIME 7.19.0
CURLINFO_APPCONNECT_TIME_T 7.61.0
@ -697,6 +701,10 @@ CURLOPT_HTTPHEADER 7.1
CURLOPT_HTTPPOST 7.1 7.56.0
CURLOPT_HTTPPROXYTUNNEL 7.3
CURLOPT_HTTPREQUEST 7.1 - 7.15.5
CURLOPT_HTTPSIG_ALGORITHM 8.22.0
CURLOPT_HTTPSIG_HEADERS 8.22.0
CURLOPT_HTTPSIG_KEY 8.22.0
CURLOPT_HTTPSIG_KEYID 8.22.0
CURLOPT_IGNORE_CONTENT_LENGTH 7.14.1
CURLOPT_INFILE 7.1 7.9.7
CURLOPT_INFILESIZE 7.1

View file

@ -98,6 +98,10 @@
--http2-prior-knowledge 7.49.0
--http3 7.66.0
--http3-only 7.88.0
--httpsig-algo 8.22.0
--httpsig-headers 8.22.0
--httpsig-key 8.22.0
--httpsig-keyid 8.22.0
--ignore-content-length 7.14.1
--ip-tos 8.9.0
--ipfs-gateway 8.4.0