Add server-side handling of Encrypted Client Hello

Reviewed-by: Matt Caswell <matt@openssl.org>
Reviewed-by: Tomas Mraz <tomas@openssl.org>
(Merged from https://github.com/openssl/openssl/pull/27561)
This commit is contained in:
sftcd 2025-05-05 14:23:55 +01:00 committed by Tomas Mraz
parent ecd40163f2
commit 497daff2b9
15 changed files with 1647 additions and 111 deletions

View File

@ -104,6 +104,8 @@ resumption and the historical servername callback.
The SSL_client_hello_* family of functions may only be called from code executing
within a ClientHello callback.
TODO(ECH): How ECH is handled here needs to be documented.
=head1 RETURN VALUES
The application's supplied ClientHello callback returns

View File

@ -17,9 +17,42 @@
# ifndef OPENSSL_NO_ECH
/*
* the max HPKE 'info' we'll process is the max ECHConfig size
* (OSSL_ECH_MAX_ECHCONFIG_LEN) plus OSSL_ECH_CONTEXT_STRING(len=7) + 1
*/
# define OSSL_ECH_MAX_INFO_LEN (OSSL_ECH_MAX_ECHCONFIG_LEN + 8)
int ossl_ech_make_enc_info(const unsigned char *encoding,
size_t encoding_length,
unsigned char *info, size_t *info_len);
/*
* Given a CH find the offsets of the session id, extensions and ECH
* ch is the encoded client hello
* ch_len is the length of ch
* sessid_off returns offset of session_id length
* exts_off points to offset of extensions
* exts_len returns length of extensions
* ech_off returns offset of ECH
* echtype returns the ext type of the ECH
* ech_len returns the length of the ECH
* sni_off returns offset of (outer) SNI
* sni_len returns the length of the SNI
* inner 1 if the ECH is marked as an inner, 0 for outer
* return 1 for success, other otherwise
*
* Offsets are set to zero if relevant thing not found.
* Offsets are returned to the type or length field in question.
*
* Note: input here is untrusted!
*/
int ossl_ech_helper_get_ch_offsets(const unsigned char *ch, size_t ch_len,
size_t *sessid_off, size_t *exts_off,
size_t *exts_len,
size_t *ech_off, uint16_t *echtype,
size_t *ech_len, size_t *sni_off,
size_t *sni_len, int *inner);
# endif
#endif

View File

@ -13,8 +13,6 @@
#include "ech_local.h"
#include "internal/ech_helpers.h"
/* TODO(ECH): move more code that's used by internals and test here */
/* used in ECH crypto derivations (odd format for EBCDIC goodness) */
/* "tls ech" */
static const char OSSL_ECH_CONTEXT_STRING[] = "\x74\x6c\x73\x20\x65\x63\x68";
@ -52,3 +50,101 @@ int ossl_ech_make_enc_info(const unsigned char *encoding,
WPACKET_cleanup(&ipkt);
return 1;
}
/*
* Given a CH find the offsets of the session id, extensions and ECH
* ch is the encoded client hello
* ch_len is the length of ch
* sessid_off returns offset of session_id length
* exts_off points to offset of extensions
* exts_len returns length of extensions
* ech_off returns offset of ECH
* echtype returns the ext type of the ECH
* ech_len returns the length of the ECH
* sni_off returns offset of (outer) SNI
* sni_len returns the length of the SNI
* inner 1 if the ECH is marked as an inner, 0 for outer
* return 1 for success, other otherwise
*
* Offsets are set to zero if relevant thing not found.
* Offsets are returned to the type or length field in question.
*
* Note: input here is untrusted!
*/
int ossl_ech_helper_get_ch_offsets(const unsigned char *ch, size_t ch_len,
size_t *sessid_off, size_t *exts_off,
size_t *exts_len,
size_t *ech_off, uint16_t *echtype,
size_t *ech_len, size_t *sni_off,
size_t *sni_len, int *inner)
{
unsigned int elen = 0, etype = 0, pi_tmp = 0;
const unsigned char *pp_tmp = NULL, *chstart = NULL, *estart = NULL;
PACKET pkt;
int done = 0;
if (ch == NULL || ch_len == 0 || sessid_off == NULL || exts_off == NULL
|| ech_off == NULL || echtype == NULL || ech_len == NULL
|| sni_off == NULL || inner == NULL)
return 0;
*sessid_off = *exts_off = *ech_off = *sni_off = *sni_len = *ech_len = 0;
*echtype = 0xffff;
if (!PACKET_buf_init(&pkt, ch, ch_len))
return 0;
chstart = PACKET_data(&pkt);
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)
return 1;
/* chew up the packet to extensions */
if (!PACKET_get_bytes(&pkt, &pp_tmp, SSL3_RANDOM_SIZE)
|| (*sessid_off = PACKET_data(&pkt) - chstart) == 0
|| !PACKET_get_1(&pkt, &pi_tmp) /* sessid len */
|| !PACKET_get_bytes(&pkt, &pp_tmp, pi_tmp) /* sessid */
|| !PACKET_get_net_2(&pkt, &pi_tmp) /* ciphersuite len */
|| !PACKET_get_bytes(&pkt, &pp_tmp, pi_tmp) /* suites */
|| !PACKET_get_1(&pkt, &pi_tmp) /* compression meths */
|| !PACKET_get_bytes(&pkt, &pp_tmp, pi_tmp) /* comp meths */
|| (*exts_off = PACKET_data(&pkt) - chstart) == 0
|| !PACKET_get_net_2(&pkt, &pi_tmp) /* len(extensions) */
|| (*exts_len = (size_t) pi_tmp) == 0)
/*
* unexpectedly, we return 1 here, as doing otherwise will
* break some non-ECH test code that truncates CH messages
* The same is true below when looking through extensions.
* That's ok though, we'll only set those offsets we've
* found.
*/
return 1;
/* no extensions is theoretically ok, if uninteresting */
if (*exts_len == 0)
return 1;
/* find what we want from extensions */
estart = PACKET_data(&pkt);
while (PACKET_remaining(&pkt) > 0
&& (size_t)(PACKET_data(&pkt) - estart) < *exts_len
&& done < 2) {
if (!PACKET_get_net_2(&pkt, &etype)
|| !PACKET_get_net_2(&pkt, &elen))
return 1; /* see note above */
if (etype == TLSEXT_TYPE_ech) {
if (elen == 0)
return 0;
*ech_off = PACKET_data(&pkt) - chstart - 4;
*echtype = etype;
*ech_len = elen;
done++;
}
if (etype == TLSEXT_TYPE_server_name) {
*sni_off = PACKET_data(&pkt) - chstart - 4;
*sni_len = elen;
done++;
}
if (!PACKET_get_bytes(&pkt, &pp_tmp, elen))
return 1; /* see note above */
if (etype == TLSEXT_TYPE_ech)
*inner = pp_tmp[0];
}
return 1;
}

File diff suppressed because it is too large Load Diff

View File

@ -118,6 +118,33 @@ typedef struct ossl_echstore_entry_st {
unsigned char *encoded; /* overall encoded content */
} OSSL_ECHSTORE_ENTRY;
/*
* What we send in the ech CH extension:
* enum { outer(0), inner(1) } ECHClientHelloType;
* struct {
* ECHClientHelloType type;
* select (ECHClientHello.type) {
* case outer:
* HpkeSymmetricCipherSuite cipher_suite;
* uint8 config_id;
* opaque enc<0..2^16-1>;
* opaque payload<1..2^16-1>;
* case inner:
* Empty;
* };
* } ECHClientHello;
*
*/
typedef struct ech_encch_st {
uint16_t kdf_id; /* ciphersuite */
uint16_t aead_id; /* ciphersuite */
uint8_t config_id; /* (maybe) identifies DNS RR value used */
size_t enc_len; /* public share */
unsigned char *enc; /* public share for sender */
size_t payload_len; /* ciphertext */
unsigned char *payload; /* ciphertext */
} OSSL_ECH_ENCCH;
DEFINE_STACK_OF(OSSL_ECHSTORE_ENTRY)
struct ossl_echstore_st {
@ -205,6 +232,22 @@ typedef struct ossl_ech_conn_st {
unsigned char *pub; /* client ephemeral public kept by server in case HRR */
size_t pub_len;
OSSL_HPKE_CTX *hpke_ctx; /* HPKE context, needed for HRR */
/*
* Offsets of various things we need to know about in an inbound
* ClientHello (CH) plus the type of ECH and whether that CH is an inner or
* outer CH. We find these once for the outer CH, by roughly parsing the CH
* so store them for later re-use. We need to re-do this parsing when we
* get the 2nd CH in the case of HRR, and when we move to processing the
* inner CH after successful ECH decyption, so we have a flag to say if
* we've done the work or not.
*/
int ch_offsets_done;
size_t sessid_off; /* offset of session_id length */
size_t exts_off; /* to offset of extensions */
size_t ech_off; /* offset of ECH */
size_t sni_off; /* offset of (outer) SNI */
int echtype; /* ext type of the ECH */
int inner; /* 1 if the ECH is marked as an inner, 0 for outer */
/*
* A pointer to, and copy of, the hrrsignal from an HRR message.
* We need both, as we zero-out the octets when re-calculating and

View File

@ -185,7 +185,7 @@ int SSL_ech_get1_status(SSL *ssl, char **inner_sni, char **outer_sni)
return SSL_ECH_STATUS_GREASE_ECH;
return SSL_ECH_STATUS_GREASE;
}
if ((s->options & SSL_OP_ECH_GREASE) !=0 && s->ext.ech.attempted != 1)
if ((s->options & SSL_OP_ECH_GREASE) != 0 && s->ext.ech.attempted != 1)
return SSL_ECH_STATUS_GREASE;
if (s->ext.ech.backend == 1) {
if (s->ext.hostname != NULL

View File

@ -498,13 +498,8 @@ static const EXTENSION_DEFINITION ext_defs[] = {
SSL_EXT_TLS1_3_HELLO_RETRY_REQUEST,
OSSL_ECH_HANDLING_CALL_BOTH,
init_ech,
/*
* TODO(ECH): add server calls as per below in a bit
* tls_parse_ctos_ech, tls_parse_stoc_ech,
* tls_construct_stoc_ech, tls_construct_ctos_ech,
*/
NULL, tls_parse_stoc_ech,
NULL, tls_construct_ctos_ech,
tls_parse_ctos_ech, tls_parse_stoc_ech,
tls_construct_stoc_ech, tls_construct_ctos_ech,
NULL
},
{

View File

@ -14,12 +14,7 @@
#include "statem_local.h"
#ifndef OPENSSL_NO_ECH
# include <openssl/rand.h>
#include "internal/ech_helpers.h"
/*
* the max HPKE 'info' we'll process is the max ECHConfig size
* (OSSL_ECH_MAX_ECHCONFIG_LEN) plus OSSL_ECH_CONTEXT_STRING(len=7) + 1
*/
#define OSSL_ECH_MAX_INFO_LEN (OSSL_ECH_MAX_ECHCONFIG_LEN + 8)
# include "internal/ech_helpers.h"
#endif
EXT_RETURN tls_construct_ctos_renegotiate(SSL_CONNECTION *s, WPACKET *pkt,
@ -2524,7 +2519,6 @@ EXT_RETURN tls_construct_ctos_ech(SSL_CONNECTION *s, WPACKET *pkt,
unsigned char *encoded = NULL, *mypub = NULL;
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));

View File

@ -209,8 +209,8 @@ int custom_ext_add(SSL_CONNECTION *s, int context, WPACKET *pkt, X509 *x,
if (s->ext.ech.n_outer_only >= OSSL_ECH_OUTERS_MAX) {
OSSL_TRACE_BEGIN(TLS) {
BIO_printf(trc_out,
"Too many outers to compress (max=%d)\n",
OSSL_ECH_OUTERS_MAX);
"Too many outers to compress (max=%d)\n",
OSSL_ECH_OUTERS_MAX);
} OSSL_TRACE_END(TLS);
SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_R_BAD_EXTENSION);
return 0;
@ -219,9 +219,9 @@ int custom_ext_add(SSL_CONNECTION *s, int context, WPACKET *pkt, X509 *x,
s->ext.ech.n_outer_only++;
OSSL_TRACE_BEGIN(TLS) {
BIO_printf(trc_out, "ECH compressing type "
"0x%04x (tot: %d)\n",
(int) meth->ext_type,
(int) s->ext.ech.n_outer_only);
"0x%04x (tot: %d)\n",
(int) meth->ext_type,
(int) s->ext.ech.n_outer_only);
} OSSL_TRACE_END(TLS);
}
if (s->ext.ech.ch_depth == 0) {
@ -247,8 +247,8 @@ int custom_ext_add(SSL_CONNECTION *s, int context, WPACKET *pkt, X509 *x,
SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_R_BAD_EXTENSION);
return 0;
}
if (ossl_ech_copy_inner2outer(s, meth->ext_type, tind, pkt)
!= OSSL_ECH_SAME_EXT_DONE) {
if (ossl_ech_copy_inner2outer(s, meth->ext_type, tind,
pkt) != OSSL_ECH_SAME_EXT_DONE) {
/* for custom exts, we really should have found it */
SSLfatal(s, SSL_AD_INTERNAL_ERROR, SSL_R_BAD_EXTENSION);
return 0;
@ -505,19 +505,7 @@ int ossl_tls_add_custom_ext_intern(SSL_CTX *ctx, custom_ext_methods *exts,
* for extension types that previously were not supported, but now are.
*/
if (SSL_extension_supported(ext_type)
#if !defined(OPENSSL_NO_ECH) && defined(OPENSSL_ECH_ALLOW_CUST_INJECT)
/*
* Do this conditionally so we can test an ECH in TLSv1.2
* via the custom extensions API.
* OPENSSL_ECH_ALLOW_CUST_INJECT is defined (or not) in
* include/openssl/ech.h and if defined enables a test in
* test/ech_test.c
*/
&& ext_type != TLSEXT_TYPE_ech
&& ext_type != TLSEXT_TYPE_signed_certificate_timestamp)
#else
&& ext_type != TLSEXT_TYPE_signed_certificate_timestamp)
#endif
return 0;
/* Extension type must fit in 16 bits */

View File

@ -13,7 +13,12 @@
#include "internal/cryptlib.h"
#include "internal/ssl_unwrap.h"
#define COOKIE_STATE_FORMAT_VERSION 1
#ifndef OPENSSL_NO_ECH
# include <openssl/rand.h>
# include <openssl/trace.h>
#endif
#define COOKIE_STATE_FORMAT_VERSION 1
/*
* 2 bytes for packet length, 2 bytes for format version, 2 bytes for
@ -2420,3 +2425,152 @@ int tls_parse_ctos_server_cert_type(SSL_CONNECTION *sc, PACKET *pkt,
SSLfatal(sc, SSL_AD_UNSUPPORTED_CERTIFICATE, SSL_R_BAD_EXTENSION);
return 0;
}
#ifndef OPENSSL_NO_ECH
/*
* ECH handling for edge cases (GREASE/inner) and errors.
* return 1 for good, 0 otherwise
*
* Real ECH handling (i.e. decryption) happens before, via
* ech_early_decrypt(), but if that failed (e.g. decryption
* failed, which may be down to GREASE) then we end up here,
* processing the ECH from the outer CH.
* Otherwise, we only expect to see an inner ECH with a fixed
* value here.
*/
int tls_parse_ctos_ech(SSL_CONNECTION *s, PACKET *pkt, unsigned int context,
X509 *x, size_t chainidx)
{
unsigned int echtype = 0;
if (s->ext.ech.grease == OSSL_ECH_IS_GREASE) {
/* GREASE is fine */
return 1;
}
if (s->ext.ech.es == NULL) {
/* If not configured for ECH then we ignore it */
return 1;
}
if (s->ext.ech.attempted_type != TLSEXT_TYPE_ech) {
/* if/when new versions of ECH are added we'll update here */
SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
return 0;
}
/*
* we only allow "inner" which is one octet, valued 0x01
* and only if we decrypted ok or are a backend
*/
if (PACKET_get_1(pkt, &echtype) != 1
|| echtype != OSSL_ECH_INNER_CH_TYPE
|| PACKET_remaining(pkt) != 0) {
SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
return 0;
}
if (s->ext.ech.success != 1 && s->ext.ech.backend != 1) {
SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
return 0;
}
/* yay - we're ok with this */
OSSL_TRACE_BEGIN(TLS) {
BIO_printf(trc_out, "ECH seen in inner as exptected.\n");
} OSSL_TRACE_END(TLS);
return 1;
}
/*
* Answer an ECH, as needed
* return 1 for good, 0 otherwise
*
* Return most-recent ECH config for retry, as needed.
* If doing HRR we include the confirmation value, but
* for now, we'll just add the zeros - the real octets
* will be added later via ech_calc_ech_confirm() which
* is called when constructing the server hello.
*/
EXT_RETURN tls_construct_stoc_ech(SSL_CONNECTION *s, WPACKET *pkt,
unsigned int context, X509 *x,
size_t chainidx)
{
unsigned char *rcfgs = NULL;
size_t rcfgslen = 0;
if (context == SSL_EXT_TLS1_3_HELLO_RETRY_REQUEST
&& (s->ext.ech.success == 1 || s->ext.ech.backend == 1)
&& s->ext.ech.attempted_type == TLSEXT_TYPE_ech) {
unsigned char eightzeros[8] = {0, 0, 0, 0, 0, 0, 0, 0};
if (!WPACKET_put_bytes_u16(pkt, s->ext.ech.attempted_type)
|| !WPACKET_sub_memcpy_u16(pkt, eightzeros, 8)) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
return 0;
}
OSSL_TRACE_BEGIN(TLS) {
BIO_printf(trc_out, "set 8 zeros for ECH accept confirm in HRR\n");
} OSSL_TRACE_END(TLS);
return EXT_RETURN_SENT;
}
/* GREASE or error => random confirmation in HRR case */
if (context == SSL_EXT_TLS1_3_HELLO_RETRY_REQUEST
&& s->ext.ech.attempted_type == TLSEXT_TYPE_ech
&& s->ext.ech.attempted == 1) {
unsigned char randomconf[8];
if (RAND_bytes_ex(s->ssl.ctx->libctx, randomconf, 8,
RAND_DRBG_STRENGTH) <= 0) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
return 0;
}
if (!WPACKET_put_bytes_u16(pkt, s->ext.ech.attempted_type)
|| !WPACKET_sub_memcpy_u16(pkt, randomconf, 8)) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
return 0;
}
OSSL_TRACE_BEGIN(TLS) {
BIO_printf(trc_out, "set random for ECH acccpt confirm in HRR\n");
} OSSL_TRACE_END(TLS);
return EXT_RETURN_SENT;
}
/* in other HRR circumstances: don't set */
if (context == SSL_EXT_TLS1_3_HELLO_RETRY_REQUEST)
return EXT_RETURN_NOT_SENT;
/* If in some weird state we ignore and send nothing */
if (s->ext.ech.grease != OSSL_ECH_IS_GREASE
|| s->ext.ech.attempted_type != TLSEXT_TYPE_ech)
return EXT_RETURN_NOT_SENT;
/*
* If the client GREASEd, or we think it did, return the
* most-recently loaded ECHConfigList, as the value of the
* extension. Most-recently loaded can be anywhere in the
* list, depending on changing or non-changing file names.
*/
if (s->ext.ech.es == NULL) {
OSSL_TRACE_BEGIN(TLS) {
BIO_printf(trc_out, "ECH - not sending ECHConfigList to client "
"even though they GREASE'd as I've no loaded configs\n");
} OSSL_TRACE_END(TLS);
return EXT_RETURN_NOT_SENT;
}
if (ossl_ech_get_retry_configs(s, &rcfgs, &rcfgslen) != 1) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
return 0;
}
if (rcfgslen == 0) {
OSSL_TRACE_BEGIN(TLS) {
BIO_printf(trc_out, "ECH - not sending ECHConfigList to client "
"even though they GREASE'd and I have configs but "
"I've no configs set to be returned\n");
} OSSL_TRACE_END(TLS);
return EXT_RETURN_NOT_SENT;
}
if (!WPACKET_put_bytes_u16(pkt, TLSEXT_TYPE_ech)
|| !WPACKET_start_sub_packet_u16(pkt)
|| !WPACKET_sub_memcpy_u16(pkt, rcfgs, rcfgslen)
|| !WPACKET_close(pkt)) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
OPENSSL_free(rcfgs);
return 0;
}
OPENSSL_free(rcfgs);
return EXT_RETURN_SENT;
}
#endif /* END OPENSSL_NO_ECH */

View File

@ -1312,7 +1312,7 @@ __owur CON_FUNC_RETURN tls_construct_client_hello(SSL_CONNECTION *s,
goto err;
}
OPENSSL_free(s->ext.ech.innerch);
s->ext.ech.innerch = (unsigned char*)inner_mem->data;
s->ext.ech.innerch = (unsigned char *)inner_mem->data;
inner_mem->data = NULL;
s->ext.ech.innerch_len = innerlen;
/* add inner to transcript */

View File

@ -574,7 +574,9 @@ int tls_parse_stoc_server_cert_type(SSL_CONNECTION *s, PACKET *pkt,
EXT_RETURN tls_construct_ctos_ech(SSL_CONNECTION *s, WPACKET *pkt,
unsigned int context, X509 *x,
size_t chainidx);
EXT_RETURN tls_construct_ctos_ech(SSL_CONNECTION *s, WPACKET *pkt,
int tls_parse_ctos_ech(SSL_CONNECTION *s, PACKET *pkt, unsigned int context,
X509 *x, size_t chainidx);
EXT_RETURN tls_construct_stoc_ech(SSL_CONNECTION *s, WPACKET *pkt,
unsigned int context, X509 *x,
size_t chainidx);
int tls_parse_stoc_ech(SSL_CONNECTION *s, PACKET *pkt, unsigned int context,

View File

@ -35,6 +35,10 @@
#define TICKET_NONCE_SIZE 8
#ifndef OPENSSL_NO_ECH
# include "../ech/ech_local.h"
#endif
typedef struct {
ASN1_TYPE *kxBlob;
ASN1_TYPE *opaqueBlob;
@ -1495,6 +1499,86 @@ MSG_PROCESS_RETURN tls_process_client_hello(SSL_CONNECTION *s, PACKET *pkt)
static const unsigned char null_compression = 0;
CLIENTHELLO_MSG *clienthello = NULL;
#ifndef OPENSSL_NO_ECH
/*
* For a split-mode backend we want to have a way to point at the CH octets
* for the accept-confirmation calculation. The split-mode backend does not
* need any ECH secrets, but it does need to see the inner CH and be the TLS
* endpoint with which the ECH encrypting client sets up the TLS session.
* The split-mode backend however does need to do an ECH confirm calculation
* so we need to tee that up. The result of that calculation will be put in
* the ServerHello.random (or ECH extension if HRR) to signal to the client
* that ECH "worked."
*/
if (s->server && PACKET_remaining(pkt) != 0) {
int rv = 0, innerflag = -1;
size_t startofsessid = 0, startofexts = 0, echoffset = 0;
size_t outersnioffset = 0; /* offset to SNI in outer */
uint16_t echtype = OSSL_ECH_type_unknown; /* type of ECH seen */
const unsigned char *pbuf = NULL;
/* reset needed in case of HRR */
s->ext.ech.ch_offsets_done = 0;
rv = ossl_ech_get_ch_offsets(s, pkt, &startofsessid, &startofexts,
&echoffset, &echtype, &innerflag,
&outersnioffset);
if (rv != 1) {
SSLfatal(s, SSL_AD_DECODE_ERROR, SSL_R_BAD_EXTENSION);
goto err;
}
if (innerflag == OSSL_ECH_INNER_CH_TYPE) {
WPACKET inner;
OSSL_TRACE(TLS, "Got inner ECH so setting backend\n");
/* For backend, include msg type & 3 octet length */
s->ext.ech.backend = 1;
s->ext.ech.attempted_type = TLSEXT_TYPE_ech;
OPENSSL_free(s->ext.ech.innerch);
s->ext.ech.innerch_len = PACKET_remaining(pkt);
if (PACKET_peek_bytes(pkt, &pbuf, s->ext.ech.innerch_len) != 1) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
goto err;
}
s->ext.ech.innerch_len += SSL3_HM_HEADER_LENGTH; /* 4 */
s->ext.ech.innerch = OPENSSL_malloc(s->ext.ech.innerch_len);
if (s->ext.ech.innerch == NULL) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
goto err;
}
if (!WPACKET_init_static_len(&inner, s->ext.ech.innerch,
s->ext.ech.innerch_len, 0)
|| !WPACKET_put_bytes_u8(&inner, SSL3_MT_CLIENT_HELLO)
|| !WPACKET_put_bytes_u24(&inner, s->ext.ech.innerch_len
- SSL3_HM_HEADER_LENGTH)
|| !WPACKET_memcpy(&inner, pbuf, s->ext.ech.innerch_len
- SSL3_HM_HEADER_LENGTH)
|| !WPACKET_finish(&inner)) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
goto err;
}
} else if (s->ext.ech.es != NULL) {
PACKET newpkt;
if (ossl_ech_early_decrypt(s, pkt, &newpkt) != 1) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
goto err;
}
if (s->ext.ech.success == 1) {
/*
* Replace the outer CH with the inner, as long as there's
* space, which there better be! (a bug triggered a bigger
* inner CH once;-)
*/
if (PACKET_remaining(&newpkt) > PACKET_remaining(pkt)) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
goto err;
}
*pkt = newpkt;
}
}
}
#endif
/* Check if this is actually an unexpected renegotiation ClientHello */
if (s->renegotiate == 0 && !SSL_IS_FIRST_HANDSHAKE(s)) {
if (!ossl_assert(!SSL_CONNECTION_IS_TLS13(s))) {
@ -1696,6 +1780,12 @@ MSG_PROCESS_RETURN tls_process_client_hello(SSL_CONNECTION *s, PACKET *pkt)
if (clienthello != NULL)
OPENSSL_free(clienthello->pre_proc_exts);
OPENSSL_free(clienthello);
#ifndef OPENSSL_NO_ECH
s->clienthello = NULL;
OPENSSL_free(s->ext.ech.innerch);
s->ext.ech.innerch = NULL;
s->ext.ech.innerch_len = 0;
#endif
return MSG_PROCESS_ERROR;
}
@ -1989,12 +2079,24 @@ static int tls_early_post_process_client_hello(SSL_CONNECTION *s)
goto err;
}
if (!s->hit
&& s->version >= TLS1_VERSION
&& !SSL_CONNECTION_IS_TLS13(s)
&& !SSL_CONNECTION_IS_DTLS(s)
&& s->ext.session_secret_cb != NULL) {
/*
* Unless ECH has worked or not been configured we won't call
* the session_secret_cb now because we'll need to calculate the
* server random later to include the ECH accept value.
* We can't do it now as we don't yet have the SH encoding.
*/
if (
#ifndef OPENSSL_NO_ECH
((s->ext.ech.es != NULL && s->ext.ech.success == 1)
|| s->ext.ech.es == NULL) &&
#endif
!s->hit
&& s->version >= TLS1_VERSION
&& !SSL_CONNECTION_IS_TLS13(s)
&& !SSL_CONNECTION_IS_DTLS(s)
&& s->ext.session_secret_cb != NULL) {
const SSL_CIPHER *pref_cipher = NULL;
/*
* s->session->master_key_length is a size_t, but this is an int for
* backwards compat reasons
@ -2151,7 +2253,8 @@ static int tls_early_post_process_client_hello(SSL_CONNECTION *s)
err:
sk_SSL_CIPHER_free(ciphers);
sk_SSL_CIPHER_free(scsvs);
OPENSSL_free(clienthello->pre_proc_exts);
if (clienthello != NULL)
OPENSSL_free(clienthello->pre_proc_exts);
OPENSSL_free(s->clienthello);
s->clienthello = NULL;
@ -2513,16 +2616,147 @@ CON_FUNC_RETURN tls_construct_server_hello(SSL_CONNECTION *s, WPACKET *pkt)
* Re-initialise the Transcript Hash. We're going to prepopulate it with
* a synthetic message_hash in place of ClientHello1.
*/
if (!create_synthetic_message_hash(s, NULL, 0, NULL, 0)) {
#ifndef OPENSSL_NO_ECH
/*
* if we're sending 2nd SH after HRR and we did ECH
* then we want to inject the hash of the inner CH1
* and not the outer (which is the default)
*/
OSSL_TRACE_BEGIN(TLS) {
BIO_printf(trc_out, "Checking success (%d)/innerCH (%p)\n",
s->ext.ech.success, (void *)s->ext.ech.innerch);
} OSSL_TRACE_END(TLS);
if ((s->ext.ech.backend == 1 || s->ext.ech.success == 1)
&& s->ext.ech.innerch != NULL) {
/* do pre-existing HRR stuff */
unsigned char hashval[EVP_MAX_MD_SIZE];
unsigned int hashlen;
EVP_MD_CTX *ctx = EVP_MD_CTX_new();
const EVP_MD *md = NULL;
OSSL_TRACE(TLS, "Adding in digest of ClientHello\n");
# ifdef OSSL_ECH_SUPERVERBOSE
ossl_ech_pbuf("innerch", s->ext.ech.innerch,
s->ext.ech.innerch_len);
# endif
if (ctx == NULL) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
return CON_FUNC_ERROR;
}
md = ssl_handshake_md(s);
if (md == NULL) {
EVP_MD_CTX_free(ctx);
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
return CON_FUNC_ERROR;
}
if (EVP_DigestInit_ex(ctx, md, NULL) <= 0
|| EVP_DigestUpdate(ctx, s->ext.ech.innerch,
s->ext.ech.innerch_len) <= 0
|| EVP_DigestFinal_ex(ctx, hashval, &hashlen) <= 0) {
EVP_MD_CTX_free(ctx);
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
return CON_FUNC_ERROR;
}
# ifdef OSSL_ECH_SUPERVERBOSE
ossl_ech_pbuf("digested CH", hashval, hashlen);
# endif
EVP_MD_CTX_free(ctx);
if (ossl_ech_reset_hs_buffer(s, NULL, 0) != 1) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
return CON_FUNC_ERROR;
}
if (!create_synthetic_message_hash(s, hashval, hashlen, NULL, 0)) {
/* SSLfatal() already called */
return CON_FUNC_ERROR;
}
} else {
if (!create_synthetic_message_hash(s, NULL, 0, NULL, 0))
return CON_FUNC_ERROR; /* SSLfatal() already called */
}
#else
if (!create_synthetic_message_hash(s, NULL, 0, NULL, 0))
/* SSLfatal() already called */
return CON_FUNC_ERROR;
}
#endif /* OPENSSL_NO_ECH */
} else if (!(s->verify_mode & SSL_VERIFY_PEER)
&& !ssl3_digest_cached_records(s, 0)) {
&& !ssl3_digest_cached_records(s, 0)) {
/* SSLfatal() already called */;
return CON_FUNC_ERROR;
}
#ifndef OPENSSL_NO_ECH
/*
* Calculate the ECH-accept server random to indicate that
* we're accepting ECH, if that's the case
*/
if (s->ext.ech.attempted_type == TLSEXT_TYPE_ech
&& (s->ext.ech.backend == 1
|| (s->ext.ech.es != NULL && s->ext.ech.success == 1))) {
unsigned char acbuf[8];
unsigned char *shbuf = NULL;
size_t shlen = 0;
size_t shoffset = 0;
int hrr = 0;
if (s->hello_retry_request == SSL_HRR_PENDING)
hrr = 1;
memset(acbuf, 0, 8);
if (WPACKET_get_total_written(pkt, &shlen) != 1) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
return CON_FUNC_ERROR;
}
shbuf = WPACKET_get_curr(pkt) - shlen;
/* we need to fixup SH length here */
shbuf[1] = ((shlen - 4)) >> 16 & 0xff;
shbuf[2] = ((shlen - 4)) >> 8 & 0xff;
shbuf[3] = (shlen - 4) & 0xff;
if (ossl_ech_intbuf_add(s, shbuf, shlen, hrr) != 1) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
return CON_FUNC_ERROR;
}
if (ossl_ech_calc_confirm(s, hrr, acbuf, shlen) != 1) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
return CON_FUNC_ERROR;
}
memcpy(s->s3.server_random + SSL3_RANDOM_SIZE - 8, acbuf, 8);
if (hrr == 0) {
/* confirm value hacked into SH.random rightmost octets */
shoffset = SSL3_HM_HEADER_LENGTH /* 4 */
+ CLIENT_VERSION_LEN /* 2 */
+ SSL3_RANDOM_SIZE /* 32 */
- 8;
memcpy(shbuf + shoffset, acbuf, 8);
} else {
/*
* confirm value is in extension in HRR case as the SH.random
* is already hacked to be a specific value in a HRR
*/
memcpy(WPACKET_get_curr(pkt) - 8, acbuf, 8);
}
}
/* call ECH callback, if appropriate */
if (s->ext.ech.attempted == 1 && s->ext.ech.cb != NULL
&& s->hello_retry_request != SSL_HRR_PENDING) {
char pstr[OSSL_ECH_PBUF_SIZE + 1];
BIO *biom = BIO_new(BIO_s_mem());
unsigned int cbrv = 0;
if (biom == NULL) {
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
return CON_FUNC_ERROR;
}
memset(pstr, 0, OSSL_ECH_PBUF_SIZE + 1);
ossl_ech_status_print(biom, s, OSSL_ECHSTORE_ALL);
BIO_read(biom, pstr, OSSL_ECH_PBUF_SIZE);
cbrv = s->ext.ech.cb(&s->ssl, pstr);
BIO_free(biom);
if (cbrv != 1) {
OSSL_TRACE(TLS, "Error from tls_construct_server_hello/ech_cb\n");
SSLfatal(s, SSL_AD_INTERNAL_ERROR, ERR_R_INTERNAL_ERROR);
return CON_FUNC_ERROR;
}
}
#endif /* OPENSSL_NO_ECH */
return CON_FUNC_SUCCESS;
}

View File

@ -893,11 +893,15 @@ static int ech_ingest_test(int run)
* Occasionally, flush_time will be 1 more than add_time. We'll
* check for that as that should catch a few more code paths
* in the flush_keys API.
* When flush_time is 1 more, we may or may not have flushed
* the one and only key (depending on which "side" of the second
* it was generated, so we may be left with 0 or 1 keys.
*/
if (!TEST_true(OSSL_ECHSTORE_flush_keys(es, flush_time - add_time))
|| !TEST_int_eq(OSSL_ECHSTORE_num_keys(es, &keysaftr), 1)
|| ((flush_time <= add_time) && !TEST_int_eq(keysaftr, 0))
|| ((flush_time > add_time) && !TEST_int_eq(keysaftr, 1))) {
|| ((flush_time > add_time) && !TEST_int_eq(keysaftr, 1)
&& !TEST_int_eq(keysaftr, 0))) {
TEST_info("Flush time: %lld, add_time: %lld", (long long)flush_time,
(long long)add_time);
goto end;
@ -1141,6 +1145,7 @@ end:
# define OSSL_ECH_TEST_EARLY 2
# define OSSL_ECH_TEST_CUSTOM 3
# define OSSL_ECH_TEST_ENOE 4 /* early + no-ech */
/* note: early-data is prohibited after HRR so no tests for that */
/*
* @brief ECH roundtrip test helper
@ -1155,13 +1160,6 @@ end:
*
* The combo input is one of the #define'd OSSL_ECH_TEST_*
* values above.
*
* TODO(ECH): we're not yet really attempting ECH, but we currently
* set the inputs as if we were doing ECH, yet don't expect to see
* real ECH status outcomes, so while we do make calls to get that
* status outcome, we don't compare vs. real expected results.
* That's done via the "if (0 &&" clauses below which will be
* removed once ECH is really being attempted.
*/
static int test_ech_roundtrip_helper(int idx, int combo)
{
@ -1205,10 +1203,9 @@ static int test_ech_roundtrip_helper(int idx, int combo)
if (combo == OSSL_ECH_TEST_EARLY || combo == OSSL_ECH_TEST_ENOE) {
if (!TEST_true(SSL_CTX_set_options(sctx, SSL_OP_NO_ANTI_REPLAY))
|| !TEST_true(SSL_CTX_set_max_early_data(sctx,
SSL3_RT_MAX_PLAIN_LENGTH)))
goto end;
if (!TEST_true(SSL_CTX_set_recv_max_early_data(sctx,
SSL3_RT_MAX_PLAIN_LENGTH)))
SSL3_RT_MAX_PLAIN_LENGTH))
|| !TEST_true(SSL_CTX_set_recv_max_early_data(sctx,
SSL3_RT_MAX_PLAIN_LENGTH)))
goto end;
}
if (combo == OSSL_ECH_TEST_CUSTOM) {
@ -1239,58 +1236,38 @@ static int test_ech_roundtrip_helper(int idx, int combo)
goto end;
if (!TEST_true(SSL_set_tlsext_host_name(clientssl, "server.example")))
goto end;
# undef DROPFORNOW
# ifdef DROPFORNOW
/* TODO(ECH): we'll re-instate this once server-side ECH code is in */
if (!TEST_true(create_ssl_connection(serverssl, clientssl,
SSL_ERROR_NONE)))
goto end;
# else
/*
* For this PR, check connections fail when client does ECH
* and server doesn't, but work if client doesn't do ECH.
* Added in early data for the no-ECH case because an
* intermediate state of the code had an issue.
*/
if (combo != OSSL_ECH_TEST_ENOE
&& !TEST_false(create_ssl_connection(serverssl, clientssl,
SSL_ERROR_NONE)))
goto end;
if (combo == OSSL_ECH_TEST_ENOE
&& !TEST_true(create_ssl_connection(serverssl, clientssl,
SSL_ERROR_NONE)))
goto end;
# endif
serverstatus = SSL_ech_get1_status(serverssl, &sinner, &souter);
if (verbose)
TEST_info("server status %d, %s, %s", serverstatus, sinner, souter);
if (0 && !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 (0 && !TEST_int_eq(clientstatus, SSL_ECH_STATUS_SUCCESS))
serverstatus = SSL_ech_get1_status(serverssl, &sinner, &souter);
if (verbose)
TEST_info("server status %d, %s, %s", serverstatus, sinner, souter);
if (combo != OSSL_ECH_TEST_ENOE
&& !TEST_int_eq(serverstatus, SSL_ECH_STATUS_SUCCESS))
goto end;
if (combo == OSSL_ECH_TEST_ENOE
&& !TEST_int_eq(serverstatus, SSL_ECH_STATUS_NOT_TRIED))
goto end;
if (combo != OSSL_ECH_TEST_ENOE
&& !TEST_int_eq(clientstatus, SSL_ECH_STATUS_SUCCESS))
goto end;
if (combo == OSSL_ECH_TEST_ENOE
&& !TEST_int_eq(clientstatus, SSL_ECH_STATUS_NOT_CONFIGURED))
goto end;
/* all good */
if (combo == OSSL_ECH_TEST_BASIC
|| combo == OSSL_ECH_TEST_HRR
if (combo == OSSL_ECH_TEST_BASIC || combo == OSSL_ECH_TEST_HRR
|| combo == OSSL_ECH_TEST_CUSTOM) {
res = 1;
goto end;
}
/* continue for EARLY test */
# ifdef DROPFORNOW
/* TODO(ECH): turn back on later */
if (combo != OSSL_ECH_TEST_EARLY && combo != OSSL_ECH_TEST_ENOE)
goto end;
# else
if (combo != OSSL_ECH_TEST_ENOE) {
res = 1;
goto end;
}
# endif
/* shutdown for start over */
sess = SSL_get1_session(clientssl);
OPENSSL_free(sinner);
@ -1310,8 +1287,8 @@ static int test_ech_roundtrip_helper(int idx, int combo)
|| !TEST_true(SSL_set_session(clientssl, sess))
|| !TEST_true(SSL_write_early_data(clientssl, ed, sizeof(ed), &written))
|| !TEST_size_t_eq(written, sizeof(ed))
|| !TEST_int_eq(SSL_read_early_data(serverssl, buf,
sizeof(buf), &readbytes),
|| !TEST_int_eq(SSL_read_early_data(serverssl, buf, sizeof(buf),
&readbytes),
SSL_READ_EARLY_DATA_SUCCESS)
|| !TEST_size_t_eq(written, readbytes))
goto end;
@ -1324,17 +1301,25 @@ static int test_ech_roundtrip_helper(int idx, int combo)
|| !TEST_true(SSL_read_ex(clientssl, buf, sizeof(buf), &readbytes))
|| !TEST_mem_eq(buf, readbytes, ed, sizeof(ed)))
goto end;
serverstatus = SSL_ech_get1_status(serverssl, &sinner, &souter);
if (verbose)
TEST_info("server status %d, %s, %s", serverstatus, sinner, souter);
if (0 && !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 (0 && !TEST_int_eq(clientstatus, SSL_ECH_STATUS_SUCCESS))
serverstatus = SSL_ech_get1_status(serverssl, &sinner, &souter);
if (verbose)
TEST_info("server status %d, %s, %s", serverstatus, sinner, souter);
if (combo != OSSL_ECH_TEST_ENOE
&& !TEST_int_eq(serverstatus, SSL_ECH_STATUS_SUCCESS))
goto end;
if (combo == OSSL_ECH_TEST_ENOE
&& !TEST_int_eq(serverstatus, SSL_ECH_STATUS_NOT_TRIED))
goto end;
if (combo != OSSL_ECH_TEST_ENOE
&& !TEST_int_eq(clientstatus, SSL_ECH_STATUS_SUCCESS))
goto end;
if (combo == OSSL_ECH_TEST_ENOE
&& !TEST_int_eq(clientstatus, SSL_ECH_STATUS_NOT_CONFIGURED))
goto end;
/* all good */
res = 1;

View File

@ -89,6 +89,7 @@ __current_exception_context
strlen
strstr
strchr
strlen
memmove
strrchr
memcmp