Add tests and documentation and fix a couple of issues identified by added tests
Reviewed-by: Tomas Mraz <tomas@openssl.org> Reviewed-by: Matt Caswell <matt@openssl.org> MergeDate: Mon Feb 16 15:41:15 2026 (Merged from https://github.com/openssl/openssl/pull/29200)
This commit is contained in:
parent
0bdae603e8
commit
5e5a76fc2c
44 changed files with 3505 additions and 313 deletions
8
.gitignore
vendored
8
.gitignore
vendored
|
|
@ -425,3 +425,11 @@ doc-nits
|
|||
# LSP (Language Server Protocol) support
|
||||
.cache/
|
||||
compile_commands.json
|
||||
|
||||
# coverage files
|
||||
*.gcda
|
||||
*.gcno
|
||||
lcov.info
|
||||
run_tests
|
||||
depend
|
||||
|
||||
|
|
|
|||
|
|
@ -771,8 +771,7 @@ from ASN.1 formats.
|
|||
|
||||
### no-ech
|
||||
|
||||
Don't build support for Encrypted Client Hello (ECH) extension (draft-ietf-tls-esni)
|
||||
TODO(ECH) update link to RFC.
|
||||
Don't build support for Encrypted Client Hello (ECH) extension.
|
||||
|
||||
### enable-ec_nistp_64_gcc_128
|
||||
|
||||
|
|
|
|||
22
apps/ech.c
22
apps/ech.c
|
|
@ -187,9 +187,13 @@ int ech_main(int argc, char **argv)
|
|||
if (es == NULL)
|
||||
goto end;
|
||||
if (mode == OSSL_ECH_KEYGEN_MODE) {
|
||||
if (public_name == NULL) {
|
||||
BIO_printf(bio_err, "public_name required\n");
|
||||
goto end;
|
||||
}
|
||||
if (verbose)
|
||||
BIO_printf(bio_err, "Calling OSSL_ECHSTORE_new_config\n");
|
||||
if ((ecf = BIO_new_file(outfile, "w")) == NULL
|
||||
if ((ecf = bio_open_owner(outfile, FORMAT_PEM, 1)) == NULL
|
||||
|| OSSL_ECHSTORE_new_config(es, ech_version, max_name_length,
|
||||
public_name, hpke_suite)
|
||||
!= 1
|
||||
|
|
@ -207,10 +211,8 @@ int ech_main(int argc, char **argv)
|
|||
for (i = 0; i != numinfiles; i++) {
|
||||
if ((ecf = BIO_new_file(infiles[i], "r")) == NULL
|
||||
|| OSSL_ECHSTORE_read_pem(es, ecf, OSSL_ECH_FOR_RETRY) != 1) {
|
||||
if (verbose)
|
||||
BIO_printf(bio_err, "OSSL_ECHSTORE_read_pem error for %s\n",
|
||||
infiles[i]);
|
||||
/* try read it as an ECHConfigList */
|
||||
BIO_printf(bio_err, "OSSL_ECHSTORE_read_pem error: %s\n",
|
||||
infiles[i]);
|
||||
goto end;
|
||||
}
|
||||
BIO_free(ecf);
|
||||
|
|
@ -226,7 +228,8 @@ int ech_main(int argc, char **argv)
|
|||
BIO_printf(bio_err, "Selected entry: %d\n", select);
|
||||
if ((ecf = BIO_new_file(outfile, "w")) == NULL
|
||||
|| OSSL_ECHSTORE_write_pem(es, select, ecf) != 1) {
|
||||
BIO_printf(bio_err, "OSSL_ECHSTORE_write_pem error\n");
|
||||
BIO_printf(bio_err, "OSSL_ECHSTORE_write_pem error: %s\n",
|
||||
outfile);
|
||||
goto end;
|
||||
}
|
||||
if (verbose)
|
||||
|
|
@ -240,7 +243,7 @@ int ech_main(int argc, char **argv)
|
|||
if (OSSL_ECHSTORE_num_entries(es, &oi_cnt) != 1)
|
||||
goto end;
|
||||
if (verbose)
|
||||
BIO_printf(bio_err, "Printing %d ECHConfigList\n", oi_cnt);
|
||||
BIO_printf(bio_err, "Printing %d ECHConfig values\n", oi_cnt);
|
||||
for (oi_ind = 0; oi_ind != oi_cnt; oi_ind++) {
|
||||
time_t secs = 0;
|
||||
char *pn = NULL, *ec = NULL;
|
||||
|
|
@ -253,9 +256,10 @@ int ech_main(int argc, char **argv)
|
|||
OPENSSL_free(ec);
|
||||
goto end;
|
||||
}
|
||||
BIO_printf(bio_err, "ECH entry: %d public_name: %s age: %lld%s\n",
|
||||
BIO_printf(bio_err, "ECH entry: %d public_name: %s age: %lld%s%s\n",
|
||||
oi_ind, pn, (long long)secs,
|
||||
has_priv ? " (has private key)" : "");
|
||||
has_priv ? " (has private key)" : "",
|
||||
for_retry ? " (will be sent in retry-configs)" : "");
|
||||
BIO_printf(bio_err, "\t%s\n", ec);
|
||||
OPENSSL_free(pn);
|
||||
OPENSSL_free(ec);
|
||||
|
|
|
|||
|
|
@ -1575,6 +1575,13 @@ static int ech_load_dir(SSL_CTX *lctx, const char *thedir,
|
|||
BIO *in = NULL;
|
||||
int loaded = 0;
|
||||
|
||||
/*
|
||||
* If you change the output to bio_s_out here you may
|
||||
* also need to change test/recipes/82-test_ech_client_server.t
|
||||
* as that test checks the server's stdout to decide if the
|
||||
* server started ok or not. Text sent to stderr won't affect
|
||||
* that test.
|
||||
*/
|
||||
if ((elen + 7) >= PATH_MAX) { /* too long, go away */
|
||||
BIO_printf(bio_err, "'%s' too long - exiting\n", thedir);
|
||||
return 0;
|
||||
|
|
@ -1599,11 +1606,15 @@ static int ech_load_dir(SSL_CTX *lctx, const char *thedir,
|
|||
#else
|
||||
r = BIO_snprintf(filepath, sizeof(filepath), "%s/%s", thedir, thisfile);
|
||||
#endif
|
||||
if (app_isdir(filepath) > 0) {
|
||||
if (s_debug)
|
||||
BIO_printf(bio_err, "Skipping directory: %s\n", filepath);
|
||||
continue;
|
||||
}
|
||||
if (r < 0
|
||||
|| app_isdir(filepath) > 0
|
||||
|| (in = BIO_new_file(filepath, "r")) == NULL
|
||||
|| OSSL_ECHSTORE_read_pem(es, in, for_retry) != 1) {
|
||||
BIO_printf(bio_err, "Failed reading from: %s\n", thisfile);
|
||||
BIO_printf(bio_err, "Failed reading from: %s\n", filepath);
|
||||
continue;
|
||||
}
|
||||
BIO_free_all(in);
|
||||
|
|
|
|||
|
|
@ -1,17 +1,11 @@
|
|||
Encrypted ClientHello (ECH) APIs
|
||||
================================
|
||||
|
||||
TODO(ECH): replace references/links to the [sftcd
|
||||
ECH-draft-13c](https://github.com/sftcd/openssl/tree/ECH-draft-13c) (the branch
|
||||
that has good integration and interop) with relative links as files are
|
||||
migrated into (PRs for) the feature branch.
|
||||
The ECH [feature branch](https://github.com/openssl/openssl/tree/feature/ech).
|
||||
has an implementation of Encrypted Client Hello (ECH) and these are design
|
||||
notes for the APIs implemented there.
|
||||
|
||||
The `OSSL_ECHSTORE` related text here matches the ECH
|
||||
[feature branch](https://github.com/openssl/openssl/tree/feature/ech).
|
||||
|
||||
There is an [OpenSSL fork](https://github.com/sftcd/openssl/tree/ECH-draft-13c)
|
||||
that has an implementation of Encrypted Client Hello (ECH) and these are design
|
||||
notes taking the APIs implemented there as a starting point.
|
||||
This text was last updated on 2025-11-20.
|
||||
|
||||
The ECH Protocol
|
||||
----------------
|
||||
|
|
@ -27,8 +21,8 @@ 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 also documented
|
||||
[here](https://github.com/sftcd/openssl/blob/ECH-draft-13c/doc/man3/SSL_ech_set1_echconfig.pod).
|
||||
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.
|
||||
|
||||
|
|
@ -37,40 +31,38 @@ error. All APIs call `SSLfatal` or `ERR_raise` macros as appropriate before
|
|||
returning an error.
|
||||
|
||||
Prototypes are mostly in
|
||||
[`include/openssl/ech.h`](https://github.com/sftcd/openssl/blob/ECH-draft-13c/include/openssl/ech.h)
|
||||
for now.
|
||||
[`include/openssl/ech.h`](../../include/openssl/ech.h).
|
||||
|
||||
General Approach
|
||||
----------------
|
||||
|
||||
This ECH implementation has been prototyped via integrations with curl, apache2,
|
||||
lighttpd, nginx and haproxy. The implementation interoperates with all other
|
||||
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 (reportedly) a rusttls client.
|
||||
server) and with wolfssl and rusttls.
|
||||
|
||||
To date, the approach taken has been to minimise the application layer code
|
||||
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.
|
||||
|
||||
In terms of implementation, it is expected (and welcome) that many details of
|
||||
the current ECH implementation will change during review.
|
||||
|
||||
Specification
|
||||
-------------
|
||||
ECH Specification
|
||||
-----------------
|
||||
|
||||
ECH 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 latest draft can be found
|
||||
[here](https://datatracker.ietf.org/doc/draft-ietf-tls-esni/).
|
||||
The specification is currently in the RFC editor's queue and is
|
||||
part of a [cluster](https://www.rfc-editor.org/cluster_info.php?cid=C430)
|
||||
of related drafts that will be published together.
|
||||
|
||||
Once browsers and others have done sufficient testing the plan is to
|
||||
proceed to publishing ECH as an RFC.
|
||||
|
||||
The only current ECHConfig version supported is 0xfe0d which will be the
|
||||
The only current ECHConfig version defined is 0xfe0d which will be the
|
||||
value to be used in the eventual RFC when that issues. (We'll replace the
|
||||
XXXX with the relevant RFC number once that's known.)
|
||||
TODO(ECH): Update XXXX when RFC published, and check other occurrences
|
||||
of XXXX throughout the source tree.
|
||||
|
||||
```c
|
||||
/* version from RFC XXXX */
|
||||
|
|
@ -88,11 +80,23 @@ Note that 0xfe0d is also the value of the ECH extension codepoint:
|
|||
The uses of those should be correctly differentiated in the implementation, to
|
||||
more easily avoid problems if/when new versions are defined.
|
||||
|
||||
Minimal Sample Code
|
||||
ECH PEM file format
|
||||
-------------------
|
||||
|
||||
TODO(ECH): This sample code has only been compiled. The `OSSL_ECHSTORE` stuff
|
||||
doesn't work yet.
|
||||
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/draft-farrell-tls-pemesni/)
|
||||
that is supported by the library. That specification is being
|
||||
processed within the IETF as an area-director sponsored draft,
|
||||
so is not a TLS WG work item, but will be an IETF stream
|
||||
RFC when completed.
|
||||
|
||||
This PEM file format is supported by code for a number of TLS servers,
|
||||
including (at the time of writing) lighttpd, freenginx, 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
|
||||
|
|
@ -122,7 +126,7 @@ 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 recently added
|
||||
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).
|
||||
|
|
@ -131,7 +135,7 @@ ECHConfig Extensions
|
|||
--------------------
|
||||
|
||||
The ECH protocol supports extensibility [within the ECHConfig
|
||||
structure](https://www.ietf.org/archive/id/draft-ietf-tls-esni-18.html#section-4.2)
|
||||
structure](https://www.ietf.org/archive/id/draft-ietf-tls-esni-25.html#name-configuration-extensions)
|
||||
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.
|
||||
|
|
@ -149,18 +153,15 @@ 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 no proven
|
||||
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 can just as easily be developed/deployed.)
|
||||
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.
|
||||
|
||||
To repeat though: after a number of years of the development of ECH, no such
|
||||
ECHConfig extensions have been proposed.
|
||||
|
||||
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
|
||||
|
|
@ -186,7 +187,8 @@ used to authenticate servers. Notably:
|
|||
|
||||
- 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.
|
||||
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
|
||||
|
|
@ -194,6 +196,14 @@ used to authenticate servers. Notably:
|
|||
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
|
||||
--------------
|
||||
|
||||
|
|
@ -236,8 +246,7 @@ int OSSL_ECHSTORE_flush_keys(OSSL_ECHSTORE *es, time_t age);
|
|||
`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 [PEMECH
|
||||
specification](https://datatracker.ietf.org/doc/draft-farrell-tls-pemesni/))
|
||||
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.)
|
||||
|
|
@ -282,9 +291,10 @@ 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. Note that the input
|
||||
`ecl` here can be either base64 or binary encoded, but for
|
||||
BoringSSL it must be binary encoded.
|
||||
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);
|
||||
|
|
@ -316,7 +326,7 @@ typedef struct ossl_echext_st {
|
|||
DEFINE_STACK_OF(OSSL_ECHEXT)
|
||||
|
||||
typedef struct ossl_echstore_entry_st {
|
||||
uint16_t version; /* 0xff0d for draft-13 */
|
||||
uint16_t version; /* 0xfe0d for RFC XXXX */
|
||||
char *public_name;
|
||||
size_t pub_len;
|
||||
unsigned char *pub;
|
||||
|
|
@ -377,17 +387,17 @@ In addition to the obvious fields from each ECHConfig, we also store:
|
|||
- The `EVP_PKEY` pointer to the private key value associated with the
|
||||
relevant ECHConfig, for use by servers.
|
||||
|
||||
- The PEM filename and file modification time from which a private key value
|
||||
and ECHConfigList were loaded. If those values are loaded from memory,
|
||||
the filename value is the SHA-256 hash of the encoded ECHConfigList and
|
||||
the load time is the time of loading. These values assist when servers
|
||||
periodically re-load sets of files or PEM structures from memory.
|
||||
- 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.
|
||||
|
||||
Split-mode handling
|
||||
-------------------
|
||||
|
||||
TODO(ECH): This ECH split-mode API should be considered tentative. It's design
|
||||
will be revisited as we get to considering the internals.
|
||||
should be revisited now, and either omitted from the initial release that'd
|
||||
only support shared-mode ECH, or else (better:-), agreed and included in the
|
||||
same time frame.
|
||||
|
||||
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
|
||||
|
|
@ -541,105 +551,166 @@ 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 when not doing
|
||||
* "real" ECH */
|
||||
#define SSL_OP_ECH_GREASE SSL_OP_BIT(36)
|
||||
/* If this is set then the server side will attempt trial decryption */
|
||||
/* of ECHs even if there is no matching record_digest. That's a bit */
|
||||
/* inefficient, but more privacy friendly */
|
||||
#define SSL_OP_ECH_TRIALDECRYPT SSL_OP_BIT(37)
|
||||
/* 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(38)
|
||||
/* If set, servers will add GREASEy ECHConfig values to those sent
|
||||
* in retry_configs */
|
||||
#define SSL_OP_ECH_GREASE_RETRY_CONFIG SSL_OP_BIT(39)
|
||||
/* 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
|
||||
-------------
|
||||
|
||||
All ECH code is protected via `#ifndef OPENSSL_NO_ECH` and there is
|
||||
a `no-ech` option to build without this code.
|
||||
Almost all ECH code is protected via `#ifndef OPENSSL_NO_ECH` and there is a
|
||||
`no-ech` option to build without this code.
|
||||
|
||||
BoringSSL APIs
|
||||
--------------
|
||||
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.
|
||||
|
||||
Brief descriptions of BoringSSL APIs are below together with initial comments
|
||||
comparing those to the above. (It may be useful to consider the extent to
|
||||
which it is useful to make OpenSSL and BoringSSL APIs resemble one another.)
|
||||
ECH Tests
|
||||
---------
|
||||
|
||||
Just as our implementation is under development, BoringSSL's
|
||||
`include/openssl/ssl.h` says: "ECH support in BoringSSL is still experimental
|
||||
and under development."
|
||||
The following tests are included in the `make test` target:
|
||||
|
||||
### GREASE
|
||||
- [`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)
|
||||
|
||||
BoringSSL uses an API to enable GREASEing rather than an option.
|
||||
There are also two external tests to check interoperability
|
||||
with the NSS and BoringSSL libraries:
|
||||
|
||||
```c
|
||||
OPENSSL_EXPORT void SSL_set_enable_ech_grease(SSL *ssl, int enable);
|
||||
- [`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` and are quite time consuming
|
||||
when first run, as they need to download and build the relevant
|
||||
NSS or BoringSSL library. 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
|
||||
```
|
||||
|
||||
This could work as well for our implementation, or BoringSSL could probably
|
||||
change to use an option, unless there's some reason to prefer not adding new
|
||||
options.
|
||||
To clean away the coverage files:
|
||||
|
||||
### Verifying the outer CH rather than inner
|
||||
|
||||
BoringSSL seems to use this API to change the DNS name being verified in order
|
||||
to validate a `retry_config`.
|
||||
|
||||
```c
|
||||
OPENSSL_EXPORT void SSL_get0_ech_name_override(const SSL *ssl,
|
||||
const char **out_name,
|
||||
size_t *out_name_len);
|
||||
```bash
|
||||
find . -name '*.gcda' -exec rm {} \;
|
||||
find . -name '*.gcno' -exec rm {} \;
|
||||
rm lcov.info
|
||||
make clean
|
||||
make -j12
|
||||
```
|
||||
|
||||
I'm not sure how this compares. Need to investigate.
|
||||
Checking memory errors
|
||||
----------------------
|
||||
|
||||
### Create an ECHConfigList
|
||||
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.
|
||||
|
||||
The first function below outputs an ECHConfig, the second adds one of those to
|
||||
an `SSL_ECH_KEYS` structure, the last emits an ECHConfigList from that
|
||||
structure. There are other APIs for managing memory for `SSL_ECH_KEYS`
|
||||
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:
|
||||
|
||||
These APIs also expose HPKE to the application via `EVP_HPKE_KEY` which is
|
||||
defined in `include/openssl/hpke.h`. HPKE handling differs quite a bit from
|
||||
the HPKE APIs merged to OpenSSL.
|
||||
```bash
|
||||
#!/bin/bash
|
||||
#
|
||||
# run 16k tests
|
||||
|
||||
```c
|
||||
OPENSSL_EXPORT int SSL_marshal_ech_config(uint8_t **out, size_t *out_len,
|
||||
uint8_t config_id,
|
||||
const EVP_HPKE_KEY *key,
|
||||
const char *public_name,
|
||||
size_t max_name_len);
|
||||
OPENSSL_EXPORT int SSL_ECH_KEYS_add(SSL_ECH_KEYS *keys, int is_retry_config,
|
||||
const uint8_t *ech_config,
|
||||
size_t ech_config_len,
|
||||
const EVP_HPKE_KEY *key);
|
||||
OPENSSL_EXPORT int SSL_ECH_KEYS_marshal_retry_configs(const SSL_ECH_KEYS *keys,
|
||||
uint8_t **out,
|
||||
size_t *out_len);
|
||||
# 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
|
||||
```
|
||||
|
||||
Collectively these are similar to `OSSL_ECH_make_echconfig()`.
|
||||
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.
|
||||
|
||||
### Setting ECH keys on a server
|
||||
You need to build with the `crypto-mdebug` option to get the memory allocation
|
||||
failure, so that'd be something like:
|
||||
|
||||
Again using the `SSL_ECH_KEYS` type and APIs, servers can build up a set of
|
||||
ECH keys using:
|
||||
|
||||
```c
|
||||
OPENSSL_EXPORT int SSL_CTX_set1_ech_keys(SSL_CTX *ctx, SSL_ECH_KEYS *keys);
|
||||
```bash
|
||||
./config --debug enable-external-tests enable-crypto-mdebug --coverage no-asm no-afalgeng no-shared -DPEDANTIC -DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION
|
||||
```
|
||||
|
||||
### Getting status
|
||||
|
||||
BoringSSL has:
|
||||
|
||||
```c
|
||||
OPENSSL_EXPORT int SSL_ech_accepted(const SSL *ssl);
|
||||
```
|
||||
|
||||
That seems to be a subset of `SSL_ech_get1_status()`.
|
||||
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.
|
||||
|
|
|
|||
|
|
@ -103,7 +103,7 @@ L<SSL_set1_echstore(3)>
|
|||
|
||||
=head1 HISTORY
|
||||
|
||||
This functionality described here was added in OpenSSL 3.5.
|
||||
This functionality described here was added in OpenSSL 4.0.
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
|
|
|
|||
|
|
@ -192,8 +192,8 @@ Specifies the ECHConfigList value to use for Encrypted Client Hello (ECH) for
|
|||
the TLS session. The value must be a base64 encoded ECHConfigList.
|
||||
|
||||
The ECHConfigList structure is defined in RFC XXXX. (That's currently in
|
||||
L<https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni/>)
|
||||
=for comment TODO(ECH): replace XXXX when RFC published.
|
||||
L<https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni/>.)
|
||||
TODO(ECH): replace XXXX when RFC published.
|
||||
|
||||
=item B<-proxy_user> I<userid>
|
||||
|
||||
|
|
|
|||
|
|
@ -94,6 +94,8 @@ SSL_set0_client_CA_list() or similar functions
|
|||
|
||||
=item any client certificate types configured via SSL_set1_client_certificate_types
|
||||
|
||||
=item any Encrypted Client Hello (ECH) settings (see L<SSL_set1_echstore(3)>).
|
||||
|
||||
=back
|
||||
|
||||
SSL_dup() is not supported on QUIC SSL objects and returns NULL if called on
|
||||
|
|
|
|||
|
|
@ -14,7 +14,7 @@ SSL_CTX_get1_echstore, SSL_get1_echstore, SSL_ech_set1_server_names,
|
|||
SSL_ech_set1_outer_server_name, SSL_ech_set1_outer_alpn_protos,
|
||||
SSL_ech_get1_status, SSL_ech_set1_grease_suite, SSL_ech_set_grease_type,
|
||||
SSL_ech_set_callback, SSL_ech_get1_retry_config,
|
||||
SSL_CTX_ech_set1_outer_alpn_protos, SSL_CTX_ech_raw_decrypt,
|
||||
SSL_CTX_ech_set1_outer_alpn_protos,
|
||||
SSL_CTX_ech_set_callback,SSL_set1_ech_config_list
|
||||
- Encrypted Client Hello (ECH) functions
|
||||
|
||||
|
|
@ -29,9 +29,9 @@ SSL_CTX_ech_set_callback,SSL_set1_ech_config_list
|
|||
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_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);
|
||||
|
|
@ -53,12 +53,6 @@ SSL_CTX_ech_set_callback,SSL_set1_ech_config_list
|
|||
int SSL_ech_set_grease_type(SSL *s, uint16_t type);
|
||||
void SSL_ech_set_callback(SSL *s, SSL_ech_cb_func f);
|
||||
int SSL_ech_get1_retry_config(SSL *s, unsigned char **ec, size_t *eclen);
|
||||
int SSL_CTX_ech_raw_decrypt(SSL_CTX *ctx,
|
||||
int *decrypted_ok,
|
||||
char **inner_sni, char **outer_sni,
|
||||
unsigned char *outer_ch, size_t outer_len,
|
||||
unsigned char *inner_ch, size_t *inner_len,
|
||||
unsigned char **hrrtok, size_t *toklen);
|
||||
void SSL_CTX_ech_set_callback(SSL_CTX *ctx, SSL_ech_cb_func f);
|
||||
int SSL_CTX_ech_set1_outer_alpn_protos(SSL_CTX *ctx,
|
||||
const unsigned char *protos,
|
||||
|
|
@ -67,42 +61,177 @@ SSL_CTX_ech_set_callback,SSL_set1_ech_config_list
|
|||
|
||||
=head1 DESCRIPTION
|
||||
|
||||
TODO(ECH): Complete this text...
|
||||
TODO(ECH): Replace XXXX and links to I-D when RFC issued.
|
||||
|
||||
The Encrypted Client Hello (ECH) APIs described here are built around
|
||||
the concept of an `OSSL_ECHSTORE` which contains ECH configuration
|
||||
information relevant for the current 'SSL_CTX' or 'SSL' connection.
|
||||
the concept of an B<OSSL_ECHSTORE> which contains ECH configuration
|
||||
information relevant for an B<SSL_CTX> or B<SSL> connection.
|
||||
|
||||
Mention SSL_set1_echstore() is a thing
|
||||
Mention OSSL_ECHSTORE_new() is a thing
|
||||
Mention OSSL_ECHSTORE_free() is a thing
|
||||
Mention OSSL_ECHSTORE_new_config() is a thing
|
||||
Mention OSSL_ECHSTORE_write_pem() is a thing
|
||||
Mention OSSL_ECHSTORE_read_echconfiglist() is a thing
|
||||
Mention OSSL_ECHSTORE_get1_info() is a thing
|
||||
Mention OSSL_ECHSTORE_downselect() is a thing
|
||||
Mention OSSL_ECHSTORE_set1_key_and_read_pem() is a thing
|
||||
Mention OSSL_ECHSTORE_read_pem() is a thing
|
||||
Mention OSSL_ECHSTORE_num_keys() is a thing
|
||||
Mention OSSL_ECHSTORE_num_entries() is a thing
|
||||
Mention OSSL_ECHSTORE_flush_keys() is a thing
|
||||
Mention SSL_CTX_set1_echstore() is a thing
|
||||
Mention SSL_CTX_get1_echstore() is a thing
|
||||
Mention SSL_get1_echstore() is a thing
|
||||
Mention SSL_ech_set1_server_names() is a thing
|
||||
Mention SSL_ech_set1_outer_server_name() is a thing
|
||||
Mention SSL_ech_set1_outer_alpn_protos() is a thing
|
||||
Mention SSL_ech_get1_status() is a thing
|
||||
Mention SSL_ech_set1_grease_suite() is a thing
|
||||
Mention SSL_ech_set_grease_type() is a thing
|
||||
Mention SSL_ech_set_callback() is a thing
|
||||
Mention SSL_ech_get1_retry_config() is a thing
|
||||
Mention SSL_CTX_ech1_set_outer_alpn_protos() is a thing
|
||||
Mention SSL_CTX_ech_raw_decrypt() is a thing
|
||||
Mention SSL_CTX_ech_set_callback() is a thing
|
||||
Mention SSL_set1_ech_config_list() is a thing
|
||||
This release only supports ECH shared-mode and has no support for
|
||||
ECH split-mode.
|
||||
|
||||
=head2 Callback Function
|
||||
=head2 OSSL_ECHSTORE APIs
|
||||
|
||||
The externally opaque type B<OSSL_ECHSTORE> allows applications to create and
|
||||
manage ECHConfigList values, ECH private keys and associated meta-data. The
|
||||
external APIs using B<OSSL_ECHSTORE> are:
|
||||
|
||||
OSSL_ECHSTORE_new() and OSSL_ECHSTORE_free() create and free the
|
||||
internal storage required.
|
||||
|
||||
OSSL_ECHSTORE_new_config() allows the caller to create a new private key
|
||||
value and a related "singleton" ECHConfigList structure.
|
||||
("Singleton" meaning the ECHConfigList only contains one public key.)
|
||||
The I<echversion> is the ECHConfig version to use, which is typically
|
||||
B<OSSL_ECH_CURRENT_VERSION> with a value of 0xfe0d. The I<max_name_length>
|
||||
specifies the maximum known DNS name length that will be present in an ECH
|
||||
extension (if known) and is used for ECH padding but should typically be zero,
|
||||
to indicate no known maximum. The I<public_name> is the DNS name to use in
|
||||
the ECHConfig I<public_name> field, and the I<suite> specifies the
|
||||
B<OSSL_HPKE_suite> to use (see L<OSSL_HPKE_CTX_new(3)> for details.)
|
||||
|
||||
OSSL_ECHSTORE_write_pem() allows the caller to produce an ECH PEM data structure
|
||||
(conforming to the ECH PEM file format) from the B<OSSL_ECHSTORE> entry
|
||||
identified by the I<index>. (An I<index> of B<OSSL_ECHSTORE_LAST> will select the
|
||||
last entry. An I<index> of B<OSSL_ECHSTORE_ALL> will output all public values,
|
||||
and no private values, otherwise the I<index> selects a specific entry with
|
||||
zero selecting the first entry.) The output will be written to the I<out>
|
||||
B<BIO>.
|
||||
|
||||
The B<OSSL_ECHSTORE> APIs above will typically be used via the "openssl ech"
|
||||
command line tool.
|
||||
|
||||
OSSL_ECHSTORE_read_echconfiglist() reads from the I<in> B<BIO> and parses a
|
||||
base64-encoded ECHConfigList value normally found in the "ech=" SvcParamKey
|
||||
present in an SVCB or HTTPS RR retrieved from the DNS. The resulting set of
|
||||
ECHConfig values, are associated with the I<es> store, and can then be
|
||||
associated with an B<SSL_CTX> or B<SSL> structure for TLS client connections.
|
||||
|
||||
Input B<BIO> values (such a I<in> above) must not use unterminated B<BIO>'s.
|
||||
|
||||
OSSL_ECHSTORE_get1_info() queries the store provided by I<es> and finds the
|
||||
entry within the store specified by I<index>. The number of seconds since that
|
||||
entry was first generated or loaded/decoded is stored in I<loaded_secs>, and
|
||||
the related public name is stored in I<*public_name>. This function also
|
||||
outputs a string useful for logging or display (as described in the
|
||||
L</String form of ECHConfig> section) and stores that in I<*echconfig>. It is
|
||||
the callers responsibility to free the strings returned in I<public_name> and
|
||||
I<echconfig> after they have been returned. If the specified entry has a
|
||||
private key associated with it then I<has_private> will be set to 1, otherwise
|
||||
it will be set to 0. Similarly if the ECHConfig for this entry will be included
|
||||
in "retry-configs" then the I<for_retry> will be set to 1 or zero if that
|
||||
ECHConfig will not be included in retry-configs.
|
||||
|
||||
The ECH fallback scheme involving retry-configs is described at
|
||||
L<https://datatracker.ietf.org/doc/html/draft-ietf-tls-esni-25#section-6.1.6>.
|
||||
|
||||
=begin comment
|
||||
|
||||
TODO(ECH): The I-D above should be replaced with the RFC when published.
|
||||
|
||||
=end comment
|
||||
|
||||
OSSL_ECHSTORE_downselect() provides the caller a way to select one particular
|
||||
ECHConfig value based on the zero-based I<index> from those stored in the
|
||||
I<es>, discarding the rest. This can be used by a client (via L<openssl-ech(1)>
|
||||
and L<openssl-s_client(1)> or equivalent) to pick a specific ECHConfig to use
|
||||
in a TLS connection.
|
||||
|
||||
OSSL_ECHSTORE_set1_key_and_read_pem() and OSSL_ECHSTORE_read_pem() can be used
|
||||
to load a private key value and associated ECHConfigList from the B<BIO> I<in>
|
||||
into an B<OSSL_ECHSTORE> structure. The former function pairs a
|
||||
previously-loaded private key (in B<EVP_PKEY> format) with an associated
|
||||
base64-encoded ECHConfigList in the I<in> B<BIO>. The latter function reads
|
||||
both from an ECH PEM file. Those can be used (by servers) to enable ECH for an
|
||||
B<SSL_CTX> or B<SSL> connection. In addition to loading those values, the
|
||||
application can also indicate via I<for_retry> which ECHConfig values are to be
|
||||
included in the I<retry_configs> fallback scheme defined by the ECH protocol.
|
||||
|
||||
An ECH PEM file may contain a private key and an ECHConfigList with more than
|
||||
one ECHConfig, for example if different public keys, I<public_name> values, or
|
||||
AEAD/KDF settings are to be supported. When such a file is read, the resulting
|
||||
B<OSSL_ECHSTORE> will contain one entry for each ECHConfig in the
|
||||
ECHConfigList, so will be presented to applications as a set of "singleton"
|
||||
ECHConfig values, with the private key associated with each matching public key
|
||||
value.
|
||||
|
||||
OSSL_ECHSTORE_num_entries() and OSSL_ECHSTORE_num_keys() allow an application
|
||||
to see how many ECH configs (in I<numentries>) and private keys (in
|
||||
I<numkeys>) are present in the I<es> B<OSSL_ECHSTORE> store.
|
||||
|
||||
OSSL_ECHSTORE_flush_keys() allows a server to flush keys from I<es> that were
|
||||
loaded more than I<age> seconds ago. The general model is that a server can
|
||||
maintain an B<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.
|
||||
|
||||
SSL_CTX_set1_echstore() and SSL_set1_echstore() allow clients and servers to
|
||||
associate I<es> (an B<OSSL_ECHSTORE>) with an B<SSL_CTX> or B<SSL> structure.
|
||||
ECH will be enabled for the relevant B<SSL_CTX> or B<SSL> connection when these
|
||||
functions succeed. Any previously associated B<OSSL_ECHSTORE> will be freed via
|
||||
OSSL_ECHSTORE_free(). Internally, B<OSSL_ECHSTORE> values within an B<SSL_CTX>
|
||||
or B<SSL> connection are deep-copied, and are not refcounted.
|
||||
|
||||
SSL_CTX_get1_echstore() and SSL_get1_echstore() provide access to the
|
||||
B<OSSL_ECHSTORE> associated with an B<SSL_CTX> or B<SSL> connection. The
|
||||
returned B<OSSL_ECHSTORE> can be modified and then re-associated with an
|
||||
B<SSL_CTX> or B<SSL> connection.
|
||||
|
||||
=head2 Client ECH Controls
|
||||
|
||||
SSL_set1_ech_config_list() allows clients to setup ECH by associating an
|
||||
ECHConfigList, I<ecl> with an B<SSL> connection I<s>. This is compatible with
|
||||
current BoringSSL APIs, allowing for smaller code changes for clients that
|
||||
support OpenSSL or BoringSSL. Note that the input I<ecl> here for OpenSSL can
|
||||
be either base64 or binary encoded, but for BoringSSL it must be binary
|
||||
encoded.
|
||||
|
||||
SSL_ech_set1_server_names() and SSL_ech_set1_outer_server_name() allow clients
|
||||
to more directly control the values to be used for inner and outer Server Name
|
||||
Indication (SNI) values for an B<SSL> connection, I<s>. The I<inner_name>
|
||||
provided will be used as with L<SSL_set_tlsext_host_name(3)> to populate the
|
||||
SNI value for the inner ClientHello. The I<outer_name>, if non-NULL, can
|
||||
over-ride the public_name field of the ECHConfig used for the connection. The
|
||||
I<no_outer> input allows a client to emit an outer ClientHello with no SNI at
|
||||
all. Providing a NULL for the I<outer_name> means to send the I<public_name>
|
||||
provided from the ECHConfigList unless the I<no_outer> provided has the value
|
||||
1.
|
||||
|
||||
If a client has called L<SSL_CTX_set_alpn_protos(3)> or
|
||||
L<SSL_set_alpn_protos(3)> then the ALPN value will be the same in the inner and
|
||||
outer ClientHello messages. SSL_ech_set1_outer_alpn_protos() and
|
||||
SSL_CTX_ech_set1_outer_alpn_protos() allow clients to set a specific value for
|
||||
the ALPN sent in the outer ClientHello of the B<SSL> connection, I<s>. The
|
||||
I<protos> and I<protos_len> inputs must be provided as for
|
||||
L<SSL_set_alpn_protos(3)>.
|
||||
|
||||
If a client attempts ECH but that fails, or sends an ECH-GREASE'd ClientHello,
|
||||
to an ECH-supporting server, then that server may return a set of ECH
|
||||
retry-config values that the client could choose to use in a subsequent
|
||||
connection. The client can detect this situation if SSL_ech_get1_status() for
|
||||
the B<SSL> connection I<s>, returns a status of B<SSL_ECH_STATUS_GREASE_ECH>,
|
||||
and can then access the ECH retry config values via SSL_ech_get1_retry_config()
|
||||
where the I<ec> value returned will contain a binary-encoded ECHConfigList
|
||||
of length I<eclen>.
|
||||
|
||||
"GREASEing" (defined in RFC8701) is a mechanism intended to discourage protocol
|
||||
ossification that can be used for ECH.
|
||||
|
||||
SSL_ech_set1_grease_suite() allows a client to GREASE ECH for the B<SSL>
|
||||
connection I<s>, using a specific B<OSSL_HPKE_SUITE> as the value for I<suite>
|
||||
(see L<OSSL_HPKE_get_grease_value(3)>). SSL_ech_set_grease_type() allows a
|
||||
client to add a GREASE'd ECH for the B<SSL> connection I<s>, using the
|
||||
specified ClientHello extension number I<type>.
|
||||
|
||||
Clients and servers can query the status of ECH for a B<SSL> connection I<s>,
|
||||
using the SSL_ech_get1_status() function. As SNI handling is core to ECH this
|
||||
will also return the I<inner_sni> and I<outer_sni> values to be used or that
|
||||
were used as well as a status code as the return value. These name values must
|
||||
be freed by the caller. The various status values returned by this function
|
||||
are as described in L</Constants>.
|
||||
|
||||
=head2 Callback Functions
|
||||
|
||||
Applications can set a callback function that will be called when the
|
||||
outcome from an attempt at ECH has been determined. On the server,
|
||||
|
|
@ -114,11 +243,11 @@ function will be triggered even if the client is only GREASEing.
|
|||
|
||||
The callback function prototype is:
|
||||
|
||||
typedef unsigned int (*SSL_ech_cb_func)(SSL *s, const char *str);
|
||||
typedef unsigned int (*SSL_ech_cb_func)(SSL *s, const char *str);
|
||||
|
||||
To set a callback function use SSL_ech_set_callback() or
|
||||
SSL_CTX_ech_set_callback() - the I<f> input should match the
|
||||
above prototype.
|
||||
To set a callback function for the B<SSL> connection I<s>, use
|
||||
SSL_ech_set_callback() or SSL_CTX_ech_set_callback() for a B<SSL_CTX> I<ctx> -
|
||||
the I<f> input should match the above prototype.
|
||||
|
||||
When the callback function is called, the I<str> will point at a string
|
||||
intended for logging describing the state of ECH processing.
|
||||
|
|
@ -139,59 +268,154 @@ An example string I<str> as seen on a client might be:
|
|||
ech_backend=0
|
||||
ech_success=1
|
||||
2 ECHConfig values loaded
|
||||
cfg(0): [fe0d,5d,cover.defo.ie,0020,[0001,0001],190984309c1a24cb944c005eb79d9c72ca9a4a979194b553dfd0bffc6b5c152d,00,00]
|
||||
cfg(1): [fe0d,fd,cover.defo.ie,0020,[0001,0001],46dd4e2c81bb15ef9d194c99b86983844e2a1387e4fb7e7d3b8d368c8e1b4d2a,00,00]
|
||||
cfg(0): [fe0d,5d,cover.defo.ie,[0020,0001,0001],190984309c1a24cb944c005eb79d9c72ca9a4a979194b553dfd0bffc6b5c152d,00,00]
|
||||
cfg(1): [fe0d,fd,cover.defo.ie,[0020,0001,0001],46dd4e2c81bb15ef9d194c99b86983844e2a1387e4fb7e7d3b8d368c8e1b4d2a,00,00]
|
||||
|
||||
=head2 Constants
|
||||
|
||||
Some externally visible limits:
|
||||
|
||||
=over 4
|
||||
|
||||
=item B<OSSL_ECH_MAX_PAYLOAD_LEN> 1500, maximum length of an ECH ciphertext to en/decode
|
||||
|
||||
=item B<OSSL_ECH_MIN_ECHCONFIG_LEN> 32, minimum length of an encoded ECHConfig
|
||||
|
||||
=item B<OSSL_ECH_MAX_ECHCONFIG_LEN> 1500, maximum length of an encoded ECHConfig
|
||||
|
||||
=item B<OSSL_ECH_MAX_ECHCONFIGEXT_LEN> 512, maximum length of an ECHConfig extension
|
||||
|
||||
=item B<OSSL_ECH_MAX_MAXNAMELEN> 255, maximum for ECHConfig max name length
|
||||
|
||||
=item B<OSSL_ECH_MAX_PUBLICNAME> 255, maximum length of an ECHConfig public name
|
||||
|
||||
=item B<OSSL_ECH_MAX_ALPN_LEN> 255, maximum overall length of an ALPN
|
||||
|
||||
=item B< OSSL_ECH_OUTERS_MAX> 20, maximum number of extensions compressed via outer-exts
|
||||
|
||||
=item B< OSSL_ECH_ALLEXTS_MAX> 32, maximum total number of extensions allowed
|
||||
|
||||
=back
|
||||
|
||||
ECH version - the only supported version is 0xfe0d currently.
|
||||
|
||||
=over 4
|
||||
|
||||
=item B<OSSL_ECH_RFCXXXX_VERSION> 0xfe0d, official ECHConfig version
|
||||
|
||||
=item B<OSSL_ECH_CURRENT_VERSION> OSSL_ECH_RFCXXXX_VERSION
|
||||
|
||||
=back
|
||||
|
||||
Return codes from SSL_ech_get_status
|
||||
|
||||
=over 4
|
||||
|
||||
=item B<SSL_ECH_STATUS_BACKEND> 4, ECH backend: saw an ech_is_inner
|
||||
|
||||
=item B<SSL_ECH_STATUS_GREASE_ECH> 3, GREASEd and got an ECH in return
|
||||
|
||||
=item B<SSL_ECH_STATUS_GREASE> 2, ECH GREASE happened
|
||||
|
||||
=item B<SSL_ECH_STATUS_SUCCESS> 1, Success
|
||||
|
||||
=item B<SSL_ECH_STATUS_FAILED> 0, Some internal or protocol error
|
||||
|
||||
=item B<SSL_ECH_STATUS_BAD_CALL> -100, Some in/out arguments were NULL
|
||||
|
||||
=item B<SSL_ECH_STATUS_NOT_TRIED> -101, ECH wasn't attempted
|
||||
|
||||
=item B<SSL_ECH_STATUS_BAD_NAME> -102, ECH ok but server cert bad
|
||||
|
||||
=item B<SSL_ECH_STATUS_NOT_CONFIGURED> -103, ECH wasn't configured
|
||||
|
||||
=item B<SSL_ECH_STATUS_FAILED_ECH> -105, We tried, failed and got an ECH, from a verified name
|
||||
|
||||
=item B<SSL_ECH_STATUS_FAILED_ECH_BAD_NAME> -106, We tried, failed and got an ECH, from a bad name
|
||||
|
||||
=back
|
||||
|
||||
Values for I<for_retry>
|
||||
|
||||
=over 4
|
||||
|
||||
=item B<SSL_ECH_USE_FOR_RETRY> 1. use corresponding ECHConfig values
|
||||
|
||||
=item B<SSL_ECH_NOT_FOR_RETRY> 0. don't use corresponding ECHConfig values
|
||||
|
||||
=back
|
||||
|
||||
Indexing an OSSL_ECHSTORE
|
||||
|
||||
=over 4
|
||||
|
||||
=item B<OSSL_ECHSTORE_LAST>, -1 select the last entry in the store
|
||||
|
||||
=item B<OSSL_ECHSTORE_ALL>, -2 select all entries in the store, e.g. to print public values
|
||||
|
||||
=back
|
||||
|
||||
=head2 String form of ECHConfig
|
||||
|
||||
OSSL_ECHSTORE_get1_info() returns the string form of the ECH public name plus
|
||||
a string describing the content of the ECHConfig for example:
|
||||
|
||||
[fe0d,4d,example.com,[0020,0001,0002],c103d20dddce9b4445829bf01f5b533b728bfa0ebe3a97da33574bc096bb846e,00,00]
|
||||
|
||||
In the order presented in the example above, the I<version> is 0xfe0d, the
|
||||
I<config_id> is 0x4d, the I<public_name> is "example.com", there is one
|
||||
ciphersuite with a I<kem-id> of 0x20, I<kdf-id> is 0x01 and I<aead-id> is 0x02;
|
||||
the I<public_key> is the hexadecimal string "c1...6e", the I<max-name-length>
|
||||
is 0x00 and the I<extensions> length is 0x00 as no ECHConfig extensions are
|
||||
present.
|
||||
|
||||
=head1 RETURN VALUES
|
||||
|
||||
All functions named here return one on success and zero on error.
|
||||
SSL_set1_echstore(), SSL_set1_ech_config_list(), OSSL_ECHSTORE_new_config(),
|
||||
OSSL_ECHSTORE_write_pem(), OSSL_ECHSTORE_read_echconfiglist(),
|
||||
OSSL_ECHSTORE_get1_info(), OSSL_ECHSTORE_downselect(),
|
||||
OSSL_ECHSTORE_set1_key_and_read_pem(), OSSL_ECHSTORE_read_pem(),
|
||||
OSSL_ECHSTORE_num_keys(), OSSL_ECHSTORE_num_entries(),
|
||||
OSSL_ECHSTORE_flush_keys(), SSL_CTX_set1_echstore(),
|
||||
SSL_ech_set_server_names(), SSL_ech_set_outer_server_name(),
|
||||
SSL_ech_set_outer_alpn_protos(), SSL_ech_get1_status(),
|
||||
SSL_ech_set_grease_suite(), SSL_ech_set_grease_type(),
|
||||
SSL_ech_get_retry_config() and SSL_CTX_ech_set1_outer_alpn_protos() all return
|
||||
zero on error and one on success.
|
||||
|
||||
SSL_set1_echstore() returns zero on error
|
||||
SSL_set1_ech_config_list() returns zero on error
|
||||
OSSL_ECHSTORE_new() returns zero on error
|
||||
OSSL_ECHSTORE_free() returns zero on error
|
||||
OSSL_ECHSTORE_new_config() returns zero on error
|
||||
OSSL_ECHSTORE_write_pem() returns zero on error
|
||||
OSSL_ECHSTORE_read_echconfiglist() returns zero on error
|
||||
OSSL_ECHSTORE_get1_info() returns zero on error
|
||||
OSSL_ECHSTORE_downselect() returns zero on error
|
||||
OSSL_ECHSTORE_set1_key_and_read_pem() returns zero on error
|
||||
OSSL_ECHSTORE_read_pem() returns zero on error
|
||||
OSSL_ECHSTORE_num_keys() returns zero on error
|
||||
OSSL_ECHSTORE_num_entries() returns zero on error
|
||||
OSSL_ECHSTORE_flush_keys() returns zero on error
|
||||
SSL_CTX_set1_echstore() returns zero on error
|
||||
SSL_CTX_get1_echstore() returns zero on error
|
||||
SSL_get1_echstore() returns zero on error
|
||||
SSL_ech_set_server_names() returns zero on error
|
||||
SSL_ech_set_outer_server_name() returns zero on error
|
||||
SSL_ech_set_outer_alpn_protos() returns zero on error
|
||||
SSL_ech_get1_status() returns zero on error
|
||||
SSL_ech_set_grease_suite() returns zero on error
|
||||
SSL_ech_set_grease_type() returns zero on error
|
||||
SSL_ech_set_callback() returns zero on error
|
||||
SSL_ech_get_retry_config() returns zero on error
|
||||
SSL_CTX_ech_set1_outer_alpn_protos() returns zero on error
|
||||
SSL_CTX_ech_raw_decrypt() returns zero on error
|
||||
SSL_CTX_ech_set_callback() returns zero on error
|
||||
SSL_ech_set_callback(), SSL_CTX_ech_set_callback(), OSSL_ECHSTORE_free() have
|
||||
no return value.
|
||||
|
||||
SSL_CTX_get1_echstore(), SSL_get1_echstore() and OSSL_ECHSTORE_new() return a
|
||||
pointer to an B<OSSL_ECHSTORE>.
|
||||
|
||||
Note that SSL_CTX_ech_set1_outer_alpn_protos() and
|
||||
SSL_ech_set1_outer_alpn_protos() return zero on error and 1 on success.
|
||||
This is in contrast to SSL_CTX_set1_alpn_protos() and SSL_set1_alpn_protos()
|
||||
SSL_ech_set1_outer_alpn_protos() return zero on error and 1 on success. This
|
||||
is in contrast to L<SSL_CTX_set_alpn_protos(3)> and L<SSL_set_alpn_protos(3)>
|
||||
which (unusually for OpenSSL) return 0 on success and 1 on error.
|
||||
|
||||
=head1 SEE ALSO
|
||||
|
||||
The Encrypted ClientHello specification: L<https://datatracker.ietf.org/doc/draft-ietf-tls-esni/>
|
||||
TODO(ECH) update link to RFC.
|
||||
=over 4
|
||||
|
||||
=item Encrypted ClientHello specification: L<https://datatracker.ietf.org/doc/draft-ietf-tls-esni/>
|
||||
|
||||
=item ECH PEM file format specification: L<https://datatracker.ietf.org/doc/draft-farrell-tls-pemesni/>
|
||||
|
||||
=item RFC8701: L<https://datatracker.ietf.org/doc/html/rfc8701>
|
||||
|
||||
=back
|
||||
|
||||
ECH command line options are described in the manual pages for
|
||||
L<openssl-s_client(1)>, L<openssl-s_server(1)> and L<openssl-ech(1)>.
|
||||
|
||||
=head1 HISTORY
|
||||
|
||||
The functionality described here was added in OpenSSL 3.5.
|
||||
The functionality described here was added in OpenSSL 4.0.
|
||||
|
||||
=head1 COPYRIGHT
|
||||
|
||||
Copyright 2024 The OpenSSL Project Authors. All Rights Reserved.
|
||||
Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
|
||||
|
||||
Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
this file except in compliance with the License. You can obtain a copy
|
||||
|
|
|
|||
|
|
@ -9,7 +9,7 @@
|
|||
|
||||
/*
|
||||
* Externally-visible data structures and prototypes for handling
|
||||
* Encrypted ClientHello (ECH).
|
||||
* shared-mode Encrypted ClientHello (ECH).
|
||||
*/
|
||||
#ifndef OPENSSL_ECH_H
|
||||
#define OPENSSL_ECH_H
|
||||
|
|
@ -120,12 +120,6 @@ int SSL_ech_get1_retry_config(SSL *s, unsigned char **ec, size_t *eclen);
|
|||
*/
|
||||
int SSL_CTX_ech_set1_outer_alpn_protos(SSL_CTX *s, const unsigned char *protos,
|
||||
const size_t protos_len);
|
||||
int SSL_CTX_ech_raw_decrypt(SSL_CTX *ctx,
|
||||
int *decrypted_ok,
|
||||
char **inner_sni, char **outer_sni,
|
||||
unsigned char *outer_ch, size_t outer_len,
|
||||
unsigned char *inner_ch, size_t *inner_len,
|
||||
unsigned char **hrrtok, size_t *toklen);
|
||||
void SSL_CTX_ech_set_callback(SSL_CTX *ctx, SSL_ech_cb_func f);
|
||||
int SSL_set1_ech_config_list(SSL *ssl, const uint8_t *ecl, size_t ecl_len);
|
||||
|
||||
|
|
|
|||
|
|
@ -95,7 +95,7 @@ int ossl_ech_helper_get_ch_offsets(const unsigned char *ch, size_t ch_len,
|
|||
if (!PACKET_get_net_2(&pkt, &pi_tmp))
|
||||
return 0;
|
||||
/* if we're not TLSv1.2+ then we can bail, but it's not an error */
|
||||
if (pi_tmp != TLS1_2_VERSION && pi_tmp != TLS1_3_VERSION)
|
||||
if (pi_tmp != TLS1_2_VERSION)
|
||||
return 1;
|
||||
/* chew up the packet to extensions */
|
||||
if (!PACKET_get_bytes(&pkt, &pp_tmp, SSL3_RANDOM_SIZE)
|
||||
|
|
|
|||
|
|
@ -281,8 +281,7 @@ err:
|
|||
/*
|
||||
* Send a random value that looks like a real ECH.
|
||||
*
|
||||
* TODO(ECH): the "best" thing to do here is not yet known. For now, we do
|
||||
* GREASEing as currently (20241102) done by chrome:
|
||||
* We do GREASEing as follows:
|
||||
* - always HKDF-SHA256
|
||||
* - always AES-128-GCM
|
||||
* - random config ID, even for requests to same server in same session
|
||||
|
|
@ -648,9 +647,9 @@ size_t ossl_ech_calc_padding(SSL_CONNECTION *s, OSSL_ECHSTORE_ENTRY *ee,
|
|||
/*
|
||||
* Finally - make sure final result is longer than padding target
|
||||
* and a multiple of our padding increment.
|
||||
* TODO(ECH): This is a local addition - we might take it out if
|
||||
* it makes us stick out; or if we take out the above more (uselessly:-)
|
||||
* complicated scheme, we may only need this in the end.
|
||||
* This is a local addition - we might want to take it out if it makes
|
||||
* us stick out; or if we take out the above more (uselessly:-)
|
||||
* complicated scheme above, we may only need this in the end.
|
||||
*/
|
||||
if ((length_with_padding % OSSL_ECH_PADDING_INCREMENT) != 0)
|
||||
length_with_padding += OSSL_ECH_PADDING_INCREMENT
|
||||
|
|
@ -1091,10 +1090,7 @@ int ossl_ech_calc_confirm(SSL_CONNECTION *s, int for_hrr,
|
|||
ossl_ech_pbuf("cx: result", acbuf, OSSL_ECH_SIGNAL_LEN);
|
||||
#endif
|
||||
/* put confirm value back into transcript */
|
||||
if (s->ext.ech.hrrsignal_p == NULL)
|
||||
memcpy(conf_loc, acbuf, OSSL_ECH_SIGNAL_LEN);
|
||||
else
|
||||
memcpy(conf_loc, s->ext.ech.hrrsignal, OSSL_ECH_SIGNAL_LEN);
|
||||
memcpy(conf_loc, acbuf, OSSL_ECH_SIGNAL_LEN);
|
||||
/* on a server, we need to reset the hs buffer now */
|
||||
if (s->server && s->hello_retry_request == SSL_HRR_NONE)
|
||||
ossl_ech_reset_hs_buffer(s, s->ext.ech.innerch, s->ext.ech.innerch_len);
|
||||
|
|
@ -1373,7 +1369,7 @@ static int ech_decode_inbound_ech(SSL_CONNECTION *s, PACKET *pkt,
|
|||
SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
|
||||
goto err;
|
||||
}
|
||||
if (pval_tmp > PACKET_remaining(pkt)) {
|
||||
if (pval_tmp == 0 || pval_tmp > PACKET_remaining(pkt)) {
|
||||
SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
|
||||
goto err;
|
||||
}
|
||||
|
|
@ -1668,7 +1664,7 @@ static int ech_decode_inner(SSL_CONNECTION *s, const unsigned char *ob,
|
|||
BUF_MEM *di_mem = NULL;
|
||||
uint16_t outers[OSSL_ECH_OUTERS_MAX]; /* compressed extension types */
|
||||
size_t n_outers = 0;
|
||||
WPACKET di;
|
||||
WPACKET di = { 0 }; /* "fake" pkt for inner */
|
||||
|
||||
if (encoded_inner == NULL || ob == NULL || ob_len == 0) {
|
||||
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
|
||||
|
|
@ -2066,8 +2062,7 @@ int ossl_ech_early_decrypt(SSL_CONNECTION *s, PACKET *outerpkt, PACKET *newpkt)
|
|||
OPENSSL_free(aad);
|
||||
aad = NULL;
|
||||
s->ext.ech.done = 1; /* decrypting worked or not, but we're done now */
|
||||
/* 3. if decrypt fails tee-up GREASE */
|
||||
s->ext.ech.grease = OSSL_ECH_IS_GREASE;
|
||||
s->ext.ech.grease = OSSL_ECH_IS_GREASE; /* if decrypt fails tee-up GREASE */
|
||||
s->ext.ech.success = 0;
|
||||
if (clear != NULL) {
|
||||
s->ext.ech.grease = OSSL_ECH_NOT_GREASE;
|
||||
|
|
@ -2103,6 +2098,7 @@ int ossl_ech_early_decrypt(SSL_CONNECTION *s, PACKET *outerpkt, PACKET *newpkt)
|
|||
goto err;
|
||||
}
|
||||
OPENSSL_free(clear);
|
||||
clear = NULL;
|
||||
#ifdef OSSL_ECH_SUPERVERBOSE
|
||||
ossl_ech_pbuf("Inner CH (decoded)", s->ext.ech.innerch,
|
||||
s->ext.ech.innerch_len);
|
||||
|
|
|
|||
|
|
@ -155,8 +155,13 @@ struct ossl_echstore_st {
|
|||
|
||||
/* ECH details associated with an SSL_CTX */
|
||||
typedef struct ossl_ech_ctx_st {
|
||||
/* TODO(ECH): consider making es ref-counted */
|
||||
OSSL_ECHSTORE *es;
|
||||
/*
|
||||
* We could make es ref-counted, but that seems like a premature
|
||||
* optimisation, given we don't currently expect many applications
|
||||
* to have many SSL_CTX/SSL structures using many ECH configurations.
|
||||
* Could fairly easily be done if experience warrants.
|
||||
*/
|
||||
OSSL_ECHSTORE *es; /* ECHConfigList details */
|
||||
unsigned char *alpn_outer;
|
||||
size_t alpn_outer_len;
|
||||
SSL_ech_cb_func cb; /* callback function for when ECH "done" */
|
||||
|
|
@ -164,7 +169,12 @@ typedef struct ossl_ech_ctx_st {
|
|||
|
||||
/* ECH details associated with an SSL_CONNECTION */
|
||||
typedef struct ossl_ech_conn_st {
|
||||
/* TODO(ECH): consider making es ref-counted */
|
||||
/*
|
||||
* We could make es ref-counted, but that seems like a premature
|
||||
* optimisation, given we don't currently expect many applications
|
||||
* to have many SSL_CTX/SSL structures using many ECH configurations.
|
||||
* Could fairly easily be done if experience warrants.
|
||||
*/
|
||||
OSSL_ECHSTORE *es; /* ECHConfigList details */
|
||||
int no_outer; /* set to 1 if we should send no outer SNI at all */
|
||||
char *outer_hostname;
|
||||
|
|
@ -203,9 +213,11 @@ typedef struct ossl_ech_conn_st {
|
|||
uint16_t outer_only[OSSL_ECH_OUTERS_MAX];
|
||||
size_t n_outer_only; /* the number of outer_only extensions so far */
|
||||
/*
|
||||
* We store/access the index of the extension handler in
|
||||
* s->ext.ech.ext_ind, as we'd otherwise not know it here.
|
||||
* Be nice were there a better way to handle that.
|
||||
* Index of the current extension's entry in ext_defs - this is
|
||||
* to avoid the need to change a couple of extension APIs.
|
||||
* TODO(ECH): check if there's another way to get that value
|
||||
*/
|
||||
int ext_ind;
|
||||
/* ECH status vars */
|
||||
|
|
|
|||
|
|
@ -248,10 +248,16 @@ int SSL_ech_get1_status(SSL *ssl, char **inner_sni, char **outer_sni)
|
|||
int SSL_ech_set1_grease_suite(SSL *ssl, const char *suite)
|
||||
{
|
||||
SSL_CONNECTION *s;
|
||||
OSSL_HPKE_SUITE hpke_suite_in = OSSL_HPKE_SUITE_DEFAULT;
|
||||
|
||||
s = SSL_CONNECTION_FROM_SSL(ssl);
|
||||
if (s == NULL)
|
||||
return 0;
|
||||
/* check suite makes sense */
|
||||
if (OSSL_HPKE_str2suite(suite, &hpke_suite_in) != 1) {
|
||||
ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT);
|
||||
return 0;
|
||||
}
|
||||
OPENSSL_free(s->ext.ech.grease_suite);
|
||||
s->ext.ech.grease_suite = NULL;
|
||||
if (suite == NULL)
|
||||
|
|
@ -259,7 +265,6 @@ int SSL_ech_set1_grease_suite(SSL *ssl, const char *suite)
|
|||
s->ext.ech.grease_suite = OPENSSL_strdup(suite);
|
||||
if (s->ext.ech.grease_suite == NULL)
|
||||
return 0;
|
||||
s->ext.ech.attempted = 1;
|
||||
s->ext.ech.grease = OSSL_ECH_IS_GREASE;
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -272,7 +277,6 @@ int SSL_ech_set_grease_type(SSL *ssl, uint16_t type)
|
|||
if (s == NULL)
|
||||
return 0;
|
||||
s->ext.ech.attempted_type = type;
|
||||
s->ext.ech.attempted = 1;
|
||||
s->ext.ech.grease = OSSL_ECH_IS_GREASE;
|
||||
return 1;
|
||||
}
|
||||
|
|
@ -370,27 +374,6 @@ int SSL_CTX_ech_set1_outer_alpn_protos(SSL_CTX *ctx,
|
|||
return 1;
|
||||
}
|
||||
|
||||
int SSL_CTX_ech_raw_decrypt(SSL_CTX *ctx,
|
||||
int *decrypted_ok,
|
||||
char **inner_sni, char **outer_sni,
|
||||
unsigned char *outer_ch, size_t outer_len,
|
||||
unsigned char *inner_ch, size_t *inner_len,
|
||||
unsigned char **hrrtok, size_t *toklen)
|
||||
{
|
||||
if (ctx == NULL) {
|
||||
/*
|
||||
* TODO(ECH): this is a bit of a bogus error, just so as
|
||||
* to get the `make update` command to add the required
|
||||
* error number. We don't need it yet, but it's involved
|
||||
* in some of the build artefacts, so may as well jump
|
||||
* the gun a bit on it.
|
||||
*/
|
||||
ERR_raise(ERR_LIB_SSL, SSL_R_ECH_REQUIRED);
|
||||
return 0;
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
void SSL_CTX_ech_set_callback(SSL_CTX *ctx, SSL_ech_cb_func f)
|
||||
{
|
||||
if (ctx == NULL) {
|
||||
|
|
|
|||
|
|
@ -46,9 +46,9 @@ static const char B64_alphabet[] = "\x41\x42\x43\x44\x45\x46\x47\x48\x49\x4a\x4b
|
|||
|
||||
#ifndef TLSEXT_MINLEN_host_name
|
||||
/*
|
||||
* TODO(ECH): shortest DNS name we allow, e.g. "a.bc" - maybe that should
|
||||
* be defined elsewhere, or should the check be skipped in case there's
|
||||
* a local deployment that uses shorter names?
|
||||
* TODO(ECH): Decide this. The shortest DNS name we allow, e.g. "a.bc" - maybe
|
||||
* that should be defined elsewhere, or should the check be skipped in case
|
||||
* there's a local deployment that uses shorter names?
|
||||
*/
|
||||
#define TLSEXT_MINLEN_host_name 4
|
||||
#endif
|
||||
|
|
@ -106,7 +106,7 @@ void ossl_echstore_entry_free(OSSL_ECHSTORE_ENTRY *ee)
|
|||
*
|
||||
* This is intended for small inputs, either files or buffers and
|
||||
* not other kinds of BIO.
|
||||
* TODO(ECH): how to check for oddball input BIOs?
|
||||
* TODO(ECH): is there really a way to check for oddball input BIOs?
|
||||
*/
|
||||
static int ech_bio2buf(BIO *in, unsigned char **buf, size_t *len)
|
||||
{
|
||||
|
|
@ -769,6 +769,7 @@ int OSSL_ECHSTORE_new_config(OSSL_ECHSTORE *es,
|
|||
ee->max_name_length = max_name_length;
|
||||
ee->config_id = config_id;
|
||||
ee->keyshare = privp;
|
||||
privp = NULL; /* don't free twice */
|
||||
/* "steal" the encoding from the memory */
|
||||
ee->encoded = (unsigned char *)epkt_mem->data;
|
||||
ee->encoded_len = bblen;
|
||||
|
|
@ -926,6 +927,7 @@ int OSSL_ECHSTORE_get1_info(OSSL_ECHSTORE *es, int index, time_t *loaded_secs,
|
|||
goto err;
|
||||
}
|
||||
*has_private = (ee->keyshare == NULL ? 0 : 1);
|
||||
*for_retry = ee->for_retry;
|
||||
/* Now "print" the ECHConfigList */
|
||||
out = BIO_new(BIO_s_mem());
|
||||
if (out == NULL) {
|
||||
|
|
|
|||
|
|
@ -572,7 +572,11 @@ int ossl_ech_same_ext(SSL_CONNECTION *s, WPACKET *pkt)
|
|||
#endif
|
||||
if (s == NULL || s->ext.ech.es == NULL)
|
||||
return OSSL_ECH_SAME_EXT_CONTINUE; /* nothing to do */
|
||||
/* TODO(ECH): we need a better way to handle indexing exts */
|
||||
/*
|
||||
* We store/access the index of the extension handler in
|
||||
* s->ext.ech.ext_ind, as we'd otherwise not know it here.
|
||||
* Be nice were there a better way to handle that.
|
||||
*/
|
||||
tind = s->ext.ech.ext_ind;
|
||||
/* If this index'd extension won't be compressed, we're done */
|
||||
if (tind < 0 || tind >= nexts)
|
||||
|
|
|
|||
|
|
@ -1430,7 +1430,6 @@ dopsksess:
|
|||
* with random values of the same length.
|
||||
*/
|
||||
if (s->ext.ech.es != NULL && s->ext.ech.ch_depth == 0) {
|
||||
/* TODO(ECH): changes here need testing with server-side code PR */
|
||||
unsigned char *rndbuf = NULL, *rndbufp = NULL;
|
||||
size_t totalrndsize = 0;
|
||||
|
||||
|
|
@ -2577,8 +2576,9 @@ EXT_RETURN tls_construct_ctos_ech(SSL_CONNECTION *s, WPACKET *pkt,
|
|||
size_t cipherlen = 0, aad_len = 0, lenclen = 0, mypub_len = 0;
|
||||
size_t info_len = OSSL_ECH_MAX_INFO_LEN, clear_len = 0, encoded_len = 0;
|
||||
/* whether or not we've been asked to GREASE, one way or another */
|
||||
int grease_opt_set = ((s->ext.ech.grease == OSSL_ECH_IS_GREASE)
|
||||
|| ((s->options & SSL_OP_ECH_GREASE) != 0));
|
||||
int grease_opt_set = (s->ext.ech.attempted != 1
|
||||
&& ((s->ext.ech.grease == OSSL_ECH_IS_GREASE)
|
||||
|| ((s->options & SSL_OP_ECH_GREASE) != 0)));
|
||||
|
||||
/* if we're not doing real ECH and not GREASEing then exit */
|
||||
if (s->ext.ech.attempted_type != TLSEXT_TYPE_ech && grease_opt_set == 0)
|
||||
|
|
@ -2651,9 +2651,28 @@ EXT_RETURN tls_construct_ctos_ech(SSL_CONNECTION *s, WPACKET *pkt,
|
|||
OSSL_TRACE_END(TLS);
|
||||
config_id_to_use = ee->config_id; /* if requested, use a random config_id instead */
|
||||
if ((s->options & SSL_OP_ECH_IGNORE_CID) != 0) {
|
||||
if (RAND_bytes_ex(sctx->libctx, &config_id_to_use, 1, 0) <= 0) {
|
||||
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
|
||||
return 0;
|
||||
int max_iters = 1000, i = 0;
|
||||
|
||||
/* rejection sample to get a different but random config_id */
|
||||
while (config_id_to_use == ee->config_id) {
|
||||
#ifdef OSSL_ECH_SUPERVERBOSE
|
||||
if (i > 0) {
|
||||
OSSL_TRACE_BEGIN(TLS)
|
||||
{
|
||||
BIO_printf(trc_out, "EAAE: rejected random-config %02x\n",
|
||||
config_id_to_use);
|
||||
}
|
||||
OSSL_TRACE_END(TLS);
|
||||
}
|
||||
#endif
|
||||
if (RAND_bytes_ex(sctx->libctx, &config_id_to_use, 1, 0) <= 0) {
|
||||
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
|
||||
return 0;
|
||||
}
|
||||
if (i++ >= max_iters) {
|
||||
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
|
||||
return 0;
|
||||
}
|
||||
}
|
||||
#ifdef OSSL_ECH_SUPERVERBOSE
|
||||
ossl_ech_pbuf("EAAE: random config_id", &config_id_to_use, 1);
|
||||
|
|
|
|||
|
|
@ -222,7 +222,11 @@ int custom_ext_add(SSL_CONNECTION *s, int context, WPACKET *pkt, X509 *x,
|
|||
OSSL_TRACE_END(TLS);
|
||||
}
|
||||
if (s->ext.ech.ch_depth == 0) {
|
||||
/* TODO(ECH): we need a better way to handle indexing exts */
|
||||
/*
|
||||
* We store/access the index of the extension handler in
|
||||
* s->ext.ech.ext_ind, as we'd otherwise not know it here.
|
||||
* Be nice were there a better way to handle that.
|
||||
*/
|
||||
/* copy over the extension octets (if any) to outer */
|
||||
int j, tind = -1;
|
||||
RAW_EXTENSION *raws = NULL;
|
||||
|
|
|
|||
|
|
@ -3391,11 +3391,6 @@ int tls_process_initial_server_flight(SSL_CONNECTION *s)
|
|||
|
||||
#ifndef OPENSSL_NO_ECH
|
||||
/* check result of ech and return error if needed */
|
||||
/*
|
||||
* TODO(ECH): check that we never get here in a server
|
||||
* during split-mode or test cases - there used be a
|
||||
* check of !s->server added to the below.
|
||||
*/
|
||||
if (s->ext.ech.es != NULL
|
||||
&& s->ext.ech.attempted == 1
|
||||
&& s->ext.ech.success != 1
|
||||
|
|
|
|||
|
|
@ -73,6 +73,10 @@ IF[{- !$disabled{tests} -}]
|
|||
x509_acert_test x509_req_test strtoultest bio_pw_callback_test \
|
||||
engine_stubs_test base64_simdutf_test bio_eof_test ech_test
|
||||
|
||||
IF[{- !$disabled{'ech'} -}]
|
||||
PROGRAMS{noinst}=ech_corrupt_test
|
||||
ENDIF
|
||||
|
||||
IF[{- !$disabled{'rpk'} -}]
|
||||
PROGRAMS{noinst}=rpktest
|
||||
ENDIF
|
||||
|
|
@ -232,6 +236,10 @@ IF[{- !$disabled{tests} -}]
|
|||
INCLUDE[ech_test]=../include ../apps/include
|
||||
DEPEND[ech_test]=../libssl.a ../libcrypto.a libtestutil.a
|
||||
|
||||
SOURCE[ech_corrupt_test]=ech_corrupt_test.c helpers/ssltestlib.c
|
||||
INCLUDE[ech_corrupt_test]=../include ../apps/include
|
||||
DEPEND[ech_corrupt_test]=../libssl.a ../libcrypto.a libtestutil.a
|
||||
|
||||
SOURCE[evp_extra_test2]=evp_extra_test2.c $INITSRC tls-provider.c
|
||||
INCLUDE[evp_extra_test2]=../include ../apps/include
|
||||
DEPEND[evp_extra_test2]=../libcrypto libtestutil.a
|
||||
|
|
|
|||
6
test/certs/echdir/echconfig-10.pem
Normal file
6
test/certs/echdir/echconfig-10.pem
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MC4CAQAwBQYDK2VuBCIEIPiqiukxstIS9ViT4zWlewgPbjN4XVQ1XrcYgxwE6sl1
|
||||
-----END PRIVATE KEY-----
|
||||
-----BEGIN ECHCONFIG-----
|
||||
AED+CgA8vwAgACCB9YyilgR2NMLVPOsESVceIrfGpXThGIUMIwGfGClmSgAEAAEAAQAAAAtleGFtcGxlLmNvbQAA
|
||||
-----END ECHCONFIG-----
|
||||
8
test/certs/echdir/echconfig-256.pem
Normal file
8
test/certs/echdir/echconfig-256.pem
Normal file
|
|
@ -0,0 +1,8 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQgfVyqIGiyInH7KDB3
|
||||
szOuOFYP41zV2gpA8wESUjpcqrChRANCAAQaLs1qXa+t31TtQeD6hXwWoltwJgL1
|
||||
zt9kMsrj1HMScUQ8yIpNt+KUjzuMLbJxG9SpuZqfFW/agXJlyQEmatGE
|
||||
-----END PRIVATE KEY-----
|
||||
-----BEGIN ECHCONFIG-----
|
||||
AGH+CgBd0QAQAEEEGi7Nal2vrd9U7UHg+oV8FqJbcCYC9c7fZDLK49RzEnFEPMiKTbfilI87jC2ycRvUqbmanxVv2oFyZckBJmrRhAAEAAEAAQAAAAtleGFtcGxlLmNvbQAA
|
||||
-----END ECHCONFIG-----
|
||||
1
test/certs/echdir/echconfig_bad_fuzz1.pem
Normal file
1
test/certs/echdir/echconfig_bad_fuzz1.pem
Normal file
|
|
@ -0,0 +1 @@
|
|||
AD7+DQA6uAAgACAogff+HZbirYdQCfXI01GBPP8AEKYyK/D/0DoeXD84fgAQAAEAAQgLZXhhbUNwbGUuYwYAAAAAQwA=
|
||||
7
test/certs/echdir/echmaxname.pem
Normal file
7
test/certs/echdir/echmaxname.pem
Normal file
|
|
@ -0,0 +1,7 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MC4CAQAwBQYDK2VuBCIEILCwRG3jPpLC4JRINol54Jo8bvua3e1EV3EDbepDCmpi
|
||||
-----END PRIVATE KEY-----
|
||||
-----BEGIN ECHCONFIG-----
|
||||
AD7+DQA6IwAgACDT+hqR+Atl9LejS6nJrr9oDspGvwWTGOW3WJHdJy+OSQAEAAEA
|
||||
ASALZXhhbXBsZS5jb20AAA==
|
||||
-----END ECHCONFIG-----
|
||||
6
test/certs/echdir/echwithexts.pem
Normal file
6
test/certs/echdir/echwithexts.pem
Normal file
|
|
@ -0,0 +1,6 @@
|
|||
-----BEGIN PRIVATE KEY-----
|
||||
MC4CAQAwBQYDK2VuBCIEIOAOgc22M+RJc5QZdY/ndZSME8akKHon13JITeB899pF
|
||||
-----END PRIVATE KEY-----
|
||||
-----BEGIN ECHCONFIG-----
|
||||
Ah3+DQIZAAAgACAgiyA/dGsDu4MO8Qn4xriSBhRxhNmfQ5jtBGJg1o5VbQAEAAEAAQALZXhhbXBsZS5jb20B3//KAAD/ywAMaGVsbG8gd29ybGQK/8wBx4lQTkcNChoKAAAADUlIRFIAAAAKAAAABggDAAAAzS7/9AAAAARnQU1BAACxjwv8YQUAAAAgY0hSTQAAeiYAAICEAAD6AAAAgOgAAHUwAADqYAAAOpgAABdwnLpRPAAAAGxQTFRFAwMFAwQHAgMFAgEBDQgIDwkKDQcIEAkJAAABAgICAAECBgUJBgQEAQICAAEBAAAABgUFAgIDCQYHCgcIBgcMAgIEAQEBAQECCAUGBAMDBQMDCAcMBAQHDQgJAgMDBwcMAQIDBQYIAQED////go9xWgAAAAFiS0dEIypibDoAAAAJcEhZcwAAASwAAAEsAHOI6VIAAAAHdElNRQfnBAQMGAw/wJ0bAAAAQ0lEQVQI1wXBiQJAIBQEwJUOuVbo5Qjx/x9pBqhUrY11toFvO9MPI92EMC9rpKRtx6FFndknIS7ejE8hCRFJ4c2f8AdkNAPhOzEsxgAAACV0RVh0ZGF0ZTpjcmVhdGUAMjAyMy0wNC0wNFQxMjoxMTo1OSswMDowMAbaIaMAAAAldEVYdGRhdGU6bW9kaWZ5ADIwMjMtMDQtMDRUMTI6MTE6NTkrMDA6MDB3h5kfAAAAAElFTkSuQmCC
|
||||
-----END ECHCONFIG-----
|
||||
1691
test/ech_corrupt_test.c
Normal file
1691
test/ech_corrupt_test.c
Normal file
File diff suppressed because it is too large
Load diff
511
test/ech_test.c
511
test/ech_test.c
|
|
@ -26,13 +26,11 @@ static char *privkey = NULL;
|
|||
static char *rootcert = NULL;
|
||||
static int ch_test_cb_ok = 0;
|
||||
|
||||
/* TODO(ECH): add some testing of SSL_OP_ECH_IGNORE_CID */
|
||||
|
||||
/* ECH callback */
|
||||
static unsigned int ech_test_cb(SSL *s, const char *str)
|
||||
{
|
||||
if (verbose)
|
||||
TEST_info("ech_test_cb called");
|
||||
TEST_info("ech_test_cb called: str=\n%s", str);
|
||||
return 1;
|
||||
}
|
||||
|
||||
|
|
@ -171,6 +169,28 @@ static const char pem_kp1[] = "-----BEGIN PRIVATE KEY-----\n"
|
|||
"AQALZXhhbXBsZS5jb20AAA==\n"
|
||||
"-----END ECHCONFIG-----\n";
|
||||
|
||||
/*
|
||||
* x25519 ech key pair with public key front.server.example, used for
|
||||
* in_out test
|
||||
*/
|
||||
static const char pem_kp2[] = "-----BEGIN PRIVATE KEY-----\n"
|
||||
"MC4CAQAwBQYDK2VuBCIEIEjRn9R/gwDu11v6bLKaf0AGoe5Etl2g6nU1GdQLTHNe\n"
|
||||
"-----END PRIVATE KEY-----\n"
|
||||
"-----BEGIN ECHCONFIG-----\n"
|
||||
"AEf+DQBDvQAgACCr9pErR7E/gNeoni+0YpDZaMd7XN+hFnCN+H0Xnm1EHQAEAAEAAQAUZnJvbnQuc2VydmVyLmV4YW1wbGUAAA==\n"
|
||||
"-----END ECHCONFIG-----\n";
|
||||
static const char ec_kp2[] = "AEf+DQBDvQAgACCr9pErR7E/gNeoni+0YpDZaMd7XN+hFnCN+H0Xnm1EHQAEAAEAAQAUZnJvbnQuc2VydmVyLmV4YW1wbGUAAA==";
|
||||
static size_t ec_kp2len = sizeof(ec_kp2) - 1;
|
||||
|
||||
/* another, used in grease/retry-config tests, pn: f1.server.example */
|
||||
static const char pem_kp3[] = "-----BEGIN PRIVATE KEY-----\n"
|
||||
"MC4CAQAwBQYDK2VuBCIEIDDbFFGbdUUQgKJzx6zaqn0rE8Bi9DJQpkfbAL6HHwtu\n"
|
||||
"-----END PRIVATE KEY-----\n"
|
||||
"-----BEGIN ECHCONFIG-----\n"
|
||||
"AET+DQBAYQAgACAUpnXhRlufbhe61F02+NR7xVA3200ujwOp2JLivunoQAAEAAEA\n"
|
||||
"AQARZjEuc2VydmVyLmV4YW1wbGUAAA==\n"
|
||||
"-----END ECHCONFIG-----\n";
|
||||
|
||||
/* standard x25519 ECHConfigList with public key example.com */
|
||||
static const char pem_pk1[] = "-----BEGIN ECHCONFIG-----\n"
|
||||
"AD7+DQA6bAAgACCY7B0f/3KvHIFdoqFaObdU8YYU+MdBf4vzbLhAAL2QCwAEAAEA\n"
|
||||
|
|
@ -814,11 +834,11 @@ typedef struct FNT_T {
|
|||
} fnt_t;
|
||||
|
||||
static fnt_t fnames[] = {
|
||||
{ "ech-eg.pem", 1 },
|
||||
{ "ech-mid.pem", 1 },
|
||||
{ "ech-big.pem", 1 },
|
||||
{ "ech-giant.pem", 0 },
|
||||
{ "ech-rsa.pem", 0 },
|
||||
{ "echdir/ech-eg.pem", 1 },
|
||||
{ "echdir/ech-mid.pem", 1 },
|
||||
{ "echdir/ech-big.pem", 1 },
|
||||
{ "echdir/ech-giant.pem", 0 },
|
||||
{ "echdir/ech-rsa.pem", 0 },
|
||||
};
|
||||
|
||||
/* string from which we construct varieties of HPKE suite */
|
||||
|
|
@ -1087,9 +1107,6 @@ static int ech_api_basic_calls(void)
|
|||
SSL_CTX_ech_set_callback(NULL, NULL);
|
||||
SSL_ech_set_callback(NULL, NULL);
|
||||
if (!TEST_false(SSL_ech_get1_retry_config(NULL, NULL, NULL))
|
||||
|| !TEST_false(SSL_CTX_ech_raw_decrypt(NULL, NULL, NULL, NULL,
|
||||
NULL, 0, NULL, NULL,
|
||||
NULL, NULL))
|
||||
|| !TEST_int_eq(SSL_ech_get1_status(NULL, &rinner, &router),
|
||||
SSL_ECH_STATUS_FAILED))
|
||||
goto end;
|
||||
|
|
@ -1193,6 +1210,7 @@ end:
|
|||
#define OSSL_ECH_TEST_ENOE 4 /* early + no-ech */
|
||||
#define OSSL_ECH_TEST_CBS 5 /* test callbacks */
|
||||
#define OSSL_ECH_TEST_V12 6 /* test TLSv1.2 */
|
||||
#define OSSL_ECH_TEST_NO_INNER 7 /* test no inner SNI */
|
||||
/* note: early-data is prohibited after HRR so no tests for that */
|
||||
|
||||
/*
|
||||
|
|
@ -1239,6 +1257,12 @@ static int test_ech_roundtrip_helper(int idx, int combo)
|
|||
kdf_str_list[kdfind], aead_str_list[aeadind]);
|
||||
if (verbose)
|
||||
TEST_info("Doing: iter: %d, suite: %s", idx, suitestr);
|
||||
/*
|
||||
* Set a max name length just to exercise more code.
|
||||
* We may as well use the index, just to make it vary:-)
|
||||
*/
|
||||
if (combo == OSSL_ECH_TEST_NO_INNER)
|
||||
max_name_length = (idx % 256);
|
||||
if (!TEST_true(OSSL_HPKE_str2suite(suitestr, &hpke_suite))
|
||||
|| !TEST_ptr(es = OSSL_ECHSTORE_new(libctx, propq))
|
||||
|| !TEST_true(OSSL_ECHSTORE_new_config(es, ech_version, max_name_length,
|
||||
|
|
@ -1286,6 +1310,8 @@ static int test_ech_roundtrip_helper(int idx, int combo)
|
|||
if (combo != OSSL_ECH_TEST_ENOE
|
||||
&& !TEST_true(SSL_CTX_set1_echstore(cctx, es)))
|
||||
goto end;
|
||||
/* set callback for client, just to exercise code */
|
||||
SSL_CTX_ech_set_callback(cctx, ech_test_cb);
|
||||
if (!TEST_true(SSL_CTX_set1_echstore(sctx, es))
|
||||
|| !TEST_true(create_ssl_objects(sctx, cctx, &serverssl,
|
||||
&clientssl, NULL, NULL)))
|
||||
|
|
@ -1293,7 +1319,8 @@ static int test_ech_roundtrip_helper(int idx, int combo)
|
|||
if (combo == OSSL_ECH_TEST_HRR
|
||||
&& !TEST_true(SSL_set1_groups_list(serverssl, "P-384")))
|
||||
goto end;
|
||||
if (!TEST_true(SSL_set_tlsext_host_name(clientssl, "server.example")))
|
||||
if (combo != OSSL_ECH_TEST_NO_INNER
|
||||
&& !TEST_true(SSL_set_tlsext_host_name(clientssl, "server.example")))
|
||||
goto end;
|
||||
if (combo == OSSL_ECH_TEST_V12) {
|
||||
if (!TEST_false(create_ssl_connection(serverssl, clientssl,
|
||||
|
|
@ -1330,7 +1357,8 @@ static int test_ech_roundtrip_helper(int idx, int combo)
|
|||
goto end;
|
||||
/* all good */
|
||||
if (combo == OSSL_ECH_TEST_BASIC || combo == OSSL_ECH_TEST_HRR
|
||||
|| combo == OSSL_ECH_TEST_CUSTOM || combo == OSSL_ECH_TEST_CBS) {
|
||||
|| combo == OSSL_ECH_TEST_CUSTOM || combo == OSSL_ECH_TEST_CBS
|
||||
|| combo == OSSL_ECH_TEST_NO_INNER) {
|
||||
res = 1;
|
||||
goto end;
|
||||
}
|
||||
|
|
@ -1423,6 +1451,14 @@ static int test_ech_hrr(int idx)
|
|||
return test_ech_roundtrip_helper(idx, OSSL_ECH_TEST_HRR);
|
||||
}
|
||||
|
||||
/* ECH with no inner SNI for the given suite */
|
||||
static int test_ech_no_inner(int idx)
|
||||
{
|
||||
if (verbose)
|
||||
TEST_info("Doing: test_ech_no_inner");
|
||||
return test_ech_roundtrip_helper(idx, OSSL_ECH_TEST_NO_INNER);
|
||||
}
|
||||
|
||||
/* ECH with early data for the given suite */
|
||||
static int test_ech_early(int idx)
|
||||
{
|
||||
|
|
@ -1463,6 +1499,451 @@ static int ech_v12_test(int idx)
|
|||
return test_ech_roundtrip_helper(idx, OSSL_ECH_TEST_V12);
|
||||
}
|
||||
|
||||
/*
|
||||
* Test roundtrip with SNI/ALPN variations.
|
||||
* Inner and outer names can be supplied to SSL_CTX or SSL
|
||||
* connection via ECH APIs, and inner can be supplied via
|
||||
* the existing non-ECH API. We can specify that no outer
|
||||
* SNI at all be sent if we want. If an outer SNI value is
|
||||
* supplied via the ECH API then that over-rides the
|
||||
* public_name field from the ECHConfig, which in this
|
||||
* cases will be example.com. We have the option of setting
|
||||
* both inner, outer and no_outer setting via eiher:
|
||||
*
|
||||
* 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);
|
||||
*
|
||||
* So there's a bunch of cases to test, as usual we pick
|
||||
* between 'em using the idx parameter.
|
||||
*
|
||||
* idx : case
|
||||
* 0 : set no names via ECH APIs;
|
||||
* set inner to inner.example.com non-ECH API
|
||||
* expect public_name as outer
|
||||
* 1 : as for 0, but additionally:
|
||||
* set NULL and "no_outer" via set_outer API
|
||||
* 2 : as for 1, but additionally:
|
||||
* set non-NULL outer and "no_outer" via set_outer API
|
||||
* 3 : override outer via ECH API
|
||||
* 4 : like 1, but using set_server_names API
|
||||
* 5 : like 2, but using set_server_names API
|
||||
* 6 : like 3, but using set_server_names API
|
||||
* 7 : like 4, but overriding previous call to non-ECH SNI
|
||||
* 8 : like 5, but overriding previous call to non-ECH SNI
|
||||
* 9 : like 6, but overriding previous call to non-ECH SNI
|
||||
* 10 : like 7, but reversing call order
|
||||
* 11 : like 8, but reversing call order
|
||||
* 12 : like 9, but reversing call order
|
||||
* 13 : like 1, but with a NULL outer input to API
|
||||
* that's a bit pointless as it's more or less a NO-OP
|
||||
* but worth checking
|
||||
*/
|
||||
static int ech_in_out_test(int idx)
|
||||
{
|
||||
int res = 0, cres = 0, sres = 0, clientstatus, serverstatus;
|
||||
SSL_CTX *cctx = NULL, *sctx = NULL;
|
||||
SSL *clientssl = NULL, *serverssl = NULL;
|
||||
char *non_ech_sni = "trad.server.example"; /* SNI set via non-ECH API */
|
||||
char *supplied_inner = "inner.server.example"; /* inner set via ECH API */
|
||||
char *supplied_outer = "outer.server.example"; /* outer set via ECH API */
|
||||
char *public_name = "front.server.example"; /* we know that's inside pem_kp2 */
|
||||
char *cinner = NULL, *couter = NULL, *sinner = NULL, *souter = NULL;
|
||||
unsigned char alpn_inner[] = { /* "inner, secret, http/1.1" */
|
||||
0x05, 0x69, 0x6e, 0x6e, 0x65, 0x72,
|
||||
0x06, 0x73, 0x65, 0x63, 0x72, 0x65, 0x74,
|
||||
0x08, 0x68, 0x74, 0x74, 0x70, 0x2f, 0x31, 0x2e, 0x31
|
||||
};
|
||||
size_t alpn_inner_len = sizeof(alpn_inner);
|
||||
unsigned char alpn_outer[] = { /* "outer, public, h2" */
|
||||
0x05, 0x6f, 0x75, 0x74, 0x65, 0x72, 0x06, 0x70,
|
||||
0x75, 0x62, 0x6c, 0x69, 0x63, 0x02, 0x68, 0x32
|
||||
};
|
||||
size_t alpn_outer_len = sizeof(alpn_outer);
|
||||
char *expected_inner = NULL, *expected_outer = NULL;
|
||||
BIO *in = NULL;
|
||||
OSSL_ECHSTORE *es = NULL;
|
||||
|
||||
/* make an OSSL_ECHSTORE for pem_kp2 */
|
||||
if ((in = BIO_new(BIO_s_mem())) == NULL
|
||||
|| BIO_write(in, pem_kp2, (int)strlen(pem_kp2)) <= 0
|
||||
|| !TEST_ptr(es = OSSL_ECHSTORE_new(libctx, propq))
|
||||
|| !TEST_true(OSSL_ECHSTORE_read_pem(es, in, OSSL_ECH_FOR_RETRY))
|
||||
|| !TEST_true(create_ssl_ctx_pair(libctx, TLS_server_method(),
|
||||
TLS_client_method(),
|
||||
TLS1_3_VERSION, TLS1_3_VERSION,
|
||||
&sctx, &cctx, cert, privkey))
|
||||
|| !TEST_true(SSL_CTX_set1_echstore(sctx, es))
|
||||
|| !TEST_false(SSL_CTX_set_alpn_protos(cctx, alpn_inner,
|
||||
(unsigned int)alpn_inner_len))
|
||||
|| !TEST_true(SSL_CTX_ech_set1_outer_alpn_protos(cctx, alpn_outer,
|
||||
alpn_outer_len))
|
||||
|| !TEST_true(create_ssl_objects(sctx, cctx, &serverssl,
|
||||
&clientssl, NULL, NULL))
|
||||
|| !TEST_true(SSL_set1_ech_config_list(clientssl,
|
||||
(unsigned char *)ec_kp2,
|
||||
ec_kp2len)))
|
||||
goto end;
|
||||
if (idx == 0) {
|
||||
if (!TEST_true(SSL_set_tlsext_host_name(clientssl, non_ech_sni)))
|
||||
goto end;
|
||||
expected_inner = non_ech_sni;
|
||||
expected_outer = public_name;
|
||||
}
|
||||
if (idx == 1) {
|
||||
if (!TEST_true(SSL_set_tlsext_host_name(clientssl, non_ech_sni))
|
||||
|| !TEST_true(SSL_ech_set1_outer_server_name(clientssl, NULL, 1)))
|
||||
goto end;
|
||||
expected_inner = non_ech_sni;
|
||||
expected_outer = NULL;
|
||||
}
|
||||
if (idx == 2) {
|
||||
if (!TEST_true(SSL_set_tlsext_host_name(clientssl, non_ech_sni))
|
||||
|| !TEST_true(SSL_ech_set1_outer_server_name(clientssl, "blah", 1)))
|
||||
goto end;
|
||||
expected_inner = non_ech_sni;
|
||||
expected_outer = NULL;
|
||||
}
|
||||
if (idx == 3) {
|
||||
if (!TEST_true(SSL_set_tlsext_host_name(clientssl, non_ech_sni))
|
||||
|| !TEST_true(SSL_ech_set1_outer_server_name(clientssl,
|
||||
supplied_outer, 0))
|
||||
|| !TEST_true(SSL_ech_set1_outer_alpn_protos(clientssl, alpn_outer,
|
||||
alpn_outer_len)))
|
||||
goto end;
|
||||
expected_inner = non_ech_sni;
|
||||
expected_outer = supplied_outer;
|
||||
}
|
||||
if (idx == 4) {
|
||||
if (!TEST_true(SSL_ech_set1_server_names(clientssl,
|
||||
supplied_inner, NULL, 0)))
|
||||
goto end;
|
||||
expected_inner = supplied_inner;
|
||||
expected_outer = public_name;
|
||||
}
|
||||
if (idx == 5) {
|
||||
if (!TEST_true(SSL_ech_set1_server_names(clientssl, supplied_inner,
|
||||
"blah", 1)))
|
||||
goto end;
|
||||
expected_inner = supplied_inner;
|
||||
expected_outer = NULL;
|
||||
}
|
||||
if (idx == 6) {
|
||||
if (!TEST_true(SSL_ech_set1_server_names(clientssl, supplied_inner,
|
||||
supplied_outer, 0)))
|
||||
goto end;
|
||||
expected_inner = supplied_inner;
|
||||
expected_outer = supplied_outer;
|
||||
}
|
||||
if (idx == 7) {
|
||||
if (!TEST_true(SSL_set_tlsext_host_name(clientssl, "blah"))
|
||||
|| !TEST_true(SSL_ech_set1_server_names(clientssl, supplied_inner,
|
||||
NULL, 0)))
|
||||
goto end;
|
||||
expected_inner = supplied_inner;
|
||||
expected_outer = public_name;
|
||||
}
|
||||
if (idx == 8) {
|
||||
if (!TEST_true(SSL_set_tlsext_host_name(clientssl, "blah"))
|
||||
|| !TEST_true(SSL_ech_set1_server_names(clientssl, supplied_inner,
|
||||
"blah", 1)))
|
||||
goto end;
|
||||
expected_inner = supplied_inner;
|
||||
expected_outer = NULL;
|
||||
}
|
||||
if (idx == 9) {
|
||||
if (!TEST_true(SSL_set_tlsext_host_name(clientssl, "blah"))
|
||||
|| !TEST_true(SSL_ech_set1_server_names(clientssl, supplied_inner,
|
||||
supplied_outer, 0)))
|
||||
goto end;
|
||||
expected_inner = supplied_inner;
|
||||
expected_outer = supplied_outer;
|
||||
}
|
||||
if (idx == 10) {
|
||||
if (!TEST_true(SSL_ech_set1_server_names(clientssl,
|
||||
supplied_inner, NULL, 0))
|
||||
|| !TEST_true(SSL_set_tlsext_host_name(clientssl, non_ech_sni)))
|
||||
goto end;
|
||||
expected_inner = non_ech_sni;
|
||||
expected_outer = public_name;
|
||||
}
|
||||
if (idx == 11) {
|
||||
if (!TEST_true(SSL_ech_set1_server_names(clientssl, supplied_inner,
|
||||
"blah", 1))
|
||||
|| !TEST_true(SSL_set_tlsext_host_name(clientssl, non_ech_sni)))
|
||||
goto end;
|
||||
expected_inner = non_ech_sni;
|
||||
expected_outer = NULL;
|
||||
}
|
||||
if (idx == 12) {
|
||||
if (!TEST_true(SSL_ech_set1_server_names(clientssl, supplied_inner,
|
||||
supplied_outer, 0))
|
||||
|| !TEST_true(SSL_set_tlsext_host_name(clientssl, non_ech_sni)))
|
||||
goto end;
|
||||
expected_inner = non_ech_sni;
|
||||
expected_outer = supplied_outer;
|
||||
}
|
||||
if (idx == 13) {
|
||||
if (!TEST_true(SSL_set_tlsext_host_name(clientssl, non_ech_sni))
|
||||
|| !TEST_true(SSL_ech_set1_outer_server_name(clientssl, NULL, 0)))
|
||||
goto end;
|
||||
expected_inner = non_ech_sni;
|
||||
expected_outer = public_name;
|
||||
}
|
||||
if (!TEST_true(create_ssl_connection(serverssl, clientssl,
|
||||
SSL_ERROR_NONE)))
|
||||
goto end;
|
||||
serverstatus = SSL_ech_get1_status(serverssl, &sinner, &souter);
|
||||
if (!TEST_int_eq(serverstatus, SSL_ECH_STATUS_SUCCESS))
|
||||
goto end;
|
||||
SSL_set_verify_result(clientssl, X509_V_OK); /* override cert check */
|
||||
clientstatus = SSL_ech_get1_status(clientssl, &cinner, &couter);
|
||||
if (!TEST_int_eq(clientstatus, SSL_ECH_STATUS_SUCCESS))
|
||||
goto end;
|
||||
cres = sres = 0; /* check result vs. expected */
|
||||
if ((expected_inner == NULL && cinner == NULL)
|
||||
|| (expected_inner != NULL && cinner != NULL
|
||||
&& strlen(expected_inner) == strlen(cinner)
|
||||
&& strcmp(expected_inner, cinner) == 0))
|
||||
cres = 1;
|
||||
if (!TEST_int_eq(cres, 1))
|
||||
goto end;
|
||||
if ((expected_inner == NULL && sinner == NULL)
|
||||
|| (expected_inner != NULL && sinner != NULL
|
||||
&& strlen(expected_inner) == strlen(sinner)
|
||||
&& strcmp(expected_inner, sinner) == 0))
|
||||
sres = 1;
|
||||
if (!TEST_int_eq(sres, 1))
|
||||
goto end;
|
||||
cres = sres = 0;
|
||||
if ((expected_outer == NULL && couter == NULL)
|
||||
|| (expected_outer != NULL && couter != NULL
|
||||
&& strlen(expected_outer) == strlen(couter)
|
||||
&& strcmp(expected_outer, couter) == 0))
|
||||
cres = 1;
|
||||
if (!TEST_int_eq(cres, 1))
|
||||
goto end;
|
||||
if ((expected_outer == NULL && souter == NULL)
|
||||
|| (expected_outer != NULL && souter != NULL
|
||||
&& strlen(expected_outer) == strlen(souter)
|
||||
&& strcmp(expected_outer, souter) == 0))
|
||||
sres = 1;
|
||||
if (!TEST_int_eq(sres, 1))
|
||||
goto end;
|
||||
res = 1; /* all good */
|
||||
end:
|
||||
OSSL_ECHSTORE_free(es);
|
||||
BIO_free_all(in);
|
||||
OPENSSL_free(sinner);
|
||||
OPENSSL_free(souter);
|
||||
OPENSSL_free(cinner);
|
||||
OPENSSL_free(couter);
|
||||
SSL_free(clientssl);
|
||||
SSL_free(serverssl);
|
||||
SSL_CTX_free(cctx);
|
||||
SSL_CTX_free(sctx);
|
||||
return res;
|
||||
}
|
||||
|
||||
/* Test roundtrip with GREASE'd ECH, then again with retry-config */
|
||||
static int ech_grease_test(int idx)
|
||||
{
|
||||
int res = 0, clientstatus, serverstatus;
|
||||
SSL_CTX *cctx = NULL, *sctx = NULL;
|
||||
SSL *clientssl = NULL, *serverssl = NULL;
|
||||
char *cinner = NULL, *couter = NULL, *sinner = NULL, *souter = NULL;
|
||||
unsigned char *retryconfig = NULL;
|
||||
size_t retryconfiglen = 0;
|
||||
X509_STORE *ch = NULL;
|
||||
OSSL_ECHSTORE *es = NULL;
|
||||
BIO *in;
|
||||
|
||||
/* make OSSL_ECHSTORE vars for pem_kp2 */
|
||||
if ((in = BIO_new(BIO_s_mem())) == NULL
|
||||
|| BIO_write(in, pem_kp2, (int)strlen(pem_kp2)) <= 0
|
||||
|| !TEST_ptr(es = OSSL_ECHSTORE_new(libctx, propq))
|
||||
|| !TEST_true(OSSL_ECHSTORE_read_pem(es, in, OSSL_ECH_FOR_RETRY)))
|
||||
goto end;
|
||||
if (idx == 2) {
|
||||
/*
|
||||
* In our last test iteration set various other ECH configs, to make
|
||||
* for a bigger retry-config. (It's ok that we set the same key pair
|
||||
* a few times here.)
|
||||
*/
|
||||
BIO_free_all(in);
|
||||
in = NULL;
|
||||
if ((in = BIO_new(BIO_s_mem())) == NULL
|
||||
|| BIO_write(in, pem_kp3, (int)strlen(pem_kp3)) <= 0
|
||||
|| !TEST_true(OSSL_ECHSTORE_read_pem(es, in, OSSL_ECH_NO_RETRY)))
|
||||
goto end;
|
||||
BIO_free_all(in);
|
||||
in = NULL;
|
||||
if ((in = BIO_new(BIO_s_mem())) == NULL
|
||||
|| BIO_write(in, pem_kp3, (int)strlen(pem_kp3)) <= 0
|
||||
|| !TEST_true(OSSL_ECHSTORE_read_pem(es, in, OSSL_ECH_FOR_RETRY)))
|
||||
goto end;
|
||||
BIO_free_all(in);
|
||||
in = NULL;
|
||||
if ((in = BIO_new(BIO_s_mem())) == NULL
|
||||
|| BIO_write(in, pem_kp3, (int)strlen(pem_kp3)) <= 0
|
||||
|| !TEST_true(OSSL_ECHSTORE_read_pem(es, in, OSSL_ECH_NO_RETRY)))
|
||||
goto end;
|
||||
}
|
||||
BIO_free_all(in);
|
||||
in = NULL;
|
||||
|
||||
if (!TEST_true(create_ssl_ctx_pair(libctx, TLS_server_method(),
|
||||
TLS_client_method(),
|
||||
TLS1_3_VERSION, TLS1_3_VERSION,
|
||||
&sctx, &cctx, cert, privkey)))
|
||||
goto end;
|
||||
if (!TEST_true(SSL_CTX_set1_echstore(sctx, es)))
|
||||
goto end;
|
||||
|
||||
/* set the client GREASE flag via SSL_CTX 1st time, and via SSL* 2nd */
|
||||
if (idx == 0 && !TEST_true(SSL_CTX_set_options(cctx, SSL_OP_ECH_GREASE)))
|
||||
goto end;
|
||||
if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl,
|
||||
&clientssl, NULL, NULL)))
|
||||
goto end;
|
||||
if (!TEST_true(SSL_set_tlsext_host_name(clientssl, "back.server.example")))
|
||||
goto end;
|
||||
|
||||
/* set the GREASE flag via SSL_CTX 1st time, and via SSL* 2nd & 3rd */
|
||||
if (idx >= 1 && !TEST_true(SSL_set_options(clientssl, SSL_OP_ECH_GREASE)))
|
||||
goto end;
|
||||
/* 3rd time, fail to set a bad grease suite, then set a good one */
|
||||
if (idx == 2 && !TEST_false(SSL_ech_set1_grease_suite(clientssl, "notanhpkesuite")))
|
||||
goto end;
|
||||
if (idx == 2 && !TEST_true(SSL_ech_set1_grease_suite(clientssl, "x25519,2,3")))
|
||||
goto end;
|
||||
if (!TEST_true(create_ssl_connection(serverssl, clientssl,
|
||||
SSL_ERROR_NONE)))
|
||||
goto end;
|
||||
serverstatus = SSL_ech_get1_status(serverssl, &sinner, &souter);
|
||||
if (verbose)
|
||||
TEST_info("ech_grease_test: server status %d, %s, %s",
|
||||
serverstatus, sinner, souter);
|
||||
if (!TEST_int_eq(serverstatus, SSL_ECH_STATUS_GREASE))
|
||||
goto end;
|
||||
/* override cert verification */
|
||||
SSL_set_verify_result(clientssl, X509_V_OK);
|
||||
clientstatus = SSL_ech_get1_status(clientssl, &cinner, &couter);
|
||||
if (verbose)
|
||||
TEST_info("ech_grease_test: client status %d, %s, %s",
|
||||
clientstatus, cinner, couter);
|
||||
if (!TEST_int_eq(clientstatus, SSL_ECH_STATUS_GREASE_ECH))
|
||||
goto end;
|
||||
if (!TEST_true(SSL_ech_get1_retry_config(clientssl, &retryconfig,
|
||||
&retryconfiglen)))
|
||||
goto end;
|
||||
if (!TEST_ptr(retryconfig))
|
||||
goto end;
|
||||
if (!TEST_int_ne((int)retryconfiglen, 0))
|
||||
goto end;
|
||||
if (verbose)
|
||||
TEST_info("ech_grease_test: retryconfglen: %d\n", (int)retryconfiglen);
|
||||
/* we kow the sizes to expect as the configs are hard-coded above */
|
||||
if (idx == 2 && !TEST_size_t_eq(retryconfiglen, 141))
|
||||
goto end;
|
||||
if (idx < 2 && !TEST_size_t_eq(retryconfiglen, 73))
|
||||
goto end;
|
||||
/* cleanup */
|
||||
OPENSSL_free(sinner);
|
||||
OPENSSL_free(souter);
|
||||
OPENSSL_free(cinner);
|
||||
OPENSSL_free(couter);
|
||||
sinner = souter = cinner = couter = NULL;
|
||||
SSL_shutdown(clientssl);
|
||||
SSL_shutdown(serverssl);
|
||||
SSL_free(serverssl);
|
||||
SSL_free(clientssl);
|
||||
serverssl = clientssl = NULL;
|
||||
|
||||
/* second connection */
|
||||
if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl,
|
||||
&clientssl, NULL, NULL)))
|
||||
goto end;
|
||||
/* setting an ECHConfig should over-ride GREASE flag */
|
||||
if (!TEST_true(SSL_set1_ech_config_list(clientssl, retryconfig,
|
||||
retryconfiglen)))
|
||||
goto end;
|
||||
if (!TEST_true(SSL_set_tlsext_host_name(clientssl, "server.example")))
|
||||
goto end;
|
||||
if (!TEST_true(create_ssl_connection(serverssl, clientssl,
|
||||
SSL_ERROR_NONE)))
|
||||
goto end;
|
||||
serverstatus = SSL_ech_get1_status(serverssl, &sinner, &souter);
|
||||
if (verbose)
|
||||
TEST_info("server status %d, %s, %s", serverstatus, sinner, souter);
|
||||
if (!TEST_int_eq(serverstatus, SSL_ECH_STATUS_SUCCESS))
|
||||
goto end;
|
||||
/* override cert verification */
|
||||
SSL_set_verify_result(clientssl, X509_V_OK);
|
||||
clientstatus = SSL_ech_get1_status(clientssl, &cinner, &couter);
|
||||
if (verbose)
|
||||
TEST_info("client status %d, %s, %s", clientstatus, cinner, couter);
|
||||
if (!TEST_int_eq(clientstatus, SSL_ECH_STATUS_SUCCESS))
|
||||
goto end;
|
||||
/* 3rd connection - this time grease+HRR which had a late fail */
|
||||
OPENSSL_free(sinner);
|
||||
OPENSSL_free(souter);
|
||||
OPENSSL_free(cinner);
|
||||
OPENSSL_free(couter);
|
||||
sinner = souter = cinner = couter = NULL;
|
||||
SSL_shutdown(clientssl);
|
||||
SSL_shutdown(serverssl);
|
||||
SSL_free(serverssl);
|
||||
SSL_free(clientssl);
|
||||
serverssl = clientssl = NULL;
|
||||
if (!TEST_true(create_ssl_objects(sctx, cctx, &serverssl,
|
||||
&clientssl, NULL, NULL)))
|
||||
goto end;
|
||||
/* force ECH+HRR */
|
||||
if (!TEST_true(SSL_set_options(clientssl, SSL_OP_ECH_GREASE)))
|
||||
goto end;
|
||||
/* setting an ECHConfig should over-ride GREASE flag */
|
||||
if (!TEST_true(SSL_set1_ech_config_list(clientssl, retryconfig,
|
||||
retryconfiglen)))
|
||||
goto end;
|
||||
if (!TEST_true(SSL_set1_groups_list(serverssl, "P-384")))
|
||||
goto end;
|
||||
if (!TEST_true(SSL_set_tlsext_host_name(clientssl, "server.example")))
|
||||
goto end;
|
||||
if (!TEST_true(create_ssl_connection(serverssl, clientssl,
|
||||
SSL_ERROR_NONE)))
|
||||
goto end;
|
||||
serverstatus = SSL_ech_get1_status(serverssl, &sinner, &souter);
|
||||
if (verbose)
|
||||
TEST_info("server status %d, %s, %s", serverstatus, sinner, souter);
|
||||
if (!TEST_int_eq(serverstatus, SSL_ECH_STATUS_SUCCESS))
|
||||
goto end;
|
||||
/* override cert verification */
|
||||
SSL_set_verify_result(clientssl, X509_V_OK);
|
||||
clientstatus = SSL_ech_get1_status(clientssl, &cinner, &couter);
|
||||
if (verbose)
|
||||
TEST_info("client status %d, %s, %s", clientstatus, cinner, couter);
|
||||
if (!TEST_int_eq(clientstatus, SSL_ECH_STATUS_SUCCESS))
|
||||
goto end;
|
||||
/* all good */
|
||||
res = 1;
|
||||
end:
|
||||
OPENSSL_free(sinner);
|
||||
OPENSSL_free(souter);
|
||||
OPENSSL_free(cinner);
|
||||
OPENSSL_free(couter);
|
||||
OPENSSL_free(retryconfig);
|
||||
SSL_free(clientssl);
|
||||
SSL_free(serverssl);
|
||||
X509_STORE_free(ch);
|
||||
SSL_CTX_free(cctx);
|
||||
SSL_CTX_free(sctx);
|
||||
OSSL_ECHSTORE_free(es);
|
||||
BIO_free_all(in);
|
||||
return res;
|
||||
}
|
||||
|
||||
#endif
|
||||
|
||||
int setup_tests(void)
|
||||
|
|
@ -1508,7 +1989,9 @@ int setup_tests(void)
|
|||
ADD_ALL_TESTS(ech_enoe_test, suite_combos);
|
||||
ADD_ALL_TESTS(ech_cb_test, suite_combos);
|
||||
ADD_ALL_TESTS(ech_v12_test, suite_combos);
|
||||
/* TODO(ECH): add more test code as other PRs done */
|
||||
ADD_ALL_TESTS(ech_in_out_test, 14);
|
||||
ADD_ALL_TESTS(ech_grease_test, 3);
|
||||
ADD_ALL_TESTS(test_ech_no_inner, suite_combos);
|
||||
return 1;
|
||||
err:
|
||||
return 0;
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ plan skip_all => "ECH tests not supported in this build"
|
|||
if disabled("ech") || disabled("tls1_3")
|
||||
|| disabled("ec") || disabled("ecx");
|
||||
|
||||
plan tests => 13;
|
||||
plan tests => 16;
|
||||
|
||||
ok(run(app(["openssl", "ech", "-help"])),
|
||||
"Run openssl ech with help");
|
||||
|
|
@ -90,4 +90,17 @@ with({ exit_checker => sub { return shift == 1; } },
|
|||
"-in", "eg3.pem",
|
||||
"-in", "eg4.pem"])),
|
||||
"Too many input files");
|
||||
ok(run(app(["openssl", "ech" ])),
|
||||
"No input files");
|
||||
ok(run(app(["openssl", "ech",
|
||||
"-public_name", "example.com",
|
||||
"-out", ""
|
||||
])),
|
||||
"(Fail to) Generate an ECH key pair to empty output file name");
|
||||
ok(run(app(["openssl", "ech",
|
||||
"-in", "eg1.pem",
|
||||
"-in", "eg2.pem",
|
||||
"-out", "",
|
||||
"-verbose"])),
|
||||
"Fail to catenate due to empty output file name");
|
||||
});
|
||||
|
|
|
|||
|
|
@ -1,5 +1,5 @@
|
|||
#! /usr/bin/env perl
|
||||
# Copyright 2022 The OpenSSL Project Authors. All Rights Reserved.
|
||||
# Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
|
||||
# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
|
|
|
|||
25
test/recipes/30-test_ech_corrupt.t
Normal file
25
test/recipes/30-test_ech_corrupt.t
Normal file
|
|
@ -0,0 +1,25 @@
|
|||
#! /usr/bin/env perl
|
||||
# Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
|
||||
# Copyright (c) 2022, Oracle and/or its affiliates. All rights reserved.
|
||||
#
|
||||
# Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
# this file except in compliance with the License. You can obtain a copy
|
||||
# in the file LICENSE in the source distribution or at
|
||||
# https://www.openssl.org/source/license.html
|
||||
|
||||
use strict;
|
||||
use OpenSSL::Test::Utils;
|
||||
use OpenSSL::Test qw/:DEFAULT srctop_file srctop_dir bldtop_dir bldtop_file/;
|
||||
|
||||
setup("test_ech_corrupt");
|
||||
|
||||
# Seeing tls1_2 below may be unexpected but we include a test case
|
||||
# where the inner CH is TLSv1.2 and the outer is TLSv1.3, but we
|
||||
# don't get the expected error in builds where TLSv1.2 is not supported
|
||||
# so we'll skip those
|
||||
plan skip_all => "ECH tests not supported in this build"
|
||||
if disabled("ech") || disabled("tls1_2") || disabled("tls1_3") || disabled("ec") || disabled("ecx");
|
||||
|
||||
plan tests => 1;
|
||||
|
||||
ok(run(test(["ech_corrupt_test", srctop_dir("test", "certs")])))
|
||||
|
|
@ -25,6 +25,17 @@ plan skip_all => "QUIC protocol is not supported by this OpenSSL build"
|
|||
plan skip_all => "These tests are not supported in a fuzz build"
|
||||
if config('options') =~ /-DFUZZING_BUILD_MODE_UNSAFE_FOR_PRODUCTION|enable-fuzz-afl/;
|
||||
|
||||
# When we support ECH, the ECH compression scheme affects the ordering
|
||||
# of extensions in the ClientHello as the set of compressed extensions
|
||||
# need to be contiguous in the outer ClientHello. We would need different
|
||||
# trace files to compare against for this test in ECH builds vs. 'no-ech'
|
||||
# buiids, so we'll just skip this test in 'no-ech' builds..
|
||||
# Note that that ordering will be affected if the ECH compression
|
||||
# choices are changed - see the comments in ssl/statem/extensions.c
|
||||
# where those choices are embedded in the ext_defs table.
|
||||
plan skip_all => "QUIC API trace tests aren't done in no-ech builds"
|
||||
if disabled('ech');
|
||||
|
||||
plan tests =>
|
||||
($no_fips ? 0 : 1) # quicapitest with fips
|
||||
+ 1; # quicapitest with default provider
|
||||
|
|
|
|||
|
|
@ -10,7 +10,7 @@ use strict;
|
|||
use warnings;
|
||||
|
||||
use IPC::Open3;
|
||||
use OpenSSL::Test qw/:DEFAULT srctop_file bldtop_file/;
|
||||
use OpenSSL::Test qw/:DEFAULT srctop_file srctop_dir bldtop_file/;
|
||||
use OpenSSL::Test::Utils;
|
||||
use Symbol 'gensym';
|
||||
|
||||
|
|
@ -23,6 +23,8 @@ my $s_client_match = 0;
|
|||
my $test_name = "test_ech_client_server";
|
||||
setup($test_name);
|
||||
|
||||
plan skip_all => "$test_name requires ECH"
|
||||
if disabled("ech");
|
||||
plan skip_all => "$test_name requires EC cryptography"
|
||||
if disabled("ec") || disabled("ecx");
|
||||
plan skip_all => "$test_name requires sock enabled"
|
||||
|
|
@ -32,16 +34,17 @@ plan skip_all => "$test_name requires TLSv1.3 enabled"
|
|||
plan skip_all => "$test_name is not available Windows or VMS"
|
||||
if $^O =~ /^(VMS|MSWin32|msys)$/;
|
||||
|
||||
plan tests => 18;
|
||||
plan tests => 22;
|
||||
|
||||
my $shlib_wrap = bldtop_file("util", "shlib_wrap.sh");
|
||||
my $apps_openssl = bldtop_file("apps", "openssl");
|
||||
|
||||
my $echconfig_pem = srctop_file("test", "certs", "ech-eg.pem");
|
||||
my $badconfig_pem = srctop_file("test", "certs", "ech-mid.pem");
|
||||
my $echconfig_pem = srctop_file("test", "certs", "echdir", "ech-eg.pem");
|
||||
my $badconfig_pem = srctop_file("test", "certs", "echdir", "ech-mid.pem");
|
||||
my $server_pem = srctop_file("test", "certs", "echserver.pem");
|
||||
my $server_key = srctop_file("test", "certs", "echserver.key");
|
||||
my $root_pem = srctop_file("test", "certs", "rootcert.pem");
|
||||
my $ech_dir = srctop_dir("test", "certs", "echdir" );
|
||||
|
||||
sub extract_ecl()
|
||||
{
|
||||
|
|
@ -88,6 +91,15 @@ sub start_ech_client_server
|
|||
"-servername", "example.com",
|
||||
"-ech_trialdecrypt",
|
||||
"-tls1_3");
|
||||
} elsif ($test_type eq "keydir" ) {
|
||||
# load keys from key dir (some will fail)
|
||||
@s_server_cmd = ("s_server", "-accept", "0", "-naccept", "1",
|
||||
"-cert", $server_pem, "-key", $server_key,
|
||||
"-cert2", $server_pem, "-key2", $server_key,
|
||||
"-ech_dir", $ech_dir,
|
||||
"-ech_noretry_dir", $ech_dir,
|
||||
"-servername", "example.com",
|
||||
"-tls1_3");
|
||||
} else {
|
||||
# default for all other tests (for now)
|
||||
@s_server_cmd = ("s_server", "-accept", "0", "-naccept", "1",
|
||||
|
|
@ -117,6 +129,8 @@ sub start_ech_client_server
|
|||
last;
|
||||
} elsif (/^Using default/) {
|
||||
;
|
||||
} elsif (/^Added (\d+) ECH/) {
|
||||
;
|
||||
} elsif (/^Added ECH key pair/) {
|
||||
;
|
||||
} elsif (/^Loaded/) {
|
||||
|
|
@ -134,13 +148,21 @@ sub start_ech_client_server
|
|||
# -prexit
|
||||
my @s_client_cmd;
|
||||
if ($test_type eq "GREASE-suite" ) {
|
||||
# GREASE
|
||||
# GREASE with suite
|
||||
@s_client_cmd = ("s_client",
|
||||
"-connect", "localhost:$s_server_port",
|
||||
"-servername", "server.example",
|
||||
"-CAfile", $root_pem,
|
||||
"-ech_grease_suite", "0x21,2,3",
|
||||
"-prexit");
|
||||
} elsif ($test_type eq "bad-GREASE-suite" ) {
|
||||
# bad GREASE suite
|
||||
@s_client_cmd = ("s_client",
|
||||
"-connect", "localhost:$s_server_port",
|
||||
"-servername", "server.example",
|
||||
"-CAfile", $root_pem,
|
||||
"-ech_grease_suite", "thisisnotagoodone",
|
||||
"-prexit");
|
||||
} elsif ($test_type eq "lots-of-options" ) {
|
||||
# real ECH with lots of options
|
||||
@s_client_cmd = ("s_client",
|
||||
|
|
@ -154,7 +176,7 @@ sub start_ech_client_server
|
|||
"-ech_outer_alpn", "http451",
|
||||
"-prexit");
|
||||
} elsif ($test_type eq "GREASE-type" ) {
|
||||
# GREASE with suite
|
||||
# GREASE with type
|
||||
@s_client_cmd = ("s_client",
|
||||
"-connect", "localhost:$s_server_port",
|
||||
"-servername", "server.example",
|
||||
|
|
@ -280,6 +302,16 @@ sub grease_suite_test {
|
|||
ok($s_client_match == 1, "s_client with GREASE-suite ECH");
|
||||
}
|
||||
|
||||
sub bad_grease_suite_test {
|
||||
print("\n\nGREASE suite ECHConfig test.\n");
|
||||
my $tt="bad-GREASE-suite";
|
||||
my $win="^ECH: NOT CONFIGURED";
|
||||
start_ech_client_server($tt, $win);
|
||||
ok($s_server_port ne "0", "s_server port check");
|
||||
print("s_server ready, on port $s_server_port pid: $s_server_pid\n");
|
||||
ok($s_client_match == 1, "s_client with bad GREASE-suite ECH");
|
||||
}
|
||||
|
||||
sub grease_type_test {
|
||||
print("\n\nGREASE type ECH test.\n");
|
||||
my $tt="GREASE-type";
|
||||
|
|
@ -330,13 +362,25 @@ sub cid_wrong_test {
|
|||
ok($s_client_match == 1, "s_client/s_server with no CID/no trial decrypt");
|
||||
}
|
||||
|
||||
sub keydir_test {
|
||||
print("\n\nServer using key dir test.\n");
|
||||
my $tt = "keydir";
|
||||
my $win = "^ECH: success";
|
||||
start_ech_client_server($tt, $win);
|
||||
ok($s_server_port ne "0", "s_server port check");
|
||||
print("s_server ready, on port $s_server_port pid: $s_server_pid\n");
|
||||
ok($s_client_match == 1, "s_server using ech keydir on command line");
|
||||
}
|
||||
|
||||
basic_test();
|
||||
wrong_test();
|
||||
grease_test();
|
||||
grease_suite_test();
|
||||
bad_grease_suite_test();
|
||||
grease_type_test();
|
||||
lots_of_options_test();
|
||||
no_outer_test();
|
||||
cid_free_test();
|
||||
cid_wrong_test();
|
||||
keydir_test();
|
||||
|
||||
|
|
|
|||
29
test/recipes/95-test_external_ech_bssl.t
Normal file
29
test/recipes/95-test_external_ech_bssl.t
Normal file
|
|
@ -0,0 +1,29 @@
|
|||
#! /usr/bin/env perl
|
||||
# Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
# this file except in compliance with the License. You can obtain a copy
|
||||
# in the file LICENSE in the source distribution or at
|
||||
# https://www.openssl.org/source/license.html
|
||||
|
||||
|
||||
use OpenSSL::Test;
|
||||
use OpenSSL::Test::Utils;
|
||||
use OpenSSL::Test qw/:DEFAULT data_file bldtop_dir srctop_dir cmdstr/;
|
||||
|
||||
setup("test_external_ech_bssl");
|
||||
|
||||
plan skip_all => "No external tests in this configuration"
|
||||
if disabled("external-tests");
|
||||
plan skip_all => "External ECH tests not available on Windows or VMS"
|
||||
if $^O =~ /^(VMS|MSWin32)$/;
|
||||
plan skip_all => "External ECH tests not supported in out of tree builds"
|
||||
if bldtop_dir() ne srctop_dir();
|
||||
|
||||
plan tests => 2;
|
||||
|
||||
ok(run(cmd(["sh", data_file("ech_bssl_external.sh")])),
|
||||
"running ECH client external boringssl tests");
|
||||
|
||||
ok(run(cmd(["sh", data_file("ech_bssl_server_external.sh")])),
|
||||
"running ECH server external boringssl tests");
|
||||
101
test/recipes/95-test_external_ech_bssl_data/ech_bssl_external.sh
Executable file
101
test/recipes/95-test_external_ech_bssl_data/ech_bssl_external.sh
Executable file
|
|
@ -0,0 +1,101 @@
|
|||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
# this file except in compliance with the License. You can obtain a copy
|
||||
# in the file LICENSE in the source distribution or at
|
||||
# https://www.openssl.org/source/license.html
|
||||
|
||||
# OpenSSL ECH external testing using boringssl client
|
||||
|
||||
PWD="$(pwd)"
|
||||
|
||||
SRCTOP="$(cd $SRCTOP; pwd)"
|
||||
BLDTOP="$(cd $BLDTOP; pwd)"
|
||||
|
||||
if [ "$SRCTOP" != "$BLDTOP" ] ; then
|
||||
echo "Out of tree builds not supported with ECH external test!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
O_EXE="$BLDTOP/apps"
|
||||
O_BINC="$BLDTOP/include"
|
||||
O_SINC="$SRCTOP/include"
|
||||
O_LIB="$BLDTOP"
|
||||
|
||||
unset OPENSSL_CONF
|
||||
|
||||
export PATH="$O_EXE:$PATH"
|
||||
export LD_LIBRARY_PATH="$O_LIB:$LD_LIBRARY_PATH"
|
||||
export OPENSSL_ROOT_DIR="$O_LIB"
|
||||
|
||||
# Check/Set openssl version
|
||||
OPENSSL_VERSION=`openssl version | cut -f 2 -d ' '`
|
||||
ECHCONFIGFILE=$SRCTOP/test/certs/echdir/ech-eg.pem
|
||||
httphost=server.example
|
||||
httpreq="GET /stats HTTP/1.1\\r\\nConnection: close\\r\\nHost: $httphost\\r\\n\\r\\n"
|
||||
BTOOL=$SRCTOP/boringssl/.local/bin
|
||||
|
||||
echo "------------------------------------------------------------------"
|
||||
echo "Testing OpenSSL s_server using ECH-enabled boringssl client:"
|
||||
echo " CWD: $PWD"
|
||||
echo " SRCTOP: $SRCTOP"
|
||||
echo " BLDTOP: $BLDTOP"
|
||||
echo " OPENSSL_ROOT_DIR: $OPENSSL_ROOT_DIR"
|
||||
echo " OpenSSL version: $OPENSSL_VERSION"
|
||||
echo " PEM ECH Config file: $ECHCONFIGFILE"
|
||||
|
||||
echo "------------------------------------------------------------------"
|
||||
|
||||
if [ ! -d $SRCTOP/boringssl ]; then
|
||||
mkdir -p $SRCTOP/boringssl
|
||||
fi
|
||||
if [ ! -d $SRCTOP/boringssl/.local ]; then
|
||||
(
|
||||
cd $SRCTOP \
|
||||
&& git clone https://boringssl.googlesource.com/boringssl \
|
||||
&& cd boringssl \
|
||||
&& mkdir build \
|
||||
&& cd build \
|
||||
&& cmake -DOPENSSL_ROOT_DIR=$OPENSSL_ROOT_DIR -DCMAKE_INSTALL_PREFIX=$SRCTOP/boringssl/.local .. \
|
||||
&& make \
|
||||
&& make install
|
||||
)
|
||||
fi
|
||||
|
||||
echo " CWD: $PWD"
|
||||
|
||||
# Start an openssl s_server
|
||||
$SRCTOP/apps/openssl s_server \
|
||||
-key $SRCTOP/test/certs/echserver.key -cert $SRCTOP/test/certs/echserver.pem \
|
||||
-key2 $SRCTOP/test/certs/echserver.key -cert2 $SRCTOP/test/certs/echserver.pem \
|
||||
-CAfile $SRCTOP/test/certs/rootcert.pem \
|
||||
-ech_key $ECHCONFIGFILE -port 8443 -tls1_3 -WWW \
|
||||
-naccept 1 -ign_eof -servername server.example &
|
||||
pids=`ps -ef | grep s_server | grep -v grep | awk '{print $2}'`
|
||||
if [ -z "$pids" ]
|
||||
then
|
||||
echo "No sign of s_server - exiting (before client)"
|
||||
exit 88
|
||||
fi
|
||||
# We want the binary ECHConfigList for bssl so extract that
|
||||
# from the PEM file
|
||||
bechfile=`mktemp`
|
||||
resfile=`mktemp`
|
||||
cat $ECHCONFIGFILE \
|
||||
| awk '/^-----BEGIN ECHCONFIG/,/^-----END ECHCONFIG/' \
|
||||
| tail -n+2 | head -n+2 | tr -d '\n' | base64 -d >$bechfile
|
||||
echo "Running bssl s_client against localhost"
|
||||
(echo -e $httpreq ; sleep 2) | \
|
||||
$BTOOL/bssl s_client -connect localhost:8443 \
|
||||
-ech-config-list $bechfile \
|
||||
-server-name $httphost \
|
||||
-root-certs $SRCTOP/test/certs/rootcert.pem > $resfile 2>&1
|
||||
rm -f $bechfile
|
||||
cat $resfile
|
||||
success=`grep -c "Encrypted ClientHello: yes" $resfile`
|
||||
rm -f $resfile
|
||||
# if success==1 we're good so exit with a zero for test success
|
||||
exit $((success != 1))
|
||||
107
test/recipes/95-test_external_ech_bssl_data/ech_bssl_server_external.sh
Executable file
107
test/recipes/95-test_external_ech_bssl_data/ech_bssl_server_external.sh
Executable file
|
|
@ -0,0 +1,107 @@
|
|||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
# this file except in compliance with the License. You can obtain a copy
|
||||
# in the file LICENSE in the source distribution or at
|
||||
# https://www.openssl.org/source/license.html
|
||||
|
||||
# OpenSSL ECH external testing using boringssl server
|
||||
|
||||
PWD="$(pwd)"
|
||||
|
||||
SRCTOP="$(cd $SRCTOP; pwd)"
|
||||
BLDTOP="$(cd $BLDTOP; pwd)"
|
||||
|
||||
if [ "$SRCTOP" != "$BLDTOP" ] ; then
|
||||
echo "Out of tree builds not supported with ECH external test!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
O_EXE="$BLDTOP/apps"
|
||||
O_BINC="$BLDTOP/include"
|
||||
O_SINC="$SRCTOP/include"
|
||||
O_LIB="$BLDTOP"
|
||||
|
||||
unset OPENSSL_CONF
|
||||
|
||||
export PATH="$O_EXE:$PATH"
|
||||
export LD_LIBRARY_PATH="$O_LIB:$LD_LIBRARY_PATH"
|
||||
export OPENSSL_ROOT_DIR="$O_LIB"
|
||||
|
||||
# Check/Set openssl version
|
||||
OPENSSL_VERSION=`openssl version | cut -f 2 -d ' '`
|
||||
ECHCONFIGFILE=$SRCTOP/test/certs/echdir/ech-eg.pem
|
||||
httphost=server.example
|
||||
httpreq="GET /stats HTTP/1.1\\r\\nConnection: close\\r\\nHost: $httphost\\r\\n\\r\\n"
|
||||
BTOOL=$SRCTOP/boringssl/.local/bin
|
||||
|
||||
echo "------------------------------------------------------------------"
|
||||
echo "Testing ECH-enabled boringssl server using s_client:"
|
||||
echo " CWD: $PWD"
|
||||
echo " SRCTOP: $SRCTOP"
|
||||
echo " BLDTOP: $BLDTOP"
|
||||
echo " OPENSSL_ROOT_DIR: $OPENSSL_ROOT_DIR"
|
||||
echo " OpenSSL version: $OPENSSL_VERSION"
|
||||
echo " PEM ECH Config file: $ECHCONFIGFILE"
|
||||
|
||||
echo "------------------------------------------------------------------"
|
||||
|
||||
if [ ! -d $SRCTOP/boringssl ]; then
|
||||
mkdir -p $SRCTOP/boringssl
|
||||
fi
|
||||
if [ ! -d $SRCTOP/boringssl/.local ]; then
|
||||
(
|
||||
cd $SRCTOP \
|
||||
&& git clone https://boringssl.googlesource.com/boringssl \
|
||||
&& cd boringssl \
|
||||
&& mkdir build \
|
||||
&& cd build \
|
||||
&& cmake -DOPENSSL_ROOT_DIR=$OPENSSL_ROOT_DIR -DCMAKE_INSTALL_PREFIX=$SRCTOP/boringssl/.local .. \
|
||||
&& make \
|
||||
&& make install
|
||||
)
|
||||
fi
|
||||
|
||||
echo " CWD: $PWD"
|
||||
|
||||
bssllist=`mktemp`
|
||||
bsslech=`mktemp`
|
||||
bsslkey=`mktemp`
|
||||
bsslpem=`mktemp`
|
||||
echo "Generating ECH keys for a bssl s_server."
|
||||
$BTOOL/bssl generate-ech -out-ech-config-list $bssllist \
|
||||
-out-ech-config $bsslech -out-private-key $bsslkey \
|
||||
-public-name example.com -config-id 222 -max-name-length 0
|
||||
res=$?
|
||||
# the b64 form is friendlier for s_client
|
||||
cat $bssllist | base64 -w0 >$bsslpem
|
||||
|
||||
# Start a boringssl s_server
|
||||
$BTOOL/bssl s_server \
|
||||
-accept 8443 \
|
||||
-key $SRCTOP/test/certs/echserver.key -cert $SRCTOP/test/certs/echserver.pem \
|
||||
-ech-config $bsslech -ech-key $bsslkey \
|
||||
-www -loop &
|
||||
pids=`ps -ef | grep 'bssl s_server' | grep -v grep | awk '{print $2}'`
|
||||
if [ -z "$pids" ]
|
||||
then
|
||||
echo "No sign of s_server - exiting (before client)"
|
||||
rm -f $bssllist $bsslech $bsslkey $bsslpem
|
||||
exit 88
|
||||
fi
|
||||
echo "Running openssl s_client against localhost"
|
||||
(echo -e $httpreq ; sleep 2) | \
|
||||
$SRCTOP/apps/openssl s_client -connect localhost:8443 \
|
||||
-CAfile $SRCTOP/test/certs/rootcert.pem \
|
||||
-ech_config_list `cat $bsslpem` \
|
||||
-servername $httphost \
|
||||
-tls1_3 -ignore_unexpected_eof
|
||||
success=$?
|
||||
# bssl server has to be killed
|
||||
kill $pids
|
||||
rm -f $bssllist $bsslech $bsslkey $bsslpem
|
||||
# s_client returns 1 if ok, we want to exit with 0 for a PASS
|
||||
exit $success
|
||||
31
test/recipes/95-test_external_ech_nss.t
Normal file
31
test/recipes/95-test_external_ech_nss.t
Normal file
|
|
@ -0,0 +1,31 @@
|
|||
#! /usr/bin/env perl
|
||||
# Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
# this file except in compliance with the License. You can obtain a copy
|
||||
# in the file LICENSE in the source distribution or at
|
||||
# https://www.openssl.org/source/license.html
|
||||
|
||||
|
||||
use OpenSSL::Test;
|
||||
use OpenSSL::Test::Utils;
|
||||
use OpenSSL::Test qw/:DEFAULT data_file bldtop_dir srctop_dir cmdstr/;
|
||||
|
||||
setup("test_external_ech_nss");
|
||||
|
||||
plan skip_all => "No external tests in this configuration"
|
||||
if disabled("external-tests");
|
||||
plan skip_all => "External ECH tests not available on Windows or VMS"
|
||||
if $^O =~ /^(VMS|MSWin32)$/;
|
||||
#plan skip_all => "External ECH tests only available in a shared build"
|
||||
#if disabled("shared");
|
||||
plan skip_all => "External ECH tests not supported in out of tree builds"
|
||||
if bldtop_dir() ne srctop_dir();
|
||||
|
||||
plan tests => 2;
|
||||
|
||||
ok(run(cmd(["sh", data_file("ech_nss_external.sh")])),
|
||||
"running ECH client external NSS tests");
|
||||
|
||||
ok(run(cmd(["sh", data_file("ech_nss_server_external.sh")])),
|
||||
"running ECH server external NSS tests");
|
||||
132
test/recipes/95-test_external_ech_nss_data/ech_nss_external.sh
Executable file
132
test/recipes/95-test_external_ech_nss_data/ech_nss_external.sh
Executable file
|
|
@ -0,0 +1,132 @@
|
|||
#!/usr/bin/env bash
|
||||
|
||||
#
|
||||
# Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
# this file except in compliance with the License. You can obtain a copy
|
||||
# in the file LICENSE in the source distribution or at
|
||||
# https://www.openssl.org/source/license.html
|
||||
|
||||
# OpenSSL ECH external testing using nss client
|
||||
|
||||
PWD="$(pwd)"
|
||||
|
||||
SRCTOP="$(cd $SRCTOP; pwd)"
|
||||
BLDTOP="$(cd $BLDTOP; pwd)"
|
||||
|
||||
if [ "$SRCTOP" != "$BLDTOP" ] ; then
|
||||
echo "Out of tree builds not supported with ECH external test!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
O_EXE="$BLDTOP/apps"
|
||||
O_BINC="$BLDTOP/include"
|
||||
O_SINC="$SRCTOP/include"
|
||||
O_LIB="$BLDTOP"
|
||||
|
||||
unset OPENSSL_CONF
|
||||
|
||||
export PATH="$O_EXE:$PATH"
|
||||
export LD_LIBRARY_PATH="$O_LIB:$LD_LIBRARY_PATH"
|
||||
export OPENSSL_ROOT_DIR="$O_LIB"
|
||||
|
||||
# Check/Set openssl version
|
||||
OPENSSL_VERSION=`openssl version | cut -f 2 -d ' '`
|
||||
ECHCONFIGFILE=$SRCTOP/test/certs/echdir/ech-eg.pem
|
||||
httphost=server.example
|
||||
httpreq="GET /stats HTTP/1.1\\r\\nConnection: close\\r\\nHost: $httphost\\r\\n\\r\\n"
|
||||
LDIR=$SRCTOP/nss/dist/Debug/bin
|
||||
NLIB=$SRCTOP/nss/dist/Debug/lib
|
||||
|
||||
echo "------------------------------------------------------------------"
|
||||
echo "Testing OpenSSL s_server using ECH-enabled nss client:"
|
||||
echo " CWD: $PWD"
|
||||
echo " SRCTOP: $SRCTOP"
|
||||
echo " BLDTOP: $BLDTOP"
|
||||
echo " OPENSSL_ROOT_DIR: $OPENSSL_ROOT_DIR"
|
||||
echo " OpenSSL version: $OPENSSL_VERSION"
|
||||
echo " PEM ECH Config file: $ECHCONFIGFILE"
|
||||
|
||||
echo "------------------------------------------------------------------"
|
||||
|
||||
if [ ! -d $SRCTOP/nss ]; then
|
||||
mkdir -p $SRCTOP/nss
|
||||
fi
|
||||
|
||||
# clone our NSS and NSPR
|
||||
if [ ! -d $SRCTOP/nss/nss ]; then
|
||||
cd $SRCTOP/nss && git clone https://github.com/nss-dev/nss.git
|
||||
fi
|
||||
if [ ! -d $SRCTOP/nss/nspr ]; then
|
||||
cd $SRCTOP/nss && hg clone https://hg.mozilla.org/projects/nspr
|
||||
fi
|
||||
|
||||
# build - used to use build.sh but there's some problem with an
|
||||
# tmp awk script running within the make environment so we'll do
|
||||
# things in a more basic fashion
|
||||
TPATH=""
|
||||
if [ -d "$SRCTOP/nss/dist" ]
|
||||
then
|
||||
TPATH="$(find $SRCTOP/nss/dist -name tstclnt)"
|
||||
fi
|
||||
if [ -z "$TPATH" ]
|
||||
then
|
||||
( cd $SRCTOP/nss/nss && USE_64=1 make nss_build_all )
|
||||
( cd $SRCTOP/nss/nss && USE_64=1 make install )
|
||||
fi
|
||||
TPATH="$(find $SRCTOP/nss/dist -name tstclnt)"
|
||||
if [ -z "$TPATH" ]
|
||||
then
|
||||
echo "Build failed - exiting"
|
||||
exit 99
|
||||
fi
|
||||
LDIR=$(dirname "$TPATH")
|
||||
NLIB="$LDIR/../lib"
|
||||
|
||||
if [ ! -f $LDIR/tstclnt ]; then
|
||||
echo "Failed to build NSS - exiting"
|
||||
exit 99
|
||||
fi
|
||||
if [ ! -f $LDIR/certutil ]; then
|
||||
echo "Failed to build NSS - exiting"
|
||||
exit 99
|
||||
fi
|
||||
|
||||
# If we have an NSS build, create an NSS DB for our fake root so we can
|
||||
# use NSS' tstclnt to talk to our s_server.
|
||||
if [ -f $LDIR/certutil ]
|
||||
then
|
||||
mkdir -p $SRCTOP/nss/ca
|
||||
LD_LIBRARY_PATH=$NLIB $LDIR/certutil -A \
|
||||
-i $SRCTOP/test/certs/rootcert.pem \
|
||||
-n "oe" -t "CT,C,C" -d $SRCTOP/nss/ca/
|
||||
fi
|
||||
|
||||
echo " CWD: $PWD"
|
||||
|
||||
# Start an openssl s_server
|
||||
$SRCTOP/apps/openssl s_server \
|
||||
-key $SRCTOP/test/certs/echserver.key -cert $SRCTOP/test/certs/echserver.pem \
|
||||
-key2 $SRCTOP/test/certs/echserver.key -cert2 $SRCTOP/test/certs/echserver.pem \
|
||||
-CAfile $SRCTOP/test/certs/rootcert.pem \
|
||||
-ech_key $ECHCONFIGFILE -port 8443 -tls1_3 -WWW -naccept 1 \
|
||||
-ign_eof -servername server.example &
|
||||
pids=`ps -ef | grep s_server | grep -v grep | awk '{print $2}'`
|
||||
if [ -z "$pids" ]
|
||||
then
|
||||
echo "No sign of s_server - exiting (before client)"
|
||||
exit 88
|
||||
fi
|
||||
|
||||
# to ensure we detect a fail, use the wrong ECHConfig ...
|
||||
# ECHCONFIGFILE=$SRCTOP/esnistuff/d13.pem
|
||||
ECH=`cat $ECHCONFIGFILE \
|
||||
| awk '/^-----BEGIN ECHCONFIG/,/^-----END ECHCONFIG/' \
|
||||
| tail -n+2 | head -n+2 | tr -d '\n'`
|
||||
|
||||
NSSPARAMS="-Q -4 -b -d $SRCTOP/nss/ca"
|
||||
LD_LIBRARY_PATH="$NLIB" $LDIR/tstclnt $NSSPARAMS -h localhost -p 8443 -a $httphost -N $ECH
|
||||
res=$?
|
||||
exit $res
|
||||
|
||||
162
test/recipes/95-test_external_ech_nss_data/ech_nss_server_external.sh
Executable file
162
test/recipes/95-test_external_ech_nss_data/ech_nss_server_external.sh
Executable file
|
|
@ -0,0 +1,162 @@
|
|||
#!/bin/sh
|
||||
|
||||
#
|
||||
# Copyright 2025 The OpenSSL Project Authors. All Rights Reserved.
|
||||
#
|
||||
# Licensed under the Apache License 2.0 (the "License"). You may not use
|
||||
# this file except in compliance with the License. You can obtain a copy
|
||||
# in the file LICENSE in the source distribution or at
|
||||
# https://www.openssl.org/source/license.html
|
||||
|
||||
#
|
||||
# OpenSSL ECH external testing using nss
|
||||
|
||||
PWD="$(pwd)"
|
||||
|
||||
SRCTOP="$(cd $SRCTOP; pwd)"
|
||||
BLDTOP="$(cd $BLDTOP; pwd)"
|
||||
|
||||
if [ "$SRCTOP" != "$BLDTOP" ] ; then
|
||||
echo "Out of tree builds not supported with ECH external test!"
|
||||
exit 1
|
||||
fi
|
||||
|
||||
O_EXE="$BLDTOP/apps"
|
||||
O_BINC="$BLDTOP/include"
|
||||
O_SINC="$SRCTOP/include"
|
||||
O_LIB="$BLDTOP"
|
||||
|
||||
unset OPENSSL_CONF
|
||||
|
||||
export PATH="$O_EXE:$PATH"
|
||||
export LD_LIBRARY_PATH="$O_LIB:$LD_LIBRARY_PATH"
|
||||
export OPENSSL_ROOT_DIR="$O_LIB"
|
||||
|
||||
# Check/Set openssl version
|
||||
OPENSSL_VERSION=`openssl version | cut -f 2 -d ' '`
|
||||
ECHCONFIGFILE=$SRCTOP/test/certs/echdir/ech-eg.pem
|
||||
httphost=server.example
|
||||
httpreq="GET / HTTP/1.1\\r\\nConnection: close\\r\\nHost: $httphost\\r\\n\\r\\n"
|
||||
|
||||
echo "------------------------------------------------------------------"
|
||||
echo "Testing OpenSSL s_client using ECH-enabled nss server:"
|
||||
echo " CWD: $PWD"
|
||||
echo " SRCTOP: $SRCTOP"
|
||||
echo " BLDTOP: $BLDTOP"
|
||||
echo " OPENSSL_ROOT_DIR: $OPENSSL_ROOT_DIR"
|
||||
echo " OpenSSL version: $OPENSSL_VERSION"
|
||||
echo " PEM ECH Config file: $ECHCONFIGFILE"
|
||||
|
||||
echo "------------------------------------------------------------------"
|
||||
|
||||
if [ ! -d $SRCTOP/nss ]; then
|
||||
mkdir -p $SRCTOP/nss
|
||||
fi
|
||||
# clone our NSS and NSPR
|
||||
if [ ! -d $SRCTOP/nss/nss ]; then
|
||||
(
|
||||
cd $SRCTOP/nss \
|
||||
&& git clone https://github.com/nss-dev/nss.git
|
||||
)
|
||||
fi
|
||||
if [ ! -d $SRCTOP/nss/nspr ]; then
|
||||
(
|
||||
cd $SRCTOP/nss \
|
||||
&& hg clone https://hg.mozilla.org/projects/nspr
|
||||
)
|
||||
fi
|
||||
|
||||
# build - used to use build.sh but there's some problem with an
|
||||
# tmp awk script running within the make environment so we'll do
|
||||
# things in a more basic fashion
|
||||
TPATH=""
|
||||
if [ -d "$SRCTOP/nss/dist" ]
|
||||
then
|
||||
TPATH="$(find $SRCTOP/nss/dist -name tstclnt)"
|
||||
fi
|
||||
if [ -z "$TPATH" ]
|
||||
then
|
||||
( cd $SRCTOP/nss/nss && USE_64=1 make nss_build_all )
|
||||
( cd $SRCTOP/nss/nss && USE_64=1 make install )
|
||||
fi
|
||||
TPATH="$(find $SRCTOP/nss/dist -name tstclnt)"
|
||||
if [ -z "$TPATH" ]
|
||||
then
|
||||
echo "Build failed - exiting"
|
||||
exit 99
|
||||
fi
|
||||
LDIR=$(dirname "$TPATH")
|
||||
LLIB="$LDIR/../lib"
|
||||
|
||||
if [ ! -f $LDIR/selfserv ]; then
|
||||
echo "Failed to build NSS - exiting"
|
||||
exit 99
|
||||
fi
|
||||
if [ ! -f $LDIR/certutil ]; then
|
||||
echo "Failed to build NSS - exiting"
|
||||
exit 99
|
||||
fi
|
||||
|
||||
# If we have an NSS build, create an NSS DB for our fake root so we can
|
||||
# use NSS' tstclnt to talk to our s_server.
|
||||
if [ ! -d $SRCTOP/nss/server ]
|
||||
then
|
||||
mkdir -p $SRCTOP/nss/server
|
||||
LD_LIBRARY_PATH=$LLIB $LDIR/certutil -A \
|
||||
-i $SRCTOP/test/certs/rootcert.pem \
|
||||
-n "oe" -t "CT,C,C" -d $SRCTOP/nss/server/
|
||||
sillypass="sillypass"
|
||||
$SRCTOP/apps/openssl pkcs12 -export -out tmp.p12 \
|
||||
-inkey $SRCTOP/test/certs/echserver.key \
|
||||
-in $SRCTOP/test/certs/echserver.pem \
|
||||
-password "pass:$sillypass"
|
||||
echo -n $sillypass >sillypassfile
|
||||
LD_LIBRARY_PATH=$LLIB $LDIR/pk12util \
|
||||
-i tmp.p12 -d $SRCTOP/nss/server -w sillypassfile
|
||||
cat sillypassfile
|
||||
# rm -f sillypassfile tmp.p12
|
||||
fi
|
||||
|
||||
echo " CWD: $PWD"
|
||||
|
||||
# Start an NSS server
|
||||
# We'll let the server generate the ECH key pair for now (see
|
||||
# below for why).
|
||||
|
||||
# need to use ``stdbuf -o0`` so that we don't get buffering and
|
||||
# can grab echconfig immediately...
|
||||
LD_LIBRARY_PATH=$LLIB stdbuf -o0 $LDIR/selfserv -p 8443 -d $SRCTOP/nss/server \
|
||||
-n server.example -X "publicname:example.com" >ss-echfile &
|
||||
if [ -s ss-echfile ]
|
||||
then
|
||||
echo "Did you remember to patch NSS? See $0 for details"
|
||||
exit 78
|
||||
fi
|
||||
|
||||
# For the future, we'd like a provide our private-key/ECHConfig to
|
||||
# NSS - looks like there could be some work required to get that
|
||||
# selfserve option working.
|
||||
# https://bugzilla.mozilla.org/show_bug.cgi?id=1876732
|
||||
|
||||
pids=`ps -ef | grep selfserv | grep -v grep | awk '{print $2}'`
|
||||
if [ -z "$pids" ]
|
||||
then
|
||||
echo "No sign of selfserv - exiting (before client)"
|
||||
exit 88
|
||||
fi
|
||||
|
||||
# to ensure we detect a fail, use the wrong ECHConfig ...
|
||||
# ECHCONFIGFILE=$SRCTOP/esnistuff/d13.pem
|
||||
# ECH=`cat $ECHCONFIGFILE | tail -2 | head -1`
|
||||
ECH=`cat ss-echfile | tail -2 | head -1`
|
||||
echo "Running openssl s_client against localhost"
|
||||
(echo -e $httpreq ; sleep 2) | \
|
||||
$SRCTOP/apps/openssl s_client -connect localhost:8443 \
|
||||
-CAfile $SRCTOP/test/certs/rootcert.pem \
|
||||
-ech_config_list $ECH \
|
||||
-servername $httphost \
|
||||
-no_ssl3 -no_tls1 -no_tls1_1 -no_tls1_2
|
||||
success=$?
|
||||
# nss server needs killing
|
||||
kill $pids
|
||||
exit $success
|
||||
|
|
@ -629,7 +629,6 @@ SSL_get1_echstore ? 4_0_0 EXIST::FUNCTION:ECH
|
|||
SSL_ech_get1_status ? 4_0_0 EXIST::FUNCTION:ECH
|
||||
SSL_ech_set_grease_type ? 4_0_0 EXIST::FUNCTION:ECH
|
||||
SSL_ech_set_callback ? 4_0_0 EXIST::FUNCTION:ECH
|
||||
SSL_CTX_ech_raw_decrypt ? 4_0_0 EXIST::FUNCTION:ECH
|
||||
SSL_CTX_ech_set_callback ? 4_0_0 EXIST::FUNCTION:ECH
|
||||
OSSL_ECHSTORE_num_entries ? 4_0_0 EXIST::FUNCTION:ECH
|
||||
SSL_ech_set1_server_names ? 4_0_0 EXIST::FUNCTION:ECH
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue