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)