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