TLS: add support for ECH (Encrypted Client Hello)
An EXPERIMENTAL feature used with CURLOPT_ECH and --ech. Closes #11922
This commit is contained in:
parent
565d28dc8e
commit
a362962b72
23
.github/scripts/spellcheck.words
vendored
23
.github/scripts/spellcheck.words
vendored
@ -2,6 +2,7 @@
|
||||
#
|
||||
# SPDX-License-Identifier: curl
|
||||
#
|
||||
AAAA
|
||||
ABI
|
||||
accessor
|
||||
ACK
|
||||
@ -10,6 +11,7 @@ AIA
|
||||
AIX
|
||||
al
|
||||
Alessandro
|
||||
aliasMode
|
||||
allocator
|
||||
alnum
|
||||
ALPN
|
||||
@ -109,6 +111,7 @@ CLA
|
||||
CLAs
|
||||
cleartext
|
||||
CLI
|
||||
ClientHello
|
||||
clientp
|
||||
cliget
|
||||
closesocket
|
||||
@ -116,6 +119,8 @@ CMake
|
||||
cmake
|
||||
CMake's
|
||||
cmake's
|
||||
CNAME
|
||||
CNAMEs
|
||||
CMakeLists
|
||||
CNA
|
||||
CodeQL
|
||||
@ -146,6 +151,7 @@ cURL
|
||||
CURLcode
|
||||
curldown
|
||||
CURLE
|
||||
CURLECH
|
||||
CURLH
|
||||
curlimages
|
||||
CURLINFO
|
||||
@ -164,6 +170,7 @@ dbg
|
||||
Debian
|
||||
DEBUGBUILD
|
||||
decrypt
|
||||
decrypting
|
||||
deepcode
|
||||
DELE
|
||||
DER
|
||||
@ -190,6 +197,7 @@ DNS
|
||||
dns
|
||||
dnsop
|
||||
DoH
|
||||
DoT
|
||||
doxygen
|
||||
drftpd
|
||||
dsa
|
||||
@ -201,6 +209,9 @@ EBCDIC
|
||||
ECC
|
||||
ECDHE
|
||||
ECH
|
||||
ecl
|
||||
ECHConfig
|
||||
ECHConfigList
|
||||
ECONNREFUSED
|
||||
eCOS
|
||||
EFnet
|
||||
@ -284,6 +295,8 @@ GOST
|
||||
GPG
|
||||
GPL
|
||||
GPLed
|
||||
GREASE
|
||||
GREASEing
|
||||
Greear
|
||||
groff
|
||||
gsasl
|
||||
@ -307,6 +320,7 @@ Hards
|
||||
Haxx
|
||||
haxx
|
||||
Heimdal
|
||||
HelloRetryRequest
|
||||
HELO
|
||||
HH
|
||||
HMAC
|
||||
@ -316,6 +330,7 @@ homebrew
|
||||
hostname
|
||||
hostnames
|
||||
Housley
|
||||
HRR
|
||||
Hruska
|
||||
HSTS
|
||||
hsts
|
||||
@ -460,6 +475,7 @@ Marek
|
||||
Mavrogiannopoulos
|
||||
Mbed
|
||||
mbedTLS
|
||||
md
|
||||
Meglio
|
||||
memdebug
|
||||
MesaLink
|
||||
@ -470,6 +486,7 @@ Michal
|
||||
Micrium
|
||||
MicroBlaze
|
||||
MicroOS
|
||||
middlebox
|
||||
mingw
|
||||
MinGW
|
||||
MINIX
|
||||
@ -590,6 +607,7 @@ pkcs
|
||||
PKGBUILD
|
||||
PKI
|
||||
pluggable
|
||||
pn
|
||||
PolarSSL
|
||||
Polhem
|
||||
pollset
|
||||
@ -625,6 +643,7 @@ py
|
||||
pycurl
|
||||
pytest
|
||||
Pytest
|
||||
qname
|
||||
QNX
|
||||
QoS
|
||||
Qubes
|
||||
@ -668,6 +687,9 @@ Roadmap
|
||||
Rockbox
|
||||
roffit
|
||||
RPG
|
||||
RR
|
||||
RRs
|
||||
RRtype
|
||||
RSA
|
||||
RTMP
|
||||
rtmp
|
||||
@ -784,6 +806,7 @@ SunSSH
|
||||
superset
|
||||
svc
|
||||
svcb
|
||||
SVCB
|
||||
Svyatoslav
|
||||
Swisscom
|
||||
sws
|
||||
|
||||
1
.gitignore
vendored
1
.gitignore
vendored
@ -65,3 +65,4 @@ curl_fuzzer_seed_corpus.zip
|
||||
libstandaloneengine.a
|
||||
tests/string
|
||||
tests/config
|
||||
tests/ech-log/
|
||||
|
||||
@ -37,6 +37,7 @@
|
||||
# HAVE_GNUTLS_SRP: `gnutls_srp_verifier` present in GnuTLS
|
||||
# HAVE_SSL_CTX_SET_QUIC_METHOD: `SSL_CTX_set_quic_method` present in OpenSSL/wolfSSL
|
||||
# HAVE_QUICHE_CONN_SET_QLOG_FD: `quiche_conn_set_qlog_fd` present in QUICHE
|
||||
# HAVE_ECH: ECH API checks for OpenSSL, boringssl or wolfSSL
|
||||
#
|
||||
# For each of the above variables, if the variable is DEFINED (either
|
||||
# to ON or OFF), the symbol detection will be skipped. If the
|
||||
@ -654,6 +655,31 @@ if(USE_OPENSSL OR USE_WOLFSSL)
|
||||
endif()
|
||||
endif()
|
||||
|
||||
option(USE_HTTPSRR "Enable HTTPS RR support for ECH (experimental)" OFF)
|
||||
option(USE_ECH "Enable ECH support" OFF)
|
||||
if(USE_ECH)
|
||||
if(USE_OPENSSL OR USE_WOLFSSL)
|
||||
# Be sure that the OpenSSL/wolfSSL library actually supports ECH.
|
||||
if(NOT DEFINED HAVE_ECH)
|
||||
if(USE_OPENSSL AND HAVE_BORINGSSL)
|
||||
openssl_check_symbol_exists(SSL_set1_ech_config_list "openssl/ssl.h" HAVE_ECH)
|
||||
elseif(USE_OPENSSL)
|
||||
openssl_check_symbol_exists(SSL_ech_set1_echconfig "openssl/ech.h" HAVE_ECH)
|
||||
elseif(USE_WOLFSSL)
|
||||
openssl_check_symbol_exists(wolfSSL_CTX_GenerateEchConfig "wolfssl/options.h;wolfssl/ssl.h" HAVE_ECH)
|
||||
endif()
|
||||
endif()
|
||||
if(NOT HAVE_ECH)
|
||||
message(FATAL_ERROR "ECH support missing in OpenSSL/BoringSSL/wolfSSL")
|
||||
else()
|
||||
message("ECH enabled.")
|
||||
endif()
|
||||
else()
|
||||
message(FATAL_ERROR "ECH requires ECH-enablded OpenSSL, BoringSSL or wolfSSL")
|
||||
endif()
|
||||
endif()
|
||||
|
||||
|
||||
option(USE_NGHTTP2 "Use nghttp2 library" OFF)
|
||||
if(USE_NGHTTP2)
|
||||
find_package(NGHTTP2 REQUIRED)
|
||||
@ -1590,6 +1616,8 @@ if(NOT CURL_DISABLE_INSTALL)
|
||||
_add_if("IPFS" NOT CURL_DISABLE_HTTP)
|
||||
_add_if("IPNS" NOT CURL_DISABLE_HTTP)
|
||||
_add_if("HTTPS" NOT CURL_DISABLE_HTTP AND SSL_ENABLED)
|
||||
_add_if("ECH" HAVE_ECH)
|
||||
_add_if("HTTPSRR" HAVE_ECH)
|
||||
_add_if("FTP" NOT CURL_DISABLE_FTP)
|
||||
_add_if("FTPS" NOT CURL_DISABLE_FTP AND SSL_ENABLED)
|
||||
_add_if("FILE" NOT CURL_DISABLE_FILE)
|
||||
|
||||
37
configure.ac
37
configure.ac
@ -51,6 +51,7 @@ CURL_CHECK_OPTION_CURLDEBUG
|
||||
CURL_CHECK_OPTION_SYMBOL_HIDING
|
||||
CURL_CHECK_OPTION_ARES
|
||||
CURL_CHECK_OPTION_RT
|
||||
CURL_CHECK_OPTION_HTTPSRR
|
||||
CURL_CHECK_OPTION_ECH
|
||||
|
||||
XC_CHECK_PATH_SEPARATOR
|
||||
@ -4538,6 +4539,16 @@ if test "x$hsts" != "xyes"; then
|
||||
AC_DEFINE(CURL_DISABLE_HSTS, 1, [disable alt-svc])
|
||||
fi
|
||||
|
||||
|
||||
dnl *************************************************************
|
||||
dnl check whether HTTPSRR support if desired
|
||||
dnl
|
||||
if test "x$want_httpsrr" != "xno"; then
|
||||
AC_MSG_RESULT([HTTPSRR support is available])
|
||||
AC_DEFINE(USE_HTTPSRR, 1, [enable HTTPS RR support])
|
||||
experimental="$experimental HTTPSRR"
|
||||
fi
|
||||
|
||||
dnl *************************************************************
|
||||
dnl check whether ECH support, if desired, is actually available
|
||||
dnl
|
||||
@ -4548,18 +4559,28 @@ if test "x$want_ech" != "xno"; then
|
||||
ECH_ENABLED=0
|
||||
ECH_SUPPORT=''
|
||||
|
||||
dnl OpenSSL with a chosen ECH function should be enough
|
||||
dnl so more exhaustive checking seems unnecessary for now
|
||||
dnl check for OpenSSL
|
||||
if test "x$OPENSSL_ENABLED" = "x1"; then
|
||||
AC_CHECK_FUNCS(SSL_get_ech_status,
|
||||
ECH_SUPPORT="ECH support available (OpenSSL with SSL_get_ech_status)"
|
||||
AC_CHECK_FUNCS(SSL_ech_set1_echconfig,
|
||||
ECH_SUPPORT="ECH support available via OpenSSL with SSL_ech_set1_echconfig"
|
||||
ECH_ENABLED=1)
|
||||
fi
|
||||
dnl check for boringssl equivalent
|
||||
if test "x$OPENSSL_ENABLED" = "x1"; then
|
||||
AC_CHECK_FUNCS(SSL_set1_ech_config_list,
|
||||
ECH_SUPPORT="ECH support available via boringssl with SSL_set1_ech_config_list"
|
||||
ECH_ENABLED=1)
|
||||
fi
|
||||
if test "x$WOLFSSL_ENABLED" = "x1"; then
|
||||
AC_CHECK_FUNCS(wolfSSL_CTX_GenerateEchConfig,
|
||||
ECH_SUPPORT="ECH support available via WolfSSL with wolfSSL_CTX_GenerateEchConfig"
|
||||
ECH_ENABLED=1)
|
||||
|
||||
dnl add 'elif' chain here for additional implementations
|
||||
fi
|
||||
|
||||
dnl now deal with whatever we found
|
||||
if test "x$ECH_ENABLED" = "x1"; then
|
||||
dnl force pre-requisites for ECH
|
||||
AC_DEFINE(USE_HTTPSRR, 1, [force HTTPS RR support for ECH])
|
||||
AC_DEFINE(USE_ECH, 1, [if ECH support is available])
|
||||
AC_MSG_RESULT($ECH_SUPPORT)
|
||||
experimental="$experimental ECH"
|
||||
@ -4777,10 +4798,6 @@ else
|
||||
AC_MSG_RESULT([no])
|
||||
fi
|
||||
|
||||
if test "x$ECH_ENABLED" = "x1"; then
|
||||
SUPPORT_FEATURES="$SUPPORT_FEATURES ECH"
|
||||
fi
|
||||
|
||||
if test ${ac_cv_sizeof_curl_off_t} -gt 4; then
|
||||
if test ${ac_cv_sizeof_off_t} -gt 4 -o \
|
||||
"$curl_win32_file_api" = "win32_large_files"; then
|
||||
|
||||
479
docs/ECH.md
Normal file
479
docs/ECH.md
Normal file
@ -0,0 +1,479 @@
|
||||
<!--
|
||||
Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
|
||||
|
||||
SPDX-License-Identifier: curl
|
||||
-->
|
||||
|
||||
# Building curl with HTTPS-RR and ECH support
|
||||
|
||||
We've added support for ECH to in this curl build. That can use HTTPS RRs
|
||||
published in the DNS, if curl is using DoH, or else can accept the relevant
|
||||
ECHConfigList values from the command line. That works with OpenSSL,
|
||||
WolfSSL or boringssl as the TLS provider, depending on how you build curl.
|
||||
|
||||
This feature is EXPERIMENTAL. DO NOT USE IN PRODUCTION.
|
||||
|
||||
This should however provide enough of a proof-of-concept to prompt an informed
|
||||
discussion about a good path forward for ECH support in curl, when using
|
||||
OpenSSL, or other TLS libraries, as those add ECH support.
|
||||
|
||||
## OpenSSL Build
|
||||
|
||||
To build our ECH-enabled OpenSSL fork:
|
||||
|
||||
```bash
|
||||
cd $HOME/code
|
||||
git clone https://github.com/defo-project/openssl
|
||||
cd openssl
|
||||
./config --libdir=lib --prefix=$HOME/code/openssl-local-inst
|
||||
...stuff...
|
||||
make -j8
|
||||
...stuff (maybe go for coffee)...
|
||||
make install_sw
|
||||
...a little bit of stuff...
|
||||
```
|
||||
|
||||
To build curl ECH-enabled, making use of the above:
|
||||
|
||||
```bash
|
||||
cd $HOME/code
|
||||
git clone https://github.com/curl/curl
|
||||
cd curl
|
||||
autoreconf -fi
|
||||
LDFLAGS="-Wl,-rpath,$HOME/code/openss-local-inst/lib/" ./configure --with-ssl=$HOME/code/openssl-local-inst --enable-ech --enable-httpsrr
|
||||
...lots of output...
|
||||
WARNING: ech ECH HTTPSRR enabled but marked EXPERIMENTAL...
|
||||
make
|
||||
...lots more output...
|
||||
```
|
||||
|
||||
If you do not get that WARNING at the end of the ``configure`` command, then ECH
|
||||
is not enabled, so go back some steps and re-do whatever needs re-doing:-) If you
|
||||
want to debug curl then you should add ``--enable-debug`` to the ``configure``
|
||||
command.
|
||||
|
||||
With the above build, I still need to set ``LD_LIBRARY_PATH`` to run the
|
||||
version of curl built against OpenSSL in my development environment (Ubuntu
|
||||
23.10).
|
||||
|
||||
## Using ECH and DoH
|
||||
|
||||
Curl supports using DoH for A/AAAA lookups so it was relatively easy to add
|
||||
retrieval of HTTPS RRs in that situation. To use ECH and DoH together:
|
||||
|
||||
```bash
|
||||
cd $HOME/code/curl
|
||||
LD_LIBRARY_PATH=$HOME/code/openssl ./src/curl --ech true --doh-url https://one.one.one.one/dns-query https://defo.ie/ech-check.php
|
||||
...
|
||||
SSL_ECH_STATUS: success <img src="greentick-small.png" alt="good" /> <br/>
|
||||
...
|
||||
```
|
||||
|
||||
The output snippet above is within the HTML for the webpage, when things work.
|
||||
|
||||
The above works for these test sites:
|
||||
|
||||
```bash
|
||||
https://defo.ie/ech-check.php
|
||||
https://draft-13.esni.defo.ie:8413/stats
|
||||
https://draft-13.esni.defo.ie:8414/stats
|
||||
https://crypto.cloudflare.com/cdn-cgi/trace
|
||||
https://tls-ech.dev
|
||||
```
|
||||
|
||||
The list above has 4 different server technologies, implemented by 3 different
|
||||
parties, and includes a case (the port 8414 server) where HelloRetryRequest
|
||||
(HRR) is forced.
|
||||
|
||||
We currently support the following new curl command line arguments/options:
|
||||
|
||||
- ``--ech <config>`` - the ``config`` value can be one of:
|
||||
- ``false`` says to not attempt ECH
|
||||
- ``true`` says to attempt ECH, if possible
|
||||
- ``grease`` if attempting ECH is not possible, then send a GREASE ECH extension
|
||||
- ``hard`` hard-fail the connection if ECH cannot be attempted
|
||||
- ``ecl:<b64value>`` a base64 encoded ECHConfigList, rather than one accessed from the DNS
|
||||
- ``pn:<name>`` over-ride the ``public_name`` from an ECHConfigList
|
||||
|
||||
Note that in the above "attempt ECH" means the client emitting a TLS
|
||||
ClientHello with a "real" ECH extension, but that does not mean that the
|
||||
relevant server can succeed in decrypting, as things can fail for other
|
||||
reasons.
|
||||
|
||||
## Supplying an ECHConfigList on the command line
|
||||
|
||||
To supply the ECHConfigList on the command line, you might need a bit of
|
||||
cut-and-paste, e.g.:
|
||||
|
||||
```bash
|
||||
dig +short https defo.ie
|
||||
1 . ipv4hint=213.108.108.101 ech=AED+DQA8PAAgACD8WhlS7VwEt5bf3lekhHvXrQBGDrZh03n/LsNtAodbUAAEAAEAAQANY292ZXIuZGVmby5pZQAA ipv6hint=2a00:c6c0:0:116:5::10
|
||||
```
|
||||
|
||||
Then paste the base64 encoded ECHConfigList onto the curl command line:
|
||||
|
||||
```bash
|
||||
LD_LIBRARY_PATH=$HOME/code/openssl ./src/curl --ech ecl:AED+DQA8PAAgACD8WhlS7VwEt5bf3lekhHvXrQBGDrZh03n/LsNtAodbUAAEAAEAAQANY292ZXIuZGVmby5pZQAA https://defo.ie/ech-check.php
|
||||
...
|
||||
SSL_ECH_STATUS: success <img src="greentick-small.png" alt="good" /> <br/>
|
||||
...
|
||||
```
|
||||
|
||||
The output snippet above is within the HTML for the webpage.
|
||||
|
||||
If you paste in the wrong ECHConfigList (it changes hourly for ``defo.ie``) you
|
||||
should get an error like this:
|
||||
|
||||
```bash
|
||||
LD_LIBRARY_PATH=$HOME/code/openssl ./src/curl -vvv --ech ecl:AED+DQA8yAAgACDRMQo+qYNsNRNj+vfuQfFIkrrUFmM4vogucxKj/4nzYgAEAAEAAQANY292ZXIuZGVmby5pZQAA https://defo.ie/ech-check.php
|
||||
...
|
||||
* OpenSSL/3.3.0: error:0A00054B:SSL routines::ech required
|
||||
...
|
||||
```
|
||||
|
||||
There is a reason to want this command line option - for use before publishing
|
||||
an ECHConfigList in the DNS as per the Internet-draft [A well-known URI for
|
||||
publishing ECHConfigList values](https://datatracker.ietf.org/doc/draft-ietf-tls-wkech/).
|
||||
|
||||
If you do use a wrong ECHConfigList value, then the server might return a
|
||||
good value, via the ``retry_configs`` mechanism. You can see that value in
|
||||
the verbose output, e.g.:
|
||||
|
||||
```bash
|
||||
LD_LIBRARY_PATH=$HOME/code/openssl ./src/curl -vvv --ech ecl:AED+DQA8yAAgACDRMQo+qYNsNRNj+vfuQfFIkrrUFmM4vogucxKj/4nzYgAEAAEAAQANY292ZXIuZGVmby5pZQAA https://defo.ie/ech-check.php
|
||||
...
|
||||
* ECH: retry_configs AQD+DQA8DAAgACBvYqJy+Hgk33wh/ZLBzKSPgwxeop7gvojQzfASq7zeZQAEAAEAAQANY292ZXIuZGVmby5pZQAA/g0APEMAIAAgXkT5r4cYs8z19q5rdittyIX8gfQ3ENW4wj1fVoiJZBoABAABAAEADWNvdmVyLmRlZm8uaWUAAP4NADw2ACAAINXSE9EdXzEQIJZA7vpwCIQsWqsFohZARXChgPsnfI1kAAQAAQABAA1jb3Zlci5kZWZvLmllAAD+DQA8cQAgACASeiD5F+UoSnVoHvA2l1EifUVMFtbVZ76xwDqmMPraHQAEAAEAAQANY292ZXIuZGVmby5pZQAA
|
||||
* ECH: retry_configs for defo.ie from cover.defo.ie, 319
|
||||
...
|
||||
```
|
||||
|
||||
At that point, you could copy the base64 encoded value above and try again.
|
||||
For now, this only works for the OpenSSL and boringssl builds.
|
||||
|
||||
## Default settings
|
||||
|
||||
Curl has various ways to configure default settings, e.g. in ``$HOME/.curlrc``,
|
||||
so one can set the DoH URL and enable ECH that way:
|
||||
|
||||
```bash
|
||||
cat ~/.curlrc
|
||||
doh-url=https://one.one.one.one/dns-query
|
||||
silent
|
||||
ech=true
|
||||
```
|
||||
|
||||
Note that when you use the system's curl command (rather than our ECH-enabled
|
||||
build), it is liable to warn that ``ech`` is an unknown option. If that is an
|
||||
issue (e.g. if some script re-directs stdout and stderr somewhere) then adding
|
||||
the ``silent`` line above seems to be a good enough fix. (Though of
|
||||
course, yet another script could depend on non-silent behavior, so you may have
|
||||
to figure out what you prefer yourself.) That seems to have changed with the
|
||||
latest build, previously ``silent=TRUE`` was what I used in ``~/.curlrc`` but
|
||||
now that seems to cause a problem, so that the following line(s) are ignored.
|
||||
|
||||
If you want to always use our OpenSSL build you can set ``LD_LIBRARY_PATH``
|
||||
in the environment:
|
||||
|
||||
```bash
|
||||
export LD_LIBRARY_PATH=$HOME/code/openssl
|
||||
```
|
||||
|
||||
When you do the above, there can be a mismatch between OpenSSL versions
|
||||
for applications that check that. A ``git push`` for example fails so you
|
||||
should unset ``LD_LIBRARY_PATH`` before doing that or use a different shell.
|
||||
|
||||
```bash
|
||||
git push
|
||||
OpenSSL version mismatch. Built against 30000080, you have 30200000
|
||||
...
|
||||
```
|
||||
|
||||
With all that setup as above the command line gets simpler:
|
||||
|
||||
```bash
|
||||
./src/curl https://defo.ie/ech-check.php
|
||||
...
|
||||
SSL_ECH_STATUS: success <img src="greentick-small.png" alt="good" /> <br/>
|
||||
...
|
||||
```
|
||||
|
||||
The ``--ech true`` option is opportunistic, so tries to do ECH but does not fail if
|
||||
the client for example cannot find any ECHConfig values. The ``--ech hard``
|
||||
option hard-fails if there is no ECHConfig found in DNS, so for now, that is not
|
||||
a good option to set as a default. Once ECH has really been attempted by
|
||||
the client, if decryption on the server side fails, then curl fails.
|
||||
|
||||
## Code changes for ECH support when using DoH
|
||||
|
||||
Code changes are ``#ifdef`` protected via ``USE_ECH`` or ``USE_HTTPSRR``:
|
||||
|
||||
- ``USE_HTTPSRR`` is used for HTTPS RR retrieval code that could be generically
|
||||
used should non-ECH uses for HTTPS RRs be identified, e.g. use of ALPN values
|
||||
or IP address hints.
|
||||
|
||||
- ``USE_ECH`` protects ECH specific code.
|
||||
|
||||
There are various obvious code blocks for handling the new command line
|
||||
arguments which aren't described here, but should be fairly clear.
|
||||
|
||||
As shown in the ``configure`` usage above, there are ``configure.ac`` changes
|
||||
that allow separately dis/enabling ``USE_HTTPSRR`` and ``USE_ECH``. If ``USE_ECH``
|
||||
is enabled, then ``USE_HTTPSRR`` is forced. In both cases ``USE_DOH``
|
||||
is required. (There may be some configuration conflicts available for the
|
||||
determined:-)
|
||||
|
||||
The main functional change, as you would expect, is in ``lib/vtls/openssl.c``
|
||||
where an ECHConfig, if available from command line or DNS cache, is fed into
|
||||
the OpenSSL library via the new APIs implemented in our OpenSSL fork for that
|
||||
purpose. This code also implements the opportunistic (``--ech true``) or hard-fail
|
||||
(``--ech hard``) logic.
|
||||
|
||||
Other than that, the main additions are in ``lib/doh.c``
|
||||
where we re-use ``dohprobe()`` to retrieve an HTTPS RR value for the target
|
||||
domain. If such a value is found, that is stored using a new ``store_https()``
|
||||
function in a new field in the ``dohentry`` structure.
|
||||
|
||||
The qname for the DoH query is modified if the port number is not 443, as
|
||||
defined in the SVCB specification.
|
||||
|
||||
When the DoH process has worked, ``Curl_doh_is_resolved()`` now also returns
|
||||
the relevant HTTPS RR value data in the ``Curl_dns_entry`` structure.
|
||||
That is later accessed when the TLS session is being established, if ECH is
|
||||
enabled (from ``lib/vtls/openssl.c`` as described above).
|
||||
|
||||
## Limitations
|
||||
|
||||
Things that need fixing, but that can probably be ignored for the
|
||||
moment:
|
||||
|
||||
- We could easily add code to make use of an ``alpn=`` value found in an HTTPS
|
||||
RR, passing that on to OpenSSL for use as the "inner" ALPN value, but have
|
||||
yet to do that.
|
||||
|
||||
Current limitations (more interesting than the above):
|
||||
|
||||
- Only the first HTTPS RR value retrieved is actually processed as described
|
||||
above, that could be extended in future, though picking the "right" HTTPS RR
|
||||
could be non-trivial if multiple RRs are published - matching IP address hints
|
||||
versus A/AAAA values might be a good basis for that. Last I checked though,
|
||||
browsers supporting ECH did not handle multiple HTTPS RRs well, though that
|
||||
needs re-checking as it has been a while.
|
||||
|
||||
- It is unclear how one should handle any IP address hints found in an HTTPS RR.
|
||||
It may be that a bit of consideration of how "multi-CDN" deployments might
|
||||
emerge would provide good answers there, but for now, it is not clear how best
|
||||
curl might handle those values when present in the DNS.
|
||||
|
||||
- The SVCB/HTTPS RR specification supports a new "CNAME at apex" indirection
|
||||
("aliasMode") - the current code takes no account of that at all. One could
|
||||
envisage implementing the equivalent of following CNAMEs in such cases, but
|
||||
it is not clear if that'd be a good plan. (As of now, chrome browsers do not seem
|
||||
to have any support for that "aliasMode" and we've not checked Firefox for that
|
||||
recently.)
|
||||
|
||||
- We have not investigated what related changes or additions might be needed
|
||||
for applications using libcurl, as opposed to use of curl as a command line
|
||||
tool.
|
||||
|
||||
- We have not yet implemented tests as part of the usual curl test harness as
|
||||
doing so would seem to require re-implementing an ECH-enabled server as part
|
||||
of the curl test harness. For now, we have a ``./tests/ech_test.sh`` script
|
||||
that attempts ECH with various test servers and with many combinations of the
|
||||
allowed command line options. While that is a useful test and has find issues,
|
||||
it is not comprehensive and we're not (as yet) sure what would be the right
|
||||
level of coverage. When running that script you should not have a
|
||||
``$HOME/.curlrc`` file that affects ECH or some of the negative tests could
|
||||
produce spurious failures.
|
||||
|
||||
## Building with cmake
|
||||
|
||||
To build with cmake, assuming our ECH-enabled OpenSSL is as before:
|
||||
|
||||
```bash
|
||||
cd $HOME/code
|
||||
git clone https://github.com/curl/curl
|
||||
cd curl
|
||||
mkdir build
|
||||
cd build
|
||||
cmake -DOPENSSL_ROOT_DIR=$HOME/code/openssl -DUSE_ECH=1 -DUSE_HTTPSRR=1 ..
|
||||
...
|
||||
make
|
||||
...
|
||||
[100%] Built target curl
|
||||
```
|
||||
|
||||
The binary produced by the cmake build does not need any ECH-specific
|
||||
``LD_LIBRARY_PATH`` setting.
|
||||
|
||||
## boringssl build
|
||||
|
||||
BoringSSL is also supported by curl and also supports ECH, so to build
|
||||
with that, instead of our ECH-enabled OpenSSL:
|
||||
|
||||
```bash
|
||||
cd $HOME/code
|
||||
git clone https://boringssl.googlesource.com/boringssl
|
||||
cd boringssl
|
||||
cmake -DCMAKE_INSTALL_PREFIX:PATH=$HOME/code/boringssl/inst -DBUILD_SHARED_LIBS=1
|
||||
make
|
||||
...
|
||||
make install
|
||||
```
|
||||
|
||||
Then:
|
||||
|
||||
```bash
|
||||
cd $HOME/code
|
||||
git clone https://github.com/curl/curl
|
||||
cd curl
|
||||
autoreconf -fi
|
||||
LDFLAGS="-Wl,-rpath,$HOME/code/boringssl/inst/lib" ./configure --with-ssl=$HOME/code/boringssl/inst --enable-ech --enable-httpsrr
|
||||
...lots of output...
|
||||
WARNING: ech ECH HTTPSRR enabled but marked EXPERIMENTAL. Use with caution!
|
||||
make
|
||||
```
|
||||
|
||||
The boringssl APIs are fairly similar to those in our ECH-enabled OpenSSL
|
||||
fork, so code changes are also in ``lib/vtls/openssl.c``, protected
|
||||
via ``#ifdef OPENSSL_IS_BORINGSSL`` and are mostly obvious API variations.
|
||||
|
||||
The boringssl APIs however do not support the ``--ech pn:`` command line
|
||||
variant as of now.
|
||||
|
||||
## WolfSSL build
|
||||
|
||||
WolfSSL also supports ECH and can be used by curl, so here's how:
|
||||
|
||||
```bash
|
||||
cd $HOME/code
|
||||
git clone https://github.com/wolfSSL/wolfssl
|
||||
cd wolfssl
|
||||
./autogen.sh
|
||||
./configure --prefix=$HOME/code/wolfssl/inst --enable-ech --enable-debug --enable-opensslextra
|
||||
make
|
||||
make install
|
||||
```
|
||||
|
||||
The install prefix (``inst``) in the above causes WolfSSL to be installed there
|
||||
and we seem to need that for the curl configure command to work out. The
|
||||
``--enable-opensslextra`` turns out (after much faffing about;-) to be
|
||||
important or else we get build problems with curl below.
|
||||
|
||||
```bash
|
||||
cd $HOME/code
|
||||
git clone https://github.com/curl/curl
|
||||
cd curl
|
||||
autoreconf -fi
|
||||
./configure --with-wolfssl=$HOME/code/wolfssl/inst --enable-ech --enable-httpsrr
|
||||
make
|
||||
```
|
||||
|
||||
There are some known issues with the ECH implementation in WolfSSL:
|
||||
|
||||
- The main issue is that the client currently handles HelloRetryRequest
|
||||
incorrectly. [HRR issue](https://github.com/wolfSSL/wolfssl/issues/6802).)
|
||||
The HRR issue means that the client does not work for
|
||||
[this ECH test web site](https://tls-ech.dev) and any other similarly configured
|
||||
sites.
|
||||
- There is also an issue related to so-called middlebox compatibility mode.
|
||||
[middlebox compatibility issue](https://github.com/wolfSSL/wolfssl/issues/6774)
|
||||
|
||||
### Code changes to support WolfSSL
|
||||
|
||||
There are what seem like oddball differences:
|
||||
|
||||
- The DoH URL in``$HOME/.curlrc`` can use "1.1.1.1" for OpenSSL but has to be
|
||||
"one.one.one.one" for WolfSSL. The latter works for both, so OK, we'll change
|
||||
to that.
|
||||
- There seems to be some difference in CA databases too - the WolfSSL version
|
||||
does not like ``defo.ie``, whereas the system and OpenSSL ones do. We can ignore
|
||||
that for our purposes via ``--insecure``/``-k`` but would need to fix for a
|
||||
real setup. (Browsers do like those certificates though.)
|
||||
|
||||
Then there are some functional code changes:
|
||||
|
||||
- tweak to ``configure.ac`` to check if WolfSSL has ECH or not
|
||||
- added code to ``lib/vtls/wolfssl.c`` mirroring what's done in the
|
||||
OpenSSL equivalent above.
|
||||
- WolfSSL does not support ``--ech false`` or the ``--ech pn:`` command line
|
||||
argument.
|
||||
|
||||
The lack of support for ``--ech false`` is because wolfSSL has decided to
|
||||
always at least GREASE if built to support ECH. In other words, GREASE is
|
||||
a compile time choice for wolfSSL, but a runtime choice for OpenSSL or
|
||||
boringssl. (Both are reasonable.)
|
||||
|
||||
## Additional notes
|
||||
|
||||
### Supporting ECH without DoH
|
||||
|
||||
All of the above only applies if DoH is being used. There should be a use-case
|
||||
for ECH when DoH is not used by curl - if a system stub resolver supports DoT
|
||||
or DoH, then, considering only ECH and the network threat model, it would make
|
||||
sense for curl to support ECH without curl itself using DoH. The author for
|
||||
example uses a combination of stubby+unbound as the system resolver listening
|
||||
on localhost:53, so would fit this use-case. That said, it is unclear if
|
||||
this is a niche that is worth trying to address. (The author is just as happy to
|
||||
let curl use DoH to talk to the same public recursive that stubby might use:-)
|
||||
|
||||
Assuming for the moment this is a use-case we'd like to support, then
|
||||
if DoH is not being used by curl, it is not clear at this time how to provide
|
||||
support for ECH. One option would seem to be to extend the ``c-ares`` library
|
||||
to support HTTPS RRs, but in that case it is not now clear whether such changes
|
||||
would be attractive to the ``c-ares`` maintainers, nor whether the "tag=value"
|
||||
extensibility inherent in the HTTPS/SVCB specification is a good match for the
|
||||
``c-ares`` approach of defining structures specific to decoded answers for each
|
||||
supported RRtype. We're also not sure how many downstream curl deployments
|
||||
actually make use of the ``c-ares`` library, which would affect the utility of
|
||||
such changes. Another option might be to consider using some other generic DNS
|
||||
library that does support HTTPS RRs, but it is unclear if such a library could
|
||||
or would be used by all or almost all curl builds and downstream releases of
|
||||
curl.
|
||||
|
||||
Our current conclusion is that doing the above is likely best left until we
|
||||
have some experience with the "using DoH" approach, so we're going to punt on
|
||||
this for now.
|
||||
|
||||
### Debugging
|
||||
|
||||
Just a note to self as remembering this is a nuisance:
|
||||
|
||||
```bash
|
||||
LD_LIBRARY_PATH=$HOME/code/openssl:./lib/.libs gdb ./src/.libs/curl
|
||||
```
|
||||
|
||||
### Localhost testing
|
||||
|
||||
It can be useful to be able to run against a localhost OpenSSL ``s_server``
|
||||
for testing. We have published instructions for such
|
||||
[localhost tests](https://github.com/defo-project/ech-dev-utils/blob/main/howtos/localhost-tests.md)
|
||||
in another repository. Once you have that set up, you can start a server
|
||||
and then run curl against that:
|
||||
|
||||
```bash
|
||||
cd $HOME/code/ech-dev-utils
|
||||
./scripts/echsvr.sh -d
|
||||
...
|
||||
```
|
||||
|
||||
The ``echsvr.sh`` script supports many ECH-related options. Use ``echsvr.sh -h``
|
||||
for details.
|
||||
|
||||
In another window:
|
||||
|
||||
```bash
|
||||
cd $HOME/code/curl/
|
||||
./src/curl -vvv --insecure --connect-to foo.example.com:8443:localhost:8443 --ech ecl:AD7+DQA6uwAgACBix2B78sX+EQhEbxMspDOc8Z3xVS5aQpYP0Cxpc2AWPAAEAAEAAQALZXhhbXBsZS5jb20AAA==
|
||||
```
|
||||
|
||||
### Automated use of ``retry_configs`` not supported so far...
|
||||
|
||||
As of now we have not added support for using ``retry_config`` handling in the
|
||||
application - for a command line tool, one can just use ``dig`` (or ``kdig``)
|
||||
to get the HTTPS RR and pass the ECHConfigList from that on the command line,
|
||||
if needed, or one can access the value from command line output in verbose more
|
||||
and then re-use that in another invocation.
|
||||
|
||||
Both our OpenSSL fork and boringssl have APIs for both controlling GREASE and
|
||||
accessing and logging ``retry_configs``, it seems WolfSSL has neither.
|
||||
|
||||
@ -28,3 +28,4 @@ Experimental support in curl means:
|
||||
- HTTP/3 support (using the quiche or msh3 backends)
|
||||
- The rustls backend
|
||||
- WebSocket
|
||||
- Use of the HTTPS resource record and Encrypted Client Hello (ECH) when using DoH
|
||||
|
||||
@ -90,6 +90,7 @@ DPAGES = \
|
||||
doh-insecure.md \
|
||||
doh-url.md \
|
||||
dump-header.md \
|
||||
ech.md \
|
||||
egd-file.md \
|
||||
engine.md \
|
||||
etag-compare.md \
|
||||
|
||||
54
docs/cmdline-opts/ech.md
Normal file
54
docs/cmdline-opts/ech.md
Normal file
@ -0,0 +1,54 @@
|
||||
---
|
||||
c: Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
|
||||
SPDX-License-Identifier: curl
|
||||
Long: ech
|
||||
Arg: <config>
|
||||
Help: Configure Encrypted Client Hello (ECH) for use with the TLS session
|
||||
Added: 8.8.0
|
||||
Category: tls ECH
|
||||
Protocols: HTTPS
|
||||
Multi: single
|
||||
See-also:
|
||||
- doh-url
|
||||
Example:
|
||||
- --ech true $URL
|
||||
---
|
||||
|
||||
# `--ech`
|
||||
|
||||
Specifies how to do ECH (Encrypted Client Hello).
|
||||
|
||||
The values allowed for \<config\> can be:
|
||||
|
||||
## "false"
|
||||
Do not attempt ECH
|
||||
|
||||
## "grease"
|
||||
|
||||
Send a GREASE ECH extension
|
||||
|
||||
## "true"
|
||||
|
||||
Attempt ECH if possible, but do not fail if ECH is not attempted.
|
||||
(The connection fails if ECH is attempted but fails.)
|
||||
|
||||
## "hard"
|
||||
|
||||
Attempt ECH and fail if that is not possible.
|
||||
ECH only works with TLS 1.3 and also requires using
|
||||
DoH or providing an ECHConfigList on the command line.
|
||||
|
||||
## "ecl:<b64val>"
|
||||
|
||||
A base64 encoded ECHConfigList that is used for ECH.
|
||||
|
||||
## "pn:<name>"
|
||||
|
||||
A name to use to over-ride the `public_name` field of an ECHConfigList
|
||||
(only available with OpenSSL TLS support)
|
||||
|
||||
## Errors
|
||||
|
||||
Most errors cause error
|
||||
*CURLE_ECH_REQUIRED* (101).
|
||||
|
||||
@ -1361,6 +1361,12 @@ int main(void)
|
||||
}
|
||||
~~~
|
||||
|
||||
# ENCRYPTED CLIENT HELLO OPTIONS
|
||||
|
||||
## CURLOPT_ECH
|
||||
|
||||
Set the configuration for ECH. See CURLOPT_ECH(3)
|
||||
|
||||
# AVAILABILITY
|
||||
|
||||
Always
|
||||
|
||||
@ -487,6 +487,10 @@ An internal call to poll() or select() returned error that is not recoverable.
|
||||
|
||||
A value or data field grew larger than allowed.
|
||||
|
||||
## CURLE_ECH_REQUIRED (101)"
|
||||
|
||||
ECH was attempted but failed.
|
||||
|
||||
# CURLMcode
|
||||
|
||||
This is the generic return code used by functions in the libcurl multi
|
||||
|
||||
83
docs/libcurl/opts/CURLOPT_ECH.md
Normal file
83
docs/libcurl/opts/CURLOPT_ECH.md
Normal file
@ -0,0 +1,83 @@
|
||||
---
|
||||
c: Copyright (C) Daniel Stenberg, <daniel.se>, et al.
|
||||
SPDX-License-Identifier: curl
|
||||
Title: CURLOPT_ECH
|
||||
Section: 3
|
||||
Source: libcurl
|
||||
See-also:
|
||||
- (3)
|
||||
Protocol:
|
||||
- TLS
|
||||
TLS-backend:
|
||||
- OpenSSL
|
||||
- wolfSSL
|
||||
---
|
||||
|
||||
# NAME
|
||||
|
||||
CURLOPT_ECH - configuration for Encrypted Client Hello
|
||||
|
||||
# SYNOPSIS
|
||||
|
||||
~~~c
|
||||
#include <curl/curl.h>
|
||||
|
||||
CURLcode curl_easy_setopt(CURL *handle, CURLOPT_ECH, char *config);
|
||||
~~~
|
||||
|
||||
# DESCRIPTION
|
||||
|
||||
ECH is only compatible with TLSv1.3.
|
||||
|
||||
This experimental feature requires a special build of OpenSSL, as ECH is not
|
||||
yet supported in OpenSSL releases. In contrast ECH is supported by the latest
|
||||
BoringSSL and wolfSSL releases. See [ECH.md](../../ECH.md) for details of how
|
||||
to build such an OpenSSL library.
|
||||
|
||||
There is also a known issue with using wolfSSL which does not support ECH
|
||||
when the HelloRetryRequest mechanism is used.
|
||||
|
||||
Pass a string that specifies configuration details for ECH.
|
||||
In all cases, if ECH is attempted, it may fail for various reasons.
|
||||
The keywords supported are:
|
||||
|
||||
## false
|
||||
Turns off ECH.
|
||||
## grease
|
||||
Instructs client to emit a GREASE ECH extension.
|
||||
(The connection fails if ECH is attempted but fails.)
|
||||
## true
|
||||
Instructs client to attempt ECH, if possible, but to not fail if attempting ECH is not possible.
|
||||
## hard
|
||||
Instructs client to attempt ECH and fail if if attempting ECH is not possible.
|
||||
## ecl:\<base64-value\>
|
||||
If the string starts with "ecl:" then the remainder of the string should be a base64-encoded
|
||||
ECHConfigList that is used for ECH rather than attempting to download such a value from
|
||||
the DNS.
|
||||
## pn:\<name\>
|
||||
If the string starts with "pn:" then the remainder of the string should be a DNS/hostname
|
||||
that is used to over-ride the public_name field of the ECHConfigList that is used
|
||||
for ECH.
|
||||
|
||||
# DEFAULT
|
||||
|
||||
NULL, meaning ECH is disabled.
|
||||
|
||||
# EXAMPLE
|
||||
|
||||
~~~c
|
||||
CURL *curl = curl_easy_init();
|
||||
|
||||
const char *config ="ecl:AED+DQA87wAgACB/RuzUCsW3uBbSFI7mzD63TUXpI8sGDTnFTbFCDpa+CAAEAAEAAQANY292ZXIuZGVmby5pZQAA";
|
||||
if(curl) {
|
||||
curl_easy_setopt(curl, CURLOPT_ECH, config);
|
||||
curl_easy_perform(curl);
|
||||
}
|
||||
~~~
|
||||
# AVAILABILITY
|
||||
|
||||
Added in 8.8.0
|
||||
|
||||
# RETURN VALUE
|
||||
|
||||
Returns CURLE_OK on success or CURLE_OUT_OF_MEMORY if there was insufficient heap space.
|
||||
@ -166,6 +166,7 @@ man_MANS = \
|
||||
CURLOPT_DOH_SSL_VERIFYPEER.3 \
|
||||
CURLOPT_DOH_SSL_VERIFYSTATUS.3 \
|
||||
CURLOPT_DOH_URL.3 \
|
||||
CURLOPT_ECH.3 \
|
||||
CURLOPT_EGDSOCKET.3 \
|
||||
CURLOPT_ERRORBUFFER.3 \
|
||||
CURLOPT_EXPECT_100_TIMEOUT_MS.3 \
|
||||
|
||||
@ -340,6 +340,7 @@ CURLE_URL_MALFORMAT_USER 7.1 7.17.0
|
||||
CURLE_USE_SSL_FAILED 7.17.0
|
||||
CURLE_WEIRD_SERVER_REPLY 7.51.0
|
||||
CURLE_WRITE_ERROR 7.1
|
||||
CURLE_ECH_REQUIRED 8.8.0
|
||||
CURLFILETYPE_DEVICE_BLOCK 7.21.0
|
||||
CURLFILETYPE_DEVICE_CHAR 7.21.0
|
||||
CURLFILETYPE_DIRECTORY 7.21.0
|
||||
@ -617,6 +618,7 @@ CURLOPT_DOH_SSL_VERIFYHOST 7.76.0
|
||||
CURLOPT_DOH_SSL_VERIFYPEER 7.76.0
|
||||
CURLOPT_DOH_SSL_VERIFYSTATUS 7.76.0
|
||||
CURLOPT_DOH_URL 7.62.0
|
||||
CURLOPT_ECH 8.8.0
|
||||
CURLOPT_EGDSOCKET 7.7 7.84.0
|
||||
CURLOPT_ENCODING 7.10 7.21.6
|
||||
CURLOPT_ERRORBUFFER 7.1
|
||||
|
||||
@ -55,6 +55,7 @@
|
||||
--doh-insecure 7.76.0
|
||||
--doh-url 7.62.0
|
||||
--dump-header (-D) 5.7
|
||||
--ech 8.8.0
|
||||
--egd-file 7.7
|
||||
--engine 7.9.3
|
||||
--etag-compare 7.68.0
|
||||
|
||||
@ -632,6 +632,7 @@ typedef enum {
|
||||
CURLE_SSL_CLIENTCERT, /* 98 - client-side certificate required */
|
||||
CURLE_UNRECOVERABLE_POLL, /* 99 - poll/select returned fatal error */
|
||||
CURLE_TOO_LARGE, /* 100 - a value/data met its maximum */
|
||||
CURLE_ECH_REQUIRED, /* 101 - ECH tried but failed */
|
||||
CURL_LAST /* never use! */
|
||||
} CURLcode;
|
||||
|
||||
@ -2209,6 +2210,9 @@ typedef enum {
|
||||
/* millisecond version */
|
||||
CURLOPT(CURLOPT_SERVER_RESPONSE_TIMEOUT_MS, CURLOPTTYPE_LONG, 324),
|
||||
|
||||
/* set ECH configuration */
|
||||
CURLOPT(CURLOPT_ECH, CURLOPTTYPE_STRINGPOINT, 325),
|
||||
|
||||
CURLOPT_LASTENTRY /* the last unused */
|
||||
} CURLoption;
|
||||
|
||||
@ -3161,7 +3165,7 @@ typedef struct curl_version_info_data curl_version_info_data;
|
||||
#define CURL_VERSION_GSASL (1<<29) /* libgsasl is supported */
|
||||
#define CURL_VERSION_THREADSAFE (1<<30) /* libcurl API is thread-safe */
|
||||
|
||||
/*
|
||||
/*
|
||||
* NAME curl_version_info()
|
||||
*
|
||||
* DESCRIPTION
|
||||
|
||||
@ -275,6 +275,7 @@ CURLWARNING(_curl_easy_getinfo_err_curl_off_t,
|
||||
(option) == CURLOPT_DNS_LOCAL_IP6 || \
|
||||
(option) == CURLOPT_DNS_SERVERS || \
|
||||
(option) == CURLOPT_DOH_URL || \
|
||||
(option) == CURLOPT_ECH || \
|
||||
(option) == CURLOPT_EGDSOCKET || \
|
||||
(option) == CURLOPT_FTP_ACCOUNT || \
|
||||
(option) == CURLOPT_FTP_ALTERNATIVE_TO_USER || \
|
||||
|
||||
@ -805,3 +805,9 @@ ${SIZEOF_TIME_T_CODE}
|
||||
|
||||
/* Define to 1 to enable TLS-SRP support. */
|
||||
#cmakedefine USE_TLS_SRP 1
|
||||
|
||||
/* Define to 1 to query for HTTPSRR when using DoH */
|
||||
#cmakedefine USE_HTTPSRR 1
|
||||
|
||||
/* if ECH support is available */
|
||||
#cmakedefine USE_ECH 1
|
||||
|
||||
413
lib/doh.c
413
lib/doh.c
@ -42,9 +42,13 @@
|
||||
#include "curl_printf.h"
|
||||
#include "curl_memory.h"
|
||||
#include "memdebug.h"
|
||||
#include "escape.h"
|
||||
|
||||
#define DNS_CLASS_IN 0x01
|
||||
|
||||
/* local_print_buf truncates if the hex string will be more than this */
|
||||
#define LOCAL_PB_HEXMAX 400
|
||||
|
||||
#ifndef CURL_DISABLE_VERBOSE_STRINGS
|
||||
static const char * const errors[]={
|
||||
"",
|
||||
@ -187,6 +191,26 @@ doh_write_cb(const void *contents, size_t size, size_t nmemb, void *userp)
|
||||
return realsize;
|
||||
}
|
||||
|
||||
#if defined(USE_HTTPSRR) && defined(CURLDEBUG)
|
||||
static void local_print_buf(struct Curl_easy *data,
|
||||
const char *prefix,
|
||||
unsigned char *buf, size_t len)
|
||||
{
|
||||
unsigned char hexstr[LOCAL_PB_HEXMAX];
|
||||
size_t hlen = LOCAL_PB_HEXMAX;
|
||||
bool truncated = false;
|
||||
|
||||
if(len > (LOCAL_PB_HEXMAX / 2))
|
||||
truncated = true;
|
||||
Curl_hexencode(buf, len, hexstr, hlen);
|
||||
if(!truncated)
|
||||
infof(data, "%s: len=%d, val=%s", prefix, (int)len, hexstr);
|
||||
else
|
||||
infof(data, "%s: len=%d (truncated)val=%s", prefix, (int)len, hexstr);
|
||||
return;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* called from multi.c when this DoH transfer is complete */
|
||||
static int doh_done(struct Curl_easy *doh, CURLcode result)
|
||||
{
|
||||
@ -379,6 +403,12 @@ struct Curl_addrinfo *Curl_doh(struct Curl_easy *data,
|
||||
int slot;
|
||||
struct dohdata *dohp;
|
||||
struct connectdata *conn = data->conn;
|
||||
#ifdef USE_HTTPSRR
|
||||
/* for now, this is only used when ECH is enabled */
|
||||
# ifdef USE_ECH
|
||||
char *qname = NULL;
|
||||
# endif
|
||||
#endif
|
||||
*waitp = FALSE;
|
||||
(void)hostname;
|
||||
(void)port;
|
||||
@ -418,6 +448,37 @@ struct Curl_addrinfo *Curl_doh(struct Curl_easy *data,
|
||||
goto error;
|
||||
dohp->pending++;
|
||||
}
|
||||
#endif
|
||||
|
||||
#ifdef USE_HTTPSRR
|
||||
/*
|
||||
* TODO: Figure out the conditions under which we want to make
|
||||
* a request for an HTTPS RR when we are not doing ECH. For now,
|
||||
* making this request breaks a bunch of DoH tests, e.g. test2100,
|
||||
* where the addiitonal request doesn't match the pre-cooked data
|
||||
* files, so there's a bit of work attached to making the request
|
||||
* in a non-ECH use-case. For the present, we'll only make the
|
||||
* request when ECH is enabled in the build and is being used for
|
||||
* the curl operation.
|
||||
*/
|
||||
# ifdef USE_ECH
|
||||
if(data->set.tls_ech & CURLECH_ENABLE
|
||||
|| data->set.tls_ech & CURLECH_HARD) {
|
||||
if(port == 443)
|
||||
qname = strdup(hostname);
|
||||
else
|
||||
qname = aprintf("_%d._https.%s", port, hostname);
|
||||
if(!qname)
|
||||
goto error;
|
||||
result = dohprobe(data, &dohp->probe[DOH_PROBE_SLOT_HTTPS],
|
||||
DNS_TYPE_HTTPS, qname, data->set.str[STRING_DOH],
|
||||
data->multi, dohp->headers);
|
||||
free(qname);
|
||||
if(result)
|
||||
goto error;
|
||||
dohp->pending++;
|
||||
}
|
||||
# endif
|
||||
#endif
|
||||
*waitp = TRUE; /* this never returns synchronously */
|
||||
return NULL;
|
||||
@ -501,6 +562,25 @@ static DOHcode store_aaaa(const unsigned char *doh,
|
||||
return DOH_OK;
|
||||
}
|
||||
|
||||
#ifdef USE_HTTPSRR
|
||||
static DOHcode store_https(const unsigned char *doh,
|
||||
int index,
|
||||
struct dohentry *d,
|
||||
uint16_t len)
|
||||
{
|
||||
/* silently ignore RRs over the limit */
|
||||
if(d->numhttps_rrs < DOH_MAX_HTTPS) {
|
||||
struct dohhttps_rr *h = &d->https_rrs[d->numhttps_rrs];
|
||||
h->val = Curl_memdup(&doh[index], len);
|
||||
if(!h->val)
|
||||
return DOH_OUT_OF_MEM;
|
||||
h->len = len;
|
||||
d->numhttps_rrs++;
|
||||
}
|
||||
return DOH_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
static DOHcode store_cname(const unsigned char *doh,
|
||||
size_t dohlen,
|
||||
unsigned int index,
|
||||
@ -563,7 +643,8 @@ static DOHcode rdata(const unsigned char *doh,
|
||||
/* RDATA
|
||||
- A (TYPE 1): 4 bytes
|
||||
- AAAA (TYPE 28): 16 bytes
|
||||
- NS (TYPE 2): N bytes */
|
||||
- NS (TYPE 2): N bytes
|
||||
- HTTPS (TYPE 65): N bytes */
|
||||
DOHcode rc;
|
||||
|
||||
switch(type) {
|
||||
@ -581,6 +662,13 @@ static DOHcode rdata(const unsigned char *doh,
|
||||
if(rc)
|
||||
return rc;
|
||||
break;
|
||||
#ifdef USE_HTTPSRR
|
||||
case DNS_TYPE_HTTPS:
|
||||
rc = store_https(doh, index, d, rdlength);
|
||||
if(rc)
|
||||
return rc;
|
||||
break;
|
||||
#endif
|
||||
case DNS_TYPE_CNAME:
|
||||
rc = store_cname(doh, dohlen, index, d);
|
||||
if(rc)
|
||||
@ -737,7 +825,11 @@ UNITTEST DOHcode doh_decode(const unsigned char *doh,
|
||||
if(index != dohlen)
|
||||
return DOH_DNS_MALFORMAT; /* something is wrong */
|
||||
|
||||
#ifdef USE_HTTTPS
|
||||
if((type != DNS_TYPE_NS) && !d->numcname && !d->numaddr && !d->numhttps_rrs)
|
||||
#else
|
||||
if((type != DNS_TYPE_NS) && !d->numcname && !d->numaddr)
|
||||
#endif
|
||||
/* nothing stored! */
|
||||
return DOH_NO_CONTENT;
|
||||
|
||||
@ -776,6 +868,16 @@ static void showdoh(struct Curl_easy *data,
|
||||
infof(data, "%s", buffer);
|
||||
}
|
||||
}
|
||||
#ifdef USE_HTTPSRR
|
||||
for(i = 0; i < d->numhttps_rrs; i++) {
|
||||
# ifdef CURLDEBUG
|
||||
local_print_buf(data, "DoH HTTPS",
|
||||
d->https_rrs[i].val, d->https_rrs[i].len);
|
||||
# else
|
||||
infof(data, "DoH HTTPS RR: length %d", d->https_rrs[i].len);
|
||||
# endif
|
||||
}
|
||||
#endif
|
||||
for(i = 0; i < d->numcname; i++) {
|
||||
infof(data, "CNAME: %s", Curl_dyn_ptr(&d->cname[i]));
|
||||
}
|
||||
@ -895,7 +997,18 @@ static CURLcode doh2ai(const struct dohentry *de, const char *hostname,
|
||||
#ifndef CURL_DISABLE_VERBOSE_STRINGS
|
||||
static const char *type2name(DNStype dnstype)
|
||||
{
|
||||
return (dnstype == DNS_TYPE_A)?"A":"AAAA";
|
||||
switch(dnstype) {
|
||||
case DNS_TYPE_A:
|
||||
return "A";
|
||||
case DNS_TYPE_AAAA:
|
||||
return "AAAA";
|
||||
#ifdef USE_HTTPSRR
|
||||
case DNS_TYPE_HTTPS:
|
||||
return "HTTPS";
|
||||
#endif
|
||||
default:
|
||||
return "unknown";
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
@ -905,8 +1018,282 @@ UNITTEST void de_cleanup(struct dohentry *d)
|
||||
for(i = 0; i < d->numcname; i++) {
|
||||
Curl_dyn_free(&d->cname[i]);
|
||||
}
|
||||
#ifdef USE_HTTPSRR
|
||||
for(i = 0; i < d->numhttps_rrs; i++)
|
||||
free(d->https_rrs[i].val);
|
||||
#endif
|
||||
}
|
||||
|
||||
#ifdef USE_HTTPSRR
|
||||
|
||||
/*
|
||||
* @brief decode the DNS name in a binary RRData
|
||||
* @param buf points to the buffer (in/out)
|
||||
* @param remaining points to the remaining buffer length (in/out)
|
||||
* @param dnsname returns the string form name on success
|
||||
* @return is 1 for success, error otherwise
|
||||
*
|
||||
* The encoding here is defined in
|
||||
* https://tools.ietf.org/html/rfc1035#section-3.1
|
||||
*
|
||||
* The input buffer pointer will be modified so it points to
|
||||
* just after the end of the DNS name encoding on output. (And
|
||||
* that's why it's an "unsigned char **" :-)
|
||||
*/
|
||||
static CURLcode local_decode_rdata_name(unsigned char **buf, size_t *remaining,
|
||||
char **dnsname)
|
||||
{
|
||||
unsigned char *cp = NULL;
|
||||
int rem = 0;
|
||||
char *thename = NULL, *tp = NULL;
|
||||
unsigned char clen = 0; /* chunk len */
|
||||
|
||||
if(!buf || !remaining || !dnsname)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
rem = (int)*remaining;
|
||||
thename = calloc(1, CURL_MAXLEN_host_name);
|
||||
if(!thename)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
cp = *buf;
|
||||
tp = thename;
|
||||
clen = *cp++;
|
||||
if(clen == 0) {
|
||||
/* special case - return "." as name */
|
||||
thename[0] = '.';
|
||||
thename[1] = 0x00;
|
||||
}
|
||||
while(clen) {
|
||||
if(clen >= rem) {
|
||||
free(thename);
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
if(((tp - thename) + clen) > CURL_MAXLEN_host_name) {
|
||||
free(thename);
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
memcpy(tp, cp, clen);
|
||||
tp += clen;
|
||||
*tp++ = '.';
|
||||
cp += clen;
|
||||
rem -= (clen + 1);
|
||||
if(rem <= 0) {
|
||||
free(thename);
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
clen = *cp++;
|
||||
}
|
||||
*buf = cp;
|
||||
if(rem <= 0) {
|
||||
free(thename);
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
*remaining = rem - 1;
|
||||
*dnsname = thename;
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
static CURLcode local_decode_rdata_alpn(unsigned char *rrval, size_t len,
|
||||
char **alpns)
|
||||
{
|
||||
/*
|
||||
* spec here is as per draft-ietf-dnsop-svcb-https, section-7.1.1
|
||||
* encoding is catenated list of strings each preceded by a one
|
||||
* octet length
|
||||
* output is comma-sep list of the strings
|
||||
* implementations may or may not handle quoting of comma within
|
||||
* string values, so we might see a comma within the wire format
|
||||
* version of a string, in which case we'll precede that by a
|
||||
* backslash - same goes for a backslash character, and of course
|
||||
* we need to use two backslashes in strings when we mean one;-)
|
||||
*/
|
||||
int remaining = (int) len;
|
||||
char *oval;
|
||||
size_t olen = 0, i;
|
||||
unsigned char *cp = rrval;
|
||||
struct dynbuf dval;
|
||||
|
||||
if(!alpns)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
Curl_dyn_init(&dval, DYN_DOH_RESPONSE);
|
||||
remaining = (int)len;
|
||||
cp = rrval;
|
||||
while(remaining > 0) {
|
||||
size_t tlen = (size_t) *cp++;
|
||||
|
||||
/* if not 1st time, add comma */
|
||||
if(remaining != (int)len && Curl_dyn_addn(&dval, ",", 1))
|
||||
goto err;
|
||||
remaining--;
|
||||
if(tlen > (size_t)remaining)
|
||||
goto err;
|
||||
/* add escape char if needed, clunky but easier to read */
|
||||
for(i = 0; i != tlen; i++) {
|
||||
if('\\' == *cp || ',' == *cp) {
|
||||
if(Curl_dyn_addn(&dval, "\\", 1))
|
||||
goto err;
|
||||
}
|
||||
if(Curl_dyn_addn(&dval, cp++, 1))
|
||||
goto err;
|
||||
}
|
||||
remaining -= (int)tlen;
|
||||
}
|
||||
olen = Curl_dyn_len(&dval);
|
||||
/* I think the + 1 here is ok but it could trigger a read error */
|
||||
oval = (char *)Curl_memdup(Curl_dyn_ptr(&dval), olen + 1);
|
||||
if(!oval)
|
||||
goto err;
|
||||
Curl_dyn_free(&dval);
|
||||
oval[olen]='\0';
|
||||
*alpns = oval;
|
||||
return CURLE_OK;
|
||||
err:
|
||||
Curl_dyn_free(&dval);
|
||||
return CURLE_BAD_CONTENT_ENCODING;
|
||||
}
|
||||
|
||||
#ifdef CURLDEBUG
|
||||
static CURLcode test_alpn_escapes(void)
|
||||
{
|
||||
/* we'll use an example from draft-ietf-dnsop-svcb, figure 10 */
|
||||
static unsigned char example[] = {
|
||||
0x08, /* length 8 */
|
||||
0x66, 0x5c, 0x6f, 0x6f, 0x2c, 0x62, 0x61, 0x72, /* value "f\\oo,bar" */
|
||||
0x02, /* length 2 */
|
||||
0x68, 0x32 /* value "h2" */
|
||||
};
|
||||
size_t example_len = sizeof(example);
|
||||
char *aval = NULL;
|
||||
static const char *expected = "f\\\\oo\\,bar,h2";
|
||||
|
||||
if(local_decode_rdata_alpn(example, example_len, &aval) != CURLE_OK)
|
||||
return CURLE_BAD_CONTENT_ENCODING;
|
||||
if(strlen(aval) != strlen(expected))
|
||||
return CURLE_BAD_CONTENT_ENCODING;
|
||||
if(memcmp(aval, expected, strlen(aval)))
|
||||
return CURLE_BAD_CONTENT_ENCODING;
|
||||
return CURLE_OK;
|
||||
}
|
||||
#endif
|
||||
|
||||
static CURLcode Curl_doh_decode_httpsrr(unsigned char *rrval, size_t len,
|
||||
struct Curl_https_rrinfo **hrr)
|
||||
{
|
||||
size_t remaining = len;
|
||||
unsigned char *cp = rrval;
|
||||
uint16_t pcode = 0, plen = 0;
|
||||
struct Curl_https_rrinfo *lhrr = NULL;
|
||||
char *dnsname = NULL;
|
||||
|
||||
#ifdef CURLDEBUG
|
||||
/* a few tests of escaping, shouldn't be here but ok for now */
|
||||
if(test_alpn_escapes() != CURLE_OK)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
#endif
|
||||
lhrr = calloc(1, sizeof(struct Curl_https_rrinfo));
|
||||
if(!lhrr)
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
lhrr->val = calloc(1, len);
|
||||
if(!lhrr->val)
|
||||
goto err;
|
||||
lhrr->len = len;
|
||||
memcpy(lhrr->val, rrval, len);
|
||||
if(remaining <= 2)
|
||||
goto err;
|
||||
lhrr->priority = (uint16_t)((cp[0] << 8) + cp[1]);
|
||||
cp += 2;
|
||||
remaining -= (uint16_t)2;
|
||||
if(local_decode_rdata_name(&cp, &remaining, &dnsname) != CURLE_OK)
|
||||
goto err;
|
||||
lhrr->target = dnsname;
|
||||
while(remaining >= 4) {
|
||||
pcode = (uint16_t)((*cp << 8) + (*(cp + 1)));
|
||||
cp += 2;
|
||||
plen = (uint16_t)((*cp << 8) + (*(cp + 1)));
|
||||
cp += 2;
|
||||
remaining -= 4;
|
||||
if(pcode == HTTPS_RR_CODE_ALPN) {
|
||||
if(local_decode_rdata_alpn(cp, plen, &lhrr->alpns) != CURLE_OK)
|
||||
goto err;
|
||||
}
|
||||
if(pcode == HTTPS_RR_CODE_NO_DEF_ALPN)
|
||||
lhrr->no_def_alpn = TRUE;
|
||||
else if(pcode == HTTPS_RR_CODE_IPV4) {
|
||||
lhrr->ipv4hints = Curl_memdup(cp, plen);
|
||||
if(!lhrr->ipv4hints)
|
||||
goto err;
|
||||
lhrr->ipv4hints_len = (size_t)plen;
|
||||
}
|
||||
else if(pcode == HTTPS_RR_CODE_ECH) {
|
||||
lhrr->echconfiglist = Curl_memdup(cp, plen);
|
||||
if(!lhrr->echconfiglist)
|
||||
goto err;
|
||||
lhrr->echconfiglist_len = (size_t)plen;
|
||||
}
|
||||
else if(pcode == HTTPS_RR_CODE_IPV6) {
|
||||
lhrr->ipv6hints = Curl_memdup(cp, plen);
|
||||
if(!lhrr->ipv6hints)
|
||||
goto err;
|
||||
lhrr->ipv6hints_len = (size_t)plen;
|
||||
}
|
||||
if(plen > 0 && plen <= remaining) {
|
||||
cp += plen;
|
||||
remaining -= plen;
|
||||
}
|
||||
}
|
||||
DEBUGASSERT(!remaining);
|
||||
*hrr = lhrr;
|
||||
return CURLE_OK;
|
||||
err:
|
||||
if(lhrr) {
|
||||
if(lhrr->target)
|
||||
free(lhrr->target);
|
||||
if(lhrr->echconfiglist)
|
||||
free(lhrr->echconfiglist);
|
||||
if(lhrr->val)
|
||||
free(lhrr->val);
|
||||
free(lhrr);
|
||||
}
|
||||
return CURLE_OUT_OF_MEMORY;
|
||||
}
|
||||
|
||||
# ifdef CURLDEBUG
|
||||
static void local_print_httpsrr(struct Curl_easy *data,
|
||||
struct Curl_https_rrinfo *hrr)
|
||||
{
|
||||
DEBUGASSERT(hrr);
|
||||
infof(data, "HTTPS RR: priority %d, target: %s",
|
||||
hrr->priority, hrr->target);
|
||||
if(hrr->alpns)
|
||||
infof(data, "HTTPS RR: alpns %s", hrr->alpns);
|
||||
else
|
||||
infof(data, "HTTPS RR: no alpns");
|
||||
if(hrr->no_def_alpn)
|
||||
infof(data, "HTTPS RR: no_def_alpn set");
|
||||
else
|
||||
infof(data, "HTTPS RR: no_def_alpn not set");
|
||||
if(hrr->ipv4hints) {
|
||||
local_print_buf(data, "HTTPS RR: ipv4hints",
|
||||
hrr->ipv4hints, hrr->ipv4hints_len);
|
||||
}
|
||||
else
|
||||
infof(data, "HTTPS RR: no ipv4hints");
|
||||
if(hrr->echconfiglist) {
|
||||
local_print_buf(data, "HTTPS RR: ECHConfigList",
|
||||
hrr->echconfiglist, hrr->echconfiglist_len);
|
||||
}
|
||||
else
|
||||
infof(data, "HTTPS RR: no ECHConfigList");
|
||||
if(hrr->ipv6hints) {
|
||||
local_print_buf(data, "HTTPS RR: ipv6hint",
|
||||
hrr->ipv6hints, hrr->ipv6hints_len);
|
||||
}
|
||||
else
|
||||
infof(data, "HTTPS RR: no ipv6hints");
|
||||
return;
|
||||
}
|
||||
# endif
|
||||
#endif
|
||||
|
||||
CURLcode Curl_doh_is_resolved(struct Curl_easy *data,
|
||||
struct Curl_dns_entry **dnsp)
|
||||
{
|
||||
@ -923,9 +1310,15 @@ CURLcode Curl_doh_is_resolved(struct Curl_easy *data,
|
||||
CURLE_COULDNT_RESOLVE_HOST;
|
||||
}
|
||||
else if(!dohp->pending) {
|
||||
#ifndef USE_HTTPSRR
|
||||
DOHcode rc[DOH_PROBE_SLOTS] = {
|
||||
DOH_OK, DOH_OK
|
||||
};
|
||||
#else
|
||||
DOHcode rc[DOH_PROBE_SLOTS] = {
|
||||
DOH_OK, DOH_OK, DOH_OK
|
||||
};
|
||||
#endif
|
||||
struct dohentry de;
|
||||
int slot;
|
||||
/* remove DoH handles from multi handle and close them */
|
||||
@ -991,6 +1384,22 @@ CURLcode Curl_doh_is_resolved(struct Curl_easy *data,
|
||||
} /* address processing done */
|
||||
|
||||
/* Now process any build-specific attributes retrieved from DNS */
|
||||
#ifdef USE_HTTPSRR
|
||||
if(de.numhttps_rrs > 0 && result == CURLE_OK && *dnsp) {
|
||||
struct Curl_https_rrinfo *hrr = NULL;
|
||||
result = Curl_doh_decode_httpsrr(de.https_rrs->val, de.https_rrs->len,
|
||||
&hrr);
|
||||
if(result) {
|
||||
infof(data, "Failed to decode HTTPS RR");
|
||||
return result;
|
||||
}
|
||||
infof(data, "Some HTTPS RR to process");
|
||||
# ifdef CURLDEBUG
|
||||
local_print_httpsrr(data, hrr);
|
||||
# endif
|
||||
(*dnsp)->hinfo = hrr;
|
||||
}
|
||||
#endif
|
||||
|
||||
/* All done */
|
||||
de_cleanup(&de);
|
||||
|
||||
39
lib/doh.h
39
lib/doh.h
@ -26,6 +26,9 @@
|
||||
|
||||
#include "urldata.h"
|
||||
#include "curl_addrinfo.h"
|
||||
#ifdef USE_HTTPSRR
|
||||
# include <stdint.h>
|
||||
#endif
|
||||
|
||||
#ifndef CURL_DISABLE_DOH
|
||||
|
||||
@ -51,7 +54,8 @@ typedef enum {
|
||||
DNS_TYPE_NS = 2,
|
||||
DNS_TYPE_CNAME = 5,
|
||||
DNS_TYPE_AAAA = 28,
|
||||
DNS_TYPE_DNAME = 39 /* RFC6672 */
|
||||
DNS_TYPE_DNAME = 39, /* RFC6672 */
|
||||
DNS_TYPE_HTTPS = 65
|
||||
} DNStype;
|
||||
|
||||
/* one of these for each DoH request */
|
||||
@ -88,6 +92,7 @@ int Curl_doh_getsock(struct connectdata *conn, curl_socket_t *socks);
|
||||
|
||||
#define DOH_MAX_ADDR 24
|
||||
#define DOH_MAX_CNAME 4
|
||||
#define DOH_MAX_HTTPS 4
|
||||
|
||||
struct dohaddr {
|
||||
int type;
|
||||
@ -97,12 +102,44 @@ struct dohaddr {
|
||||
} ip;
|
||||
};
|
||||
|
||||
#ifdef USE_HTTPSRR
|
||||
|
||||
/*
|
||||
* These are the code points for DNS wire format SvcParams as
|
||||
* per draft-ietf-dnsop-svcb-https
|
||||
* Not all are supported now, and even those that are may need
|
||||
* more work in future to fully support the spec.
|
||||
*/
|
||||
#define HTTPS_RR_CODE_ALPN 0x01
|
||||
#define HTTPS_RR_CODE_NO_DEF_ALPN 0x02
|
||||
#define HTTPS_RR_CODE_PORT 0x03
|
||||
#define HTTPS_RR_CODE_IPV4 0x04
|
||||
#define HTTPS_RR_CODE_ECH 0x05
|
||||
#define HTTPS_RR_CODE_IPV6 0x06
|
||||
|
||||
/*
|
||||
* These may need escaping when found within an alpn string
|
||||
* value.
|
||||
*/
|
||||
#define COMMA_CHAR ','
|
||||
#define BACKSLASH_CHAR '\\'
|
||||
|
||||
struct dohhttps_rr {
|
||||
uint16_t len; /* raw encoded length */
|
||||
unsigned char *val; /* raw encoded octets */
|
||||
};
|
||||
#endif
|
||||
|
||||
struct dohentry {
|
||||
struct dynbuf cname[DOH_MAX_CNAME];
|
||||
struct dohaddr addr[DOH_MAX_ADDR];
|
||||
int numaddr;
|
||||
unsigned int ttl;
|
||||
int numcname;
|
||||
#ifdef USE_HTTPSRR
|
||||
struct dohhttps_rr https_rrs[DOH_MAX_HTTPS];
|
||||
int numhttps_rrs;
|
||||
#endif
|
||||
};
|
||||
|
||||
|
||||
|
||||
@ -86,6 +86,7 @@ struct curl_easyoption Curl_easyopts[] = {
|
||||
{"DOH_SSL_VERIFYPEER", CURLOPT_DOH_SSL_VERIFYPEER, CURLOT_LONG, 0},
|
||||
{"DOH_SSL_VERIFYSTATUS", CURLOPT_DOH_SSL_VERIFYSTATUS, CURLOT_LONG, 0},
|
||||
{"DOH_URL", CURLOPT_DOH_URL, CURLOT_STRING, 0},
|
||||
{"ECH", CURLOPT_ECH, CURLOT_STRING, 0},
|
||||
{"EGDSOCKET", CURLOPT_EGDSOCKET, CURLOT_STRING, 0},
|
||||
{"ENCODING", CURLOPT_ACCEPT_ENCODING, CURLOT_STRING, CURLOT_FLAG_ALIAS},
|
||||
{"ERRORBUFFER", CURLOPT_ERRORBUFFER, CURLOT_OBJECT, 0},
|
||||
@ -375,6 +376,6 @@ struct curl_easyoption Curl_easyopts[] = {
|
||||
*/
|
||||
int Curl_easyopts_check(void)
|
||||
{
|
||||
return ((CURLOPT_LASTENTRY%10000) != (324 + 1));
|
||||
return ((CURLOPT_LASTENTRY%10000) != (325 + 1));
|
||||
}
|
||||
#endif
|
||||
|
||||
17
lib/hostip.c
17
lib/hostip.c
@ -1070,6 +1070,23 @@ static void freednsentry(void *freethis)
|
||||
dns->inuse--;
|
||||
if(dns->inuse == 0) {
|
||||
Curl_freeaddrinfo(dns->addr);
|
||||
#ifdef USE_HTTPSRR
|
||||
if(dns->hinfo) {
|
||||
if(dns->hinfo->target)
|
||||
free(dns->hinfo->target);
|
||||
if(dns->hinfo->alpns)
|
||||
free(dns->hinfo->alpns);
|
||||
if(dns->hinfo->ipv4hints)
|
||||
free(dns->hinfo->ipv4hints);
|
||||
if(dns->hinfo->echconfiglist)
|
||||
free(dns->hinfo->echconfiglist);
|
||||
if(dns->hinfo->ipv6hints)
|
||||
free(dns->hinfo->ipv6hints);
|
||||
if(dns->hinfo->val)
|
||||
free(dns->hinfo->val);
|
||||
free(dns->hinfo);
|
||||
}
|
||||
#endif
|
||||
free(dns);
|
||||
}
|
||||
}
|
||||
|
||||
37
lib/hostip.h
37
lib/hostip.h
@ -32,6 +32,10 @@
|
||||
|
||||
#include <setjmp.h>
|
||||
|
||||
#ifdef USE_HTTPSRR
|
||||
# include <stdint.h>
|
||||
#endif
|
||||
|
||||
/* Allocate enough memory to hold the full name information structs and
|
||||
* everything. OSF1 is known to require at least 8872 bytes. The buffer
|
||||
* required for storing all possible aliases and IP numbers is according to
|
||||
@ -58,8 +62,41 @@ struct connectdata;
|
||||
*/
|
||||
struct Curl_hash *Curl_global_host_cache_init(void);
|
||||
|
||||
#ifdef USE_HTTPSRR
|
||||
|
||||
#define CURL_MAXLEN_host_name 253
|
||||
|
||||
struct Curl_https_rrinfo {
|
||||
size_t len; /* raw encoded length */
|
||||
unsigned char *val; /* raw encoded octets */
|
||||
/*
|
||||
* fields from HTTPS RR, with the mandatory fields
|
||||
* first (priority, target), then the others in the
|
||||
* order of the keytag numbers defined at
|
||||
* https://datatracker.ietf.org/doc/html/rfc9460#section-14.3.2
|
||||
*/
|
||||
uint16_t priority;
|
||||
char *target;
|
||||
char *alpns; /* keytag = 1 */
|
||||
bool no_def_alpn; /* keytag = 2 */
|
||||
/*
|
||||
* we don't support ports (keytag = 3) as we don't support
|
||||
* port-switching yet
|
||||
*/
|
||||
unsigned char *ipv4hints; /* keytag = 4 */
|
||||
size_t ipv4hints_len;
|
||||
unsigned char *echconfiglist; /* keytag = 5 */
|
||||
size_t echconfiglist_len;
|
||||
unsigned char *ipv6hints; /* keytag = 6 */
|
||||
size_t ipv6hints_len;
|
||||
};
|
||||
#endif
|
||||
|
||||
struct Curl_dns_entry {
|
||||
struct Curl_addrinfo *addr;
|
||||
#ifdef USE_HTTPSRR
|
||||
struct Curl_https_rrinfo *hinfo;
|
||||
#endif
|
||||
/* timestamp == 0 -- permanent CURLOPT_RESOLVE entry (doesn't time out) */
|
||||
time_t timestamp;
|
||||
/* use-counter, use Curl_resolv_unlock to release reference */
|
||||
|
||||
43
lib/setopt.c
43
lib/setopt.c
@ -3141,6 +3141,49 @@ CURLcode Curl_vsetopt(struct Curl_easy *data, CURLoption option, va_list param)
|
||||
data->set.ws_raw_mode = raw;
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ECH
|
||||
case CURLOPT_ECH: {
|
||||
size_t plen = 0;
|
||||
|
||||
argptr = va_arg(param, char *);
|
||||
if(!argptr) {
|
||||
data->set.tls_ech = CURLECH_DISABLE;
|
||||
result = CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
return result;
|
||||
}
|
||||
plen = strlen(argptr);
|
||||
if(plen > CURL_MAX_INPUT_LENGTH) {
|
||||
data->set.tls_ech = CURLECH_DISABLE;
|
||||
result = CURLE_BAD_FUNCTION_ARGUMENT;
|
||||
return result;
|
||||
}
|
||||
/* set tls_ech flag value, preserving CLA_CFG bit */
|
||||
if(plen == 5 && !strcmp(argptr, "false"))
|
||||
data->set.tls_ech = CURLECH_DISABLE
|
||||
| (data->set.tls_ech & CURLECH_CLA_CFG);
|
||||
else if(plen == 6 && !strcmp(argptr, "grease"))
|
||||
data->set.tls_ech = CURLECH_GREASE
|
||||
| (data->set.tls_ech & CURLECH_CLA_CFG);
|
||||
else if(plen == 4 && !strcmp(argptr, "true"))
|
||||
data->set.tls_ech = CURLECH_ENABLE
|
||||
| (data->set.tls_ech & CURLECH_CLA_CFG);
|
||||
else if(plen == 4 && !strcmp(argptr, "hard"))
|
||||
data->set.tls_ech = CURLECH_HARD
|
||||
| (data->set.tls_ech & CURLECH_CLA_CFG);
|
||||
else if(plen > 5 && !strncmp(argptr, "ecl:", 4)) {
|
||||
result = Curl_setstropt(&data->set.str[STRING_ECH_CONFIG], argptr + 4);
|
||||
if(result)
|
||||
return result;
|
||||
data->set.tls_ech |= CURLECH_CLA_CFG;
|
||||
}
|
||||
else if(plen > 4 && !strncmp(argptr, "pn:", 3)) {
|
||||
result = Curl_setstropt(&data->set.str[STRING_ECH_PUBLIC], argptr + 3);
|
||||
if(result)
|
||||
return result;
|
||||
}
|
||||
break;
|
||||
}
|
||||
#endif
|
||||
case CURLOPT_QUICK_EXIT:
|
||||
data->set.quick_exit = (0 != va_arg(param, long)) ? 1L:0L;
|
||||
|
||||
@ -322,6 +322,9 @@ curl_easy_strerror(CURLcode error)
|
||||
case CURLE_TOO_LARGE:
|
||||
return "A value or data field grew larger than allowed";
|
||||
|
||||
case CURLE_ECH_REQUIRED:
|
||||
return "ECH attempted but failed";
|
||||
|
||||
/* error codes not used by current libcurl */
|
||||
case CURLE_OBSOLETE20:
|
||||
case CURLE_OBSOLETE24:
|
||||
|
||||
@ -55,6 +55,15 @@
|
||||
|
||||
struct curl_trc_featt;
|
||||
|
||||
#ifdef USE_ECH
|
||||
/* CURLECH_ bits for the tls_ech option */
|
||||
# define CURLECH_DISABLE (1<<0)
|
||||
# define CURLECH_GREASE (1<<1)
|
||||
# define CURLECH_ENABLE (1<<2)
|
||||
# define CURLECH_HARD (1<<3)
|
||||
# define CURLECH_CLA_CFG (1<<4)
|
||||
#endif
|
||||
|
||||
#ifdef USE_WEBSOCKETS
|
||||
/* CURLPROTO_GOPHERS (29) is the highest publicly used protocol bit number,
|
||||
* the rest are internal information. If we use higher bits we only do this on
|
||||
@ -627,6 +636,9 @@ enum doh_slots {
|
||||
DOH_PROBE_SLOT_IPADDR_V6 = 1, /* 'V6' likewise */
|
||||
|
||||
/* Space here for (possibly build-specific) additional slot definitions */
|
||||
#ifdef USE_HTTPSRR
|
||||
DOH_PROBE_SLOT_HTTPS = 2, /* for HTTPS RR */
|
||||
#endif
|
||||
|
||||
/* for example */
|
||||
/* #ifdef WANT_DOH_FOOBAR_TXT */
|
||||
@ -1532,6 +1544,8 @@ enum dupstring {
|
||||
#ifndef CURL_DISABLE_PROXY
|
||||
STRING_HAPROXY_CLIENT_IP, /* CURLOPT_HAPROXY_CLIENT_IP */
|
||||
#endif
|
||||
STRING_ECH_CONFIG, /* CURLOPT_ECH_CONFIG */
|
||||
STRING_ECH_PUBLIC, /* CURLOPT_ECH_PUBLIC */
|
||||
|
||||
/* -- end of null-terminated strings -- */
|
||||
|
||||
@ -1859,6 +1873,9 @@ struct UserDefined {
|
||||
#ifdef USE_WEBSOCKETS
|
||||
BIT(ws_raw_mode);
|
||||
#endif
|
||||
#ifdef USE_ECH
|
||||
int tls_ech; /* TLS ECH configuration */
|
||||
#endif
|
||||
};
|
||||
|
||||
#ifndef CURL_DISABLE_MIME
|
||||
|
||||
@ -82,6 +82,17 @@
|
||||
#include <openssl/tls1.h>
|
||||
#include <openssl/evp.h>
|
||||
|
||||
#ifdef USE_ECH
|
||||
# ifndef OPENSSL_IS_BORINGSSL
|
||||
# include <openssl/ech.h>
|
||||
# endif
|
||||
# include "curl_base64.h"
|
||||
# define ECH_ENABLED(__data__) \
|
||||
(__data__->set.tls_ech && \
|
||||
!(__data__->set.tls_ech & CURLECH_DISABLE)\
|
||||
)
|
||||
#endif /* USE_ECH */
|
||||
|
||||
#if (OPENSSL_VERSION_NUMBER >= 0x0090808fL) && !defined(OPENSSL_NO_OCSP)
|
||||
#include <openssl/ocsp.h>
|
||||
#endif
|
||||
@ -3508,6 +3519,9 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx,
|
||||
const char * const ssl_cert_type = ssl_config->cert_type;
|
||||
const bool verifypeer = conn_config->verifypeer;
|
||||
char error_buffer[256];
|
||||
#ifdef USE_ECH
|
||||
struct ssl_connect_data *connssl = cf->ctx;
|
||||
#endif
|
||||
|
||||
/* Make funny stuff to get random input */
|
||||
result = ossl_seed(data);
|
||||
@ -3843,6 +3857,135 @@ CURLcode Curl_ossl_ctx_init(struct ossl_ctx *octx,
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
}
|
||||
|
||||
#ifdef USE_ECH
|
||||
if(ECH_ENABLED(data)) {
|
||||
unsigned char *ech_config = NULL;
|
||||
size_t ech_config_len = 0;
|
||||
char *outername = data->set.str[STRING_ECH_PUBLIC];
|
||||
int trying_ech_now = 0;
|
||||
|
||||
if(data->set.tls_ech & CURLECH_GREASE) {
|
||||
infof(data, "ECH: will GREASE ClientHello");
|
||||
# ifdef OPENSSL_IS_BORINGSSL
|
||||
SSL_set_enable_ech_grease(octx->ssl, 1);
|
||||
# else
|
||||
SSL_set_options(octx->ssl, SSL_OP_ECH_GREASE);
|
||||
# endif
|
||||
}
|
||||
else if(data->set.tls_ech & CURLECH_CLA_CFG) {
|
||||
# ifdef OPENSSL_IS_BORINGSSL
|
||||
/* have to do base64 decode here for boring */
|
||||
const char *b64 = data->set.str[STRING_ECH_CONFIG];
|
||||
|
||||
if(!b64) {
|
||||
infof(data, "ECH: ECHConfig from command line empty");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
ech_config_len = 2 * strlen(b64);
|
||||
result = Curl_base64_decode(b64, &ech_config, &ech_config_len);
|
||||
if(result || !ech_config) {
|
||||
infof(data, "ECH: can't base64 decode ECHConfig from command line");
|
||||
if(data->set.tls_ech & CURLECH_HARD)
|
||||
return result;
|
||||
}
|
||||
if(SSL_set1_ech_config_list(octx->ssl, ech_config,
|
||||
ech_config_len) != 1) {
|
||||
infof(data, "ECH: SSL_ECH_set1_echconfig failed");
|
||||
if(data->set.tls_ech & CURLECH_HARD) {
|
||||
free(ech_config);
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
}
|
||||
free(ech_config);
|
||||
trying_ech_now = 1;
|
||||
# else
|
||||
ech_config = (unsigned char *) data->set.str[STRING_ECH_CONFIG];
|
||||
if(!ech_config) {
|
||||
infof(data, "ECH: ECHConfig from command line empty");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
ech_config_len = strlen(data->set.str[STRING_ECH_CONFIG]);
|
||||
if(SSL_ech_set1_echconfig(octx->ssl, ech_config, ech_config_len) != 1) {
|
||||
infof(data, "ECH: SSL_ECH_set1_echconfig failed");
|
||||
if(data->set.tls_ech & CURLECH_HARD)
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
else
|
||||
trying_ech_now = 1;
|
||||
# endif
|
||||
infof(data, "ECH: ECHConfig from command line");
|
||||
}
|
||||
else {
|
||||
struct Curl_dns_entry *dns = NULL;
|
||||
|
||||
dns = Curl_fetch_addr(data, connssl->peer.hostname, connssl->peer.port);
|
||||
if(!dns) {
|
||||
infof(data, "ECH: requested but no DNS info available");
|
||||
if(data->set.tls_ech & CURLECH_HARD)
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
else {
|
||||
struct Curl_https_rrinfo *rinfo = NULL;
|
||||
|
||||
rinfo = dns->hinfo;
|
||||
if(rinfo && rinfo->echconfiglist) {
|
||||
unsigned char *ecl = rinfo->echconfiglist;
|
||||
size_t elen = rinfo->echconfiglist_len;
|
||||
|
||||
infof(data, "ECH: ECHConfig from DoH HTTPS RR");
|
||||
# ifndef OPENSSL_IS_BORINGSSL
|
||||
if(SSL_ech_set1_echconfig(octx->ssl, ecl, elen) != 1) {
|
||||
infof(data, "ECH: SSL_ECH_set1_echconfig failed");
|
||||
if(data->set.tls_ech & CURLECH_HARD)
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
# else
|
||||
if(SSL_set1_ech_config_list(octx->ssl, ecl, elen) != 1) {
|
||||
infof(data, "ECH: SSL_set1_ech_config_list failed (boring)");
|
||||
if(data->set.tls_ech & CURLECH_HARD)
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
# endif
|
||||
else {
|
||||
trying_ech_now = 1;
|
||||
infof(data, "ECH: imported ECHConfigList of length %ld", elen);
|
||||
}
|
||||
}
|
||||
else {
|
||||
infof(data, "ECH: requested but no ECHConfig available");
|
||||
if(data->set.tls_ech & CURLECH_HARD)
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
Curl_resolv_unlock(data, dns);
|
||||
}
|
||||
}
|
||||
# ifdef OPENSSL_IS_BORINGSSL
|
||||
if(trying_ech_now && outername) {
|
||||
infof(data, "ECH: setting public_name not supported with boringssl");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
# else
|
||||
if(trying_ech_now && outername) {
|
||||
infof(data, "ECH: inner: '%s', outer: '%s'",
|
||||
connssl->peer.hostname, outername);
|
||||
result = SSL_ech_set_server_names(octx->ssl,
|
||||
connssl->peer.hostname, outername,
|
||||
0 /* do send outer */);
|
||||
if(result != 1) {
|
||||
infof(data, "ECH: rv failed to set server name(s) %d [ERROR]", result);
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
}
|
||||
# endif /* not BORING */
|
||||
if(trying_ech_now
|
||||
&& SSL_set_min_proto_version(octx->ssl, TLS1_3_VERSION) != 1) {
|
||||
infof(data, "ECH: Can't force TLSv1.3 [ERROR]");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
}
|
||||
#endif /* USE_ECH */
|
||||
|
||||
#endif
|
||||
|
||||
octx->reused_session = FALSE;
|
||||
@ -3926,6 +4069,70 @@ static CURLcode ossl_connect_step1(struct Curl_cfilter *cf,
|
||||
return CURLE_OK;
|
||||
}
|
||||
|
||||
#ifdef USE_ECH
|
||||
/* If we have retry configs, then trace those out */
|
||||
static void ossl_trace_ech_retry_configs(struct Curl_easy *data, SSL* ssl,
|
||||
int reason)
|
||||
{
|
||||
CURLcode result = CURLE_OK;
|
||||
size_t rcl = 0;
|
||||
int rv = 1;
|
||||
# ifndef OPENSSL_IS_BORINGSSL
|
||||
char *inner = NULL;
|
||||
unsigned char *rcs = NULL;
|
||||
char *outer = NULL;
|
||||
# else
|
||||
const char *inner = NULL;
|
||||
const uint8_t *rcs = NULL;
|
||||
const char *outer = NULL;
|
||||
size_t out_name_len = 0;
|
||||
int servername_type = 0;
|
||||
# endif
|
||||
|
||||
/* nothing to trace if not doing ECH */
|
||||
if(!ECH_ENABLED(data))
|
||||
return;
|
||||
# ifndef OPENSSL_IS_BORINGSSL
|
||||
rv = SSL_ech_get_retry_config(ssl, &rcs, &rcl);
|
||||
# else
|
||||
SSL_get0_ech_retry_configs(ssl, &rcs, &rcl);
|
||||
rv = (int)rcl;
|
||||
# endif
|
||||
|
||||
if(rv && rcs) {
|
||||
# define HEXSTR_MAX 800
|
||||
char *b64str = NULL;
|
||||
size_t blen = 0;
|
||||
|
||||
result = Curl_base64_encode((const char *)rcs, rcl,
|
||||
&b64str, &blen);
|
||||
if(!result && b64str)
|
||||
infof(data, "ECH: retry_configs %s", b64str);
|
||||
free(b64str);
|
||||
# ifndef OPENSSL_IS_BORINGSSL
|
||||
rv = SSL_ech_get_status(ssl, &inner, &outer);
|
||||
infof(data, "ECH: retry_configs for %s from %s, %d %d",
|
||||
inner ? inner : "NULL", outer ? outer : "NULL", reason, rv);
|
||||
#else
|
||||
rv = SSL_ech_accepted(ssl);
|
||||
servername_type = SSL_get_servername_type(ssl);
|
||||
inner = SSL_get_servername(ssl, servername_type);
|
||||
SSL_get0_ech_name_override(ssl, &outer, &out_name_len);
|
||||
/* TODO: get the inner from boring */
|
||||
infof(data, "ECH: retry_configs for %s from %s, %d %d",
|
||||
inner ? inner : "NULL", outer ? outer : "NULL", reason, rv);
|
||||
#endif
|
||||
}
|
||||
else
|
||||
infof(data, "ECH: no retry_configs (rv = %d)", rv);
|
||||
# ifndef OPENSSL_IS_BORINGSSL
|
||||
OPENSSL_free((void *)rcs);
|
||||
# endif
|
||||
return;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
static CURLcode ossl_connect_step2(struct Curl_cfilter *cf,
|
||||
struct Curl_easy *data)
|
||||
{
|
||||
@ -4038,6 +4245,21 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf,
|
||||
result = CURLE_SSL_CLIENTCERT;
|
||||
ossl_strerror(errdetail, error_buffer, sizeof(error_buffer));
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ECH
|
||||
else if((lib == ERR_LIB_SSL) &&
|
||||
# ifndef OPENSSL_IS_BORINGSSL
|
||||
(reason == SSL_R_ECH_REQUIRED)) {
|
||||
# else
|
||||
(reason == SSL_R_ECH_REJECTED)) {
|
||||
# endif
|
||||
|
||||
/* trace retry_configs if we got some */
|
||||
ossl_trace_ech_retry_configs(data, octx->ssl, reason);
|
||||
|
||||
result = CURLE_ECH_REQUIRED;
|
||||
ossl_strerror(errdetail, error_buffer, sizeof(error_buffer));
|
||||
}
|
||||
#endif
|
||||
else {
|
||||
result = CURLE_SSL_CONNECT_ERROR;
|
||||
@ -4092,6 +4314,68 @@ static CURLcode ossl_connect_step2(struct Curl_cfilter *cf,
|
||||
negotiated_group_name? negotiated_group_name : "[blank]",
|
||||
OBJ_nid2sn(psigtype_nid));
|
||||
|
||||
#ifdef USE_ECH
|
||||
# ifndef OPENSSL_IS_BORINGSSL
|
||||
if(ECH_ENABLED(data)) {
|
||||
char *inner = NULL, *outer = NULL;
|
||||
const char *status = NULL;
|
||||
int rv;
|
||||
|
||||
rv = SSL_ech_get_status(octx->ssl, &inner, &outer);
|
||||
switch(rv) {
|
||||
case SSL_ECH_STATUS_SUCCESS:
|
||||
status = "succeeded";
|
||||
break;
|
||||
case SSL_ECH_STATUS_GREASE_ECH:
|
||||
status = "sent GREASE, got retry-configs";
|
||||
break;
|
||||
case SSL_ECH_STATUS_GREASE:
|
||||
status = "sent GREASE";
|
||||
break;
|
||||
case SSL_ECH_STATUS_NOT_TRIED:
|
||||
status = "not attempted";
|
||||
break;
|
||||
case SSL_ECH_STATUS_NOT_CONFIGURED:
|
||||
status = "not configured";
|
||||
break;
|
||||
case SSL_ECH_STATUS_BACKEND:
|
||||
status = "backend (unexpected)";
|
||||
break;
|
||||
case SSL_ECH_STATUS_FAILED:
|
||||
status = "failed";
|
||||
break;
|
||||
case SSL_ECH_STATUS_BAD_CALL:
|
||||
status = "bad call (unexpected)";
|
||||
break;
|
||||
case SSL_ECH_STATUS_BAD_NAME:
|
||||
status = "bad name (unexpected)";
|
||||
break;
|
||||
default:
|
||||
status = "unexpected status";
|
||||
infof(data, "ECH: unexpected status %d",rv);
|
||||
}
|
||||
infof(data, "ECH: result: status is %s, inner is %s, outer is %s",
|
||||
(status?status:"NULL"),
|
||||
(inner?inner:"NULL"),
|
||||
(outer?outer:"NULL"));
|
||||
OPENSSL_free(inner);
|
||||
OPENSSL_free(outer);
|
||||
if(rv == SSL_ECH_STATUS_GREASE_ECH) {
|
||||
/* trace retry_configs if we got some */
|
||||
ossl_trace_ech_retry_configs(data, octx->ssl, 0);
|
||||
}
|
||||
if(rv != SSL_ECH_STATUS_SUCCESS
|
||||
&& data->set.tls_ech & CURLECH_HARD) {
|
||||
infof(data, "ECH: ech-hard failed");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
}
|
||||
else {
|
||||
infof(data, "ECH: result: status is not attempted");
|
||||
}
|
||||
# endif /* BORING */
|
||||
#endif /* USE_ECH */
|
||||
|
||||
#ifdef HAS_ALPN
|
||||
/* Sets data and len to negotiated protocol, len is 0 if no protocol was
|
||||
* negotiated
|
||||
|
||||
@ -74,6 +74,14 @@
|
||||
#include "curl_memory.h"
|
||||
#include "memdebug.h"
|
||||
|
||||
#ifdef USE_ECH
|
||||
# include "curl_base64.h"
|
||||
# define ECH_ENABLED(__data__) \
|
||||
(__data__->set.tls_ech && \
|
||||
!(__data__->set.tls_ech & CURLECH_DISABLE)\
|
||||
)
|
||||
#endif /* USE_ECH */
|
||||
|
||||
/* KEEP_PEER_CERT is a product of the presence of build time symbol
|
||||
OPENSSL_EXTRA without NO_CERTS, depending on the version. KEEP_PEER_CERT is
|
||||
in wolfSSL's settings.h, and the latter two are build time symbols in
|
||||
@ -725,6 +733,82 @@ wolfssl_connect_step1(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
Curl_ssl_sessionid_unlock(data);
|
||||
}
|
||||
|
||||
#ifdef USE_ECH
|
||||
if(ECH_ENABLED(data)) {
|
||||
int trying_ech_now = 0;
|
||||
|
||||
if(data->set.str[STRING_ECH_PUBLIC]) {
|
||||
infof(data, "ECH: outername not (yet) supported with WolfSSL");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
if(data->set.tls_ech == CURLECH_GREASE) {
|
||||
infof(data, "ECH: GREASE'd ECH not yet supported for wolfSSL");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
if(data->set.tls_ech & CURLECH_CLA_CFG
|
||||
&& data->set.str[STRING_ECH_CONFIG]) {
|
||||
char *b64val = data->set.str[STRING_ECH_CONFIG];
|
||||
word32 b64len = 0;
|
||||
|
||||
b64len = (word32) strlen(b64val);
|
||||
if(b64len
|
||||
&& wolfSSL_SetEchConfigsBase64(backend->handle, b64val, b64len)
|
||||
!= WOLFSSL_SUCCESS) {
|
||||
if(data->set.tls_ech & CURLECH_HARD)
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
else {
|
||||
trying_ech_now = 1;
|
||||
infof(data, "ECH: ECHConfig from command line");
|
||||
}
|
||||
}
|
||||
else {
|
||||
struct Curl_dns_entry *dns = NULL;
|
||||
|
||||
dns = Curl_fetch_addr(data, connssl->peer.hostname, connssl->peer.port);
|
||||
if(!dns) {
|
||||
infof(data, "ECH: requested but no DNS info available");
|
||||
if(data->set.tls_ech & CURLECH_HARD)
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
else {
|
||||
struct Curl_https_rrinfo *rinfo = NULL;
|
||||
|
||||
rinfo = dns->hinfo;
|
||||
if(rinfo && rinfo->echconfiglist) {
|
||||
unsigned char *ecl = rinfo->echconfiglist;
|
||||
size_t elen = rinfo->echconfiglist_len;
|
||||
|
||||
infof(data, "ECH: ECHConfig from DoH HTTPS RR");
|
||||
if(wolfSSL_SetEchConfigs(backend->handle, ecl, (word32) elen) !=
|
||||
WOLFSSL_SUCCESS) {
|
||||
infof(data, "ECH: wolfSSL_SetEchConfigs failed");
|
||||
if(data->set.tls_ech & CURLECH_HARD)
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
else {
|
||||
trying_ech_now = 1;
|
||||
infof(data, "ECH: imported ECHConfigList of length %ld", elen);
|
||||
}
|
||||
}
|
||||
else {
|
||||
infof(data, "ECH: requested but no ECHConfig available");
|
||||
if(data->set.tls_ech & CURLECH_HARD)
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
Curl_resolv_unlock(data, dns);
|
||||
}
|
||||
}
|
||||
|
||||
if(trying_ech_now
|
||||
&& SSL_set_min_proto_version(backend->handle, TLS1_3_VERSION) != 1) {
|
||||
infof(data, "ECH: Can't force TLSv1.3 [ERROR]");
|
||||
return CURLE_SSL_CONNECT_ERROR;
|
||||
}
|
||||
|
||||
}
|
||||
#endif /* USE_ECH */
|
||||
|
||||
#ifdef USE_BIO_CHAIN
|
||||
{
|
||||
WOLFSSL_BIO *bio;
|
||||
@ -858,6 +942,31 @@ wolfssl_connect_step2(struct Curl_cfilter *cf, struct Curl_easy *data)
|
||||
"continuing anyway");
|
||||
}
|
||||
}
|
||||
#endif
|
||||
#ifdef USE_ECH
|
||||
else if(-1 == detail) {
|
||||
/* try access a retry_config ECHConfigList for tracing */
|
||||
byte echConfigs[1000];
|
||||
word32 echConfigsLen = 1000;
|
||||
int rv = 0;
|
||||
|
||||
/* this currently doesn't produce the retry_configs */
|
||||
rv = wolfSSL_GetEchConfigs(backend->handle, echConfigs,
|
||||
&echConfigsLen);
|
||||
if(rv != WOLFSSL_SUCCESS) {
|
||||
infof(data, "Failed to get ECHConfigs");
|
||||
}
|
||||
else {
|
||||
char *b64str = NULL;
|
||||
size_t blen = 0;
|
||||
|
||||
rv = Curl_base64_encode((const char *)echConfigs, echConfigsLen,
|
||||
&b64str, &blen);
|
||||
if(!rv && b64str)
|
||||
infof(data, "ECH: (not yet) retry_configs %s", b64str);
|
||||
free(b64str);
|
||||
}
|
||||
}
|
||||
#endif
|
||||
else if(backend->io_result == CURLE_AGAIN) {
|
||||
return CURLE_OK;
|
||||
|
||||
@ -568,6 +568,105 @@ AC_DEFUN([CURL_CHECK_LIB_ARES], [
|
||||
fi
|
||||
])
|
||||
|
||||
dnl CURL_CHECK_OPTION_NTLM_WB
|
||||
dnl -------------------------------------------------
|
||||
dnl Verify if configure has been invoked with option
|
||||
dnl --enable-ntlm-wb or --disable-ntlm-wb, and set
|
||||
dnl shell variable want_ntlm_wb and want_ntlm_wb_file
|
||||
dnl as appropriate.
|
||||
|
||||
AC_DEFUN([CURL_CHECK_OPTION_NTLM_WB], [
|
||||
AC_BEFORE([$0],[CURL_CHECK_NTLM_WB])dnl
|
||||
OPT_NTLM_WB="default"
|
||||
AC_ARG_ENABLE(ntlm-wb,
|
||||
AS_HELP_STRING([--enable-ntlm-wb@<:@=FILE@:>@],[Enable NTLM delegation to winbind's ntlm_auth helper, where FILE is ntlm_auth's absolute filename (default: /usr/bin/ntlm_auth)])
|
||||
AS_HELP_STRING([--disable-ntlm-wb],[Disable NTLM delegation to winbind's ntlm_auth helper]),
|
||||
OPT_NTLM_WB=$enableval)
|
||||
want_ntlm_wb_file="/usr/bin/ntlm_auth"
|
||||
case "$OPT_NTLM_WB" in
|
||||
no)
|
||||
dnl --disable-ntlm-wb option used
|
||||
want_ntlm_wb="no"
|
||||
;;
|
||||
default)
|
||||
dnl configure option not specified
|
||||
want_ntlm_wb="yes"
|
||||
;;
|
||||
*)
|
||||
dnl --enable-ntlm-wb option used
|
||||
want_ntlm_wb="yes"
|
||||
if test -n "$enableval" && test "$enableval" != "yes"; then
|
||||
want_ntlm_wb_file="$enableval"
|
||||
fi
|
||||
;;
|
||||
esac
|
||||
])
|
||||
|
||||
|
||||
dnl CURL_CHECK_NTLM_WB
|
||||
dnl -------------------------------------------------
|
||||
dnl Check if support for NTLM delegation to winbind's
|
||||
dnl ntlm_auth helper will finally be enabled depending
|
||||
dnl on given configure options and target platform.
|
||||
|
||||
AC_DEFUN([CURL_CHECK_NTLM_WB], [
|
||||
AC_REQUIRE([CURL_CHECK_OPTION_NTLM_WB])dnl
|
||||
AC_REQUIRE([CURL_CHECK_NATIVE_WINDOWS])dnl
|
||||
AC_MSG_CHECKING([whether to enable NTLM delegation to winbind's helper])
|
||||
if test "$curl_cv_native_windows" = "yes" ||
|
||||
test "x$SSL_ENABLED" = "x"; then
|
||||
want_ntlm_wb_file=""
|
||||
want_ntlm_wb="no"
|
||||
elif test "x$ac_cv_func_fork" != "xyes"; then
|
||||
dnl ntlm_wb requires fork
|
||||
want_ntlm_wb="no"
|
||||
fi
|
||||
AC_MSG_RESULT([$want_ntlm_wb])
|
||||
if test "$want_ntlm_wb" = "yes"; then
|
||||
AC_DEFINE(NTLM_WB_ENABLED, 1,
|
||||
[Define to enable NTLM delegation to winbind's ntlm_auth helper.])
|
||||
AC_DEFINE_UNQUOTED(NTLM_WB_FILE, "$want_ntlm_wb_file",
|
||||
[Define absolute filename for winbind's ntlm_auth helper.])
|
||||
NTLM_WB_ENABLED=1
|
||||
fi
|
||||
])
|
||||
|
||||
dnl CURL_CHECK_OPTION_HTTPSRR
|
||||
dnl -----------------------------------------------------
|
||||
dnl Verify whether configure has been invoked with option
|
||||
dnl --enable-httpsrr or --disable-httpsrr, and set
|
||||
dnl shell variable want_httpsrr as appropriate.
|
||||
|
||||
AC_DEFUN([CURL_CHECK_OPTION_HTTPSRR], [
|
||||
AC_MSG_CHECKING([whether to enable HTTPSRR support])
|
||||
OPT_HTTPSRR="default"
|
||||
AC_ARG_ENABLE(httpsrr,
|
||||
AS_HELP_STRING([--enable-httpsrr],[Enable HTTPSRR support])
|
||||
AS_HELP_STRING([--disable-httpsrr],[Disable HTTPSRR support]),
|
||||
OPT_HTTPSRR=$enableval)
|
||||
case "$OPT_HTTPSRR" in
|
||||
no)
|
||||
dnl --disable-httpsrr option used
|
||||
want_httpsrr="no"
|
||||
curl_httpsrr_msg="no (--enable-httpsrr)"
|
||||
AC_MSG_RESULT([no])
|
||||
;;
|
||||
default)
|
||||
dnl configure option not specified
|
||||
want_httpsrr="no"
|
||||
curl_httpsrr_msg="no (--enable-httpsrr)"
|
||||
AC_MSG_RESULT([no])
|
||||
;;
|
||||
*)
|
||||
dnl --enable-httpsrr option used
|
||||
want_httpsrr="yes"
|
||||
curl_httpsrr_msg="enabled (--disable-httpsrr)"
|
||||
experimental="httpsrr"
|
||||
AC_MSG_RESULT([yes])
|
||||
;;
|
||||
esac
|
||||
])
|
||||
|
||||
dnl CURL_CHECK_OPTION_ECH
|
||||
dnl -----------------------------------------------------
|
||||
dnl Verify whether configure has been invoked with option
|
||||
@ -603,3 +702,4 @@ AS_HELP_STRING([--disable-ech],[Disable ECH support]),
|
||||
;;
|
||||
esac
|
||||
])
|
||||
])
|
||||
|
||||
@ -1097,6 +1097,9 @@ curl_easy_setopt_ccsid(CURL *easy, CURLoption tag, ...)
|
||||
case CURLOPT_DNS_LOCAL_IP6:
|
||||
case CURLOPT_DNS_SERVERS:
|
||||
case CURLOPT_DOH_URL:
|
||||
#ifdef USE_ECH
|
||||
case CURLOPT_ECH:
|
||||
#endif
|
||||
case CURLOPT_EGDSOCKET:
|
||||
case CURLOPT_FTPPORT:
|
||||
case CURLOPT_FTP_ACCOUNT:
|
||||
|
||||
@ -176,6 +176,14 @@ static void free_config_fields(struct OperationConfig *config)
|
||||
Curl_safefree(config->aws_sigv4);
|
||||
Curl_safefree(config->proto_str);
|
||||
Curl_safefree(config->proto_redir_str);
|
||||
#ifdef USE_ECH
|
||||
Curl_safefree(config->ech);
|
||||
config->ech = NULL;
|
||||
Curl_safefree(config->ech_config);
|
||||
config->ech_config = NULL;
|
||||
Curl_safefree(config->ech_public);
|
||||
config->ech_public = NULL;
|
||||
#endif
|
||||
}
|
||||
|
||||
void config_free(struct OperationConfig *config)
|
||||
|
||||
@ -298,6 +298,12 @@ struct OperationConfig {
|
||||
struct State state; /* for create_transfer() */
|
||||
bool rm_partial; /* on error, remove partially written output
|
||||
files */
|
||||
#ifdef USE_ECH
|
||||
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 */
|
||||
#endif
|
||||
|
||||
};
|
||||
|
||||
struct GlobalConfig {
|
||||
|
||||
@ -123,6 +123,7 @@ typedef enum {
|
||||
C_DOH_INSECURE,
|
||||
C_DOH_URL,
|
||||
C_DUMP_HEADER,
|
||||
C_ECH,
|
||||
C_EGD_FILE,
|
||||
C_ENGINE,
|
||||
C_EPRT,
|
||||
@ -404,6 +405,7 @@ static const struct LongShort aliases[]= {
|
||||
{"doh-insecure", ARG_BOOL, ' ', C_DOH_INSECURE},
|
||||
{"doh-url" , ARG_STRG, ' ', C_DOH_URL},
|
||||
{"dump-header", ARG_FILE, 'D', C_DUMP_HEADER},
|
||||
{"ech", ARG_STRG, ' ', C_ECH},
|
||||
{"egd-file", ARG_STRG, ' ', C_EGD_FILE},
|
||||
{"engine", ARG_STRG, ' ', C_ENGINE},
|
||||
{"eprt", ARG_BOOL, ' ', C_EPRT},
|
||||
@ -2079,6 +2081,57 @@ ParameterError getparameter(const char *flag, /* f or -long-flag */
|
||||
err = PARAM_ENGINES_REQUESTED;
|
||||
}
|
||||
break;
|
||||
#ifndef USE_ECH
|
||||
case C_ECH: /* --ech, not implemented by default */
|
||||
err = PARAM_LIBCURL_DOESNT_SUPPORT;
|
||||
break;
|
||||
#else
|
||||
case C_ECH: /* --ech */
|
||||
if(strlen(nextarg) > 4 && strncasecompare("pn:", nextarg, 3)) {
|
||||
/* a public_name */
|
||||
err = getstr(&config->ech_public, nextarg, DENY_BLANK);
|
||||
}
|
||||
else if(strlen(nextarg) > 5 && strncasecompare("ecl:", nextarg, 4)) {
|
||||
/* an ECHConfigList */
|
||||
if('@' != *(nextarg + 4)) {
|
||||
err = getstr(&config->ech_config, nextarg, DENY_BLANK);
|
||||
}
|
||||
else {
|
||||
/* Indirect case: @filename or @- for stdin */
|
||||
char *tmpcfg = NULL;
|
||||
FILE *file;
|
||||
|
||||
nextarg++; /* skip over '@' */
|
||||
if(!strcmp("-", nextarg)) {
|
||||
file = stdin;
|
||||
}
|
||||
else {
|
||||
file = fopen(nextarg, FOPEN_READTEXT);
|
||||
}
|
||||
if(!file) {
|
||||
warnf(global,
|
||||
"Couldn't read file \"%s\" "
|
||||
"specified for \"--ech ecl:\" option",
|
||||
nextarg);
|
||||
return PARAM_BAD_USE; /* */
|
||||
}
|
||||
err = file2string(&tmpcfg, file);
|
||||
if(file != stdin)
|
||||
fclose(file);
|
||||
if(err)
|
||||
return err;
|
||||
config->ech_config = aprintf("ecl:%s",tmpcfg);
|
||||
if(!config->ech_config)
|
||||
return PARAM_NO_MEM;
|
||||
free(tmpcfg);
|
||||
} /* file done */
|
||||
}
|
||||
else {
|
||||
/* Simple case: just a string, with a keyword */
|
||||
err = getstr(&config->ech, nextarg, DENY_BLANK);
|
||||
}
|
||||
break;
|
||||
#endif
|
||||
case C_CAPATH: /* --capath */
|
||||
err = getstr(&config->capath, nextarg, DENY_BLANK);
|
||||
break;
|
||||
|
||||
@ -67,6 +67,7 @@ static const struct category_descriptors categories[] = {
|
||||
{"telnet", "TELNET protocol options", CURLHELP_TELNET},
|
||||
{"tftp", "TFTP protocol options", CURLHELP_TFTP},
|
||||
{"tls", "All TLS/SSL related options", CURLHELP_TLS},
|
||||
{"ech", "All Encrypted Client Hello (ECH) options", CURLHELP_ECH},
|
||||
{"upload", "All options for uploads",
|
||||
CURLHELP_UPLOAD},
|
||||
{"verbose", "Options related to any kind of command line output of curl",
|
||||
|
||||
@ -68,6 +68,7 @@ struct helptxt {
|
||||
#define CURLHELP_TLS 1u << 22u
|
||||
#define CURLHELP_UPLOAD 1u << 23u
|
||||
#define CURLHELP_VERBOSE 1u << 24u
|
||||
#define CURLHELP_ECH 1u << 25u
|
||||
|
||||
extern const struct helptxt helptext[];
|
||||
|
||||
|
||||
@ -168,6 +168,9 @@ const struct helptxt helptext[] = {
|
||||
{"-D, --dump-header <filename>",
|
||||
"Write the received headers to <filename>",
|
||||
CURLHELP_HTTP | CURLHELP_FTP},
|
||||
{" --ech <config>",
|
||||
"Configure Encrypted Client Hello (ECH) for use with the TLS session",
|
||||
CURLHELP_TLS | CURLHELP_ECH},
|
||||
{" --egd-file <file>",
|
||||
"EGD socket path for random data",
|
||||
CURLHELP_TLS},
|
||||
|
||||
@ -2187,6 +2187,16 @@ static CURLcode single_transfer(struct GlobalConfig *global,
|
||||
if(config->hsts)
|
||||
my_setopt_str(curl, CURLOPT_HSTS, config->hsts);
|
||||
|
||||
#ifdef USE_ECH
|
||||
/* only if enabled in configure */
|
||||
if(config->ech) /* only if set (optional) */
|
||||
my_setopt_str(curl, CURLOPT_ECH, config->ech);
|
||||
if(config->ech_public) /* only if set (optional) */
|
||||
my_setopt_str(curl, CURLOPT_ECH, config->ech_public);
|
||||
if(config->ech_config) /* only if set (optional) */
|
||||
my_setopt_str(curl, CURLOPT_ECH, config->ech_config);
|
||||
#endif
|
||||
|
||||
/* initialize retry vars for loop below */
|
||||
per->retry_sleep_default = (config->retry_delay) ?
|
||||
config->retry_delay*1000L : RETRY_SLEEP_DEFAULT; /* ms */
|
||||
|
||||
@ -54,6 +54,7 @@ Invalid category provided, here is a list of all categories:
|
||||
telnet TELNET protocol options
|
||||
tftp TFTP protocol options
|
||||
tls All TLS/SSL related options
|
||||
ech All Encrypted Client Hello (ECH) options
|
||||
upload All options for uploads
|
||||
verbose Options related to any kind of command line output of curl
|
||||
</stdout>
|
||||
|
||||
@ -133,7 +133,8 @@ e97: proxy handshake error
|
||||
e98: SSL Client Certificate required
|
||||
e99: Unrecoverable error in select/poll
|
||||
e100: A value or data field grew larger than allowed
|
||||
e101: Unknown error
|
||||
e101: ECH attempted but failed
|
||||
e102: Unknown error
|
||||
m-1: Please call curl_multi_perform() soon
|
||||
m0: No error
|
||||
m1: Invalid multi handle
|
||||
|
||||
99
tests/ech_combos.py
Executable file
99
tests/ech_combos.py
Executable file
@ -0,0 +1,99 @@
|
||||
#!/usr/bin/env python3
|
||||
# -*- coding: utf-8 -*-
|
||||
#***************************************************************************
|
||||
# _ _ ____ _
|
||||
# Project ___| | | | _ \| |
|
||||
# / __| | | | |_) | |
|
||||
# | (__| |_| | _ <| |___
|
||||
# \___|\___/|_| \_\_____|
|
||||
#
|
||||
# Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
|
||||
#
|
||||
# This software is licensed as described in the file COPYING, which
|
||||
# you should have received as part of this distribution. The terms
|
||||
# are also available at https://curl.se/docs/copyright.html.
|
||||
#
|
||||
# You may opt to use, copy, modify, merge, publish, distribute and/or sell
|
||||
# copies of the Software, and permit persons to whom the Software is
|
||||
# furnished to do so, under the terms of the COPYING file.
|
||||
#
|
||||
# This software is distributed on an "AS IS" basis, WITHOUT WARRANTY OF ANY
|
||||
# KIND, either express or implied.
|
||||
#
|
||||
# SPDX-License-Identifier: curl
|
||||
#
|
||||
###########################################################################
|
||||
#
|
||||
# Python3 program to print all combination of size r in an array of size n.
|
||||
# This is used to generate test lines in tests/ech_test.sh.
|
||||
# This will be discarded in the process of moving from experimental,
|
||||
# but is worth preserving for the moment in case of changes to the
|
||||
# ECH command line args
|
||||
|
||||
def CombinationRepetitionUtil(chosen, arr, badarr, index,
|
||||
r, start, end):
|
||||
|
||||
# Current combination is ready,
|
||||
# print it
|
||||
if index == r:
|
||||
# figure out if result should be good or bad and
|
||||
# print prefix, assuming $turl does support ECH so
|
||||
# should work if given "positive" parameters
|
||||
res = 1
|
||||
j = len(chosen) - 1
|
||||
while res and j >= 0:
|
||||
if chosen[j] in badarr:
|
||||
res = 0
|
||||
j = j - 1
|
||||
print("cli_test $turl 1", res, end = " ")
|
||||
# print combination but eliminating any runs of
|
||||
# two identical params
|
||||
for j in range(r):
|
||||
if j != 0 and chosen[j] != chosen[j-1]:
|
||||
print(chosen[j], end = " ")
|
||||
|
||||
print()
|
||||
return
|
||||
|
||||
# When no more elements are
|
||||
# there to put in chosen[]
|
||||
if start > n:
|
||||
return
|
||||
|
||||
# Current is included, put
|
||||
# next at next location
|
||||
chosen[index] = arr[start]
|
||||
|
||||
# Current is excluded, replace it
|
||||
# with next (Note that i+1 is passed,
|
||||
# but index is not changed)
|
||||
CombinationRepetitionUtil(chosen, arr, badarr, index + 1,
|
||||
r, start, end)
|
||||
CombinationRepetitionUtil(chosen, arr, badarr, index,
|
||||
r, start + 1, end)
|
||||
|
||||
# The main function that prints all
|
||||
# combinations of size r in arr[] of
|
||||
# size n. This function mainly uses
|
||||
# CombinationRepetitionUtil()
|
||||
def CombinationRepetition(arr, badarr, n, r):
|
||||
|
||||
# A temporary array to store
|
||||
# all combination one by one
|
||||
chosen = [0] * r
|
||||
|
||||
# Print all combination using
|
||||
# temporary array 'chosen[]'
|
||||
CombinationRepetitionUtil(chosen, arr, badarr, 0, r, 0, n)
|
||||
|
||||
# Driver code
|
||||
badarr = [ '--ech grease', '--ech false', '--ech ecl:$badecl', '--ech pn:$badpn' ]
|
||||
goodarr = [ '--ech hard', '--ech true', '--ech ecl:$goodecl', '--ech pn:$goodpn' ]
|
||||
arr = badarr + goodarr
|
||||
r = 8
|
||||
n = len(arr) - 1
|
||||
|
||||
CombinationRepetition(arr, badarr, n, r)
|
||||
|
||||
# This code is contributed by Vaibhav Kumar 12.
|
||||
|
||||
1151
tests/ech_tests.sh
Executable file
1151
tests/ech_tests.sh
Executable file
File diff suppressed because it is too large
Load Diff
Loading…
Reference in New Issue
Block a user