• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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've added support for ECH to in this curl build. That can use HTTPS RRs
10published in the DNS, if curl is using DoH, or else can accept the relevant
11ECHConfigList values from the command line. That works with OpenSSL,
12WolfSSL or boringssl as the TLS provider, depending on how you build curl.
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, when using
18OpenSSL, or other TLS libraries, as those add ECH support.
19
20## OpenSSL Build
21
22To build our ECH-enabled OpenSSL fork:
23
24```bash
25    cd $HOME/code
26    git clone https://github.com/defo-project/openssl
27    cd openssl
28    ./config --libdir=lib --prefix=$HOME/code/openssl-local-inst
29    ...stuff...
30    make -j8
31    ...stuff (maybe go for coffee)...
32    make install_sw
33    ...a little bit of stuff...
34```
35
36To 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 --enable-httpsrr
44    ...lots of output...
45    WARNING: ech ECH HTTPSRR enabled but marked EXPERIMENTAL...
46    make
47    ...lots more output...
48```
49
50If you do not get that WARNING at the end of the ``configure`` command, then ECH
51is not enabled, so go back some steps and re-do whatever needs re-doing:-) If you
52want to debug curl then you should add ``--enable-debug`` to the ``configure``
53command.
54
55In a recent (2024-05-20) build on one machine, configure failed to find the
56ECH-enabled SSL library, apparently due to the existence of
57``$HOME/code/openssl-local-inst/lib/pkgconfig`` as a directory containing
58various settings. Deleting that directory worked around the problem but may not
59be the best solution.
60
61## Using ECH and DoH
62
63Curl supports using DoH for A/AAAA lookups so it was relatively easy to add
64retrieval 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
74The output snippet above is within the HTML for the webpage, when things work.
75
76The 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
86The list above has 4 different server technologies, implemented by 3 different
87parties, and includes a case (the port 8414 server) where HelloRetryRequest
88(HRR) is forced.
89
90We 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>`` over-ride the ``public_name`` from an ECHConfigList
99
100Note that in the above "attempt ECH" means the client emitting a TLS
101ClientHello with a "real" ECH extension, but that does not mean that the
102relevant server can succeed in decrypting, as things can fail for other
103reasons.
104
105## Supplying an ECHConfigList on the command line
106
107To supply the ECHConfigList on the command line, you might need a bit of
108cut-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
115Then 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
124The output snippet above is within the HTML for the webpage.
125
126If you paste in the wrong ECHConfigList (it changes hourly for ``defo.ie``) you
127should 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
136There is a reason to want this command line option - for use before publishing
137an ECHConfigList in the DNS as per the Internet-draft [A well-known URI for
138publishing ECHConfigList values](https://datatracker.ietf.org/doc/draft-ietf-tls-wkech/).
139
140If you do use a wrong ECHConfigList value, then the server might return a
141good value, via the ``retry_configs`` mechanism. You can see that value in
142the 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
152At that point, you could copy the base64 encoded value above and try again.
153For now, this only works for the OpenSSL and boringssl builds.
154
155## Default settings
156
157Curl has various ways to configure default settings, e.g. in ``$HOME/.curlrc``,
158so 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
167Note that when you use the system's curl command (rather than our ECH-enabled
168build), it is liable to warn that ``ech`` is an unknown option. If that is an
169issue (e.g. if some script re-directs stdout and stderr somewhere) then adding
170the ``silent`` line above seems to be a good enough fix. (Though of
171course, yet another script could depend on non-silent behavior, so you may have
172to figure out what you prefer yourself.) That seems to have changed with the
173latest build, previously ``silent=TRUE`` was what I used in ``~/.curlrc`` but
174now that seems to cause a problem, so that the following line(s) are ignored.
175
176If you want to always use our OpenSSL build you can set ``LD_LIBRARY_PATH``
177in the environment:
178
179```bash
180    export LD_LIBRARY_PATH=$HOME/code/openssl
181```
182
183When you do the above, there can be a mismatch between OpenSSL versions
184for applications that check that. A ``git push`` for example fails so you
185should 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
193With 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
202The ``--ech true`` option is opportunistic, so tries to do ECH but does not fail if
203the client for example cannot find any ECHConfig values. The ``--ech hard``
204option hard-fails if there is no ECHConfig found in DNS, so for now, that is not
205a good option to set as a default. Once ECH has really been attempted by
206the client, if decryption on the server side fails, then curl fails.
207
208## Code changes for ECH support when using DoH
209
210Code 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
214or IP address hints.
215
216- ``USE_ECH`` protects ECH specific code.
217
218There are various obvious code blocks for handling the new command line
219arguments which aren't described here, but should be fairly clear.
220
221As shown in the ``configure`` usage above, there are ``configure.ac`` changes
222that allow separately dis/enabling ``USE_HTTPSRR`` and ``USE_ECH``. If ``USE_ECH``
223is enabled, then ``USE_HTTPSRR`` is forced. In both cases ``USE_DOH``
224is required. (There may be some configuration conflicts available for the
225determined:-)
226
227The main functional change, as you would expect, is in ``lib/vtls/openssl.c``
228where an ECHConfig, if available from command line or DNS cache, is fed into
229the OpenSSL library via the new APIs implemented in our OpenSSL fork for that
230purpose. This code also implements the opportunistic (``--ech true``) or hard-fail
231(``--ech hard``) logic.
232
233Other than that, the main additions are in ``lib/doh.c``
234where we re-use ``dohprobe()`` to retrieve an HTTPS RR value for the target
235domain. If such a value is found, that is stored using a new ``store_https()``
236function in a new field in the ``dohentry`` structure.
237
238The qname for the DoH query is modified if the port number is not 443, as
239defined in the SVCB specification.
240
241When the DoH process has worked, ``Curl_doh_is_resolved()`` now also returns
242the relevant HTTPS RR value data in the ``Curl_dns_entry`` structure.
243That is later accessed when the TLS session is being established, if ECH is
244enabled (from ``lib/vtls/openssl.c`` as described above).
245
246## Limitations
247
248Things that need fixing, but that can probably be ignored for the
249moment:
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
253yet to do that.
254
255Current 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
259could be non-trivial if multiple RRs are published - matching IP address hints
260versus A/AAAA values might be a good basis for that. Last I checked though,
261browsers supporting ECH did not handle multiple HTTPS RRs well, though that
262needs 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
266emerge would provide good answers there, but for now, it is not clear how best
267curl 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
271envisage implementing the equivalent of following CNAMEs in such cases, but
272it is not clear if that'd be a good plan. (As of now, chrome browsers do not seem
273to have any support for that "aliasMode" and we've not checked Firefox for that
274recently.)
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
278tool.
279
280- We have not yet implemented tests as part of the usual curl test harness as
281doing so would seem to require re-implementing an ECH-enabled server as part
282of the curl test harness. For now, we have a ``./tests/ech_test.sh`` script
283that attempts ECH with various test servers and with many combinations of the
284allowed command line options. While that is a useful test and has find issues,
285it is not comprehensive and we're not (as yet) sure what would be the right
286level 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
288produce spurious failures.
289
290## Building with cmake
291
292To 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 -DUSE_HTTPSRR=1 ..
301    ...
302    make
303    ...
304    [100%] Built target curl
305```
306
307The binary produced by the cmake build does not need any ECH-specific
308``LD_LIBRARY_PATH`` setting.
309
310## boringssl build
311
312BoringSSL is also supported by curl and also supports ECH, so to build
313with 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
325Then:
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 --enable-httpsrr
333    ...lots of output...
334    WARNING: ech ECH HTTPSRR enabled but marked EXPERIMENTAL. Use with caution!
335    make
336```
337
338The boringssl APIs are fairly similar to those in our ECH-enabled OpenSSL
339fork, so code changes are also in ``lib/vtls/openssl.c``, protected
340via ``#ifdef OPENSSL_IS_BORINGSSL`` and are mostly obvious API variations.
341
342The boringssl APIs however do not support the ``--ech pn:`` command line
343variant as of now.
344
345## WolfSSL build
346
347WolfSSL 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
359The install prefix (``inst``) in the above causes WolfSSL to be installed there
360and 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
362important 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 --enable-httpsrr
370    make
371```
372
373There 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
385There 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'll change
389  to that.
390- There seems to be some difference in CA databases too - the WolfSSL version
391  does not like ``defo.ie``, whereas the system and OpenSSL ones do. We can ignore
392  that for our purposes via ``--insecure``/``-k`` but would need to fix for a
393  real setup. (Browsers do like those certificates though.)
394
395Then there are some functional code changes:
396
397- tweak to ``configure.ac`` to check if WolfSSL has ECH or not
398- added code to ``lib/vtls/wolfssl.c`` mirroring what's done in the
399  OpenSSL equivalent above.
400- WolfSSL does not support ``--ech false`` or the ``--ech pn:`` command line
401  argument.
402
403The lack of support for ``--ech false`` is because wolfSSL has decided to
404always at least GREASE if built to support ECH. In other words, GREASE is
405a compile time choice for wolfSSL, but a runtime choice for OpenSSL or
406boringssl. (Both are reasonable.)
407
408## Additional notes
409
410### Supporting ECH without DoH
411
412All of the above only applies if DoH is being used. There should be a use-case
413for ECH when DoH is not used by curl - if a system stub resolver supports DoT
414or DoH, then, considering only ECH and the network threat model, it would make
415sense for curl to support ECH without curl itself using DoH. The author for
416example uses a combination of stubby+unbound as the system resolver listening
417on localhost:53, so would fit this use-case. That said, it is unclear if
418this is a niche that is worth trying to address. (The author is just as happy to
419let curl use DoH to talk to the same public recursive that stubby might use:-)
420
421Assuming for the moment this is a use-case we'd like to support, then
422if DoH is not being used by curl, it is not clear at this time how to provide
423support for ECH. One option would seem to be to extend the ``c-ares`` library
424to support HTTPS RRs, but in that case it is not now clear whether such changes
425would be attractive to the ``c-ares`` maintainers, nor whether the "tag=value"
426extensibility inherent in the HTTPS/SVCB specification is a good match for the
427``c-ares`` approach of defining structures specific to decoded answers for each
428supported RRtype. We're also not sure how many downstream curl deployments
429actually make use of the ``c-ares`` library, which would affect the utility of
430such changes. Another option might be to consider using some other generic DNS
431library that does support HTTPS RRs, but it is unclear if such a library could
432or would be used by all or almost all curl builds and downstream releases of
433curl.
434
435Our current conclusion is that doing the above is likely best left until we
436have some experience with the "using DoH" approach, so we're going to punt on
437this for now.
438
439### Debugging
440
441Just a note to self as remembering this is a nuisance:
442
443```bash
444LD_LIBRARY_PATH=$HOME/code/openssl:./lib/.libs gdb ./src/.libs/curl
445```
446
447### Localhost testing
448
449It can be useful to be able to run against a localhost OpenSSL ``s_server``
450for testing. We have published instructions for such
451[localhost tests](https://github.com/defo-project/ech-dev-utils/blob/main/howtos/localhost-tests.md)
452in another repository. Once you have that set up, you can start a server
453and then run curl against that:
454
455```bash
456    cd $HOME/code/ech-dev-utils
457    ./scripts/echsvr.sh -d
458    ...
459```
460
461The ``echsvr.sh`` script supports many ECH-related options. Use ``echsvr.sh -h``
462for details.
463
464In another window:
465
466```bash
467    cd $HOME/code/curl/
468    ./src/curl -vvv --insecure  --connect-to foo.example.com:8443:localhost:8443  --ech ecl:AD7+DQA6uwAgACBix2B78sX+EQhEbxMspDOc8Z3xVS5aQpYP0Cxpc2AWPAAEAAEAAQALZXhhbXBsZS5jb20AAA==
469```
470
471### Automated use of ``retry_configs`` not supported so far...
472
473As of now we have not added support for using ``retry_config`` handling in the
474application - for a command line tool, one can just use ``dig`` (or ``kdig``)
475to get the HTTPS RR and pass the ECHConfigList from that on the command line,
476if needed, or one can access the value from command line output in verbose more
477and then re-use that in another invocation.
478
479Both our OpenSSL fork and boringssl have APIs for both controlling GREASE and
480accessing and logging ``retry_configs``, it seems WolfSSL has neither.
481