httpsig: add RFC 9421 HTTP Message Signatures support
No binary (build failed)
—
(log yok)
| @@ -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 | |
| @@ -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 | |
| @@ -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 |
| @@ -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 \ | |
| @@ -0,0 +1,36 @@ | |
| + | --- |
| + | c: Copyright (C) Daniel Stenberg, <[email protected]>, 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. |
| @@ -0,0 +1,45 @@ | |
| + | --- |
| + | c: Copyright (C) Daniel Stenberg, <[email protected]>, 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. |
| @@ -0,0 +1,35 @@ | |
| + | --- |
| + | c: Copyright (C) Daniel Stenberg, <[email protected]>, 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`. |
| @@ -0,0 +1,23 @@ | |
| + | --- |
| + | c: Copyright (C) Daniel Stenberg, <[email protected]>, 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 | |
| @@ -0,0 +1,89 @@ | |
| + | --- |
| + | c: Copyright (C) Daniel Stenberg, <[email protected]>, 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). |
| @@ -0,0 +1,108 @@ | |
| + | --- |
| + | c: Copyright (C) Daniel Stenberg, <[email protected]>, 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). |
| @@ -0,0 +1,90 @@ | |
| + | --- |
| + | c: Copyright (C) Daniel Stenberg, <[email protected]>, 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). |
| @@ -0,0 +1,76 @@ | |
| + | --- |
| + | c: Copyright (C) Daniel Stenberg, <[email protected]>, 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 | |
| @@ -0,0 +1,170 @@ | |
| + | /*************************************************************************** |
| + | * _ _ ____ _ |
| + | * Project ___| | | | _ \| | |
| + | * / __| | | | |_) | | |
| + | * | (__| |_| | _ <| |___ |
| + | * \___|\___/|_| \_\_____| |
| + | * |
| + | * Copyright (C) Daniel Stenberg, <[email protected]>, 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 */ |
| @@ -0,0 +1,44 @@ | |
| + | #ifndef HEADER_CURL_ED25519_H |
| + | #define HEADER_CURL_ED25519_H |
| + | /*************************************************************************** |
| + | * _ _ ____ _ |
| + | * Project ___| | | | _ \| | |
| + | * / __| | | | |_) | | |
| + | * | (__| |_| | _ <| |___ |
| + | * \___|\___/|_| \_\_____| |
| + | * |
| + | * Copyright (C) Daniel Stenberg, <[email protected]>, 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 | |
| @@ -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 */ | |
| @@ -0,0 +1,692 @@ | |
| + | /*************************************************************************** |
| + | * _ _ ____ _ |
| + | * Project ___| | | | _ \| | |
| + | * / __| | | | |_) | | |
| + | * | (__| |_| | _ <| |___ |
| + | * \___|\___/|_| \_\_____| |
| + | * |
| + | * Copyright (C) Daniel Stenberg, <[email protected]>, 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 */ |
| @@ -0,0 +1,35 @@ | |
| + | #ifndef HEADER_CURL_HTTP_HTTPSIG_H |
| + | #define HEADER_CURL_HTTP_HTTPSIG_H |
| + | /*************************************************************************** |
| + | * _ _ ____ _ |
| + | * Project ___| | | | _ \| | |
| + | * / __| | | | |_) | | |
| + | * | (__| |_| | _ <| |___ |
| + | * \___|\___/|_| \_\_____| |
| + | * |
| + | * Copyright (C) Daniel Stenberg, <[email protected]>, 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 */ |
| @@ -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 |
| @@ -0,0 +1 @@ | |
| + | 9f8362f87a484a954e6e740c5b4c0e84229139a20aa8ab56ff66586f6a7d29c5 |
| @@ -0,0 +1 @@ | |
| + | 0123456789abcdef0123456789abcdef0123456789abcdef0123456789abcdef |
| @@ -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> |
| @@ -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> |
| @@ -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> |
| @@ -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> |
| @@ -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> |
| @@ -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> |
| @@ -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> |
| @@ -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> |
| @@ -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> |
| @@ -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> |
| @@ -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> |
| @@ -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> |
| @@ -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> |
| @@ -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> |
| @@ -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> |
| @@ -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> |
| @@ -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> |
| @@ -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> |
| @@ -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> |
| @@ -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> |
| @@ -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> |
| @@ -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 |
| @@ -0,0 +1,66 @@ | |
| + | /*************************************************************************** |
| + | * _ _ ____ _ |
| + | * Project ___| | | | _ \| | |
| + | * / __| | | | |_) | | |
| + | * | (__| |_| | _ <| |___ |
| + | * \___|\___/|_| \_\_____| |
| + | * |
| + | * Copyright (C) Daniel Stenberg, <[email protected]>, 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; |
| + | } |
| @@ -0,0 +1,85 @@ | |
| + | /*************************************************************************** |
| + | * _ _ ____ _ |
| + | * Project ___| | | | _ \| | |
| + | * / __| | | | |_) | | |
| + | * | (__| |_| | _ <| |___ |
| + | * \___|\___/|_| \_\_____| |
| + | * |
| + | * Copyright (C) Daniel Stenberg, <[email protected]>, 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; |
| + | } |