From 61451686e836f20c50b92d4ed4fa10ac7d38a0de Mon Sep 17 00:00:00 2001 From: Stephen Farrell Date: Thu, 6 Jun 2024 22:33:27 +0100 Subject: [PATCH] move ECH specific padding into library --- apps/s_server.c | 90 +------------- doc/designs/ech-api.md | 65 +++++++++- doc/man3/SSL_ech_set1_echconfig.pod | 52 +++++++- include/openssl/ech.h | 36 ++++++ include/openssl/ssl.h.in | 3 + ssl/ech.c | 34 ++++++ ssl/ech_local.h | 1 + ssl/record/methods/tls13_meth.c | 53 +++++++++ ssl/ssl_lib.c | 8 ++ ssl/ssl_local.h | 1 + test/ech_test.c | 177 +++++++++++++++++++++++++++- util/libssl.num | 2 + 12 files changed, 429 insertions(+), 93 deletions(-) diff --git a/apps/s_server.c b/apps/s_server.c index 90fb674b6ee28..e0f1aa52305a5 100644 --- a/apps/s_server.c +++ b/apps/s_server.c @@ -74,19 +74,6 @@ typedef unsigned int u_int; #endif #include "internal/sockets.h" -#ifndef OPENSSL_NO_ECH -/* - * A lack of padding can expose information intended to be hidden via ECH, - * e.g. if only two inner CH SNI values were in live use. In that case we - * pad the Certificate, CertificateVerify and EncryptedExtensions handshake - * messages from the server. These are the minimum lengths to which those - * will be padded in that case. - */ -# define ECH_CERTSPECIFIC_MIN 1808 -# define ECH_CERTVERSPECIFIC_MIN 480 -# define ECH_ENCEXTSPECIFIC_MIN 32 -#endif - static int not_resumable_sess_cb(SSL *s, int is_forward_secure); static int sv_body(int s, int stype, int prot, unsigned char *context); static int www_body(int s, int stype, int prot, unsigned char *context); @@ -106,60 +93,6 @@ static unsigned int ech_print_cb(SSL *s, const char *str); static size_t ech_trace_cb(const char *buf, size_t cnt, int category, int cmd, void *vdata); # endif - -/* ECH padding size info, var of this type is passed via callback */ -typedef struct { - /* Certificate messages to be a multiple of this size */ - size_t certpad; - /* CertificateVerify messages to be a multiple of this size */ - size_t certverifypad; - /* EncryptedExtensions are padded to be a multiple of this size */ - size_t eepad; -} ech_padding_sizes; - -/* passed as an argument to callback */ -static ech_padding_sizes *ech_ps=NULL; - -/* - * @brief pad Certificate and CertificateVerify messages - * @param s is the SSL connection - * @param len is the plaintext length before padding - * @param arg is a pointer to an esni_padding_sizes struct - * @return is the number of bytes of padding to add to the plaintext - * - * This is passed to SSL_CTX_set_record_padding_callback - * and pads the Certificate, CertificateVerify and - * EncryptedExtensions handshake messages to a size derived - * from the argument arg - */ -static size_t ech_padding_cb(SSL *s, int type, size_t len, void *arg) -{ - ech_padding_sizes *ps = (ech_padding_sizes *)arg; - int state = SSL_get_state(s); - - if (state == TLS_ST_SW_CERT) { - int newlen = ps->certpad - (len % ps->certpad) - 16; - - if (newlen < 0) - newlen += ps->certpad; - return (newlen > 0 ? newlen : 0); - } - if (state == TLS_ST_SW_CERT_VRFY) { - int newlen = ps->certverifypad - (len % ps->certverifypad) - 16; - - if (newlen < 0) - newlen += ps->certverifypad; - return (newlen > 0 ? newlen : 0); - } - if (state == TLS_ST_SW_ENCRYPTED_EXTENSIONS) { - int newlen = ps->eepad - (len % ps->eepad) - 16; - - if (newlen < 0) - newlen += ps->eepad; - return (newlen > 0 ? newlen : 0); - } - return 0; -} #endif static const int bufsize = 16 * 1024; @@ -2538,14 +2471,8 @@ int s_server_main(int argc, char *argv[]) /* * Set padding sizes */ - if (echspecificpad != 0) { - ech_ps = OPENSSL_malloc(sizeof(ech_padding_sizes)); - ech_ps->certpad = ECH_CERTSPECIFIC_MIN; - ech_ps->certverifypad = ECH_CERTVERSPECIFIC_MIN ; - ech_ps->eepad = ECH_ENCEXTSPECIFIC_MIN ; - SSL_CTX_set_record_padding_callback_arg(ctx, (void *)ech_ps); - SSL_CTX_set_record_padding_callback(ctx, ech_padding_cb); - } + if (echspecificpad != 0) + SSL_CTX_set_options(ctx, SSL_OP_ECH_SPECIFIC_PADDING); #endif if (s_cert2) { @@ -2556,16 +2483,12 @@ int s_server_main(int argc, char *argv[]) } #ifndef OPENSSL_NO_ECH - if (echtrialdecrypt != 0) { + if (echtrialdecrypt != 0) SSL_CTX_set_options(ctx2, SSL_OP_ECH_TRIALDECRYPT); - } - if (echgrease_rc != 0) { + if (echgrease_rc != 0) SSL_CTX_set_options(ctx, SSL_OP_ECH_GREASE_RETRY_CONFIG); - } - if (echspecificpad != 0) { - SSL_CTX_set_record_padding_callback_arg(ctx2, (void *)ech_ps); - SSL_CTX_set_record_padding_callback(ctx2, ech_padding_cb); - } + if (echspecificpad != 0) + SSL_CTX_set_options(ctx2, SSL_OP_ECH_SPECIFIC_PADDING); #endif } @@ -2941,7 +2864,6 @@ int s_server_main(int argc, char *argv[]) BIO_meth_free(methods_ebcdic); #endif #ifndef OPENSSL_NO_ECH - OPENSSL_free(ech_ps); #endif return ret; } diff --git a/doc/designs/ech-api.md b/doc/designs/ech-api.md index 4201defb5d3b7..e5493761f237a 100644 --- a/doc/designs/ech-api.md +++ b/doc/designs/ech-api.md @@ -246,6 +246,60 @@ ChangeCipherSuite or Early data), then the caller is responsible for disentangling those, and for assembling a new flight containing the inner ClientHello. +### ECH-specific Padding of server messages + +If a web server were to host a set of web sites, one of which had a much longer +name than the others, the size of some TLS handshake server messages could +expose which web site was being accessed. Similarly, if the TLS server +certificate for one web site were significantly larger or smaller than others, +message sizes could reveal which web site was being visited. For these +reasons, we provide a way to enable additional ECH-specific padding of the +Certifiate, CertificateVerify and EncryptedExtensions messages sent from the +server to the client during the handshake. + +To enable ECH-specific padding, one makes a call to: + +```c + SSL_CTX_set_options(ctx, SSL_OP_ECH_SPECIFIC_PADDING); +``` + +The default padding scheme is to ensure the following sizes for the plaintext +form of these messages: + +| ------------------- | ------------ | ------------------- | +| Message | Minimum Size | Size is multiple of | +| ------------------- | ------------ | ------------------- | +| Certificate | 1792 | 128 | +| CertificateVerify | 480 | 16 | +| EncryptedExtensions | 32 | 16 | +| ------------------- | ------------ | ------------------- | + +The ciphertext form of these messages, as seen on the network in the record +layer protocol, will usually be 16 octets more, due to the AEAD tag that is +added as part of encryption. + +If a server wishes to have finer-grained control of these sizes, then it can +make use of the ``SSL_CTX_ech_set_pad_sizes()`` or ``SSL_ech_set_pad_sizes()`` +APIs. Both involve populating an ``OSSL_ECH_PAD_SIZES`` data structure as +described below in the obvious manner. + +```c +/* + * Fine-grained ECH-spacific padding controls for a server + */ +typedef struct ossl_ech_pad_sizes_st { + size_t cert_min; /* minimum size */ + size_t cert_unit; /* size will be multiple of */ + size_t certver_min; /* minimum size */ + size_t certver_unit; /* size will be multiple of */ + size_t ee_min; /* minimum size */ + size_t ee_unit; /* size will be multiple of */ +} OSSL_ECH_PAD_SIZES; + +int SSL_CTX_ech_set_pad_sizes(SSL_CTX *ctx, OSSL_ECH_PAD_SIZES *sizes); +int SSL_ech_set_pad_sizes(SSL *s, OSSL_ECH_PAD_SIZES *sizes); +``` + Client-side APIs ---------------- @@ -422,17 +476,20 @@ The following options are defined for ECH and may be set via ```c /* set this to tell client to emit greased ECH values when not doing * "real" ECH */ -#define SSL_OP_ECH_GREASE SSL_OP_BIT(34) +#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(35) +#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(36) +#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(37) +#define SSL_OP_ECH_GREASE_RETRY_CONFIG SSL_OP_BIT(39) +/* If set, servers will add ECH-specific padding to Certificate, + * CertificateVerify and EncryptedExtensions messages */ +#define SSL_OP_ECH_SPECIFIC_PADDING SSL_OP_BIT(40) ``` Build Options diff --git a/doc/man3/SSL_ech_set1_echconfig.pod b/doc/man3/SSL_ech_set1_echconfig.pod index d52891f1d297d..7132c44937033 100644 --- a/doc/man3/SSL_ech_set1_echconfig.pod +++ b/doc/man3/SSL_ech_set1_echconfig.pod @@ -13,7 +13,8 @@ SSL_CTX_ech_server_flush_keys, SSL_CTX_ech_server_enable_file, SSL_CTX_ech_server_enable_buffer, SSL_CTX_ech_server_enable_dir, SSL_CTX_ech_raw_decrypt, SSL_CTX_ech_set_callback, SSL_CTX_ech_set_outer_alpn_protos, SSL_ech_set_outer_alpn_protos, -OSSL_ech_make_echconfig, OSSL_ech_find_echconfigs +OSSL_ech_make_echconfig, OSSL_ech_find_echconfigs, +SSL_CTX_ech_set_pad_sizes,SSL_ech_set_pad_sizes - Encrypted ClientHello (ECH) functions =head1 SYNOPSIS @@ -69,6 +70,8 @@ OSSL_ech_make_echconfig, OSSL_ech_find_echconfigs int OSSL_ech_find_echconfigs(int *num_echs, unsigned char ***echconfigs, size_t **echlens, const unsigned char *val, size_t len); + int SSL_ech_set_pad_sizes(SSL *s, OSSL_ECH_PAD_SIZES *sizes); + int SSL_CTX_ech_set_pad_sizes(SSL_CTX *ctx, OSSL_ECH_PAD_SIZES *sizes); =head1 DESCRIPTION @@ -246,7 +249,6 @@ make use of ECH extensions. OSSL_ech_make_echconfig() is intended for use by applications that generate ECH configuration data, such as L. - =head2 Enabling ECH for servers In order to enable ECH for servers it is necessary to load one or more ECH @@ -320,6 +322,52 @@ succeeds, the I and I values will be populated based on the values seen. (The caller must free those strings using OPENSSL_free() when done.) +=head2 ECH-specific Padding of server messages + +If a web server were to host a set of web sites, one of which had a much longer +name than the others, the size of some TLS handshake server messages could +expose which web site was being accessed. Similarly, if the TLS server +certificate for one web site were significantly larger or smaller than others, +message sizes could reveal which web site was being visited. For these +reasons, we provide a way to enable additional ECH-specific padding of the +Certifiate, CertificateVerify and EncryptedExtensions messages sent from the +server to the client during the handshake. + +To enable ECH-specific padding, one makes a call to +``SSL_CTX_set_options(ctx, SSL_OP_ECH_SPECIFIC_PADDING)`` + +The default padding scheme is to ensure the following sizes for the plaintext +form of these messages: + + | ------------------- | ------------ | ------------------- | + | Message | Minimum Size | Size is multiple of | + | ------------------- | ------------ | ------------------- | + | Certificate | 1792 | 128 | + | CertificateVerify | 304 | 16 | + | EncryptedExtensions | 32 | 16 | + | ------------------- | ------------ | ------------------- | + +The ciphertext form of these messages, as seen on the network in the record +layer protocol, will usually be 16 octets more, due to the AEAD tag that is +added as part of encryption. + +If a server wishes to have finer-grained control of these sizes, then it can +make use of the ``SSL_CTX_ech_set_pad_sizes()`` or ``SSL_ech_set_pad_sizes()`` +APIs. Both involve populating an ``OSSL_ECH_PAD_SIZES`` data structure as +described below in the obvious manner. + + /* + * Fine-grained ECH-spacific padding controls for a server + */ + typedef struct ossl_ech_pad_sizes_st { + size_t cert_min; /* minimum size */ + size_t cert_unit; /* size will be multiple of */ + size_t certver_min; /* minimum size */ + size_t certver_unit; /* size will be multiple of */ + size_t ee_min; /* minimum size */ + size_t ee_unit; /* size will be multiple of */ + } OSSL_ECH_PAD_SIZES; + =head2 Accessing ECH information Given multiple ECH public values can be associated with a single I diff --git a/include/openssl/ech.h b/include/openssl/ech.h index d09b8e573ceaa..1855ab5344258 100644 --- a/include/openssl/ech.h +++ b/include/openssl/ech.h @@ -86,6 +86,39 @@ typedef struct ossl_ech_info_st { char *echconfig; /* a JSON-like version of the associated ECHConfig */ } OSSL_ECH_INFO; +/* + * Default padding minima and multiples + * If a web server were to host a set of web sites, one of which had a much + * longer name than the others, the size of some TLS handshake server messages + * could expose which web site was being accessed. Similarly, if the TLS server + * certificate for one web site were significantly larger or smaller than + * others, message sizes could reveal which web site was being visited. For + * these reasons, we provide a way to enable additional ECH-specific padding of + * the Certifiate, CertificateVerify and EncryptedExtensions messages sent from + * the server to the client during the handshake. + * + * To enable ECH-specific padding, one makes a call to: + * SSL_CTX_set_options(ctx, SSL_OP_ECH_SPECIFIC_PADDING); + */ +# define OSSL_ECH_CERTPAD_MIN 1792 +# define OSSL_ECH_CERTVERPAD_MIN 304 +# define OSSL_ECH_ENCEXTPAD_MIN 32 +# define OSSL_ECH_CERTPAD_UNIT 128 +# define OSSL_ECH_CERTVERPAD_UNIT 16 +# define OSSL_ECH_ENCEXTPAD_UNIT 16 + +/* + * Fine-grained ECH-spacific padding controls for a server + */ +typedef struct ossl_ech_pad_sizes_st { + size_t cert_min; /* minimum size */ + size_t cert_unit; /* size will be multiple of */ + size_t certver_min; /* minimum size */ + size_t certver_unit; /* size will be multiple of */ + size_t ee_min; /* minimum size */ + size_t ee_unit; /* size will be multiple of */ +} OSSL_ECH_PAD_SIZES; + /* Values for the for_retry inputs */ # define SSL_ECH_USE_FOR_RETRY 1 # define SSL_ECH_NOT_FOR_RETRY 0 @@ -116,6 +149,7 @@ int SSL_ech_set_grease_type(SSL *s, uint16_t type); typedef unsigned int (*SSL_ech_cb_func)(SSL *s, const char *str); void SSL_ech_set_callback(SSL *s, SSL_ech_cb_func f); +int SSL_ech_set_pad_sizes(SSL *s, OSSL_ECH_PAD_SIZES *sizes); int SSL_ech_get_retry_config(SSL *s, unsigned char **ec, size_t *eclen); @@ -140,6 +174,8 @@ int SSL_CTX_ech_raw_decrypt(SSL_CTX *ctx, 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_set_pad_sizes(SSL_CTX *ctx, OSSL_ECH_PAD_SIZES *sizes); + /* Misc API calls */ int OSSL_ech_make_echconfig(unsigned char *echconfig, size_t *echconfiglen, diff --git a/include/openssl/ssl.h.in b/include/openssl/ssl.h.in index eabd82238318f..434ccdafd2830 100644 --- a/include/openssl/ssl.h.in +++ b/include/openssl/ssl.h.in @@ -448,6 +448,9 @@ typedef int (*SSL_async_callback_fn)(SSL *s, void *arg); * in retry_configs. */ # define SSL_OP_ECH_GREASE_RETRY_CONFIG SSL_OP_BIT(39) +/* If set, servers will add ECH-specific padding to Certificate, + * CertificateVerify and EncryptedExtensions messages */ +# define SSL_OP_ECH_SPECIFIC_PADDING SSL_OP_BIT(40) #endif diff --git a/ssl/ech.c b/ssl/ech.c index 6b589bd93e921..6ceace92fa113 100644 --- a/ssl/ech.c +++ b/ssl/ech.c @@ -4698,6 +4698,40 @@ int OSSL_ECH_INFO_print(BIO *out, OSSL_ECH_INFO *se, int count) return 1; } +int SSL_CTX_ech_set_pad_sizes(SSL_CTX *ctx, OSSL_ECH_PAD_SIZES *sizes) +{ + if (ctx == NULL || sizes == NULL) { + ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + if (sizes->cert_min == 0 || sizes->certver_min == 0 || sizes->ee_min == 0 + || sizes->cert_unit == 0 || sizes->certver_unit == 0 + || sizes->ee_unit == 0) { + ERR_raise(ERR_LIB_SSL, ERR_R_PASSED_INVALID_ARGUMENT); + return 0; + } + ctx->ext.pad_sizes = *sizes; + return 1; +} + +int SSL_ech_set_pad_sizes(SSL *ssl, OSSL_ECH_PAD_SIZES *sizes) +{ + SSL_CONNECTION *s = SSL_CONNECTION_FROM_SSL(ssl); + + if (s == NULL || sizes == NULL) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_PASSED_NULL_PARAMETER); + return 0; + } + if (sizes->cert_min == 0 || sizes->certver_min == 0 || sizes->ee_min == 0 + || sizes->cert_unit == 0 || sizes->certver_unit == 0 + || sizes->ee_unit == 0) { + SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_R_BAD_LENGTH); + return 0; + } + s->ext.ech.pad_sizes = *sizes; + return 1; +} + int SSL_ech_set1_echconfig(SSL *ssl, const unsigned char *val, size_t len) { SSL_CONNECTION *s = SSL_CONNECTION_FROM_SSL(ssl); diff --git a/ssl/ech_local.h b/ssl/ech_local.h index 7cd52dc25bcbf..b0e9297720bdd 100644 --- a/ssl/ech_local.h +++ b/ssl/ech_local.h @@ -310,6 +310,7 @@ typedef struct ssl_connection_ech_st { SSL_ECH *cfgs; /* array of configured ECH configurations */ int ncfgs; /* number of elements in array */ SSL_ech_cb_func cb; /* callback function for when ECH "done" */ + OSSL_ECH_PAD_SIZES pad_sizes; /* padding sizes */ } SSL_CONNECTION_ECH; /** diff --git a/ssl/record/methods/tls13_meth.c b/ssl/record/methods/tls13_meth.c index afae14ad22b20..a611bbb9661dd 100644 --- a/ssl/record/methods/tls13_meth.c +++ b/ssl/record/methods/tls13_meth.c @@ -329,6 +329,59 @@ static int tls13_add_record_padding(OSSL_RECORD_LAYER *rl, } TLS_RL_RECORD_add_length(thiswr, 1); +#ifndef OPENSSL_NO_ECH + /* + * If we are doing ECH and we want ECH specific padding then + * now is the time to ensure that happens. + * A lack of padding can expose information intended to be hidden via ECH, + * e.g. if only two inner CH SNI values were in live use. In that case we + * pad the Certificate, CertificateVerify and EncryptedExtensions handshake + * messages from the server. These are the minimum lengths to which those + * will be padded in that case. + * We do this before calling any padding callback supplied by the + * application, so they get to see the final sizes. + */ + if ((rl->options & SSL_OP_ECH_SPECIFIC_PADDING) != 0) { + SSL_CONNECTION *s = rl->cbarg; + SSL *ssl = SSL_CONNECTION_GET_SSL(s); + int do_pad = 0, echpad = 0, state = SSL_get_state(ssl); + size_t len = 0, min = 0, unit = 0; + + if (!WPACKET_get_length(thispkt, &len)) { + RLAYERfatal(rl, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + if (s != NULL && s->server == 1 && s->ext.ech.attempted == 1 + && s->ext.ech.success == 1) { + if (state == TLS_ST_SW_CERT) { + min = s->ext.ech.pad_sizes.cert_min; + unit = s->ext.ech.pad_sizes.cert_unit; + do_pad = 1; + } + if (state == TLS_ST_SW_CERT_VRFY) { + min = s->ext.ech.pad_sizes.certver_min; + unit = s->ext.ech.pad_sizes.certver_unit; + do_pad = 1; + } + if (state == TLS_ST_SW_ENCRYPTED_EXTENSIONS) { + min = s->ext.ech.pad_sizes.ee_min; + unit = s->ext.ech.pad_sizes.ee_unit; + do_pad = 1; + } + if (do_pad == 1) { + if (len < min) + echpad = min - len; + echpad += ((len + echpad) % unit ? unit - ((len + echpad) % unit) : 0); + if (echpad > 0 && !WPACKET_memset(thispkt, 0, echpad)) { + RLAYERfatal(rl, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR); + return 0; + } + TLS_RL_RECORD_add_length(thiswr, echpad); + } + } + } +#endif + /* Add TLS1.3 padding */ rlen = TLS_RL_RECORD_get_length(thiswr); if (rlen < rl->max_frag_len) { diff --git a/ssl/ssl_lib.c b/ssl/ssl_lib.c index 9d630384ce7ca..288aa2599f5c7 100644 --- a/ssl/ssl_lib.c +++ b/ssl/ssl_lib.c @@ -973,6 +973,7 @@ SSL *ossl_ssl_connection_new_int(SSL_CTX *ctx, const SSL_METHOD *method) s->ext.ech.innerch1 = NULL; s->ext.ech.encoded_innerch = NULL; s->ext.ech.kepthrr = NULL; + s->ext.ech.pad_sizes = ctx->ext.pad_sizes; #endif return ssl; cerr: @@ -4194,6 +4195,12 @@ SSL_CTX *SSL_CTX_new_ex(OSSL_LIB_CTX *libctx, const char *propq, ret->ext.ech_cb = NULL; ret->ext.alpn_outer = NULL; ret->ext.alpn_outer_len = 0; + ret->ext.pad_sizes.cert_min = OSSL_ECH_CERTPAD_MIN; + ret->ext.pad_sizes.cert_unit = OSSL_ECH_CERTPAD_UNIT; + ret->ext.pad_sizes.certver_min = OSSL_ECH_CERTVERPAD_MIN; + ret->ext.pad_sizes.certver_unit = OSSL_ECH_CERTVERPAD_UNIT; + ret->ext.pad_sizes.ee_min = OSSL_ECH_ENCEXTPAD_MIN; + ret->ext.pad_sizes.ee_unit = OSSL_ECH_ENCEXTPAD_UNIT; #endif return ret; err: @@ -5183,6 +5190,7 @@ SSL *SSL_dup(SSL *s) sc->ext.ech.returned_len); retsc->ext.ech.returned_len = sc->ext.ech.returned_len; } + retsc->ext.ech.pad_sizes = sc->ext.ech.pad_sizes; #endif return ret; diff --git a/ssl/ssl_local.h b/ssl/ssl_local.h index 4d76c02c3c8bc..aff85950e7988 100644 --- a/ssl/ssl_local.h +++ b/ssl/ssl_local.h @@ -1092,6 +1092,7 @@ struct ssl_ctx_st { SSL_ech_cb_func ech_cb; /* ECH call back */ unsigned char *alpn_outer; /* Outer ALPN (if any) */ size_t alpn_outer_len; + OSSL_ECH_PAD_SIZES pad_sizes; /* padding sizes */ #endif unsigned char cookie_hmac_key[SHA256_DIGEST_LENGTH]; } ext; diff --git a/test/ech_test.c b/test/ech_test.c index 3aabe21f58af2..15680f979a14d 100644 --- a/test/ech_test.c +++ b/test/ech_test.c @@ -911,6 +911,179 @@ static int extended_echconfig(int idx) return res; } +/* + * Test ECH-specific padding. + * + * Make one call per entry in pad_array and compare the + * resulting message sizes against the expected values. + * We check the actual values via a padding callback. + * + * pad_array[0] is a dummy that's written to in the 1st + * iteration so that we can calculate the expected sizes + * for subsequent iterations. + */ +static OSSL_ECH_PAD_SIZES pad_array[] = { + /* dummy for 1st iteration */ + { 0, 0, 0, 0, 0, 0}, + /* + * cert min is > actual + * certver min is set to 1, well lower than actual + * ee min is close to size + */ + { 1000, 16, 1, 16, 40, 16}, +}; + +/* callback code needs to tell test code if we see unexpected values */ +static int ech_pad_cb_fail = 0; + +/* + * This is passed to SSL_CTX_set_record_padding_callback + * and checks padding of the Certificate, CertificateVerify and + * EncryptedExtensions handshake messages vs. the expected + * size from the argument arg. + */ +static size_t ech_padding_cb(SSL *s, int type, size_t len, void *arg) +{ + OSSL_ECH_PAD_SIZES *pl = (OSSL_ECH_PAD_SIZES *)arg; + int state = SSL_get_state(s), do_check = 0; + size_t expected = 0, unpadded = 0, min = 0, unit = 0; + + /* for the first case, we remember the length in actual use + which depends on the pem files */ + if (state == TLS_ST_SW_CERT && pl->cert_min == 0) + pad_array[0].cert_min = len; + else if (state == TLS_ST_SW_CERT && pl->cert_min != 0) { + unpadded = pad_array[0].cert_min; + min = pl->cert_min; + unit = pl->cert_unit; + do_check = 1; + } + if (state == TLS_ST_SW_CERT_VRFY && pl->certver_min == 0) + pad_array[0].certver_min = len; + else if (state == TLS_ST_SW_CERT_VRFY && pl->certver_min != 0) { + unpadded = pad_array[0].certver_min; + min = pl->certver_min; + unit = pl->certver_unit; + do_check = 1; + } + if (state == TLS_ST_SW_ENCRYPTED_EXTENSIONS && pl->ee_min == 0) + pad_array[0].ee_min = len; + else if (state == TLS_ST_SW_ENCRYPTED_EXTENSIONS && pl->ee_min != 0) { + unpadded = pad_array[0].ee_min; + min = pl->ee_min; + unit = pl->ee_unit; + do_check = 1; + } + if (do_check == 1) { + /* check if we got what we expected */ + if (unpadded < min) + expected = min; + else + expected = unpadded; + expected += (expected % unit ? unit - (expected % unit) : 0); + if (len != expected) { + TEST_info("ech_pad_test: bad length %ld for type %d expected %ld)\n", + len, type, expected); + ech_pad_cb_fail = 1; + } + } + return 0; +} + +static int ech_pad_test(int idx) +{ + int res = 0; + char *echkeyfile = NULL; + char *echconfig = NULL; + size_t echconfiglen = 0; + SSL_CTX *cctx = NULL, *sctx = NULL; + SSL *clientssl = NULL, *serverssl = NULL; + int clientstatus, serverstatus; + char *cinner = NULL, *couter = NULL, *sinner = NULL, *souter = NULL; + + /* + * Ensure 1st iteration happens in all cases - needed so that + * we can calculate the expected values for subsequent iterations + * if e.g. someone uses "-iter 2" on the command line + * Doing this via recursion may be odd, but is ok:-) + */ + if (idx != 0 && pad_array[0].cert_min == 0) + ech_pad_test(0); + + /* read our pre-cooked ECH PEM file */ + echkeyfile = test_mk_file_path(certsdir, "echconfig.pem"); + if (!TEST_ptr(echkeyfile)) + goto end; + echconfig = echconfiglist_from_PEM(echkeyfile); + if (!TEST_ptr(echconfig)) + goto end; + echconfiglen = strlen(echconfig); + 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; + /* set padding callback */ + SSL_CTX_set_record_padding_callback_arg(sctx, (void *)&(pad_array[idx])); + SSL_CTX_set_record_padding_callback(sctx, ech_padding_cb); + /* if not in 1st test then use the API to set fine-grained sizes */ + if (idx == 0) { + /* check zeros cause a fail */ + if (!TEST_false(SSL_CTX_ech_set_pad_sizes(sctx, &pad_array[idx]))) + goto end; + /* don't pad 1st time. */ + } else { + /* turn on padding */ + SSL_CTX_set_options(sctx, SSL_OP_ECH_SPECIFIC_PADDING); + if (!TEST_true(SSL_CTX_ech_set_pad_sizes(sctx, &pad_array[idx]))) + goto end; + } + if (!TEST_true(SSL_CTX_ech_set1_echconfig(cctx, (unsigned char *)echconfig, + echconfiglen))) + goto end; + if (!TEST_true(SSL_CTX_ech_server_enable_file(sctx, echkeyfile, + SSL_ECH_USE_FOR_RETRY))) + 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, "server.example"))) + goto end; + if (!TEST_true(create_ssl_connection(serverssl, clientssl, + SSL_ERROR_NONE))) + goto end; + serverstatus = SSL_ech_get_status(serverssl, &sinner, &souter); + if (verbose) + TEST_info("ech_pad_test: 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_get_status(clientssl, &cinner, &couter); + if (verbose) + TEST_info("ech_pad_test: client status %d, %s, %s", + clientstatus, cinner, couter); + if (!TEST_int_eq(clientstatus, SSL_ECH_STATUS_SUCCESS)) + goto end; + if (!TEST_int_eq(ech_pad_cb_fail,0)) + goto end; + /* all good */ + res = 1; +end: + OPENSSL_free(sinner); + OPENSSL_free(souter); + OPENSSL_free(cinner); + OPENSSL_free(couter); + OPENSSL_free(echkeyfile); + OPENSSL_free(echconfig); + SSL_free(clientssl); + SSL_free(serverssl); + SSL_CTX_free(cctx); + SSL_CTX_free(sctx); + return res; +} + /* Test a basic roundtrip with ECH, with a PEM file input */ static int ech_roundtrip_test(int idx) { @@ -998,12 +1171,10 @@ static int ech_tls12_with_ech_test(int idx) size_t badprivlen = sizeof(badpriv); uint16_t ech_version = OSSL_ECH_CURRENT_VERSION; OSSL_HPKE_SUITE hpke_suite = OSSL_HPKE_SUITE_DEFAULT; - // int err = 0, connrv = 0, err_reason = 0; char *good_public_name = "front.server.example"; char *public_name = good_public_name; X509_STORE *vfy = NULL; int cver; - // int exp_conn_err = SSL_R_ECH_REQUIRED; int client = 0; /* for these tests we want to chain to our root */ @@ -1078,7 +1249,6 @@ static int ech_tls12_with_ech_test(int idx) OPENSSL_free(souter); OPENSSL_free(cinner); OPENSSL_free(couter); - //OPENSSL_free(retryconfig); OPENSSL_free(echkeyfile); SSL_free(clientssl); SSL_free(serverssl); @@ -2376,6 +2546,7 @@ int setup_tests(void) ADD_ALL_TESTS(ech_wrong_pub_test, 3); ADD_ALL_TESTS(ech_tls12_with_ech_test, 1); ADD_ALL_TESTS(ech_sni_cb_test,1); + ADD_ALL_TESTS(ech_pad_test,OSSL_NELEM(pad_array)); return 1; err: return 0; diff --git a/util/libssl.num b/util/libssl.num index 1a2936d412bc8..d2204fd6ae51a 100644 --- a/util/libssl.num +++ b/util/libssl.num @@ -614,3 +614,5 @@ SSL_CTX_ech_raw_decrypt ? 3_4_0 EXIST::FUNCTION:ECH SSL_CTX_ech_set_callback ? 3_4_0 EXIST::FUNCTION:ECH OSSL_ech_make_echconfig ? 3_4_0 EXIST::FUNCTION:ECH OSSL_ech_find_echconfigs ? 3_4_0 EXIST::FUNCTION:ECH +SSL_CTX_ech_set_pad_sizes ? 3_4_0 EXIST::FUNCTION:ECH +SSL_ech_set_pad_sizes ? 3_4_0 EXIST::FUNCTION:ECH