mirror of
https://github.com/curl/curl.git
synced 2026-07-27 22:43:12 +03:00
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:
parent
ebc5212dac
commit
a55731050e
78 changed files with 3192 additions and 10 deletions
5
.github/scripts/pyspelling.words
vendored
5
.github/scripts/pyspelling.words
vendored
|
|
@ -368,6 +368,9 @@ httpget
|
|||
HttpGet
|
||||
HTTPS
|
||||
https
|
||||
httpsig
|
||||
HTTPSIG
|
||||
CURLHTTPSIG
|
||||
HTTPSRR
|
||||
hyper's
|
||||
IANA
|
||||
|
|
@ -428,6 +431,7 @@ KDE
|
|||
keepalive
|
||||
Keil
|
||||
kerberos
|
||||
keyid
|
||||
Keychain
|
||||
keychain
|
||||
KiB
|
||||
|
|
@ -996,6 +1000,7 @@ WinIDN
|
|||
WinLDAP
|
||||
winsock
|
||||
Wireshark
|
||||
wolfCrypt
|
||||
wolfSSH
|
||||
wolfSSL
|
||||
ws
|
||||
|
|
|
|||
4
.github/workflows/linux.yml
vendored
4
.github/workflows/linux.yml
vendored
|
|
@ -200,7 +200,7 @@ jobs:
|
|||
install_steps: wolfssl-opensslextra-arm
|
||||
tflags: '--min=815 1 to 1000'
|
||||
LDFLAGS: -Wl,-rpath,/home/runner/wolfssl-opensslextra/lib
|
||||
configure: --with-wolfssl=/home/runner/wolfssl-opensslextra --enable-ech --enable-debug
|
||||
configure: --with-wolfssl=/home/runner/wolfssl-opensslextra --enable-ech --enable-debug --enable-httpsig
|
||||
|
||||
- name: 'wolfssl-opensslextra valgrind 2'
|
||||
image: ubuntu-26.04-arm
|
||||
|
|
@ -208,7 +208,7 @@ jobs:
|
|||
install_steps: wolfssl-opensslextra-arm
|
||||
tflags: '--min=835 1001 to 9999'
|
||||
LDFLAGS: -Wl,-rpath,/home/runner/wolfssl-opensslextra/lib
|
||||
configure: --with-wolfssl=/home/runner/wolfssl-opensslextra --enable-ech --enable-debug
|
||||
configure: --with-wolfssl=/home/runner/wolfssl-opensslextra --enable-ech --enable-debug --enable-httpsig
|
||||
|
||||
- name: 'openssl default'
|
||||
install_steps: pytest
|
||||
|
|
|
|||
|
|
@ -435,6 +435,8 @@ option(CURL_DISABLE_NEGOTIATE_AUTH "Disable negotiate authentication" OFF)
|
|||
mark_as_advanced(CURL_DISABLE_NEGOTIATE_AUTH)
|
||||
option(CURL_DISABLE_AWS "Disable aws-sigv4" OFF)
|
||||
mark_as_advanced(CURL_DISABLE_AWS)
|
||||
option(CURL_DISABLE_HTTPSIG "Disable HTTP Message Signatures (RFC 9421)" ON)
|
||||
mark_as_advanced(CURL_DISABLE_HTTPSIG)
|
||||
option(CURL_DISABLE_DICT "Disable DICT" OFF)
|
||||
mark_as_advanced(CURL_DISABLE_DICT)
|
||||
option(CURL_DISABLE_DOH "Disable DNS-over-HTTPS" OFF)
|
||||
|
|
@ -2037,6 +2039,7 @@ curl_add_if("SSPI" USE_WINDOWS_SSPI)
|
|||
curl_add_if("GSS-API" HAVE_GSSAPI)
|
||||
curl_add_if("alt-svc" NOT CURL_DISABLE_ALTSVC)
|
||||
curl_add_if("HSTS" NOT CURL_DISABLE_HSTS)
|
||||
curl_add_if("HTTPSIG" NOT CURL_DISABLE_HTTPSIG)
|
||||
curl_add_if("SPNEGO" NOT CURL_DISABLE_NEGOTIATE_AUTH AND
|
||||
(HAVE_GSSAPI OR USE_WINDOWS_SSPI))
|
||||
curl_add_if("Kerberos" NOT CURL_DISABLE_KERBEROS_AUTH AND
|
||||
|
|
|
|||
28
configure.ac
28
configure.ac
|
|
@ -4566,6 +4566,29 @@ AS_HELP_STRING([--disable-aws],[Disable AWS sig support]),
|
|||
AC_MSG_RESULT(yes)
|
||||
)
|
||||
|
||||
dnl ************************************************************
|
||||
dnl disable httpsig (RFC 9421)
|
||||
dnl
|
||||
AC_MSG_CHECKING([whether to enable HTTP Message Signatures (RFC 9421)])
|
||||
AC_ARG_ENABLE(httpsig,
|
||||
AS_HELP_STRING([--enable-httpsig],[Enable HTTP Message Signatures support])
|
||||
AS_HELP_STRING([--disable-httpsig],[Disable HTTP Message Signatures support (default)]),
|
||||
[ case "$enableval" in
|
||||
no)
|
||||
AC_MSG_RESULT(no)
|
||||
AC_DEFINE(CURL_DISABLE_HTTPSIG, 1, [to disable HTTP Message Signatures support])
|
||||
want_httpsig="no"
|
||||
;;
|
||||
*)
|
||||
AC_MSG_RESULT(yes)
|
||||
want_httpsig="yes"
|
||||
;;
|
||||
esac ],
|
||||
AC_MSG_RESULT(no)
|
||||
AC_DEFINE(CURL_DISABLE_HTTPSIG, 1, [to disable HTTP Message Signatures support])
|
||||
want_httpsig="no"
|
||||
)
|
||||
|
||||
dnl ************************************************************
|
||||
dnl disable NTLM support
|
||||
dnl
|
||||
|
|
@ -5313,6 +5336,11 @@ if test "$want_httpsrr" != "no"; then
|
|||
SUPPORT_FEATURES="$SUPPORT_FEATURES HTTPSRR"
|
||||
fi
|
||||
|
||||
if test "$want_httpsig" = "yes"; then
|
||||
SUPPORT_FEATURES="$SUPPORT_FEATURES HTTPSIG"
|
||||
experimental="$experimental HTTPSIG"
|
||||
fi
|
||||
|
||||
if test "$SSLS_EXPORT_ENABLED" = "1"; then
|
||||
SUPPORT_FEATURES="$SUPPORT_FEATURES SSLS-EXPORT"
|
||||
fi
|
||||
|
|
|
|||
|
|
@ -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.
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
1
docs/cmdline-opts/.gitignore
vendored
1
docs/cmdline-opts/.gitignore
vendored
|
|
@ -3,3 +3,4 @@
|
|||
# SPDX-License-Identifier: curl
|
||||
|
||||
curl.txt
|
||||
asciipage.tmp.*
|
||||
|
|
|
|||
|
|
@ -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 \
|
||||
|
|
|
|||
36
docs/cmdline-opts/httpsig-algo.md
Normal file
36
docs/cmdline-opts/httpsig-algo.md
Normal 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.
|
||||
45
docs/cmdline-opts/httpsig-headers.md
Normal file
45
docs/cmdline-opts/httpsig-headers.md
Normal 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.
|
||||
35
docs/cmdline-opts/httpsig-key.md
Normal file
35
docs/cmdline-opts/httpsig-key.md
Normal 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`.
|
||||
23
docs/cmdline-opts/httpsig-keyid.md
Normal file
23
docs/cmdline-opts/httpsig-keyid.md
Normal 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.
|
||||
|
|
@ -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)
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
89
docs/libcurl/opts/CURLOPT_HTTPSIG_ALGORITHM.md
Normal file
89
docs/libcurl/opts/CURLOPT_HTTPSIG_ALGORITHM.md
Normal 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).
|
||||
108
docs/libcurl/opts/CURLOPT_HTTPSIG_HEADERS.md
Normal file
108
docs/libcurl/opts/CURLOPT_HTTPSIG_HEADERS.md
Normal 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).
|
||||
90
docs/libcurl/opts/CURLOPT_HTTPSIG_KEY.md
Normal file
90
docs/libcurl/opts/CURLOPT_HTTPSIG_KEY.md
Normal 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).
|
||||
76
docs/libcurl/opts/CURLOPT_HTTPSIG_KEYID.md
Normal file
76
docs/libcurl/opts/CURLOPT_HTTPSIG_KEYID.md
Normal 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).
|
||||
|
|
@ -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 \
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -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
|
||||
|
|
|
|||
|
|
@ -843,12 +843,20 @@ typedef enum {
|
|||
#endif
|
||||
#define CURLAUTH_BEARER (((unsigned long)1) << 6)
|
||||
#define CURLAUTH_AWS_SIGV4 (((unsigned long)1) << 7)
|
||||
#define CURLAUTH_HTTPSIG (((unsigned long)1) << 8)
|
||||
#define CURLAUTH_ONLY (((unsigned long)1) << 31)
|
||||
#define CURLAUTH_ANY ((~CURLAUTH_DIGEST_IE) & \
|
||||
#define CURLAUTH_ANY ((~(CURLAUTH_DIGEST_IE | \
|
||||
CURLAUTH_HTTPSIG)) & \
|
||||
((unsigned long)0xffffffff))
|
||||
#define CURLAUTH_ANYSAFE ((~(CURLAUTH_BASIC | CURLAUTH_DIGEST_IE)) & \
|
||||
#define CURLAUTH_ANYSAFE ((~(CURLAUTH_BASIC | CURLAUTH_DIGEST_IE | \
|
||||
CURLAUTH_HTTPSIG)) & \
|
||||
((unsigned long)0xffffffff))
|
||||
|
||||
/* constants for CURLOPT_HTTPSIG_ALGORITHM */
|
||||
#define CURLHTTPSIG_NONE 0L
|
||||
#define CURLHTTPSIG_ED25519 1L
|
||||
#define CURLHTTPSIG_HMAC_SHA256 2L
|
||||
|
||||
/* all types supported by server */
|
||||
#define CURLSSH_AUTH_ANY ((unsigned long)0xffffffff)
|
||||
#define CURLSSH_AUTH_NONE 0L /* none allowed, silly but complete */
|
||||
|
|
@ -2266,6 +2274,18 @@ typedef enum {
|
|||
/* set TLS supported signature algorithms */
|
||||
CURLOPT(CURLOPT_SSL_SIGNATURE_ALGORITHMS, CURLOPTTYPE_STRINGPOINT, 328),
|
||||
|
||||
/* RFC 9421 HTTP Message Signatures algorithm */
|
||||
CURLOPT(CURLOPT_HTTPSIG_ALGORITHM, CURLOPTTYPE_VALUES, 329),
|
||||
|
||||
/* Hex-encoded key for HTTP Message Signatures */
|
||||
CURLOPT(CURLOPT_HTTPSIG_KEY, CURLOPTTYPE_STRINGPOINT, 330),
|
||||
|
||||
/* Key identifier for HTTP Message Signatures */
|
||||
CURLOPT(CURLOPT_HTTPSIG_KEYID, CURLOPTTYPE_STRINGPOINT, 331),
|
||||
|
||||
/* Space-separated list of components to sign for HTTP Message Signatures */
|
||||
CURLOPT(CURLOPT_HTTPSIG_HEADERS, CURLOPTTYPE_STRINGPOINT, 332),
|
||||
|
||||
CURLOPT_LASTENTRY /* the last unused */
|
||||
} CURLoption;
|
||||
|
||||
|
|
|
|||
|
|
@ -496,6 +496,9 @@ CURLWARNING(Wcurl_easy_getinfo_err_curl_off_t,
|
|||
(option) == CURLOPT_USERAGENT || \
|
||||
(option) == CURLOPT_USERNAME || \
|
||||
(option) == CURLOPT_AWS_SIGV4 || \
|
||||
(option) == CURLOPT_HTTPSIG_HEADERS || \
|
||||
(option) == CURLOPT_HTTPSIG_KEY || \
|
||||
(option) == CURLOPT_HTTPSIG_KEYID || \
|
||||
(option) == CURLOPT_USERPWD || \
|
||||
(option) == CURLOPT_XOAUTH2_BEARER || \
|
||||
0)
|
||||
|
|
|
|||
|
|
@ -222,6 +222,8 @@ LIB_CFILES = \
|
|||
http1.c \
|
||||
http2.c \
|
||||
http_aws_sigv4.c \
|
||||
http_httpsig.c \
|
||||
curl_ed25519.c \
|
||||
http_chunks.c \
|
||||
http_digest.c \
|
||||
http_negotiate.c \
|
||||
|
|
@ -363,6 +365,8 @@ LIB_HFILES = \
|
|||
http1.h \
|
||||
http2.h \
|
||||
http_aws_sigv4.h \
|
||||
http_httpsig.h \
|
||||
curl_ed25519.h \
|
||||
http_chunks.h \
|
||||
http_digest.h \
|
||||
http_negotiate.h \
|
||||
|
|
|
|||
|
|
@ -61,6 +61,9 @@
|
|||
/* disables aws-sigv4 */
|
||||
#cmakedefine CURL_DISABLE_AWS 1
|
||||
|
||||
/* disables HTTP Message Signatures (RFC 9421) */
|
||||
#cmakedefine CURL_DISABLE_HTTPSIG 1
|
||||
|
||||
/* disables DICT */
|
||||
#cmakedefine CURL_DISABLE_DICT 1
|
||||
|
||||
|
|
|
|||
170
lib/curl_ed25519.c
Normal file
170
lib/curl_ed25519.c
Normal file
|
|
@ -0,0 +1,170 @@
|
|||
/***************************************************************************
|
||||
* _ _ ____ _
|
||||
* 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)
|
||||
|
||||
/* Please keep the SSL backend-specific #if branches in this order:
|
||||
*
|
||||
* 1. USE_OPENSSL
|
||||
* 2. USE_WOLFSSL
|
||||
* 3. USE_GNUTLS
|
||||
* 4. USE_MBEDTLS
|
||||
*/
|
||||
|
||||
#include "curl_ed25519.h"
|
||||
|
||||
#ifdef USE_OPENSSL
|
||||
#include <openssl/evp.h>
|
||||
|
||||
CURLcode Curl_ed25519_sign(const unsigned char *key, size_t keylen,
|
||||
const unsigned char *msg, size_t msglen,
|
||||
unsigned char *sig, size_t *siglen)
|
||||
{
|
||||
EVP_PKEY *pkey;
|
||||
EVP_MD_CTX *mdctx;
|
||||
size_t slen;
|
||||
int rc;
|
||||
|
||||
if(keylen != CURL_ED25519_KEYLEN)
|
||||
return CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
|
||||
pkey = EVP_PKEY_new_raw_private_key(EVP_PKEY_ED25519, NULL, key, keylen);
|
||||
if(!pkey)
|
||||
return CURLE_AUTH_ERROR;
|
||||
|
||||
mdctx = EVP_MD_CTX_new();
|
||||
if(!mdctx) {
|
||||
EVP_PKEY_free(pkey);
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
rc = EVP_DigestSignInit(mdctx, NULL, NULL, NULL, pkey);
|
||||
if(rc != 1) {
|
||||
EVP_MD_CTX_free(mdctx);
|
||||
EVP_PKEY_free(pkey);
|
||||
return CURLE_AUTH_ERROR;
|
||||
}
|
||||
|
||||
slen = CURL_ED25519_SIGLEN;
|
||||
rc = EVP_DigestSign(mdctx, sig, &slen, msg, msglen);
|
||||
|
||||
EVP_MD_CTX_free(mdctx);
|
||||
EVP_PKEY_free(pkey);
|
||||
|
||||
if(rc != 1)
|
||||
return CURLE_AUTH_ERROR;
|
||||
|
||||
*siglen = slen;
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
#elif defined(USE_WOLFSSL)
|
||||
#include <wolfssl/options.h>
|
||||
#include <wolfssl/wolfcrypt/settings.h>
|
||||
#if (defined(HAVE_ED25519) || defined(WOLFSSL_CURVE25519_USE_ED25519)) && \
|
||||
defined(HAVE_ED25519_KEY_IMPORT) && defined(HAVE_ED25519_SIGN)
|
||||
#include <wolfssl/wolfcrypt/ed25519.h>
|
||||
#include <wolfssl/wolfcrypt/error-crypt.h>
|
||||
#include <wolfssl/wolfcrypt/random.h>
|
||||
|
||||
CURLcode Curl_ed25519_sign(const unsigned char *key, size_t keylen,
|
||||
const unsigned char *msg, size_t msglen,
|
||||
unsigned char *sig, size_t *siglen)
|
||||
{
|
||||
int ret;
|
||||
WC_RNG rng;
|
||||
ed25519_key edkey;
|
||||
word32 outlen;
|
||||
unsigned char pubkey[ED25519_PUB_KEY_SIZE];
|
||||
|
||||
if(keylen != ED25519_KEY_SIZE)
|
||||
return CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
|
||||
ret = wc_InitRng(&rng);
|
||||
if(ret)
|
||||
return CURLE_AUTH_ERROR;
|
||||
|
||||
ret = wc_ed25519_init(&edkey);
|
||||
if(ret) {
|
||||
wc_FreeRng(&rng);
|
||||
return CURLE_AUTH_ERROR;
|
||||
}
|
||||
|
||||
ret = wc_ed25519_import_private_only(key, ED25519_KEY_SIZE, &edkey);
|
||||
if(ret)
|
||||
goto fail;
|
||||
|
||||
ret = wc_ed25519_make_public(&edkey, pubkey, ED25519_PUB_KEY_SIZE);
|
||||
if(ret)
|
||||
goto fail;
|
||||
|
||||
ret = wc_ed25519_import_private_key(key, ED25519_KEY_SIZE,
|
||||
pubkey, ED25519_PUB_KEY_SIZE, &edkey);
|
||||
if(ret)
|
||||
goto fail;
|
||||
|
||||
outlen = ED25519_SIG_SIZE;
|
||||
ret = wc_ed25519_sign_msg(msg, (word32)msglen, sig, &outlen, &edkey);
|
||||
if(ret)
|
||||
goto fail;
|
||||
|
||||
*siglen = (size_t)outlen;
|
||||
wc_ed25519_free(&edkey);
|
||||
wc_FreeRng(&rng);
|
||||
return CURLE_OK;
|
||||
|
||||
fail:
|
||||
wc_ed25519_free(&edkey);
|
||||
wc_FreeRng(&rng);
|
||||
return CURLE_AUTH_ERROR;
|
||||
}
|
||||
|
||||
#else /* USE_WOLFSSL but no Ed25519 sign/import in this wolfSSL build */
|
||||
|
||||
CURLcode Curl_ed25519_sign(const unsigned char *key, size_t keylen,
|
||||
const unsigned char *msg, size_t msglen,
|
||||
unsigned char *sig, size_t *siglen)
|
||||
{
|
||||
(void)key; (void)keylen; (void)msg; (void)msglen;
|
||||
(void)sig; (void)siglen;
|
||||
return CURLE_NOT_BUILT_IN;
|
||||
}
|
||||
|
||||
#endif /* wolfSSL Ed25519 */
|
||||
|
||||
#else /* no Ed25519-capable backend */
|
||||
|
||||
CURLcode Curl_ed25519_sign(const unsigned char *key, size_t keylen,
|
||||
const unsigned char *msg, size_t msglen,
|
||||
unsigned char *sig, size_t *siglen)
|
||||
{
|
||||
(void)key; (void)keylen; (void)msg; (void)msglen;
|
||||
(void)sig; (void)siglen;
|
||||
return CURLE_NOT_BUILT_IN;
|
||||
}
|
||||
|
||||
#endif /* Ed25519 backends */
|
||||
|
||||
#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_HTTPSIG */
|
||||
44
lib/curl_ed25519.h
Normal file
44
lib/curl_ed25519.h
Normal file
|
|
@ -0,0 +1,44 @@
|
|||
#ifndef HEADER_CURL_ED25519_H
|
||||
#define HEADER_CURL_ED25519_H
|
||||
/***************************************************************************
|
||||
* _ _ ____ _
|
||||
* 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)
|
||||
|
||||
#define CURL_ED25519_SIGLEN 64
|
||||
#define CURL_ED25519_KEYLEN 32
|
||||
|
||||
/* Sign with Ed25519 (RFC 8032).
|
||||
* key/keylen: raw 32-byte private seed
|
||||
* msg/msglen: data to sign
|
||||
* sig: output buffer (at least CURL_ED25519_SIGLEN bytes)
|
||||
* siglen: out - actual signature length on success
|
||||
* Returns CURLE_OK or CURLE_NOT_BUILT_IN if no backend supports Ed25519. */
|
||||
CURLcode Curl_ed25519_sign(const unsigned char *key, size_t keylen,
|
||||
const unsigned char *msg, size_t msglen,
|
||||
unsigned char *sig, size_t *siglen);
|
||||
|
||||
#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_HTTPSIG */
|
||||
#endif /* HEADER_CURL_ED25519_H */
|
||||
|
|
@ -26,6 +26,7 @@
|
|||
|
||||
#if (defined(USE_CURL_NTLM_CORE) && !defined(USE_WINDOWS_SSPI)) || \
|
||||
!defined(CURL_DISABLE_AWS) || !defined(CURL_DISABLE_DIGEST_AUTH) || \
|
||||
!defined(CURL_DISABLE_HTTPSIG) || \
|
||||
defined(USE_LIBSSH2) || defined(USE_SSL)
|
||||
|
||||
#define HMAC_MD5_LENGTH 16
|
||||
|
|
|
|||
|
|
@ -298,6 +298,9 @@
|
|||
# ifndef CURL_DISABLE_AWS
|
||||
# define CURL_DISABLE_AWS
|
||||
# endif
|
||||
# ifndef CURL_DISABLE_HTTPSIG
|
||||
# define CURL_DISABLE_HTTPSIG
|
||||
# endif
|
||||
# ifndef CURL_DISABLE_DOH
|
||||
# define CURL_DISABLE_DOH
|
||||
# endif
|
||||
|
|
|
|||
|
|
@ -26,7 +26,8 @@
|
|||
***************************************************************************/
|
||||
#include "curl_setup.h"
|
||||
|
||||
#if !defined(CURL_DISABLE_AWS) || !defined(CURL_DISABLE_DIGEST_AUTH) || \
|
||||
#if !defined(CURL_DISABLE_AWS) || !defined(CURL_DISABLE_HTTPSIG) || \
|
||||
!defined(CURL_DISABLE_DIGEST_AUTH) || \
|
||||
defined(USE_LIBSSH2) || defined(USE_SSL)
|
||||
|
||||
#include "curl_hmac.h"
|
||||
|
|
|
|||
|
|
@ -140,6 +140,10 @@ const struct curl_easyoption Curl_easyopts[] = {
|
|||
{ "HTTPHEADER", CURLOPT_HTTPHEADER, CURLOT_SLIST, 0 },
|
||||
{ "HTTPPOST", CURLOPT_HTTPPOST, CURLOT_OBJECT, 0 },
|
||||
{ "HTTPPROXYTUNNEL", CURLOPT_HTTPPROXYTUNNEL, CURLOT_LONG, 0 },
|
||||
{ "HTTPSIG_ALGORITHM", CURLOPT_HTTPSIG_ALGORITHM, CURLOT_VALUES, 0 },
|
||||
{ "HTTPSIG_HEADERS", CURLOPT_HTTPSIG_HEADERS, CURLOT_STRING, 0 },
|
||||
{ "HTTPSIG_KEY", CURLOPT_HTTPSIG_KEY, CURLOT_STRING, 0 },
|
||||
{ "HTTPSIG_KEYID", CURLOPT_HTTPSIG_KEYID, CURLOT_STRING, 0 },
|
||||
{ "HTTP_CONTENT_DECODING", CURLOPT_HTTP_CONTENT_DECODING, CURLOT_LONG, 0 },
|
||||
{ "HTTP_TRANSFER_DECODING", CURLOPT_HTTP_TRANSFER_DECODING,
|
||||
CURLOT_LONG, 0 },
|
||||
|
|
@ -385,6 +389,6 @@ const struct curl_easyoption Curl_easyopts[] = {
|
|||
*/
|
||||
int Curl_easyopts_check(void)
|
||||
{
|
||||
return (CURLOPT_LASTENTRY % 10000) != (328 + 1);
|
||||
return (CURLOPT_LASTENTRY % 10000) != (332 + 1);
|
||||
}
|
||||
#endif
|
||||
|
|
|
|||
17
lib/http.c
17
lib/http.c
|
|
@ -61,6 +61,7 @@
|
|||
#include "http_ntlm.h"
|
||||
#include "http_negotiate.h"
|
||||
#include "http_aws_sigv4.h"
|
||||
#include "http_httpsig.h"
|
||||
#include "url.h"
|
||||
#include "urlapi-int.h"
|
||||
#include "curl_share.h"
|
||||
|
|
@ -376,6 +377,10 @@ static bool pickoneauth(struct auth *pick, unsigned long mask,
|
|||
#ifndef CURL_DISABLE_AWS
|
||||
else if(avail & CURLAUTH_AWS_SIGV4)
|
||||
pick->picked = CURLAUTH_AWS_SIGV4;
|
||||
#endif
|
||||
#ifndef CURL_DISABLE_HTTPSIG
|
||||
else if(avail & CURLAUTH_HTTPSIG)
|
||||
pick->picked = CURLAUTH_HTTPSIG;
|
||||
#endif
|
||||
else {
|
||||
pick->picked = CURLAUTH_PICKNONE; /* we select to use nothing */
|
||||
|
|
@ -665,6 +670,15 @@ static CURLcode output_auth_headers(struct Curl_easy *data,
|
|||
}
|
||||
else
|
||||
#endif
|
||||
#ifndef CURL_DISABLE_HTTPSIG
|
||||
if((authstatus->picked == CURLAUTH_HTTPSIG) && !proxy) {
|
||||
auth = "HTTPSIG";
|
||||
result = Curl_output_httpsig(data);
|
||||
if(result)
|
||||
return result;
|
||||
}
|
||||
else
|
||||
#endif
|
||||
#ifdef USE_SPNEGO
|
||||
if(authstatus->picked == CURLAUTH_NEGOTIATE) {
|
||||
auth = "Negotiate";
|
||||
|
|
@ -792,6 +806,9 @@ CURLcode Curl_http_output_auth(struct Curl_easy *data,
|
|||
#ifdef USE_SPNEGO
|
||||
!(authhost->want & CURLAUTH_NEGOTIATE) &&
|
||||
!(authproxy->want & CURLAUTH_NEGOTIATE) &&
|
||||
#endif
|
||||
#ifndef CURL_DISABLE_HTTPSIG
|
||||
!(authhost->want & CURLAUTH_HTTPSIG) &&
|
||||
#endif
|
||||
!data->state.creds) {
|
||||
/* no authentication with no user or password */
|
||||
|
|
|
|||
692
lib/http_httpsig.c
Normal file
692
lib/http_httpsig.c
Normal file
|
|
@ -0,0 +1,692 @@
|
|||
/***************************************************************************
|
||||
* _ _ ____ _
|
||||
* 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 */
|
||||
35
lib/http_httpsig.h
Normal file
35
lib/http_httpsig.h
Normal file
|
|
@ -0,0 +1,35 @@
|
|||
#ifndef HEADER_CURL_HTTP_HTTPSIG_H
|
||||
#define HEADER_CURL_HTTP_HTTPSIG_H
|
||||
/***************************************************************************
|
||||
* _ _ ____ _
|
||||
* 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"
|
||||
|
||||
CURLcode Curl_output_httpsig(struct Curl_easy *data);
|
||||
|
||||
#endif /* !CURL_DISABLE_HTTP && !CURL_DISABLE_HTTPSIG */
|
||||
#endif /* HEADER_CURL_HTTP_HTTPSIG_H */
|
||||
24
lib/setopt.c
24
lib/setopt.c
|
|
@ -1103,6 +1103,19 @@ static CURLcode setopt_long_http(struct Curl_easy *data, CURLoption option,
|
|||
#else
|
||||
result = CURLE_NOT_BUILT_IN;
|
||||
break;
|
||||
#endif
|
||||
#ifndef CURL_DISABLE_HTTPSIG
|
||||
case CURLOPT_HTTPSIG_ALGORITHM:
|
||||
if(arg != CURLHTTPSIG_NONE &&
|
||||
arg != CURLHTTPSIG_ED25519 &&
|
||||
arg != CURLHTTPSIG_HMAC_SHA256)
|
||||
return CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
s->httpsig_algorithm = (uint8_t)arg;
|
||||
if(arg)
|
||||
s->httpauth = (uint32_t)CURLAUTH_HTTPSIG;
|
||||
else
|
||||
s->httpauth &= ~(uint32_t)CURLAUTH_HTTPSIG;
|
||||
break;
|
||||
#endif
|
||||
default:
|
||||
return CURLE_UNKNOWN_OPTION;
|
||||
|
|
@ -2081,6 +2094,17 @@ static CURLcode setopt_cptr_http_mqtt(struct Curl_easy *data,
|
|||
if(s->str[STRING_AWS_SIGV4])
|
||||
s->httpauth = CURLAUTH_AWS_SIGV4;
|
||||
break;
|
||||
#endif
|
||||
#ifndef CURL_DISABLE_HTTPSIG
|
||||
case CURLOPT_HTTPSIG_KEY:
|
||||
result = Curl_setstropt(&s->str[STRING_HTTPSIG_KEY], ptr);
|
||||
break;
|
||||
case CURLOPT_HTTPSIG_KEYID:
|
||||
result = Curl_setstropt(&s->str[STRING_HTTPSIG_KEYID], ptr);
|
||||
break;
|
||||
case CURLOPT_HTTPSIG_HEADERS:
|
||||
result = Curl_setstropt(&s->str[STRING_HTTPSIG_HEADERS], ptr);
|
||||
break;
|
||||
#endif
|
||||
case CURLOPT_REFERER:
|
||||
/*
|
||||
|
|
|
|||
|
|
@ -849,6 +849,11 @@ enum dupstring {
|
|||
#ifndef CURL_DISABLE_AWS
|
||||
STRING_AWS_SIGV4, /* Parameters for V4 signature */
|
||||
#endif
|
||||
#ifndef CURL_DISABLE_HTTPSIG
|
||||
STRING_HTTPSIG_KEY, /* hex-encoded key data */
|
||||
STRING_HTTPSIG_KEYID, /* key identifier */
|
||||
STRING_HTTPSIG_HEADERS, /* space-separated components to sign */
|
||||
#endif
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
STRING_HAPROXY_CLIENT_IP, /* CURLOPT_HAPROXY_CLIENT_IP */
|
||||
#endif
|
||||
|
|
@ -890,6 +895,7 @@ struct UserDefined {
|
|||
void *writeheader; /* write the header to this if non-NULL */
|
||||
uint32_t httpauth; /* kind of HTTP authentication to use (bitmask) */
|
||||
uint32_t proxyauth; /* kind of proxy authentication to use (bitmask) */
|
||||
uint8_t httpsig_algorithm; /* CURLHTTPSIG_* algorithm for RFC 9421 */
|
||||
void *postfields; /* if POST, set the fields' values here */
|
||||
curl_seek_callback seek_func; /* function that seeks the input */
|
||||
curl_off_t postfieldsize; /* if POST, this might have a size to use instead
|
||||
|
|
|
|||
|
|
@ -469,6 +469,9 @@ static const struct feat features_table[] = {
|
|||
!defined(CURL_DISABLE_HTTP)
|
||||
FEATURE("HTTPS-proxy", https_proxy_present, CURL_VERSION_HTTPS_PROXY),
|
||||
#endif
|
||||
#ifndef CURL_DISABLE_HTTPSIG
|
||||
FEATURE("HTTPSIG", NULL, 0),
|
||||
#endif
|
||||
#ifdef USE_HTTPSRR
|
||||
FEATURE("HTTPSRR", NULL, 0),
|
||||
#endif
|
||||
|
|
|
|||
|
|
@ -71,6 +71,9 @@ options:
|
|||
CURLOPT_FTP_ALTERNATIVE_TO_USER
|
||||
CURLOPT_HAPROXY_CLIENT_IP
|
||||
CURLOPT_HSTS
|
||||
CURLOPT_HTTPSIG_HEADERS
|
||||
CURLOPT_HTTPSIG_KEY
|
||||
CURLOPT_HTTPSIG_KEYID
|
||||
CURLOPT_INTERFACE
|
||||
CURLOPT_ISSUERCERT
|
||||
CURLOPT_KEYPASSWD
|
||||
|
|
|
|||
|
|
@ -1060,6 +1060,9 @@ CURLcode curl_easy_setopt_ccsid(CURL *easy, CURLoption tag, ...)
|
|||
case CURLOPT_FTP_ALTERNATIVE_TO_USER:
|
||||
case CURLOPT_HAPROXY_CLIENT_IP:
|
||||
case CURLOPT_HSTS:
|
||||
case CURLOPT_HTTPSIG_HEADERS:
|
||||
case CURLOPT_HTTPSIG_KEY:
|
||||
case CURLOPT_HTTPSIG_KEYID:
|
||||
case CURLOPT_INTERFACE:
|
||||
case CURLOPT_ISSUERCERT:
|
||||
case CURLOPT_KEYPASSWD:
|
||||
|
|
|
|||
|
|
@ -1143,7 +1143,14 @@ HEAD
|
|||
elsif(length($desc) > 78) {
|
||||
print STDERR "WARN: the --$long description is too long\n";
|
||||
}
|
||||
print $line;
|
||||
my $ifdef = "";
|
||||
my $endif = "";
|
||||
|
||||
if($long =~ /^(ipfs|httpsig)/) {
|
||||
$ifdef = sprintf "#ifndef CURL_DISABLE_%s\n", uc($1);
|
||||
$endif = "#endif\n";
|
||||
}
|
||||
print $ifdef.$line.$endif;
|
||||
}
|
||||
print <<FOOT
|
||||
{ NULL, NULL, 0 }
|
||||
|
|
|
|||
|
|
@ -38,6 +38,7 @@
|
|||
#include "tool_cb_see.h"
|
||||
#include "tool_cb_dbg.h"
|
||||
#include "tool_helpers.h"
|
||||
#include "tool_paramhlp.h"
|
||||
#include "tool_version.h"
|
||||
|
||||
#ifdef HAVE_NETINET_IN_H
|
||||
|
|
@ -564,6 +565,80 @@ static CURLcode cookie_setopts(struct OperationConfig *config, CURL *curl)
|
|||
return result;
|
||||
}
|
||||
|
||||
/* only --httpsig-* options */
|
||||
#ifndef CURL_DISABLE_HTTPSIG
|
||||
static CURLcode httpsig_setopts(struct OperationConfig *config, CURL *curl)
|
||||
{
|
||||
CURLcode result = CURLE_OK;
|
||||
/* HTTP Message Signatures are enabled when any of the --httpsig-* options
|
||||
is given. --httpsig-key and --httpsig-keyid are then required, while
|
||||
--httpsig-algo is optional and defaults to ed25519. */
|
||||
if(config->httpsig_algorithm || config->httpsig_key ||
|
||||
config->httpsig_keyid || config->httpsig_headers) {
|
||||
long httpsig_alg = CURLHTTPSIG_NONE;
|
||||
|
||||
if(!config->httpsig_key) {
|
||||
errorf("--httpsig-key is required");
|
||||
return CURLE_FAILED_INIT;
|
||||
}
|
||||
if(!config->httpsig_keyid) {
|
||||
errorf("--httpsig-keyid is required");
|
||||
return CURLE_FAILED_INIT;
|
||||
}
|
||||
|
||||
if(!config->httpsig_algorithm ||
|
||||
curl_strequal(config->httpsig_algorithm, "ed25519"))
|
||||
httpsig_alg = CURLHTTPSIG_ED25519;
|
||||
else if(curl_strequal(config->httpsig_algorithm, "hmac-sha256"))
|
||||
httpsig_alg = CURLHTTPSIG_HMAC_SHA256;
|
||||
else {
|
||||
errorf("--httpsig-algo: unsupported algorithm '%s'",
|
||||
config->httpsig_algorithm);
|
||||
return CURLE_FAILED_INIT;
|
||||
}
|
||||
my_setopt_long(curl, CURLOPT_HTTPSIG_ALGORITHM, httpsig_alg);
|
||||
MY_SETOPT_STR(curl, CURLOPT_HTTPSIG_HEADERS, config->httpsig_headers);
|
||||
MY_SETOPT_STR(curl, CURLOPT_HTTPSIG_KEYID, config->httpsig_keyid);
|
||||
{
|
||||
FILE *keyf = curlx_fopen(config->httpsig_key, FOPEN_READTEXT);
|
||||
if(keyf) {
|
||||
char *hexdata = NULL;
|
||||
ParameterError pe = file2string(&hexdata, keyf);
|
||||
|
||||
curlx_fclose(keyf);
|
||||
if(pe == PARAM_NO_MEM) {
|
||||
curlx_safefree(hexdata);
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
if(pe == PARAM_READ_ERROR) {
|
||||
curlx_safefree(hexdata);
|
||||
errorf("httpsig: cannot read key file '%s'", config->httpsig_key);
|
||||
return CURLE_READ_ERROR;
|
||||
}
|
||||
if(!hexdata || !*hexdata) {
|
||||
curlx_safefree(hexdata);
|
||||
errorf("httpsig: key file '%s' is empty", config->httpsig_key);
|
||||
return CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
}
|
||||
/* can't use the MY_SETOPT_STR() macro here since it returns on error
|
||||
and we must free the hexdata */
|
||||
result = my_setopt_str(curl, CURLOPT_HTTPSIG_KEY, hexdata);
|
||||
curlx_safefree(hexdata);
|
||||
if(setopt_bad(result))
|
||||
return result;
|
||||
}
|
||||
else {
|
||||
errorf("httpsig: cannot open key file '%s'", config->httpsig_key);
|
||||
return CURLE_READ_ERROR;
|
||||
}
|
||||
}
|
||||
}
|
||||
return CURLE_OK;
|
||||
}
|
||||
#else
|
||||
#define httpsig_setopts(x,y) CURLE_OK
|
||||
#endif
|
||||
|
||||
/* only for HTTP transfers */
|
||||
static CURLcode http_setopts(struct OperationConfig *config, CURL *curl,
|
||||
const char *use_proto)
|
||||
|
|
@ -579,6 +654,9 @@ static CURLcode http_setopts(struct OperationConfig *config, CURL *curl,
|
|||
#ifndef CURL_DISABLE_AWS
|
||||
MY_SETOPT_STR(curl, CURLOPT_AWS_SIGV4, config->aws_sigv4);
|
||||
#endif
|
||||
result = httpsig_setopts(config, curl);
|
||||
if(result)
|
||||
return result;
|
||||
my_setopt_long(curl, CURLOPT_AUTOREFERER, config->autoreferer);
|
||||
|
||||
if(config->proxyheaders) {
|
||||
|
|
|
|||
|
|
@ -93,6 +93,13 @@ static const char *disabled[] = {
|
|||
"OFF"
|
||||
#else
|
||||
"ON"
|
||||
#endif
|
||||
,
|
||||
"httpsig: "
|
||||
#ifdef CURL_DISABLE_HTTPSIG
|
||||
"OFF"
|
||||
#else
|
||||
"ON"
|
||||
#endif
|
||||
,
|
||||
"DoH: "
|
||||
|
|
|
|||
|
|
@ -181,6 +181,10 @@ static void free_config_fields(struct OperationConfig *config)
|
|||
curlx_safefree(config->ftp_account);
|
||||
curlx_safefree(config->ftp_alternative_to_user);
|
||||
curlx_safefree(config->aws_sigv4);
|
||||
curlx_safefree(config->httpsig_algorithm);
|
||||
curlx_safefree(config->httpsig_headers);
|
||||
curlx_safefree(config->httpsig_key);
|
||||
curlx_safefree(config->httpsig_keyid);
|
||||
curlx_safefree(config->ech);
|
||||
curlx_safefree(config->ech_config);
|
||||
curlx_safefree(config->ech_public);
|
||||
|
|
|
|||
|
|
@ -153,6 +153,10 @@ struct OperationConfig {
|
|||
char *unix_socket_path; /* path to Unix domain socket */
|
||||
char *haproxy_clientip; /* client IP for HAProxy protocol */
|
||||
char *aws_sigv4;
|
||||
char *httpsig_algorithm;
|
||||
char *httpsig_headers;
|
||||
char *httpsig_key;
|
||||
char *httpsig_keyid;
|
||||
char *ech; /* Config set by --ech keywords */
|
||||
char *ech_config; /* Config set by "--ech esl:" option */
|
||||
char *ech_public; /* Config set by "--ech pn:" option */
|
||||
|
|
|
|||
|
|
@ -170,6 +170,12 @@ static const struct LongShort aliases[] = {
|
|||
{"http2-prior-knowledge", ARG_NONE, ' ', C_HTTP2_PRIOR_KNOWLEDGE},
|
||||
{"http3", ARG_NONE|ARG_TLS, ' ', C_HTTP3},
|
||||
{"http3-only", ARG_NONE|ARG_TLS, ' ', C_HTTP3_ONLY},
|
||||
#ifndef CURL_DISABLE_HTTPSIG
|
||||
{"httpsig-algo", ARG_STRG, ' ', C_HTTPSIG_ALGORITHM},
|
||||
{"httpsig-headers", ARG_STRG, ' ', C_HTTPSIG_HEADERS},
|
||||
{"httpsig-key", ARG_FILE, ' ', C_HTTPSIG_KEY},
|
||||
{"httpsig-keyid", ARG_STRG, ' ', C_HTTPSIG_KEYID},
|
||||
#endif
|
||||
{"ignore-content-length", ARG_BOOL, ' ', C_IGNORE_CONTENT_LENGTH},
|
||||
{"include", ARG_BOOL, ' ', C_INCLUDE},
|
||||
{"insecure", ARG_BOOL, 'k', C_INSECURE},
|
||||
|
|
@ -2363,6 +2369,12 @@ static ParameterError opt_file(struct OperationConfig *config,
|
|||
case C_UPLOAD_FILE: /* --upload-file */
|
||||
err = parse_upload_file(config, nextarg);
|
||||
break;
|
||||
case C_HTTPSIG_KEY: /* --httpsig-key */
|
||||
if(!feature_httpsig)
|
||||
err = PARAM_LIBCURL_DOESNT_SUPPORT;
|
||||
else
|
||||
err = getstr(&config->httpsig_key, nextarg, DENY_BLANK);
|
||||
break;
|
||||
}
|
||||
return err;
|
||||
}
|
||||
|
|
@ -2557,6 +2569,26 @@ static ParameterError opt_string(struct OperationConfig *config,
|
|||
config->authtype |= CURLAUTH_AWS_SIGV4;
|
||||
err = getstr(&config->aws_sigv4, nextarg, ALLOW_BLANK);
|
||||
break;
|
||||
case C_HTTPSIG_ALGORITHM: /* --httpsig-algo */
|
||||
if(!feature_httpsig)
|
||||
err = PARAM_LIBCURL_DOESNT_SUPPORT;
|
||||
else {
|
||||
config->authtype |= CURLAUTH_HTTPSIG;
|
||||
err = getstr(&config->httpsig_algorithm, nextarg, DENY_BLANK);
|
||||
}
|
||||
break;
|
||||
case C_HTTPSIG_KEYID: /* --httpsig-keyid */
|
||||
if(!feature_httpsig)
|
||||
err = PARAM_LIBCURL_DOESNT_SUPPORT;
|
||||
else
|
||||
err = getstr(&config->httpsig_keyid, nextarg, DENY_BLANK);
|
||||
break;
|
||||
case C_HTTPSIG_HEADERS: /* --httpsig-headers */
|
||||
if(!feature_httpsig)
|
||||
err = PARAM_LIBCURL_DOESNT_SUPPORT;
|
||||
else
|
||||
err = getstr(&config->httpsig_headers, nextarg, DENY_BLANK);
|
||||
break;
|
||||
case C_INTERFACE: /* --interface */
|
||||
/* interface */
|
||||
err = getstr(&config->iface, nextarg, DENY_BLANK);
|
||||
|
|
|
|||
|
|
@ -125,6 +125,10 @@ typedef enum {
|
|||
C_HTTP2_PRIOR_KNOWLEDGE,
|
||||
C_HTTP3,
|
||||
C_HTTP3_ONLY,
|
||||
C_HTTPSIG_ALGORITHM,
|
||||
C_HTTPSIG_HEADERS,
|
||||
C_HTTPSIG_KEY,
|
||||
C_HTTPSIG_KEYID,
|
||||
C_IGNORE_CONTENT_LENGTH,
|
||||
C_INCLUDE,
|
||||
C_INSECURE,
|
||||
|
|
|
|||
|
|
@ -69,6 +69,7 @@ bool feature_brotli = FALSE;
|
|||
bool feature_hsts = FALSE;
|
||||
bool feature_http2 = FALSE;
|
||||
bool feature_http3 = FALSE;
|
||||
bool feature_httpsig = FALSE;
|
||||
bool feature_httpsproxy = FALSE;
|
||||
bool feature_libz = FALSE;
|
||||
bool feature_ntlm = FALSE;
|
||||
|
|
@ -97,6 +98,7 @@ static struct feature_name_presentp {
|
|||
{ "HTTP2", &feature_http2, CURL_VERSION_HTTP2 },
|
||||
{ "HTTP3", &feature_http3, CURL_VERSION_HTTP3 },
|
||||
{ "HTTPS-proxy", &feature_httpsproxy, CURL_VERSION_HTTPS_PROXY },
|
||||
{ "HTTPSIG", &feature_httpsig, 0 },
|
||||
{ "IDN", NULL, CURL_VERSION_IDN },
|
||||
{ "IPv6", NULL, CURL_VERSION_IPV6 },
|
||||
{ "Kerberos", NULL, CURL_VERSION_KERBEROS5 },
|
||||
|
|
|
|||
|
|
@ -52,6 +52,7 @@ extern bool feature_brotli;
|
|||
extern bool feature_hsts;
|
||||
extern bool feature_http2;
|
||||
extern bool feature_http3;
|
||||
extern bool feature_httpsig;
|
||||
extern bool feature_httpsproxy;
|
||||
extern bool feature_libz;
|
||||
extern bool feature_ntlm;
|
||||
|
|
|
|||
|
|
@ -303,6 +303,26 @@ const struct helptxt helptext[] = {
|
|||
{ " --http3-only",
|
||||
"Use HTTP/3 only",
|
||||
CURLHELP_HTTP },
|
||||
#ifndef CURL_DISABLE_HTTPSIG
|
||||
{ " --httpsig-algo <algorithm>",
|
||||
"Algorithm for HTTP Message Signatures",
|
||||
CURLHELP_AUTH | CURLHELP_HTTP },
|
||||
#endif
|
||||
#ifndef CURL_DISABLE_HTTPSIG
|
||||
{ " --httpsig-headers <components>",
|
||||
"Components to sign for HTTP Message Signatures",
|
||||
CURLHELP_AUTH | CURLHELP_HTTP },
|
||||
#endif
|
||||
#ifndef CURL_DISABLE_HTTPSIG
|
||||
{ " --httpsig-key <file>",
|
||||
"Key file for HTTP Message Signatures",
|
||||
CURLHELP_AUTH | CURLHELP_HTTP },
|
||||
#endif
|
||||
#ifndef CURL_DISABLE_HTTPSIG
|
||||
{ " --httpsig-keyid <id>",
|
||||
"Key identifier for HTTP Message Signatures",
|
||||
CURLHELP_AUTH | CURLHELP_HTTP },
|
||||
#endif
|
||||
{ " --ignore-content-length",
|
||||
"Ignore the size of the remote resource",
|
||||
CURLHELP_HTTP | CURLHELP_FTP },
|
||||
|
|
@ -315,9 +335,11 @@ const struct helptxt helptext[] = {
|
|||
{ " --ip-tos <string>",
|
||||
"Set IP Type of Service or Traffic Class",
|
||||
CURLHELP_CONNECTION },
|
||||
#ifndef CURL_DISABLE_IPFS
|
||||
{ " --ipfs-gateway <URL>",
|
||||
"Gateway for IPFS",
|
||||
CURLHELP_CURL },
|
||||
#endif
|
||||
{ "-4, --ipv4",
|
||||
"Resolve names to IPv4 addresses",
|
||||
CURLHELP_CONNECTION | CURLHELP_DNS },
|
||||
|
|
|
|||
|
|
@ -290,11 +290,16 @@ test3300 test3301 test3302 test3303 test3304 test3305 test3306 \
|
|||
\
|
||||
test3400 test3401 \
|
||||
\
|
||||
test4000 test4001
|
||||
test4000 test4001 \
|
||||
\
|
||||
test5000 test5001 test5002 test5003 test5004 test5005 test5006 test5007 \
|
||||
test5008 test5009 test5010 test5011 test5012 test5013 test5014 test5015 \
|
||||
test5016 test5017 test5018 test5019 test5020 test5021
|
||||
|
||||
EXTRA_DIST = $(TESTCASES) DISABLED data-xml1 \
|
||||
data1461.txt data1463.txt \
|
||||
data1400.c data1401.c data1402.c data1403.c data1404.c data1405.c data1406.c \
|
||||
data1407.c data1420.c data1465.c data1481.c \
|
||||
data1705-1.md data1705-2.md data1705-3.md data1705-4.md data1705-stdout.1 \
|
||||
data1706-1.md data1706-2.md data1706-3.md data1706-4.md data1706-stdout.txt
|
||||
data1706-1.md data1706-2.md data1706-3.md data1706-4.md data1706-stdout.txt \
|
||||
data-httpsig-ed25519.key data-httpsig-hmac-sha256.key
|
||||
|
|
|
|||
1
tests/data/data-httpsig-ed25519.key
Normal file
1
tests/data/data-httpsig-ed25519.key
Normal file
|
|
@ -0,0 +1 @@
|
|||
9f8362f87a484a954e6e740c5b4c0e84229139a20aa8ab56ff66586f6a7d29c5
|
||||
1
tests/data/data-httpsig-hmac-sha256.key
Normal file
1
tests/data/data-httpsig-hmac-sha256.key
Normal file
|
|
@ -0,0 +1 @@
|
|||
0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef
|
||||
57
tests/data/test5000
Normal file
57
tests/data/test5000
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
<?xml version="1.0" encoding="US-ASCII"?>
|
||||
<testcase>
|
||||
<info>
|
||||
<keywords>
|
||||
HTTP
|
||||
CURLOPT_HTTPSIG_ALGORITHM
|
||||
RFC9421
|
||||
</keywords>
|
||||
</info>
|
||||
|
||||
# Server-side
|
||||
<reply>
|
||||
<data nocheck="yes">
|
||||
HTTP/1.1 200 OK
|
||||
Date: Tue, 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 allows tests to set the time
|
||||
<features>
|
||||
Debug
|
||||
httpsig
|
||||
</features>
|
||||
|
||||
<name>
|
||||
HTTP RFC 9421 Message Signatures: basic GET with Ed25519
|
||||
</name>
|
||||
<tool>
|
||||
lib%TESTNUMBER
|
||||
</tool>
|
||||
|
||||
<command>
|
||||
http://example.com:9000/%TESTNUMBER/resource example.com:9000:%HOSTIP:%HTTPPORT
|
||||
</command>
|
||||
</client>
|
||||
|
||||
# Verify data after the test has been "shot"
|
||||
<verify>
|
||||
<protocol crlf="headers">
|
||||
GET /%TESTNUMBER/resource HTTP/1.1
|
||||
Host: example.com:9000
|
||||
Signature-Input: sig1=("@method" "@authority" "@path");created=0;keyid="test-key-ed25519";alg="ed25519"
|
||||
Signature: sig1=:Olp3ugYSVwc47PTNBMrkV2P+8p5w2VPFzURxNaG44UA2On6OFG2FzFLEl8QEN1khiOdRNlFDouZsOwpJoPHvBA==:
|
||||
Accept: */*
|
||||
|
||||
</protocol>
|
||||
</verify>
|
||||
</testcase>
|
||||
55
tests/data/test5001
Normal file
55
tests/data/test5001
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0" encoding="US-ASCII"?>
|
||||
<testcase>
|
||||
<info>
|
||||
<keywords>
|
||||
HTTP
|
||||
httpsig
|
||||
RFC9421
|
||||
</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
|
||||
Content-Length: 6
|
||||
Connection: close
|
||||
Content-Type: text/html
|
||||
|
||||
-foo-
|
||||
</data>
|
||||
</reply>
|
||||
|
||||
# Client-side
|
||||
<client>
|
||||
<server>
|
||||
http
|
||||
</server>
|
||||
<features>
|
||||
Debug
|
||||
httpsig
|
||||
</features>
|
||||
<name>
|
||||
HTTP RFC 9421 Message Signatures: CLI GET with query
|
||||
</name>
|
||||
<command>
|
||||
"http://example.com:8000/%TESTNUMBER/resource?action=read" --httpsig-algo "ed25519" --httpsig-key %SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "my-key-1" --connect-to example.com:8000:%HOSTIP:%HTTPPORT
|
||||
</command>
|
||||
</client>
|
||||
|
||||
# Verify data after the test has been "shot"
|
||||
<verify>
|
||||
<protocol crlf="headers">
|
||||
GET /%TESTNUMBER/resource?action=read HTTP/1.1
|
||||
Host: example.com:8000
|
||||
Signature-Input: sig1=("@method" "@authority" "@path" "@query");created=0;keyid="my-key-1";alg="ed25519"
|
||||
Signature: sig1=:RQniOeqmwdRzGvoDIMJ8XJha75evJWgqo5/66EeuJeEGczZtnP2U/F52Lzd/y7Vd1DCb8oUcCKHrKi2VJI7lBA==:
|
||||
User-Agent: curl/%VERSION
|
||||
Accept: */*
|
||||
|
||||
</protocol>
|
||||
</verify>
|
||||
</testcase>
|
||||
55
tests/data/test5002
Normal file
55
tests/data/test5002
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0" encoding="US-ASCII"?>
|
||||
<testcase>
|
||||
<info>
|
||||
<keywords>
|
||||
HTTP
|
||||
httpsig
|
||||
RFC9421
|
||||
hmac-sha256
|
||||
</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
|
||||
Content-Length: 6
|
||||
Connection: close
|
||||
Content-Type: text/html
|
||||
|
||||
-foo-
|
||||
</data>
|
||||
</reply>
|
||||
|
||||
# Client-side
|
||||
<client>
|
||||
<server>
|
||||
http
|
||||
</server>
|
||||
<features>
|
||||
Debug
|
||||
httpsig
|
||||
</features>
|
||||
<name>
|
||||
HTTP RFC 9421 Message Signatures: CLI GET with HMAC-SHA256
|
||||
</name>
|
||||
<command>
|
||||
"http://example.com:7000/%TESTNUMBER/resource" --httpsig-algo "hmac-sha256" --httpsig-key %SRCDIR/data/data-httpsig-hmac-sha256.key --httpsig-keyid "shared-key-1" --connect-to example.com:7000:%HOSTIP:%HTTPPORT
|
||||
</command>
|
||||
</client>
|
||||
|
||||
# Verify data after the test has been "shot"
|
||||
<verify>
|
||||
<protocol crlf="headers">
|
||||
GET /%TESTNUMBER/resource HTTP/1.1
|
||||
Host: example.com:7000
|
||||
Signature-Input: sig1=("@method" "@authority" "@path");created=0;keyid="shared-key-1";alg="hmac-sha256"
|
||||
Signature: sig1=:jvajJheXE4H/TqWfAn8m0d0Rx0XWYKADN/1P0qn+FLw=:
|
||||
User-Agent: curl/%VERSION
|
||||
Accept: */*
|
||||
|
||||
</protocol>
|
||||
</verify>
|
||||
</testcase>
|
||||
55
tests/data/test5003
Normal file
55
tests/data/test5003
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0" encoding="US-ASCII"?>
|
||||
<testcase>
|
||||
<info>
|
||||
<keywords>
|
||||
HTTP
|
||||
httpsig
|
||||
RFC9421
|
||||
httpsig-headers
|
||||
</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
|
||||
Content-Length: 6
|
||||
Connection: close
|
||||
Content-Type: text/html
|
||||
|
||||
-foo-
|
||||
</data>
|
||||
</reply>
|
||||
|
||||
# Client-side
|
||||
<client>
|
||||
<server>
|
||||
http
|
||||
</server>
|
||||
<features>
|
||||
Debug
|
||||
httpsig
|
||||
</features>
|
||||
<name>
|
||||
HTTP RFC 9421 Message Signatures: custom --httpsig-headers
|
||||
</name>
|
||||
<command>
|
||||
"http://example.com:6000/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-key %SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "test-key-ed25519" --httpsig-headers "method authority" --connect-to example.com:6000:%HOSTIP:%HTTPPORT
|
||||
</command>
|
||||
</client>
|
||||
|
||||
# Verify data after the test has been "shot"
|
||||
<verify>
|
||||
<protocol crlf="headers">
|
||||
GET /%TESTNUMBER/resource HTTP/1.1
|
||||
Host: example.com:6000
|
||||
Signature-Input: sig1=("@method" "@authority");created=0;keyid="test-key-ed25519";alg="ed25519"
|
||||
Signature: sig1=:q+8VHrGBOhkJm9d9dxKv3BfhyVWKIFiYXqdLZVx8yGrTzZO6Q6zJicFFuLvwJex6DS+Pw6YEMQzmtyj7dKOCCw==:
|
||||
User-Agent: curl/%VERSION
|
||||
Accept: */*
|
||||
|
||||
</protocol>
|
||||
</verify>
|
||||
</testcase>
|
||||
66
tests/data/test5004
Normal file
66
tests/data/test5004
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
<?xml version="1.0" encoding="US-ASCII"?>
|
||||
<testcase>
|
||||
<info>
|
||||
<keywords>
|
||||
HTTP
|
||||
CURLOPT_HTTPSIG_ALGORITHM
|
||||
RFC9421
|
||||
ed25519
|
||||
B.2.6
|
||||
</keywords>
|
||||
</info>
|
||||
|
||||
# Server-side
|
||||
<reply>
|
||||
<data nocheck="yes">
|
||||
HTTP/1.1 200 OK
|
||||
Date: Tue, 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>
|
||||
<features>
|
||||
Debug
|
||||
httpsig
|
||||
</features>
|
||||
|
||||
<name>
|
||||
HTTP RFC 9421 B.2.6: Ed25519 POST with headers (RFC test vector)
|
||||
</name>
|
||||
<tool>
|
||||
lib%TESTNUMBER
|
||||
</tool>
|
||||
|
||||
<setenv>
|
||||
CURL_HTTPSIG_CREATED=1618884473
|
||||
</setenv>
|
||||
|
||||
<command>
|
||||
http://example.com/%TESTNUMBER/foo example.com:80:%HOSTIP:%HTTPPORT
|
||||
</command>
|
||||
</client>
|
||||
|
||||
# Verify data after the test has been "shot"
|
||||
<verify>
|
||||
<protocol crlf="headers" nonewline="yes">
|
||||
POST /%TESTNUMBER/foo HTTP/1.1
|
||||
Host: example.com
|
||||
Signature-Input: sig1=("date" "@method" "@path" "@authority" "content-type" "content-length");created=1618884473;keyid="test-key-ed25519";alg="ed25519"
|
||||
Signature: sig1=:6QriGerNuao/A02UMre01lQOwlVQ0L9Cx3WJPQonQpUdKxSNg/XXXqjKdcC9PELzmAKJ10loLIq10yYLTzmzCA==:
|
||||
Accept: */*
|
||||
Date: Tue, 20 Apr 2021 02:07:55 GMT
|
||||
Content-Type: application/json
|
||||
Content-Length: 18
|
||||
|
||||
{"hello": "world"}
|
||||
</protocol>
|
||||
</verify>
|
||||
</testcase>
|
||||
56
tests/data/test5005
Normal file
56
tests/data/test5005
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<?xml version="1.0" encoding="US-ASCII"?>
|
||||
<testcase>
|
||||
<info>
|
||||
<keywords>
|
||||
HTTP
|
||||
httpsig
|
||||
RFC9421
|
||||
header-signing
|
||||
</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
|
||||
Content-Length: 6
|
||||
Connection: close
|
||||
Content-Type: text/html
|
||||
|
||||
-foo-
|
||||
</data>
|
||||
</reply>
|
||||
|
||||
# Client-side
|
||||
<client>
|
||||
<server>
|
||||
http
|
||||
</server>
|
||||
<features>
|
||||
Debug
|
||||
httpsig
|
||||
</features>
|
||||
<name>
|
||||
HTTP RFC 9421 Message Signatures: sign with a regular HTTP header
|
||||
</name>
|
||||
<command>
|
||||
"http://example.com:5000/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-key %SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "test-key-ed25519" --httpsig-headers "method authority content-type:" -H "Content-Type: application/json" --connect-to example.com:5000:%HOSTIP:%HTTPPORT
|
||||
</command>
|
||||
</client>
|
||||
|
||||
# Verify data after the test has been "shot"
|
||||
<verify>
|
||||
<protocol crlf="headers">
|
||||
GET /%TESTNUMBER/resource HTTP/1.1
|
||||
Host: example.com:5000
|
||||
Signature-Input: sig1=("@method" "@authority" "content-type");created=0;keyid="test-key-ed25519";alg="ed25519"
|
||||
Signature: sig1=:lyo8O1BjxYmPJchwmIO6ZwOEycijlfsLsqVpVVMiuPFAWrZ7zm0eoIF0X0wET8bsO9jiDRWjDx0b0dPK7FHPCA==:
|
||||
User-Agent: curl/%VERSION
|
||||
Accept: */*
|
||||
Content-Type: application/json
|
||||
|
||||
</protocol>
|
||||
</verify>
|
||||
</testcase>
|
||||
56
tests/data/test5006
Normal file
56
tests/data/test5006
Normal file
|
|
@ -0,0 +1,56 @@
|
|||
<?xml version="1.0" encoding="US-ASCII"?>
|
||||
<testcase>
|
||||
<info>
|
||||
<keywords>
|
||||
HTTP
|
||||
httpsig
|
||||
RFC9421
|
||||
case-normalization
|
||||
</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
|
||||
Content-Length: 6
|
||||
Connection: close
|
||||
Content-Type: text/html
|
||||
|
||||
-foo-
|
||||
</data>
|
||||
</reply>
|
||||
|
||||
# Client-side
|
||||
<client>
|
||||
<server>
|
||||
http
|
||||
</server>
|
||||
<features>
|
||||
Debug
|
||||
httpsig
|
||||
</features>
|
||||
<name>
|
||||
HTTP RFC 9421 Message Signatures: component name lowercasing
|
||||
</name>
|
||||
<command>
|
||||
"http://example.com:5100/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-key %SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "test-key-ed25519" --httpsig-headers "method Content-Type:" -H "Content-Type: text/plain" --connect-to example.com:5100:%HOSTIP:%HTTPPORT
|
||||
</command>
|
||||
</client>
|
||||
|
||||
# Verify data after the test has been "shot"
|
||||
<verify>
|
||||
<protocol crlf="headers">
|
||||
GET /%TESTNUMBER/resource HTTP/1.1
|
||||
Host: example.com:5100
|
||||
Signature-Input: sig1=("@method" "content-type");created=0;keyid="test-key-ed25519";alg="ed25519"
|
||||
Signature: sig1=:tu3WEN25K59evFpiHY5GZurVRtbaex5QM1W9b+P1rPCgNDsEEOepNZap/+nm48QZSensuc4tOpy1gbvAj25gAg==:
|
||||
User-Agent: curl/%VERSION
|
||||
Accept: */*
|
||||
Content-Type: text/plain
|
||||
|
||||
</protocol>
|
||||
</verify>
|
||||
</testcase>
|
||||
58
tests/data/test5007
Normal file
58
tests/data/test5007
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="US-ASCII"?>
|
||||
<testcase>
|
||||
<info>
|
||||
<keywords>
|
||||
HTTP
|
||||
HTTP POST
|
||||
httpsig
|
||||
RFC9421
|
||||
</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
|
||||
Content-Length: 6
|
||||
Connection: close
|
||||
Content-Type: text/html
|
||||
|
||||
-foo-
|
||||
</data>
|
||||
</reply>
|
||||
|
||||
# Client-side
|
||||
<client>
|
||||
<server>
|
||||
http
|
||||
</server>
|
||||
<features>
|
||||
Debug
|
||||
httpsig
|
||||
</features>
|
||||
<name>
|
||||
HTTP RFC 9421 Message Signatures: POST method
|
||||
</name>
|
||||
<command>
|
||||
"http://example.com:5200/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-key %SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "test-key-ed25519" -d "postbody" --connect-to example.com:5200:%HOSTIP:%HTTPPORT
|
||||
</command>
|
||||
</client>
|
||||
|
||||
# Verify data after the test has been "shot"
|
||||
<verify>
|
||||
<protocol crlf="headers" nonewline="yes">
|
||||
POST /%TESTNUMBER/resource HTTP/1.1
|
||||
Host: example.com:5200
|
||||
Signature-Input: sig1=("@method" "@authority" "@path");created=0;keyid="test-key-ed25519";alg="ed25519"
|
||||
Signature: sig1=:fqvzBFSBFu9oys7DawypZIrt2VS+nOnN1TQ7aTLcPUbEv7Zqm4leYd0q5r9FwW+kvabAjMj8J8N8F7FmPpIWCA==:
|
||||
User-Agent: curl/%VERSION
|
||||
Accept: */*
|
||||
Content-Length: 8
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
postbody
|
||||
</protocol>
|
||||
</verify>
|
||||
</testcase>
|
||||
46
tests/data/test5008
Normal file
46
tests/data/test5008
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<?xml version="1.0" encoding="US-ASCII"?>
|
||||
<testcase>
|
||||
<info>
|
||||
<keywords>
|
||||
HTTP
|
||||
httpsig
|
||||
RFC9421
|
||||
error
|
||||
</keywords>
|
||||
</info>
|
||||
|
||||
# Server-side
|
||||
<reply>
|
||||
<data nocheck="yes">
|
||||
HTTP/1.1 200 OK
|
||||
Date: Tue, 09 Nov 2010 14:49:00 GMT
|
||||
Server: test-server/fake
|
||||
Content-Length: 0
|
||||
|
||||
</data>
|
||||
</reply>
|
||||
|
||||
# Client-side
|
||||
<client>
|
||||
<server>
|
||||
http
|
||||
</server>
|
||||
<features>
|
||||
Debug
|
||||
httpsig
|
||||
</features>
|
||||
<name>
|
||||
HTTP RFC 9421 Message Signatures: error - missing --httpsig-key
|
||||
</name>
|
||||
<command>
|
||||
"http://example.com/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-keyid "my-key" --connect-to example.com::%HOSTIP:%HTTPPORT
|
||||
</command>
|
||||
</client>
|
||||
|
||||
# Verify data after the test has been "shot"
|
||||
<verify>
|
||||
<errorcode>
|
||||
2
|
||||
</errorcode>
|
||||
</verify>
|
||||
</testcase>
|
||||
46
tests/data/test5009
Normal file
46
tests/data/test5009
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<?xml version="1.0" encoding="US-ASCII"?>
|
||||
<testcase>
|
||||
<info>
|
||||
<keywords>
|
||||
HTTP
|
||||
httpsig
|
||||
RFC9421
|
||||
error
|
||||
</keywords>
|
||||
</info>
|
||||
|
||||
# Server-side
|
||||
<reply>
|
||||
<data nocheck="yes">
|
||||
HTTP/1.1 200 OK
|
||||
Date: Tue, 09 Nov 2010 14:49:00 GMT
|
||||
Server: test-server/fake
|
||||
Content-Length: 0
|
||||
|
||||
</data>
|
||||
</reply>
|
||||
|
||||
# Client-side
|
||||
<client>
|
||||
<server>
|
||||
http
|
||||
</server>
|
||||
<features>
|
||||
Debug
|
||||
httpsig
|
||||
</features>
|
||||
<name>
|
||||
HTTP RFC 9421 Message Signatures: error - unsupported algorithm
|
||||
</name>
|
||||
<command>
|
||||
"http://example.com/%TESTNUMBER/resource" --httpsig-algo "rsa-pss-sha512" --httpsig-key %SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "my-key" --connect-to example.com::%HOSTIP:%HTTPPORT
|
||||
</command>
|
||||
</client>
|
||||
|
||||
# Verify data after the test has been "shot"
|
||||
<verify>
|
||||
<errorcode>
|
||||
2
|
||||
</errorcode>
|
||||
</verify>
|
||||
</testcase>
|
||||
46
tests/data/test5010
Normal file
46
tests/data/test5010
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<?xml version="1.0" encoding="US-ASCII"?>
|
||||
<testcase>
|
||||
<info>
|
||||
<keywords>
|
||||
HTTP
|
||||
httpsig
|
||||
RFC9421
|
||||
error
|
||||
</keywords>
|
||||
</info>
|
||||
|
||||
# Server-side
|
||||
<reply>
|
||||
<data nocheck="yes">
|
||||
HTTP/1.1 200 OK
|
||||
Date: Tue, 09 Nov 2010 14:49:00 GMT
|
||||
Server: test-server/fake
|
||||
Content-Length: 0
|
||||
|
||||
</data>
|
||||
</reply>
|
||||
|
||||
# Client-side
|
||||
<client>
|
||||
<server>
|
||||
http
|
||||
</server>
|
||||
<features>
|
||||
Debug
|
||||
httpsig
|
||||
</features>
|
||||
<name>
|
||||
HTTP RFC 9421 Message Signatures: error - non-existent key file
|
||||
</name>
|
||||
<command>
|
||||
"http://example.com/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-key /nonexistent/key.hex --httpsig-keyid "my-key" --connect-to example.com::%HOSTIP:%HTTPPORT
|
||||
</command>
|
||||
</client>
|
||||
|
||||
# Verify data after the test has been "shot"
|
||||
<verify>
|
||||
<errorcode>
|
||||
26
|
||||
</errorcode>
|
||||
</verify>
|
||||
</testcase>
|
||||
55
tests/data/test5011
Normal file
55
tests/data/test5011
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0" encoding="US-ASCII"?>
|
||||
<testcase>
|
||||
<info>
|
||||
<keywords>
|
||||
HTTP
|
||||
httpsig
|
||||
RFC9421
|
||||
query-special-chars
|
||||
</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
|
||||
Content-Length: 6
|
||||
Connection: close
|
||||
Content-Type: text/html
|
||||
|
||||
-foo-
|
||||
</data>
|
||||
</reply>
|
||||
|
||||
# Client-side
|
||||
<client>
|
||||
<server>
|
||||
http
|
||||
</server>
|
||||
<features>
|
||||
Debug
|
||||
httpsig
|
||||
</features>
|
||||
<name>
|
||||
HTTP RFC 9421 Message Signatures: query string with special characters
|
||||
</name>
|
||||
<command>
|
||||
"http://example.com:5300/%TESTNUMBER/resource?name=me%AMPnoval%AMPaim=b%25ad" --httpsig-algo "ed25519" --httpsig-key %SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "test-key-ed25519" --connect-to example.com:5300:%HOSTIP:%HTTPPORT
|
||||
</command>
|
||||
</client>
|
||||
|
||||
# Verify data after the test has been "shot"
|
||||
<verify>
|
||||
<protocol crlf="headers">
|
||||
GET /%TESTNUMBER/resource?name=me%AMPnoval%AMPaim=b%25ad HTTP/1.1
|
||||
Host: example.com:5300
|
||||
Signature-Input: sig1=("@method" "@authority" "@path" "@query");created=0;keyid="test-key-ed25519";alg="ed25519"
|
||||
Signature: sig1=:ZFaJcDMTJEBbr90c0FaqupdJYDhNSz1i/eVCpMTDJ+1phLql2U4ROLiO26SGL6D0Nd5rr/3VXbDcWJ2vnV6IBA==:
|
||||
User-Agent: curl/%VERSION
|
||||
Accept: */*
|
||||
|
||||
</protocol>
|
||||
</verify>
|
||||
</testcase>
|
||||
58
tests/data/test5012
Normal file
58
tests/data/test5012
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="US-ASCII"?>
|
||||
<testcase>
|
||||
<info>
|
||||
<keywords>
|
||||
HTTP
|
||||
httpsig
|
||||
RFC9421
|
||||
default-port
|
||||
</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
|
||||
Content-Length: 6
|
||||
Connection: close
|
||||
Content-Type: text/html
|
||||
|
||||
-foo-
|
||||
</data>
|
||||
</reply>
|
||||
|
||||
# Client-side
|
||||
<client>
|
||||
<server>
|
||||
http
|
||||
</server>
|
||||
<features>
|
||||
Debug
|
||||
httpsig
|
||||
</features>
|
||||
<name>
|
||||
HTTP RFC 9421 Message Signatures: default port omitted from @authority
|
||||
</name>
|
||||
<command>
|
||||
"http://example.com/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-key %SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "test-key-ed25519" --connect-to example.com::%HOSTIP:%HTTPPORT
|
||||
</command>
|
||||
</client>
|
||||
|
||||
# Verify data after the test has been "shot"
|
||||
<verify>
|
||||
<strippart>
|
||||
s/Signature: sig1=:.*:/Signature: sig1=:STRIPPED:/
|
||||
</strippart>
|
||||
<protocol crlf="headers">
|
||||
GET /%TESTNUMBER/resource HTTP/1.1
|
||||
Host: example.com
|
||||
Signature-Input: sig1=("@method" "@authority" "@path");created=0;keyid="test-key-ed25519";alg="ed25519"
|
||||
Signature: sig1=:STRIPPED:
|
||||
User-Agent: curl/%VERSION
|
||||
Accept: */*
|
||||
|
||||
</protocol>
|
||||
</verify>
|
||||
</testcase>
|
||||
58
tests/data/test5013
Normal file
58
tests/data/test5013
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="US-ASCII"?>
|
||||
<testcase>
|
||||
<info>
|
||||
<keywords>
|
||||
HTTP
|
||||
httpsig
|
||||
RFC9421
|
||||
non-default-port
|
||||
</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
|
||||
Content-Length: 6
|
||||
Connection: close
|
||||
Content-Type: text/html
|
||||
|
||||
-foo-
|
||||
</data>
|
||||
</reply>
|
||||
|
||||
# Client-side
|
||||
<client>
|
||||
<server>
|
||||
http
|
||||
</server>
|
||||
<features>
|
||||
Debug
|
||||
httpsig
|
||||
</features>
|
||||
<name>
|
||||
HTTP RFC 9421 Message Signatures: non-default port in @authority
|
||||
</name>
|
||||
<command>
|
||||
"http://example.com:9000/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-key %SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "test-key-ed25519" --connect-to example.com:9000:%HOSTIP:%HTTPPORT
|
||||
</command>
|
||||
</client>
|
||||
|
||||
# Verify data after the test has been "shot"
|
||||
<verify>
|
||||
<strippart>
|
||||
s/Signature: sig1=:.*:/Signature: sig1=:STRIPPED:/
|
||||
</strippart>
|
||||
<protocol crlf="headers">
|
||||
GET /%TESTNUMBER/resource HTTP/1.1
|
||||
Host: example.com:9000
|
||||
Signature-Input: sig1=("@method" "@authority" "@path");created=0;keyid="test-key-ed25519";alg="ed25519"
|
||||
Signature: sig1=:STRIPPED:
|
||||
User-Agent: curl/%VERSION
|
||||
Accept: */*
|
||||
|
||||
</protocol>
|
||||
</verify>
|
||||
</testcase>
|
||||
58
tests/data/test5014
Normal file
58
tests/data/test5014
Normal file
|
|
@ -0,0 +1,58 @@
|
|||
<?xml version="1.0" encoding="US-ASCII"?>
|
||||
<testcase>
|
||||
<info>
|
||||
<keywords>
|
||||
HTTP
|
||||
httpsig
|
||||
RFC9421
|
||||
empty-query
|
||||
</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
|
||||
Content-Length: 6
|
||||
Connection: close
|
||||
Content-Type: text/html
|
||||
|
||||
-foo-
|
||||
</data>
|
||||
</reply>
|
||||
|
||||
# Client-side
|
||||
<client>
|
||||
<server>
|
||||
http
|
||||
</server>
|
||||
<features>
|
||||
Debug
|
||||
httpsig
|
||||
</features>
|
||||
<name>
|
||||
HTTP RFC 9421 Message Signatures: @query explicitly requested, no query in URL
|
||||
</name>
|
||||
<command>
|
||||
"http://example.com:5400/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-key %SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "test-key-ed25519" --httpsig-headers "method authority query" --connect-to example.com:5400:%HOSTIP:%HTTPPORT
|
||||
</command>
|
||||
</client>
|
||||
|
||||
# Verify data after the test has been "shot"
|
||||
<verify>
|
||||
<strippart>
|
||||
s/Signature: sig1=:.*:/Signature: sig1=:STRIPPED:/
|
||||
</strippart>
|
||||
<protocol crlf="headers">
|
||||
GET /%TESTNUMBER/resource HTTP/1.1
|
||||
Host: example.com:5400
|
||||
Signature-Input: sig1=("@method" "@authority" "@query");created=0;keyid="test-key-ed25519";alg="ed25519"
|
||||
Signature: sig1=:STRIPPED:
|
||||
User-Agent: curl/%VERSION
|
||||
Accept: */*
|
||||
|
||||
</protocol>
|
||||
</verify>
|
||||
</testcase>
|
||||
62
tests/data/test5015
Normal file
62
tests/data/test5015
Normal file
|
|
@ -0,0 +1,62 @@
|
|||
<?xml version="1.0" encoding="US-ASCII"?>
|
||||
<testcase>
|
||||
<info>
|
||||
<keywords>
|
||||
HTTP
|
||||
HTTP POST
|
||||
httpsig
|
||||
RFC9421
|
||||
hmac-sha256
|
||||
</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
|
||||
Content-Length: 6
|
||||
Connection: close
|
||||
Content-Type: text/html
|
||||
|
||||
-foo-
|
||||
</data>
|
||||
</reply>
|
||||
|
||||
# Client-side
|
||||
<client>
|
||||
<server>
|
||||
http
|
||||
</server>
|
||||
<features>
|
||||
Debug
|
||||
httpsig
|
||||
</features>
|
||||
<name>
|
||||
HTTP RFC 9421 Message Signatures: POST with HMAC-SHA256
|
||||
</name>
|
||||
<command>
|
||||
"http://example.com:5500/%TESTNUMBER/resource" --httpsig-algo "hmac-sha256" --httpsig-key %SRCDIR/data/data-httpsig-hmac-sha256.key --httpsig-keyid "shared-key-1" -d "postbody" --connect-to example.com:5500:%HOSTIP:%HTTPPORT
|
||||
</command>
|
||||
</client>
|
||||
|
||||
# Verify data after the test has been "shot"
|
||||
<verify>
|
||||
<strippart>
|
||||
s/Signature: sig1=:.*:/Signature: sig1=:STRIPPED:/
|
||||
</strippart>
|
||||
<protocol crlf="headers" nonewline="yes">
|
||||
POST /%TESTNUMBER/resource HTTP/1.1
|
||||
Host: example.com:5500
|
||||
Signature-Input: sig1=("@method" "@authority" "@path");created=0;keyid="shared-key-1";alg="hmac-sha256"
|
||||
Signature: sig1=:STRIPPED:
|
||||
User-Agent: curl/%VERSION
|
||||
Accept: */*
|
||||
Content-Length: 8
|
||||
Content-Type: application/x-www-form-urlencoded
|
||||
|
||||
postbody
|
||||
</protocol>
|
||||
</verify>
|
||||
</testcase>
|
||||
60
tests/data/test5016
Normal file
60
tests/data/test5016
Normal file
|
|
@ -0,0 +1,60 @@
|
|||
<?xml version="1.0" encoding="US-ASCII"?>
|
||||
<testcase>
|
||||
<info>
|
||||
<keywords>
|
||||
HTTP
|
||||
httpsig
|
||||
RFC9421
|
||||
hmac-sha256
|
||||
header-signing
|
||||
</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
|
||||
Content-Length: 6
|
||||
Connection: close
|
||||
Content-Type: text/html
|
||||
|
||||
-foo-
|
||||
</data>
|
||||
</reply>
|
||||
|
||||
# Client-side
|
||||
<client>
|
||||
<server>
|
||||
http
|
||||
</server>
|
||||
<features>
|
||||
Debug
|
||||
httpsig
|
||||
</features>
|
||||
<name>
|
||||
HTTP RFC 9421 Message Signatures: HMAC-SHA256 with custom headers
|
||||
</name>
|
||||
<command>
|
||||
"http://example.com:5600/%TESTNUMBER/resource" --httpsig-algo "hmac-sha256" --httpsig-key %SRCDIR/data/data-httpsig-hmac-sha256.key --httpsig-keyid "shared-key-1" --httpsig-headers "method authority content-type:" -H "Content-Type: application/json" --connect-to example.com:5600:%HOSTIP:%HTTPPORT
|
||||
</command>
|
||||
</client>
|
||||
|
||||
# Verify data after the test has been "shot"
|
||||
<verify>
|
||||
<strippart>
|
||||
s/Signature: sig1=:.*:/Signature: sig1=:STRIPPED:/
|
||||
</strippart>
|
||||
<protocol crlf="headers">
|
||||
GET /%TESTNUMBER/resource HTTP/1.1
|
||||
Host: example.com:5600
|
||||
Signature-Input: sig1=("@method" "@authority" "content-type");created=0;keyid="shared-key-1";alg="hmac-sha256"
|
||||
Signature: sig1=:STRIPPED:
|
||||
User-Agent: curl/%VERSION
|
||||
Accept: */*
|
||||
Content-Type: application/json
|
||||
|
||||
</protocol>
|
||||
</verify>
|
||||
</testcase>
|
||||
55
tests/data/test5017
Normal file
55
tests/data/test5017
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
<?xml version="1.0" encoding="US-ASCII"?>
|
||||
<testcase>
|
||||
<info>
|
||||
<keywords>
|
||||
HTTP
|
||||
httpsig
|
||||
RFC9421
|
||||
preexisting-signature
|
||||
</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
|
||||
Content-Length: 6
|
||||
Connection: close
|
||||
Content-Type: text/html
|
||||
|
||||
-foo-
|
||||
</data>
|
||||
</reply>
|
||||
|
||||
# Client-side
|
||||
<client>
|
||||
<server>
|
||||
http
|
||||
</server>
|
||||
<features>
|
||||
Debug
|
||||
httpsig
|
||||
</features>
|
||||
<name>
|
||||
HTTP RFC 9421 Message Signatures: user-supplied Signature header skips signing
|
||||
</name>
|
||||
<command>
|
||||
"http://example.com:5700/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-key %SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "test-key-ed25519" -H "Signature: preexisting" -H "Signature-Input: preexisting" --connect-to example.com:5700:%HOSTIP:%HTTPPORT
|
||||
</command>
|
||||
</client>
|
||||
|
||||
# Verify data after the test has been "shot"
|
||||
<verify>
|
||||
<protocol crlf="headers">
|
||||
GET /%TESTNUMBER/resource HTTP/1.1
|
||||
Host: example.com:5700
|
||||
User-Agent: curl/%VERSION
|
||||
Accept: */*
|
||||
Signature: preexisting
|
||||
Signature-Input: preexisting
|
||||
|
||||
</protocol>
|
||||
</verify>
|
||||
</testcase>
|
||||
63
tests/data/test5018
Normal file
63
tests/data/test5018
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
<?xml version="1.0" encoding="US-ASCII"?>
|
||||
<testcase>
|
||||
<info>
|
||||
<keywords>
|
||||
HTTP
|
||||
HTTP PUT
|
||||
httpsig
|
||||
RFC9421
|
||||
</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
|
||||
Content-Length: 6
|
||||
Connection: close
|
||||
Content-Type: text/html
|
||||
|
||||
-foo-
|
||||
</data>
|
||||
</reply>
|
||||
|
||||
# Client-side
|
||||
<client>
|
||||
<server>
|
||||
http
|
||||
</server>
|
||||
<features>
|
||||
Debug
|
||||
httpsig
|
||||
</features>
|
||||
<file name="log/upload5018">
|
||||
putdata
|
||||
</file>
|
||||
<name>
|
||||
HTTP RFC 9421 Message Signatures: PUT upload
|
||||
</name>
|
||||
<command>
|
||||
"http://example.com:5800/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-key %SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "test-key-ed25519" -T log/upload5018 --connect-to example.com:5800:%HOSTIP:%HTTPPORT
|
||||
</command>
|
||||
</client>
|
||||
|
||||
# Verify data after the test has been "shot"
|
||||
<verify>
|
||||
<strippart>
|
||||
s/Signature: sig1=:.*:/Signature: sig1=:STRIPPED:/
|
||||
</strippart>
|
||||
<protocol crlf="headers">
|
||||
PUT /%TESTNUMBER/resource HTTP/1.1
|
||||
Host: example.com:5800
|
||||
Signature-Input: sig1=("@method" "@authority" "@path");created=0;keyid="test-key-ed25519";alg="ed25519"
|
||||
Signature: sig1=:STRIPPED:
|
||||
User-Agent: curl/%VERSION
|
||||
Accept: */*
|
||||
Content-Length: 8
|
||||
|
||||
putdata
|
||||
</protocol>
|
||||
</verify>
|
||||
</testcase>
|
||||
46
tests/data/test5019
Normal file
46
tests/data/test5019
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<?xml version="1.0" encoding="US-ASCII"?>
|
||||
<testcase>
|
||||
<info>
|
||||
<keywords>
|
||||
HTTP
|
||||
httpsig
|
||||
RFC9421
|
||||
error
|
||||
</keywords>
|
||||
</info>
|
||||
|
||||
# Server-side
|
||||
<reply>
|
||||
<data nocheck="yes">
|
||||
HTTP/1.1 200 OK
|
||||
Date: Tue, 09 Nov 2010 14:49:00 GMT
|
||||
Server: test-server/fake
|
||||
Content-Length: 0
|
||||
|
||||
</data>
|
||||
</reply>
|
||||
|
||||
# Client-side
|
||||
<client>
|
||||
<server>
|
||||
http
|
||||
</server>
|
||||
<features>
|
||||
Debug
|
||||
httpsig
|
||||
</features>
|
||||
<name>
|
||||
HTTP RFC 9421 Message Signatures: error - duplicate component
|
||||
</name>
|
||||
<command>
|
||||
"http://example.com/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-key %SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "my-key" --httpsig-headers "method method" --connect-to example.com::%HOSTIP:%HTTPPORT
|
||||
</command>
|
||||
</client>
|
||||
|
||||
# Verify data after the test has been "shot"
|
||||
<verify>
|
||||
<errorcode>
|
||||
43
|
||||
</errorcode>
|
||||
</verify>
|
||||
</testcase>
|
||||
46
tests/data/test5020
Normal file
46
tests/data/test5020
Normal file
|
|
@ -0,0 +1,46 @@
|
|||
<?xml version="1.0" encoding="US-ASCII"?>
|
||||
<testcase>
|
||||
<info>
|
||||
<keywords>
|
||||
HTTP
|
||||
httpsig
|
||||
RFC9421
|
||||
error
|
||||
</keywords>
|
||||
</info>
|
||||
|
||||
# Server-side
|
||||
<reply>
|
||||
<data nocheck="yes">
|
||||
HTTP/1.1 200 OK
|
||||
Date: Tue, 09 Nov 2010 14:49:00 GMT
|
||||
Server: test-server/fake
|
||||
Content-Length: 0
|
||||
|
||||
</data>
|
||||
</reply>
|
||||
|
||||
# Client-side
|
||||
<client>
|
||||
<server>
|
||||
http
|
||||
</server>
|
||||
<features>
|
||||
Debug
|
||||
httpsig
|
||||
</features>
|
||||
<name>
|
||||
HTTP RFC 9421 Message Signatures: error - rejects legacy '@' component syntax
|
||||
</name>
|
||||
<command>
|
||||
"http://example.com/%TESTNUMBER/resource" --httpsig-algo "ed25519" --httpsig-key %SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "my-key" --httpsig-headers "@method authority" --connect-to example.com::%HOSTIP:%HTTPPORT
|
||||
</command>
|
||||
</client>
|
||||
|
||||
# Verify data after the test has been "shot"
|
||||
<verify>
|
||||
<errorcode>
|
||||
43
|
||||
</errorcode>
|
||||
</verify>
|
||||
</testcase>
|
||||
57
tests/data/test5021
Normal file
57
tests/data/test5021
Normal file
|
|
@ -0,0 +1,57 @@
|
|||
<?xml version="1.0" encoding="US-ASCII"?>
|
||||
<testcase>
|
||||
<info>
|
||||
<keywords>
|
||||
HTTP
|
||||
httpsig
|
||||
RFC9421
|
||||
</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
|
||||
Content-Length: 6
|
||||
Connection: close
|
||||
Content-Type: text/html
|
||||
|
||||
-foo-
|
||||
</data>
|
||||
</reply>
|
||||
|
||||
# Client-side
|
||||
<client>
|
||||
<server>
|
||||
http
|
||||
</server>
|
||||
<features>
|
||||
Debug
|
||||
httpsig
|
||||
</features>
|
||||
<name>
|
||||
HTTP RFC 9421 Message Signatures: default algorithm (ed25519) when --httpsig-algo omitted
|
||||
</name>
|
||||
<command>
|
||||
"http://example.com:6100/%TESTNUMBER/resource" --httpsig-key %SRCDIR/data/data-httpsig-ed25519.key --httpsig-keyid "test-key-ed25519" --connect-to example.com:6100:%HOSTIP:%HTTPPORT
|
||||
</command>
|
||||
</client>
|
||||
|
||||
# Verify data after the test has been "shot"
|
||||
<verify>
|
||||
<strippart>
|
||||
s/Signature: sig1=:.*:/Signature: sig1=:STRIPPED:/
|
||||
</strippart>
|
||||
<protocol crlf="headers">
|
||||
GET /%TESTNUMBER/resource HTTP/1.1
|
||||
Host: example.com:6100
|
||||
Signature-Input: sig1=("@method" "@authority" "@path");created=0;keyid="test-key-ed25519";alg="ed25519"
|
||||
Signature: sig1=:STRIPPED:
|
||||
User-Agent: curl/%VERSION
|
||||
Accept: */*
|
||||
|
||||
</protocol>
|
||||
</verify>
|
||||
</testcase>
|
||||
|
|
@ -124,4 +124,5 @@ TESTS_C = \
|
|||
lib2700.c \
|
||||
lib3010.c lib3025.c lib3026.c lib3027.c lib3033.c lib3034.c \
|
||||
lib3100.c lib3101.c lib3102.c lib3103.c lib3104.c lib3105.c \
|
||||
lib3207.c lib3208.c
|
||||
lib3207.c lib3208.c \
|
||||
lib5000.c lib5004.c
|
||||
|
|
|
|||
66
tests/libtest/lib5000.c
Normal file
66
tests/libtest/lib5000.c
Normal file
|
|
@ -0,0 +1,66 @@
|
|||
/***************************************************************************
|
||||
* _ _ ____ _
|
||||
* 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 "first.h"
|
||||
|
||||
static CURLcode test_lib5000(const char *URL)
|
||||
{
|
||||
CURL *curl;
|
||||
CURLcode result = TEST_ERR_MAJOR_BAD;
|
||||
struct curl_slist *connect_to = NULL;
|
||||
|
||||
if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
|
||||
curl_mfprintf(stderr, "curl_global_init() failed\n");
|
||||
return TEST_ERR_MAJOR_BAD;
|
||||
}
|
||||
|
||||
curl = curl_easy_init();
|
||||
if(!curl) {
|
||||
curl_mfprintf(stderr, "curl_easy_init() failed\n");
|
||||
curl_global_cleanup();
|
||||
return TEST_ERR_MAJOR_BAD;
|
||||
}
|
||||
|
||||
easy_setopt(curl, CURLOPT_VERBOSE, 1L);
|
||||
easy_setopt(curl, CURLOPT_HTTPSIG_ALGORITHM, (long)CURLHTTPSIG_ED25519);
|
||||
easy_setopt(curl, CURLOPT_HTTPSIG_KEY,
|
||||
"9f8362f87a484a954e6e740c5b4c0e84"
|
||||
"229139a20aa8ab56ff66586f6a7d29c5");
|
||||
easy_setopt(curl, CURLOPT_HTTPSIG_KEYID, "test-key-ed25519");
|
||||
easy_setopt(curl, CURLOPT_HEADER, 0L);
|
||||
easy_setopt(curl, CURLOPT_URL, URL);
|
||||
if(libtest_arg2) {
|
||||
connect_to = curl_slist_append(connect_to, libtest_arg2);
|
||||
}
|
||||
easy_setopt(curl, CURLOPT_CONNECT_TO, connect_to);
|
||||
|
||||
result = curl_easy_perform(curl);
|
||||
|
||||
test_cleanup:
|
||||
|
||||
curl_slist_free_all(connect_to);
|
||||
curl_easy_cleanup(curl);
|
||||
curl_global_cleanup();
|
||||
|
||||
return result;
|
||||
}
|
||||
85
tests/libtest/lib5004.c
Normal file
85
tests/libtest/lib5004.c
Normal file
|
|
@ -0,0 +1,85 @@
|
|||
/***************************************************************************
|
||||
* _ _ ____ _
|
||||
* 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
|
||||
*
|
||||
***************************************************************************/
|
||||
|
||||
/* RFC 9421 Appendix B.2.6 test vector: Ed25519 signing of a POST request
|
||||
* with headers. Uses CURL_HTTPSIG_CREATED=1618884473 to match the RFC
|
||||
* expected timestamp. */
|
||||
|
||||
#include "first.h"
|
||||
|
||||
static CURLcode test_lib5004(const char *URL)
|
||||
{
|
||||
CURL *curl;
|
||||
CURLcode result = TEST_ERR_MAJOR_BAD;
|
||||
struct curl_slist *connect_to = NULL;
|
||||
struct curl_slist *headers = NULL;
|
||||
|
||||
if(curl_global_init(CURL_GLOBAL_ALL) != CURLE_OK) {
|
||||
curl_mfprintf(stderr, "curl_global_init() failed\n");
|
||||
return TEST_ERR_MAJOR_BAD;
|
||||
}
|
||||
|
||||
curl = curl_easy_init();
|
||||
if(!curl) {
|
||||
curl_mfprintf(stderr, "curl_easy_init() failed\n");
|
||||
curl_global_cleanup();
|
||||
return TEST_ERR_MAJOR_BAD;
|
||||
}
|
||||
|
||||
easy_setopt(curl, CURLOPT_VERBOSE, 1L);
|
||||
easy_setopt(curl, CURLOPT_HTTPSIG_ALGORITHM, (long)CURLHTTPSIG_ED25519);
|
||||
easy_setopt(curl, CURLOPT_HTTPSIG_KEY,
|
||||
"9f8362f87a484a954e6e740c5b4c0e84"
|
||||
"229139a20aa8ab56ff66586f6a7d29c5");
|
||||
easy_setopt(curl, CURLOPT_HTTPSIG_KEYID, "test-key-ed25519");
|
||||
easy_setopt(curl, CURLOPT_HTTPSIG_HEADERS,
|
||||
"date: method path authority content-type: content-length:");
|
||||
easy_setopt(curl, CURLOPT_POSTFIELDS, "{\"hello\": \"world\"}");
|
||||
|
||||
headers = curl_slist_append(headers,
|
||||
"Date: Tue, 20 Apr 2021 02:07:55 GMT");
|
||||
headers = curl_slist_append(headers,
|
||||
"Content-Type: application/json");
|
||||
headers = curl_slist_append(headers,
|
||||
"Content-Length: 18");
|
||||
easy_setopt(curl, CURLOPT_HTTPHEADER, headers);
|
||||
|
||||
easy_setopt(curl, CURLOPT_HEADER, 0L);
|
||||
easy_setopt(curl, CURLOPT_URL, URL);
|
||||
if(libtest_arg2) {
|
||||
connect_to = curl_slist_append(connect_to, libtest_arg2);
|
||||
}
|
||||
easy_setopt(curl, CURLOPT_CONNECT_TO, connect_to);
|
||||
|
||||
result = curl_easy_perform(curl);
|
||||
|
||||
test_cleanup:
|
||||
|
||||
curl_slist_free_all(headers);
|
||||
curl_slist_free_all(connect_to);
|
||||
curl_easy_cleanup(curl);
|
||||
curl_global_cleanup();
|
||||
|
||||
return result;
|
||||
}
|
||||
Loading…
Add table
Add a link
Reference in a new issue