quickjs-tart

quickjs-based runtime for wallet-core logic
Log | Files | Refs | README | LICENSE

ECH.md (19836B)


      1 <!--
      2 Copyright (C) Daniel Stenberg, <daniel@haxx.se>, et al.
      3 
      4 SPDX-License-Identifier: curl
      5 -->
      6 
      7 # Building curl with HTTPS-RR and ECH support
      8 
      9 We have added support for ECH to curl. It can use HTTPS RRs published in the
     10 DNS if curl uses DoH, or else can accept the relevant ECHConfigList values
     11 from the command line. This works with OpenSSL, wolfSSL, BoringSSL, AWS-LC
     12 or rustls-ffi as the TLS provider.
     13 
     14 This feature is EXPERIMENTAL. DO NOT USE IN PRODUCTION.
     15 
     16 This should however provide enough of a proof-of-concept to prompt an informed
     17 discussion about a good path forward for ECH support in curl.
     18 
     19 ## OpenSSL Build
     20 
     21 To build the OpenSSL project's ECH feature branch:
     22 
     23 ```bash
     24     cd $HOME/code
     25     git clone https://github.com/openssl/openssl
     26     cd openssl
     27     git checkout feature/ech
     28     ./config --libdir=lib --prefix=$HOME/code/openssl-local-inst
     29     ...stuff...
     30     make -j8
     31     ...more stuff...
     32     make install_sw
     33     ...a little bit of stuff...
     34 ```
     35 
     36 To build curl ECH-enabled, making use of the above:
     37 
     38 ```bash
     39     cd $HOME/code
     40     git clone https://github.com/curl/curl
     41     cd curl
     42     autoreconf -fi
     43     LDFLAGS="-Wl,-rpath,$HOME/code/openssl-local-inst/lib/" ./configure --with-ssl=$HOME/code/openssl-local-inst --enable-ech
     44     ...lots of output...
     45     WARNING: ECH HTTPSRR enabled but marked EXPERIMENTAL...
     46     make
     47     ...lots more output...
     48 ```
     49 
     50 If you do not get that WARNING at the end of the ``configure`` command, then
     51 ECH is not enabled, so go back some steps and re-do whatever needs re-doing:-)
     52 If you want to debug curl then you should add ``--enable-debug`` to the
     53 ``configure`` command.
     54 
     55 In a recent (2024-05-20) build on one machine, configure failed to find the
     56 ECH-enabled SSL library, apparently due to the existence of
     57 ``$HOME/code/openssl-local-inst/lib/pkgconfig`` as a directory containing
     58 various settings. Deleting that directory worked around the problem but may
     59 not be the best solution.
     60 
     61 ## Using ECH and DoH
     62 
     63 curl supports using DoH for A/AAAA lookups so it was relatively easy to add
     64 retrieval of HTTPS RRs in that situation. To use ECH and DoH together:
     65 
     66 ```bash
     67     cd $HOME/code/curl
     68     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
     69     ...
     70     SSL_ECH_STATUS: success <img src="greentick-small.png" alt="good" /> <br/>
     71     ...
     72 ```
     73 
     74 The output snippet above is within the HTML for the webpage, when things work.
     75 
     76 The above works for these test sites:
     77 
     78 ```bash
     79     https://defo.ie/ech-check.php
     80     https://draft-13.esni.defo.ie:8413/stats
     81     https://draft-13.esni.defo.ie:8414/stats
     82     https://crypto.cloudflare.com/cdn-cgi/trace
     83     https://tls-ech.dev
     84 ```
     85 
     86 The list above has 4 different server technologies, implemented by 3 different
     87 parties, and includes a case (the port 8414 server) where HelloRetryRequest
     88 (HRR) is forced.
     89 
     90 We currently support the following new curl command line arguments/options:
     91 
     92 - ``--ech <config>`` - the ``config`` value can be one of:
     93     - ``false`` says to not attempt ECH
     94     - ``true`` says to attempt ECH, if possible
     95     - ``grease`` if attempting ECH is not possible, then send a GREASE ECH extension
     96     - ``hard`` hard-fail the connection if ECH cannot be attempted
     97     - ``ecl:<b64value>`` a base64 encoded ECHConfigList, rather than one accessed from the DNS
     98     - ``pn:<name>`` override the ``public_name`` from an ECHConfigList
     99 
    100 Note that in the above "attempt ECH" means the client emitting a TLS
    101 ClientHello with a "real" ECH extension, but that does not mean that the
    102 relevant server can succeed in decrypting, as things can fail for other
    103 reasons.
    104 
    105 ## Supplying an ECHConfigList on the command line
    106 
    107 To supply the ECHConfigList on the command line, you might need a bit of
    108 cut-and-paste, e.g.:
    109 
    110 ```bash
    111     dig +short https defo.ie
    112     1 . ipv4hint=213.108.108.101 ech=AED+DQA8PAAgACD8WhlS7VwEt5bf3lekhHvXrQBGDrZh03n/LsNtAodbUAAEAAEAAQANY292ZXIuZGVmby5pZQAA ipv6hint=2a00:c6c0:0:116:5::10
    113 ```
    114 
    115 Then paste the base64 encoded ECHConfigList onto the curl command line:
    116 
    117 ```bash
    118     LD_LIBRARY_PATH=$HOME/code/openssl ./src/curl --ech ecl:AED+DQA8PAAgACD8WhlS7VwEt5bf3lekhHvXrQBGDrZh03n/LsNtAodbUAAEAAEAAQANY292ZXIuZGVmby5pZQAA https://defo.ie/ech-check.php
    119     ...
    120     SSL_ECH_STATUS: success <img src="greentick-small.png" alt="good" /> <br/>
    121     ...
    122 ```
    123 
    124 The output snippet above is within the HTML for the webpage.
    125 
    126 If you paste in the wrong ECHConfigList (it changes hourly for ``defo.ie``) you
    127 should get an error like this:
    128 
    129 ```bash
    130     LD_LIBRARY_PATH=$HOME/code/openssl ./src/curl -vvv --ech ecl:AED+DQA8yAAgACDRMQo+qYNsNRNj+vfuQfFIkrrUFmM4vogucxKj/4nzYgAEAAEAAQANY292ZXIuZGVmby5pZQAA https://defo.ie/ech-check.php
    131     ...
    132     * OpenSSL/3.3.0: error:0A00054B:SSL routines::ech required
    133     ...
    134 ```
    135 
    136 There is a reason to want this command line option - for use before publishing
    137 an ECHConfigList in the DNS as per the Internet-draft [A well-known URI for
    138 publishing ECHConfigList values](https://datatracker.ietf.org/doc/draft-ietf-tls-wkech/).
    139 
    140 If you do use a wrong ECHConfigList value, then the server might return a
    141 good value, via the ``retry_configs`` mechanism. You can see that value in
    142 the verbose output, e.g.:
    143 
    144 ```bash
    145     LD_LIBRARY_PATH=$HOME/code/openssl ./src/curl -vvv --ech ecl:AED+DQA8yAAgACDRMQo+qYNsNRNj+vfuQfFIkrrUFmM4vogucxKj/4nzYgAEAAEAAQANY292ZXIuZGVmby5pZQAA https://defo.ie/ech-check.php
    146     ...
    147 * ECH: retry_configs AQD+DQA8DAAgACBvYqJy+Hgk33wh/ZLBzKSPgwxeop7gvojQzfASq7zeZQAEAAEAAQANY292ZXIuZGVmby5pZQAA/g0APEMAIAAgXkT5r4cYs8z19q5rdittyIX8gfQ3ENW4wj1fVoiJZBoABAABAAEADWNvdmVyLmRlZm8uaWUAAP4NADw2ACAAINXSE9EdXzEQIJZA7vpwCIQsWqsFohZARXChgPsnfI1kAAQAAQABAA1jb3Zlci5kZWZvLmllAAD+DQA8cQAgACASeiD5F+UoSnVoHvA2l1EifUVMFtbVZ76xwDqmMPraHQAEAAEAAQANY292ZXIuZGVmby5pZQAA
    148 * ECH: retry_configs for defo.ie from cover.defo.ie, 319
    149     ...
    150 ```
    151 
    152 At that point, you could copy the base64 encoded value above and try again.
    153 For now, this only works for the OpenSSL and BoringSSL/AWS-LC builds.
    154 
    155 ## Default settings
    156 
    157 curl has various ways to configure default settings, e.g. in ``$HOME/.curlrc``,
    158 so one can set the DoH URL and enable ECH that way:
    159 
    160 ```bash
    161     cat ~/.curlrc
    162     doh-url=https://one.one.one.one/dns-query
    163     silent
    164     ech=true
    165 ```
    166 
    167 Note that when you use the system's curl command (rather than our ECH-enabled
    168 build), it is liable to warn that ``ech`` is an unknown option. If that is an
    169 issue (e.g. if some script re-directs stdout and stderr somewhere) then adding
    170 the ``silent`` line above seems to be a good enough fix. (Though of
    171 course, yet another script could depend on non-silent behavior, so you may have
    172 to figure out what you prefer yourself.) That seems to have changed with the
    173 latest build, previously ``silent=TRUE`` was what I used in ``~/.curlrc`` but
    174 now that seems to cause a problem, so that the following line(s) are ignored.
    175 
    176 If you want to always use our OpenSSL build you can set ``LD_LIBRARY_PATH``
    177 in the environment:
    178 
    179 ```bash
    180     export LD_LIBRARY_PATH=$HOME/code/openssl
    181 ```
    182 
    183 When you do the above, there can be a mismatch between OpenSSL versions
    184 for applications that check that. A ``git push`` for example fails so you
    185 should unset ``LD_LIBRARY_PATH`` before doing that or use a different shell.
    186 
    187 ```bash
    188     git push
    189     OpenSSL version mismatch. Built against 30000080, you have 30200000
    190     ...
    191 ```
    192 
    193 With all that setup as above the command line gets simpler:
    194 
    195 ```bash
    196     ./src/curl https://defo.ie/ech-check.php
    197     ...
    198     SSL_ECH_STATUS: success <img src="greentick-small.png" alt="good" /> <br/>
    199     ...
    200 ```
    201 
    202 The ``--ech true`` option is opportunistic, so tries to do ECH but does not fail if
    203 the client for example cannot find any ECHConfig values. The ``--ech hard``
    204 option hard-fails if there is no ECHConfig found in DNS, so for now, that is not
    205 a good option to set as a default. Once ECH has really been attempted by
    206 the client, if decryption on the server side fails, then curl fails.
    207 
    208 ## Code changes for ECH support when using DoH
    209 
    210 Code changes are ``#ifdef`` protected via ``USE_ECH`` or ``USE_HTTPSRR``:
    211 
    212 - ``USE_HTTPSRR`` is used for HTTPS RR retrieval code that could be generically
    213   used should non-ECH uses for HTTPS RRs be identified, e.g. use of ALPN values
    214   or IP address hints.
    215 
    216 - ``USE_ECH`` protects ECH specific code.
    217 
    218 There are various obvious code blocks for handling the new command line
    219 arguments which are not described here, but should be fairly clear.
    220 
    221 As shown in the ``configure`` usage above, there are ``configure.ac`` changes
    222 that allow separately dis/enabling ``USE_HTTPSRR`` and ``USE_ECH``. If ``USE_ECH``
    223 is enabled, then ``USE_HTTPSRR`` is forced. In both cases ``CURL_DISABLE_DOH``
    224 must not be enabled. (There may be some configuration conflicts available for the
    225 determined :-)
    226 
    227 The main functional change, as you would expect, is in ``lib/vtls/openssl.c``
    228 where an ECHConfig, if available from command line or DNS cache, is fed into
    229 the OpenSSL library via the new APIs implemented in our OpenSSL fork for that
    230 purpose. This code also implements the opportunistic (``--ech true``) or hard-fail
    231 (``--ech hard``) logic.
    232 
    233 Other than that, the main additions are in ``lib/doh.c``
    234 where we reuse ``dohprobe()`` to retrieve an HTTPS RR value for the target
    235 domain. If such a value is found, that is stored using a new ``doh_store_https()``
    236 function in a new field in the ``dohentry`` structure.
    237 
    238 The qname for the DoH query is modified if the port number is not 443, as
    239 defined in the SVCB specification.
    240 
    241 When the DoH process has worked, ``Curl_doh_is_resolved()`` now also returns
    242 the relevant HTTPS RR value data in the ``Curl_dns_entry`` structure.
    243 That is later accessed when the TLS session is being established, if ECH is
    244 enabled (from ``lib/vtls/openssl.c`` as described above).
    245 
    246 ## Limitations
    247 
    248 Things that need fixing, but that can probably be ignored for the
    249 moment:
    250 
    251 - We could easily add code to make use of an ``alpn=`` value found in an HTTPS
    252   RR, passing that on to OpenSSL for use as the "inner" ALPN value, but have
    253 yet to do that.
    254 
    255 Current limitations (more interesting than the above):
    256 
    257 - Only the first HTTPS RR value retrieved is actually processed as described
    258   above, that could be extended in future, though picking the "right" HTTPS RR
    259 could be non-trivial if multiple RRs are published - matching IP address hints
    260 versus A/AAAA values might be a good basis for that. Last I checked though,
    261 browsers supporting ECH did not handle multiple HTTPS RRs well, though that
    262 needs re-checking as it has been a while.
    263 
    264 - It is unclear how one should handle any IP address hints found in an HTTPS RR.
    265   It may be that a bit of consideration of how "multi-CDN" deployments might
    266 emerge would provide good answers there, but for now, it is not clear how best
    267 curl might handle those values when present in the DNS.
    268 
    269 - The SVCB/HTTPS RR specification supports a new "CNAME at apex" indirection
    270   ("aliasMode") - the current code takes no account of that at all. One could
    271 envisage implementing the equivalent of following CNAMEs in such cases, but
    272 it is not clear if that'd be a good plan. (As of now, chrome browsers do not seem
    273 to have any support for that "aliasMode" and we have not checked Firefox for that
    274 recently.)
    275 
    276 - We have not investigated what related changes or additions might be needed
    277   for applications using libcurl, as opposed to use of curl as a command line
    278 tool.
    279 
    280 - We have not yet implemented tests as part of the usual curl test harness as
    281 doing so would seem to require re-implementing an ECH-enabled server as part
    282 of the curl test harness. For now, we have a ``./tests/ech_test.sh`` script
    283 that attempts ECH with various test servers and with many combinations of the
    284 allowed command line options. While that is a useful test and has find issues,
    285 it is not comprehensive and we are not (as yet) sure what would be the right
    286 level of coverage. When running that script you should not have a
    287 ``$HOME/.curlrc`` file that affects ECH or some of the negative tests could
    288 produce spurious failures.
    289 
    290 ## Building with cmake
    291 
    292 To build with cmake, assuming our ECH-enabled OpenSSL is as before:
    293 
    294 ```bash
    295     cd $HOME/code
    296     git clone https://github.com/curl/curl
    297     cd curl
    298     mkdir build
    299     cd build
    300     cmake -DOPENSSL_ROOT_DIR=$HOME/code/openssl -DUSE_ECH=1 ..
    301     ...
    302     make
    303     ...
    304     [100%] Built target curl
    305 ```
    306 
    307 The binary produced by the cmake build does not need any ECH-specific
    308 ``LD_LIBRARY_PATH`` setting.
    309 
    310 ## BoringSSL build
    311 
    312 BoringSSL is also supported by curl and also supports ECH, so to build
    313 with that, instead of our ECH-enabled OpenSSL:
    314 
    315 ```bash
    316     cd $HOME/code
    317     git clone https://boringssl.googlesource.com/boringssl
    318     cd boringssl
    319     cmake -DCMAKE_INSTALL_PREFIX:PATH=$HOME/code/boringssl/inst -DBUILD_SHARED_LIBS=1
    320     make
    321     ...
    322     make install
    323 ```
    324 
    325 Then:
    326 
    327 ```bash
    328     cd $HOME/code
    329     git clone https://github.com/curl/curl
    330     cd curl
    331     autoreconf -fi
    332     LDFLAGS="-Wl,-rpath,$HOME/code/boringssl/inst/lib" ./configure --with-ssl=$HOME/code/boringssl/inst --enable-ech
    333     ...lots of output...
    334     WARNING: ECH HTTPSRR enabled but marked EXPERIMENTAL. Use with caution.
    335     make
    336 ```
    337 
    338 The BoringSSL/AWS-LC APIs are fairly similar to those in our ECH-enabled
    339 OpenSSL fork, so code changes are also in ``lib/vtls/openssl.c``, protected
    340 via ``#ifdef OPENSSL_IS_BORINGSSL`` and are mostly obvious API variations.
    341 
    342 The BoringSSL/AWS-LC APIs however do not support the ``--ech pn:`` command
    343 line variant as of now.
    344 
    345 ## wolfSSL build
    346 
    347 wolfSSL also supports ECH and can be used by curl, so here's how:
    348 
    349 ```bash
    350     cd $HOME/code
    351     git clone https://github.com/wolfSSL/wolfssl
    352     cd wolfssl
    353     ./autogen.sh
    354     ./configure --prefix=$HOME/code/wolfssl/inst --enable-ech --enable-debug --enable-opensslextra
    355     make
    356     make install
    357 ```
    358 
    359 The install prefix (``inst``) in the above causes wolfSSL to be installed there
    360 and we seem to need that for the curl configure command to work out. The
    361 ``--enable-opensslextra`` turns out (after much faffing about;-) to be
    362 important or else we get build problems with curl below.
    363 
    364 ```bash
    365     cd $HOME/code
    366     git clone https://github.com/curl/curl
    367     cd curl
    368     autoreconf -fi
    369     ./configure --with-wolfssl=$HOME/code/wolfssl/inst --enable-ech
    370     make
    371 ```
    372 
    373 There are some known issues with the ECH implementation in wolfSSL:
    374 
    375 - The main issue is that the client currently handles HelloRetryRequest
    376   incorrectly.  [HRR issue](https://github.com/wolfSSL/wolfssl/issues/6802).)
    377   The HRR issue means that the client does not work for
    378   [this ECH test web site](https://tls-ech.dev) and any other similarly configured
    379   sites.
    380 - There is also an issue related to so-called middlebox compatibility mode.
    381   [middlebox compatibility issue](https://github.com/wolfSSL/wolfssl/issues/6774)
    382 
    383 ### Code changes to support wolfSSL
    384 
    385 There are what seem like oddball differences:
    386 
    387 - The DoH URL in``$HOME/.curlrc`` can use `1.1.1.1` for OpenSSL but has to be
    388   `one.one.one.one` for wolfSSL. The latter works for both, so OK, we us that.
    389 - There seems to be some difference in CA databases too - the wolfSSL version
    390   does not like ``defo.ie``, whereas the system and OpenSSL ones do. We can
    391   ignore that for our purposes via ``--insecure``/``-k`` but would need to fix
    392   for a real setup. (Browsers do like those certificates though.)
    393 
    394 Then there are some functional code changes:
    395 
    396 - tweak to ``configure.ac`` to check if wolfSSL has ECH or not
    397 - added code to ``lib/vtls/wolfssl.c`` mirroring what's done in the
    398   OpenSSL equivalent above.
    399 - wolfSSL does not support ``--ech false`` or the ``--ech pn:`` command line
    400   argument.
    401 
    402 The lack of support for ``--ech false`` is because wolfSSL has decided to
    403 always at least GREASE if built to support ECH. In other words, GREASE is
    404 a compile time choice for wolfSSL, but a runtime choice for OpenSSL or
    405 BoringSSL/AWS-LC. (Both are reasonable.)
    406 
    407 ## Additional notes
    408 
    409 ### Supporting ECH without DoH
    410 
    411 All of the above only applies if DoH is being used. There should be a use-case
    412 for ECH when DoH is not used by curl - if a system stub resolver supports DoT
    413 or DoH, then, considering only ECH and the network threat model, it would make
    414 sense for curl to support ECH without curl itself using DoH. The author for
    415 example uses a combination of stubby+unbound as the system resolver listening
    416 on localhost:53, so would fit this use-case. That said, it is unclear if
    417 this is a niche that is worth trying to address. (The author is just as happy to
    418 let curl use DoH to talk to the same public recursive that stubby might use:-)
    419 
    420 Assuming for the moment this is a use-case we would like to support, then if
    421 DoH is not being used by curl, it is not clear at this time how to provide
    422 support for ECH. One option would seem to be to extend the ``c-ares`` library
    423 to support HTTPS RRs, but in that case it is not now clear whether such
    424 changes would be attractive to the ``c-ares`` maintainers, nor whether the
    425 "tag=value" extensibility inherent in the HTTPS/SVCB specification is a good
    426 match for the ``c-ares`` approach of defining structures specific to decoded
    427 answers for each supported RRtype. We are also not sure how many downstream
    428 curl deployments actually make use of the ``c-ares`` library, which would
    429 affect the utility of such changes. Another option might be to consider using
    430 some other generic DNS library that does support HTTPS RRs, but it is unclear
    431 if such a library could or would be used by all or almost all curl builds and
    432 downstream releases of curl.
    433 
    434 Our current conclusion is that doing the above is likely best left until we
    435 have some experience with the "using DoH" approach, so we are going to punt on
    436 this for now.
    437 
    438 ### Debugging
    439 
    440 Just a note to self as remembering this is a nuisance:
    441 
    442 ```bash
    443 LD_LIBRARY_PATH=$HOME/code/openssl:./lib/.libs gdb ./src/.libs/curl
    444 ```
    445 
    446 ### Localhost testing
    447 
    448 It can be useful to be able to run against a localhost OpenSSL ``s_server``
    449 for testing. We have published instructions for such
    450 [localhost tests](https://github.com/defo-project/ech-dev-utils/blob/main/howtos/localhost-tests.md)
    451 in another repository. Once you have that set up, you can start a server
    452 and then run curl against that:
    453 
    454 ```bash
    455     cd $HOME/code/ech-dev-utils
    456     ./scripts/echsvr.sh -d
    457     ...
    458 ```
    459 
    460 The ``echsvr.sh`` script supports many ECH-related options. Use ``echsvr.sh -h``
    461 for details.
    462 
    463 In another window:
    464 
    465 ```bash
    466     cd $HOME/code/curl/
    467     ./src/curl -vvv --insecure  --connect-to foo.example.com:8443:localhost:8443  --ech ecl:AD7+DQA6uwAgACBix2B78sX+EQhEbxMspDOc8Z3xVS5aQpYP0Cxpc2AWPAAEAAEAAQALZXhhbXBsZS5jb20AAA==
    468 ```
    469 
    470 ### Automated use of ``retry_configs`` not supported so far...
    471 
    472 As of now we have not added support for using ``retry_config`` handling in the
    473 application - for a command line tool, one can just use ``dig`` (or ``kdig``)
    474 to get the HTTPS RR and pass the ECHConfigList from that on the command line,
    475 if needed, or one can access the value from command line output in verbose more
    476 and then reuse that in another invocation.
    477 
    478 Both our OpenSSL fork and BoringSSL/AWS-LC have APIs for both controlling GREASE
    479 and accessing and logging ``retry_configs``, it seems wolfSSL has neither.
    480 
    481 ### Testing ECH
    482 
    483 We have yet to add a robust test setup for ECH as that requires an ECH-enabled
    484 test server.
    485 
    486 We have added two basic tests though, aiming to ensure that the client sends a
    487 GREASE or real ECH extension when requested, and reacts correctly to the
    488 failure of ECH in the latter case. (Given that `stunnel` has no ECH support.)
    489 
    490 As with other similar tests, those tests require the `stunnel` tool be
    491 installed. On Ubuntu `sudo apt install stunnel4` achieves that.
    492 
    493 The test cases are:
    494 
    495 - data/test4000: GREASE ECH, expected result: connection succeeds
    496 - data/test4001: real ECH, connection fails with error 101 (ECH required)