Encrypted ClientHello (ECH) APIs
================================

OpenSSL 4.0 includes an implementation of Encrypted Client Hello (ECH), as
specified in RFC 9849. These are design notes for the APIs implemented for ECH.

The ECH Protocol
----------------

ECH involves creating an "inner" ClientHello (CH) that contains the potentially
sensitive content of a CH, primarily the SNI and perhaps the ALPN values. That
inner CH is then encrypted and embedded (as a CH extension) in an outer CH that
contains presumably less sensitive values. The spec includes a "compression"
scheme that allows the inner CH to refer to extensions from the outer CH where
the same value would otherwise be present in both.

ECH makes use of [HPKE](https://datatracker.ietf.org/doc/rfc9180/) for the
encryption of the inner CH. HPKE code was merged to the master branch in
November 2022.

The ECH APIs are documented
[here](../../doc/man3/SSL_set1_echstore.pod)
The descriptions here are less formal and provide some justification for the
API design.

Unless otherwise stated all APIs return 1 in the case of success and 0 for
error. All APIs call `SSLfatal` or `ERR_raise` macros as appropriate before
returning an error.

Prototypes are mostly in
[`include/openssl/ech.h`](../../include/openssl/ech.h).

General Approach
----------------

This ECH implementation was prototyped via integrations with curl, apache2,
lighttpd, nginx, freenginx and HAProxy. The implementation interoperates with all other
known ECH implementations, including browsers, the libraries they use
(NSS/BoringSSL), a closed-source server implementation (Cloudflare's test
server) and with wolfSSL and Rustls.

The approach taken has been to minimise the application layer code
changes required to ECH-enable those applications. There is of course a tension
between that minimisation goal and providing generic and future-proof
interfaces.

ECH Specification
-----------------

RFC 9849 is an IETF TLS WG specification. It has been stable since
[draft-13](https://datatracker.ietf.org/doc/draft-ietf-tls-esni/13/), published
in August 2021.

The only current ECHConfig version defined is 0xfe0d which is the value used in
RFC 9849.

```c
/* version from RFC 9849 */
#  define OSSL_ECH_RFC9849_VERSION 0xfe0d
/* latest version from an RFC */
#  define OSSL_ECH_CURRENT_VERSION OSSL_ECH_RFC9849_VERSION
```

Note that 0xfe0d is also the value of the ECH extension codepoint:

```c
#  define TLSEXT_TYPE_ech                       0xfe0d
```

The uses of those should be correctly differentiated in the implementation, to
more easily avoid problems if/when new versions are defined.

ECH PEM file format
-------------------

Servers supporting ECH need to read a set of ECH private keys and
ECHConfigLists from storage. There is a specification for a
[PEM file format for ECH](https://datatracker.ietf.org/doc/rfc9934)
that is supported by the library.

This PEM file format is supported by code for a number of TLS servers,
including (at the time of writing) lighttpd, freenginx, nginx, apache2 and HAProxy.
ECH support in those servers is currently an experimental feature or similar.

Minimal Sample Code
-------------------

OpenSSL includes code for an [`sslecho`](../../demos/sslecho) demo.  We've
added a minimal [`echecho`](../../demos/sslecho/echecho.c) that shows how to
ECH-enable this demo.

Handling Custom Extensions
--------------------------

OpenSSL supports custom extensions (via `SSL_CTX_add_custom_ext()`) so that
extension values are supplied and parsed by client and server applications via
a callback.  The ECH specification of course doesn't deal with such
implementation matters, but comprehensive ECH support for such custom
extensions could quickly become complex. At present, in the absence of evidence
of sensitive custom extension values, we handle all such extensions by using
the ECH compression mechanism.  That means we require no API changes, only make
one call to the application callbacks and get interoperability, but that such
extension values remain visible to network observers. That could change if some
custom value turns out to be sensitive such that we'd prefer to not include it
in the outer CH.

Padding
-------

The privacy protection provided by ECH benefits from an observer not being able
to differentiate access to different web origins based on TLS handshake
packets. Some TLS handshake messages can however reduce the size of the
anonymity-set due to message-sizes. In particular the Certificate message size
will depend on the name of the SNI from the inner ClientHello. TLS however does
allow for record layer padding which can reduce the impact of underlying
message sizes on the size of the anonymity set. The
`SSL_CTX_record_padding_ex()` and `SSL_record_padding_ex()` APIs allow for
setting separate padding sizes for the handshake messages, (that most affect
ECH), and application data messages (where padding may affect efficiency more).

ECHConfig Extensions
--------------------

The ECH protocol supports extensibility within the ECHConfig structure
via a typical TLS type, length, value scheme.  However, to date, there are no
extensions defined, nor do other implementations provide APIs for adding or
manipulating ECHConfig extensions. We therefore take the same approach here.

When running the ECH protocol, implementations are required to skip over
unknown ECHConfig extensions, or to fail for so-called "mandatory" unsupported
ECHConfig extensions. Our library code is compliant in that respect - it will
skip over extensions that are not "mandatory" (extension type high bit clear)
and fail if any "mandatory" ECHConfig extension (extension type high bit set)
is seen.

For testing purposes, ECHConfigList values that contain ECHConfig extensions
can be produced using external scripts, and used with the library, but there is
no API support for generating such, and the library has no support for any
specific ECHConfig extension type.  (Other than skipping over or failing as
described above.)

In general, the ECHConfig extensibility mechanism seems to have little proven
utility. (If new fields for an ECHConfig are required, a new ECHConfig version
with the proposed changes could just as easily be developed/deployed.)

The theory for ECHConfig extensions is that such values might be used to
control the outer ClientHello - controls to affect the inner ClientHello, when
ECH is used, are envisaged to be published as SvcParamKey values in SVCB/HTTP
resource records in the DNS.

Should some useful ECHConfig extensions be defined in future, then the
`OSSL_ECHSTORE` APIs could be extended to enable management of such, or, new
opaque types could be developed enabling further manipulation of ECHConfig and
ECHConfigList values.

ECH keys versus TLS server keys
-------------------------------

ECH private keys are similar to, but different from, TLS server private keys
used to authenticate servers. Notably:

- ECH private keys are expected to be rotated roughly hourly, rather than every
  month or two for TLS server private keys. Hourly ECH key rotation is an
  attempt to provide better forward secrecy, given ECH implements an
  ephemeral-static ECDH scheme.

- ECH private keys stand alone - there are no hierarchies and there is no
  chaining, and no certificates and no defined relationships between current
  and older ECH private keys. The expectation is that a "current" ECH public key
  will be published in the DNS and that plus approx. 2 "older" ECH private keys
  will remain usable for decryption at any given time. This is a way to balance
  DNS TTLs versus forward secrecy and robustness.

- In particular, the above means that we do not see any need to repeatedly
  parse or process related ECHConfigList structures - each can be processed
  independently for all practical purposes, and there is no equivalent to
  X.509 path processing.

- There are all the usual algorithm variations, and those will likely result in
  the same x25519 versus p256 combinatorics. How that plays out has yet to be
  seen as FIPS compliance for ECH is not (yet) a thing. For OpenSSL, it seems
  wise to be agnostic and support all relevant combinations. (And doing so is not
  that hard.)

- At the time of writing there is work ongoing to specify use of post-quantum
  KEMs with HPKE. Once that work matures, and the relevant (hybrid) KEMs are
  supported by OpenSSL, then they should be usable with ECH. It is quite likely
  at least some test code will need changes due to the increase in the size
  of the ECH extension. For now, there is no support for e.g. use of an
  equivalent to X25519MLKEM768 for ECH encryption. ECH does work fine if
  X25519MLKEM768 is used for the TLS key exchange.

ECH Store APIs
--------------

We introduce an externally opaque type `OSSL_ECHSTORE` to allow applications
to create and manage ECHConfigList values and associated meta-data. The
external APIs using `OSSL_ECHSTORE` are:

```c
typedef struct ossl_echstore_st OSSL_ECHSTORE;

/* if a caller wants to index the last entry in the store */
# define OSSL_ECHSTORE_LAST -1
/* if a caller wants all entries in the store, e.g. to print public values */
#  define OSSL_ECHSTORE_ALL -2

OSSL_ECHSTORE *OSSL_ECHSTORE_new(OSSL_LIB_CTX *libctx, const char *propq);
void OSSL_ECHSTORE_free(OSSL_ECHSTORE *es);
int OSSL_ECHSTORE_new_config(OSSL_ECHSTORE *es,
                             uint16_t echversion, uint8_t max_name_length,
                             const char *public_name, OSSL_HPKE_SUITE suite);
int OSSL_ECHSTORE_write_pem(OSSL_ECHSTORE *es, int index, BIO *out);

int OSSL_ECHSTORE_read_echconfiglist(OSSL_ECHSTORE *es, BIO *in);

int OSSL_ECHSTORE_get1_info(OSSL_ECHSTORE *es, int index, time_t *loaded_secs,
                            char **public_name, char **echconfig,
                            int *has_private, int *for_retry);
int OSSL_ECHSTORE_downselect(OSSL_ECHSTORE *es, int index);

int OSSL_ECHSTORE_set1_key_and_read_pem(OSSL_ECHSTORE *es, EVP_PKEY *priv,
                                        BIO *in, int for_retry);
int OSSL_ECHSTORE_read_pem(OSSL_ECHSTORE *es, BIO *in, int for_retry);
int OSSL_ECHSTORE_num_entries(OSSL_ECHSTORE *es, int *numentries);
int OSSL_ECHSTORE_num_keys(OSSL_ECHSTORE *es, int *numkeys);
int OSSL_ECHSTORE_flush_keys(OSSL_ECHSTORE *es, time_t age);
```

`OSSL_ECHSTORE_new()` and `OSSL_ECHSTORE_free()` are relatively obvious.

`OSSL_ECHSTORE_new_config()` allows the caller to create a new private key
value and the related "singleton" ECHConfigList structure.
`OSSL_ECHSTORE_write_pem()` allows the caller to produce a "PEM" data
structure (conforming to the ECH PEM file format)
from the `OSSL_ECHSTORE` entry identified by the `index`. (An `index` of
`OSSL_ECHSTORE_LAST` will select the last entry. An `index` of
`OSSL_ECHSTORE_ALL` will output all public values, and no private values.)
These two APIs will typically be used via the `openssl ech` command line tool.

`OSSL_ECHSTORE_read_echconfiglist()` will typically be used by a client to
ingest the "ech=" SvcParamKey value found in an SVCB or HTTPS RR retrieved from
the DNS. The resulting set of ECHConfig values can then be associated with an
`SSL_CTX` or `SSL` structure for TLS connections.

`OSSL_ECHSTORE_get1_info()` presents the caller with information about the
content of the store for logging or for display, e.g. in a command line tool.
`OSSL_ECHSTORE_downselect()` API gives the client a way to select one
particular ECHConfig value from the set stored (discarding the rest).

`OSSL_ECHSTORE_set1_key_and_read_pem()` and `OSSL_ECHSTORE_read_pem()` can be
used to load a private key value and associated "singleton" ECHConfigList.
Those can be used (by servers) to enable ECH for an `SSL_CTX` or `SSL`
connection. In addition to loading those values, the application can also
indicate via `for_retry` which ECHConfig value(s) are to be included in the
`retry_configs` fallback scheme defined by the ECH protocol.

`OSSL_ECHSTORE_num_entries()` and `OSSL_ECHSTORE_num_keys()` allow an
application  to see how many usable ECH configs and private keys are currently
in the store, and `OSSL_ECHSTORE_flush_keys()` allows a server to flush keys
that are older than `age` seconds.  The general model is that a server can
maintain an `OSSL_ECHSTORE` into which it periodically loads the "latest" set
of keys, e.g.  hourly, and also discards the keys that are too old, e.g. more
than 3 hours old. This allows for more robust private key management even if
public key distribution suffers temporary failures.

The APIs the clients and servers can use to associate an `OSSL_ECHSTORE`
with an `SSL_CTX` or `SSL` structure:

```c
int SSL_CTX_set1_echstore(SSL_CTX *ctx, OSSL_ECHSTORE *es);
int SSL_set1_echstore(SSL *s, OSSL_ECHSTORE *es);
```

ECH will be enabled for the relevant `SSL_CTX` or `SSL` connection
when these functions succeed. Any previously associated `OSSL_ECHSTORE`
will be `OSSL_ECHSTORE_free()`ed.

There is also an API that allows setting an ECHConfigList for an SSL
connection, that is compatible with BoringSSL, leading to smaller code changes
for clients that support OpenSSL or BoringSSL. Note that the input `ecl` here
for OpenSSL can be either base64 or binary encoded, but for BoringSSL it must
be binary encoded.

```c
int SSL_set1_ech_config_list(SSL *ssl, const uint8_t *ecl, size_t ecl_len);
```

To access the `OSSL_ECHSTORE` associated with an `SSL_CTX` or
`SSL` connection:

```c
OSSL_ECHSTORE *SSL_CTX_get1_echstore(const SSL_CTX *ctx);
OSSL_ECHSTORE *SSL_get1_echstore(const SSL *s);
```

The resulting `OSSL_ECHSTORE` can be modified and then re-associated
with an `SSL_CTX` or `SSL` connection.

ECH Store Internals
-------------------

The internal structure of an ECH Store is as described below:

```c
typedef struct ossl_echext_st {
    uint16_t type;
    uint16_t len;
    unsigned char *val;
} OSSL_ECHEXT;

DEFINE_STACK_OF(OSSL_ECHEXT)

typedef struct ossl_echstore_entry_st {
    uint16_t version; /* 0xfe0d for RFC 9849 */
    char *public_name;
    size_t pub_len;
    unsigned char *pub;
    unsigned int nsuites;
    OSSL_HPKE_SUITE *suites;
    uint8_t max_name_length;
    uint8_t config_id;
    STACK_OF(OSSL_ECHEXT) *exts;
    time_t loadtime; /* time public and private key were loaded from file */
    EVP_PKEY *keyshare; /* long(ish) term ECH private keyshare on a server */
    int for_retry; /* whether to use this ECHConfigList in a retry */
    size_t encoded_len; /* length of overall encoded content */
    unsigned char *encoded; /* overall encoded content */
} OSSL_ECHSTORE_ENTRY;

DEFINE_STACK_OF(OSSL_ECHSTORE_ENTRY)

struct ossl_echstore_st {
    STACK_OF(OSSL_ECHSTORE_ENTRY) *entries;
    OSSL_LIB_CTX *libctx;
    const char *propq;
};
```

Some notes on the above ECHConfig fields:

- `version` should be `OSSL_ECH_CURRENT_VERSION` for the current version.

- `public_name` field is the name used in the SNI of the outer ClientHello, and
  that a server ought be able to authenticate if using the `retry_configs`
  fallback mechanism.

- `config_id` is a one-octet value used by servers to select which private
  value to use to attempt ECH decryption. Servers can also do trial decryption
  if desired, as clients might use a random value for the `confid_id` as an
  anti-fingerprinting mechanism. (The use of one octet for this value was the
  result of an extended debate about efficiency versus fingerprinting.)

- The `max_name_length` is an element of the ECHConfigList that is used by
  clients as part of a padding algorithm. (That design is part of the spec, but
  isn't necessarily great - the idea is to include the longest value that might
  be the length of a DNS name included as an inner CH SNI.) A value of 0 is
  perhaps most likely to be used, indicating that the maximum isn't known.

Essentially, an ECH store is a set of ECHConfig values, plus optionally
(for servers), relevant private key value information.

When a non-singleton ECHConfigList is ingested, that is expanded into
a store that is the same as if a set of singleton ECHConfigList values
had been ingested sequentially.

In addition to the obvious fields from each ECHConfig, we also store:

- The `encoded` value (and length) of the ECHConfig, as that is used
  as an input for the HPKE encapsulation of the inner ClientHello. (Used
  by both clients and servers.)

- The `EVP_PKEY` pointer to the private key value associated with the
  relevant ECHConfig, for use by servers.

- The time at which a private key value and/or ECHConfigList were loaded.
  This value is useful when servers periodically re-load sets of files
  or PEM structures from memory, e.g. for the HAProxy server.

ECH Split-mode
--------------

RFC 9849 defines two modes for ECH - 'shared-mode' which is supported in
this release, and a 'split-mode' which is not yet supported.

ECH split-mode involves a front-end server that only does ECH decryption and
then passes on the decrypted inner CH to a back-end TLS server that negotiates
the actual TLS session with the client, based on the inner CH content.

This release does however support servers that act as the back-end TLS server
in an ECH split-mode scenario, as the functionality required is internal and
also required when using shared-mode.

Different encodings
-------------------

ECHConfigList values may be provided via a command line argument to the calling
application or (more likely) have been retrieved from DNS resource records by
the application. ECHConfigList values may be provided in various encodings
(base64 or binary) each of which may suit different applications.

If the input contains more than one (syntactically correct) ECHConfigList, then only
those that contain locally supported options (e.g. AEAD ciphers) will be
returned. If no ECHConfigList found has supported options then none will be
returned and the function will return NULL.

Additional Client Controls
--------------------------

Clients can additionally more directly control the values to be used for inner
and outer SNI and ALPN values via specific APIs. This allows a client to
override the `public_name` present in an ECHConfigList that will otherwise
be used for the outer SNI. The `no_outer` input allows a client to emit an
outer CH with no SNI at all. Providing a `NULL` for the `outer_name` means
to send the `public_name` provided from the ECHConfigList.

```c
int SSL_ech_set1_server_names(SSL *s, const char *inner_name,
                              const char *outer_name, int no_outer);
int SSL_ech_set1_outer_server_name(SSL *s, const char *outer_name, int no_outer);
int SSL_ech_set1_outer_alpn_protos(SSL *s, const unsigned char *protos,
                                   size_t protos_len);
int SSL_CTX_ech_set1_outer_alpn_protos(SSL_CTX *s, const unsigned char *protos,
                                       size_t protos_len);
```

If a client attempts ECH but that fails, or sends an ECH-GREASEd CH, to
an ECH-supporting server, then that server may return an ECH "retry-config"
value that the client could choose to use in a subsequent connection. The
client can detect this situation via the `SSL_ech_get1_status()` API and
can access the retry config value via:

```c
OSSL_ECHSTORE *SSL_ech_get1_retry_config(SSL *s);
```

GREASEing
---------

"GREASEing" is defined in
[RFC8701](https://datatracker.ietf.org/doc/html/rfc8701) and is a mechanism
intended to discourage protocol ossification that can be used for ECH.  GREASEd
ECH may turn out to be important as an initial step towards widespread
deployment of ECH.

If a client wishes to GREASE ECH using a specific HPKE suite or ECH version
(represented by the TLS extension type code-point) then it can set those values
via:

```c
int SSL_ech_set1_grease_suite(SSL *s, const char *suite);
int SSL_ech_set_grease_type(SSL *s, uint16_t type);
```

ECH Status API
--------------

Clients and servers can check the status of ECH processing
on an SSL connection using this API:

```c
int SSL_ech_get1_status(SSL *s, char **inner_sni, char **outer_sni);

/* Return codes from SSL_ech_get1_status */
#  define SSL_ECH_STATUS_BACKEND    4 /* ECH back-end: saw an ech_is_inner */
#  define SSL_ECH_STATUS_GREASE_ECH 3 /* GREASEd and got an ECH in return */
#  define SSL_ECH_STATUS_GREASE     2 /* ECH GREASE happened  */
#  define SSL_ECH_STATUS_SUCCESS    1 /* Success */
#  define SSL_ECH_STATUS_FAILED     0 /* Some internal or protocol error */
#  define SSL_ECH_STATUS_BAD_CALL   -100 /* Some in/out arguments were NULL */
#  define SSL_ECH_STATUS_NOT_TRIED  -101 /* ECH wasn't attempted  */
#  define SSL_ECH_STATUS_BAD_NAME   -102 /* ECH ok but server cert bad */
#  define SSL_ECH_STATUS_NOT_CONFIGURED -103 /* ECH wasn't configured */
#  define SSL_ECH_STATUS_FAILED_ECH -105 /* We tried, failed and got an ECH, from a good name */
#  define SSL_ECH_STATUS_FAILED_ECH_BAD_NAME -106 /* We tried, failed and got an ECH, from a bad name */
```

The `inner_sni` and `outer_sni` values should be freed by callers
via `OPENSSL_free()`.

The function returns one of the status values above.

Call-backs and options
----------------------

Clients and servers can set a callback that will be triggered when ECH is
attempted and the result of ECH processing is known. The callback function can
access a string (`str`) that can be used for logging (but not for branching).
Callback functions might typically call `SSL_ech_get1_status()` if branching
is required.

```c
typedef unsigned int (*SSL_ech_cb_func)(SSL *s, const char *str);

void SSL_ech_set_callback(SSL *s, SSL_ech_cb_func f);
void SSL_CTX_ech_set_callback(SSL_CTX *ctx, SSL_ech_cb_func f);
```

The following options are defined for ECH and may be set via
`SSL_set_options()`:

```c
/* Set this to tell client to emit greased ECH values */
# define SSL_OP_ECH_GREASE                               SSL_OP_BIT(37)
/*
 * If this is set then the server side will attempt trial decryption
 * of ECHs even if there is no matching ECH config_id. That's a bit
 * inefficient, but more privacy friendly.
 */
# define SSL_OP_ECH_TRIALDECRYPT                         SSL_OP_BIT(38)
/*
 * If set, clients will ignore the supplied ECH config_id and replace
 * that with a random value.
 */
# define SSL_OP_ECH_IGNORE_CID                           SSL_OP_BIT(39)
/*
 * If set, servers will add GREASEy ECHConfig values to those sent
 * in retry_configs.
 */
# define SSL_OP_ECH_GREASE_RETRY_CONFIG                  SSL_OP_BIT(40)
```

Build Options
-------------

Almost all ECH code is protected via `#ifndef OPENSSL_NO_ECH` and there is a
`no-ech` option to build without this code.

Applications using ECH may choose to detect the availability of ECH in
the library by checking that `SSL_OP_ECH_GREASE` is defined. This is
used by some server applications today.

ECH Tests
---------

The following tests are included in the `make test` target:

- [`test_app_ech`](../../test/recipes/20-test_app_ech.t)
- [`test_ech`](../../test/ech_test.c)
- [`test_ech_corrupt`](../../test/ech_corrupt_test.c)
- [`test_ech_client_server`](../../test/recipes/82-test_ech_client_server.t)

There are also two external tests to check interoperability
with the NSS and BoringSSL libraries:

- [`test_external_ech_nss`](../../test/recipes/95-test_external_ech_nss.t)
- [`test_external_ech_bssl`](../../test/recipes/95-test_external_ech_bssl.t)

The `test_app_ech` test exercises the `openssl ech` command line utility that
can be used to generate and manipulate ECH keys and configurations.

The `test_ech` test exercises ECH APIs, including round-trip tests that use ECH
in TLS sessions. The code for this includes many valid and invalid test vectors
and is designed to be relatively easily extended with additional tests.

`test_ech_corrupt` is modelled on [sslcorruptetst.c](../../test/sslcorrupttest.c)
and mainly includes tests where variously incorrectly encoded inner ClientHello
test vectors are encrypted using HPKE and then successfully decrypted by a
server that then rejects the connection returning the expected error code.

`test_ech_client_server` exercises the various ECH command line
options for the OpenSSL `s_client` and `s_server` commands. Changes to
the output from those command may require changes to these tests as
they use pattern matching on the outputs to detect expected successes
or failures.

The external tests check that the library correctly interoperates, as a client
or server, with NSS or BoringSSL. These require a build configured with
`enable-external-tests`.  In order to avoid potential supply-chain issues, you
need to download and build the relevant NSS or BoringSSL library manually in
the correct location. When you first attempt to run these external tests, they
will provide instructions for how to download and build NSS or BoringSSL in the
correct manner. The client/server tests here are not very extensive and just
check that a basic configuration interoperates.

How to measure coverage of ECH tests
------------------------------------

There are likely many ways to do this, but the following is the
recipe used during ECH development:

```bash
./config --debug enable-external-tests --coverage no-asm no-afalgeng no-shared -DPEDANTIC -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
make -s -j12
make test TESTS='test_ech test_ech_corrupt test_app_ech test_ech_client_server test_external_ech_bssl test_external_ech_nss'
# next line failed, was replaced the the one following
# lcov -d . -c -o ./lcov.info
/usr/bin/geninfo . --output-filename ./lcov.info --memory 0 --ignore-errors mismatch
genhtml ./lcov.info --output-directory $HOME/tmp/myco
```

To clean away the coverage files:

```bash
find . -name '*.gcda'  -exec rm {} \;
find . -name '*.gcno'  -exec rm {} \;
rm lcov.info
make clean
make -j12
```

Checking memory errors
----------------------

As is typical with the library there is a good bit of code that handles error
cases that are hard to test. Causing memory allocation failures though allows
us to get at most of those code fragments.

To get to those error handling lines of code, one can use an exhaustive script
that incrementally allows more and more memory allocations to work before
triggering failures such as:

```bash
#!/bin/bash
#
# run 16k tests

# if you want tracing
# export OPENSSL_TRACE="TLS"
logfile=loads.log
iter=0

function whenisitagain()
{
    /bin/date -u +%Y%m%d-%H%M%S
}
NOW=$(whenisitagain)
echo "==================================" >>$logfile
echo "Started at $NOW" >>$logfile

# 16,000 calls without failures should be enough for the
# test to pass - gprof says we have 15,558 calls to
# CRYPTO_malloc for the test below
while ((iter < 16000))
do
    echo "Doing $iter" >>$logfile
    iter=$((iter+1))
    export OPENSSL_MALLOC_FAILURES="$iter@0;0@99;"
    ./test/ech_test -test 6 -iter 1 >>$logfile 2>&1
    echo "Done $iter"
    echo "Done $iter" >>$logfile
    echo "" >>$logfile
    echo "" >>$logfile
done

NOW=$(whenisitagain)
echo "Ended at $NOW" >>$logfile
echo "==================================" >>$logfile
```

An `OPENSSL_MALLOC_FAILURES` value of `100@0;0@99` means to allow the first 100
memory allocations to work, and to then switch to a mode where there's a 99%
chance of memory allocation failing. By using `$iter` we just keep incrementing
how far into the run we allow nominal memory allocation before we break things,
which should result in (eventually:-) hitting every possible memory allocation
failure handling line of code.

You need to build with the `crypto-mdebug` option to get the memory allocation
failure, so that'd be something like:

```bash
./config --debug enable-external-tests enable-crypto-mdebug --coverage no-asm no-afalgeng no-shared -DPEDANTIC -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
```

The script above gets us from 75% lines of code covered based on normal tests
to 85.6% for the `ech_internal.c` file, which is our least well covered by
normal tests. The script takes about 20 minutes to run on a developer laptop.
