blob: b42196e210c423c976c63fcaf63c025c4460b7b8 [file] [log] [blame] [edit]
/*
* Copyright 2024-2026 The OpenSSL Project Authors. All Rights Reserved.
*
* Licensed under the OpenSSL license (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
*/
/*
* Internal data structures and prototypes for handling
* Encrypted ClientHello (ECH)
*/
#ifndef OPENSSL_NO_ECH
#ifndef HEADER_ECH_LOCAL_H
#define HEADER_ECH_LOCAL_H
#include <openssl/ssl.h>
#include <openssl/ech.h>
#include <openssl/hpke.h>
/*
* Define this to get loads more lines of tracing which is
* very useful for interop.
* This needs tracing enabled at build time, e.g.:
* $ ./config enable-ssl-trace enable-trace
* This added tracing will finally (mostly) disappear once the ECH RFC
* has issued, but is very useful for interop testing so some of it might
* be retained.
*/
#define OSSL_ECH_SUPERVERBOSE
/* values for s->ext.ech.grease */
#define OSSL_ECH_GREASE_UNKNOWN -1 /* when we're not yet sure */
#define OSSL_ECH_NOT_GREASE 0 /* when decryption worked */
#define OSSL_ECH_IS_GREASE 1 /* when decryption failed or GREASE wanted */
/* value for uninitialised ECH version */
#define OSSL_ECH_type_unknown 0xffff
/* value for not yet set ECH config_id */
#define OSSL_ECH_config_id_unset -1
#define OSSL_ECH_OUTER_CH_TYPE 0 /* outer ECHClientHello enum */
#define OSSL_ECH_INNER_CH_TYPE 1 /* inner ECHClientHello enum */
#define OSSL_ECH_CIPHER_LEN 4 /* ECHCipher length (2 for kdf, 2 for aead) */
#define OSSL_ECH_SIGNAL_LEN 8 /* length of ECH acceptance signal */
/* size of string buffer returned via ECH callback */
#define OSSL_ECH_PBUF_SIZE 8 * 1024
#ifndef CLIENT_VERSION_LEN
/*
* This is the legacy version length, i.e. len(0x0303). The same
* label is used in e.g. test/sslapitest.c and elsewhere but not
* defined in a header file I could find.
*/
#define CLIENT_VERSION_LEN 2
#endif
/*
* Reminder of what goes in DNS for ECH RFC XXXX
*
* opaque HpkePublicKey<1..2^16-1>;
* uint16 HpkeKemId; // Defined in I-D.irtf-cfrg-hpke
* uint16 HpkeKdfId; // Defined in I-D.irtf-cfrg-hpke
* uint16 HpkeAeadId; // Defined in I-D.irtf-cfrg-hpke
* struct {
* HpkeKdfId kdf_id;
* HpkeAeadId aead_id;
* } HpkeSymmetricCipherSuite;
* struct {
* uint8 config_id;
* HpkeKemId kem_id;
* HpkePublicKey public_key;
* HpkeSymmetricCipherSuite cipher_suites<4..2^16-4>;
* } HpkeKeyConfig;
* struct {
* HpkeKeyConfig key_config;
* uint8 maximum_name_length;
* opaque public_name<1..255>;
* Extension extensions<0..2^16-1>;
* } ECHConfigContents;
* struct {
* uint16 version;
* uint16 length;
* select (ECHConfig.version) {
* case 0xfe0d: ECHConfigContents contents;
* }
* } ECHConfig;
* ECHConfig ECHConfigList<1..2^16-1>;
*/
typedef struct ossl_echext_st {
uint16_t type;
uint16_t len;
unsigned char *val;
} OSSL_ECHEXT;
DEFINE_STACK_OF(OSSL_ECHEXT)
typedef struct ossl_echstore_entry_st {
uint16_t version; /* 0xfe0d for RFC XXXX */
char *public_name;
size_t pub_len;
unsigned char *pub;
unsigned int nsuites;
OSSL_HPKE_SUITE *suites;
uint8_t max_name_length;
uint8_t config_id;
STACK_OF(OSSL_ECHEXT) *exts;
time_t loadtime; /* time public and private key were loaded from file */
EVP_PKEY *keyshare; /* long(ish) term ECH private keyshare on a server */
int for_retry; /* whether to use this ECHConfigList in a retry */
size_t encoded_len; /* length of overall encoded content */
unsigned char *encoded; /* overall encoded content */
} OSSL_ECHSTORE_ENTRY;
/*
* 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 {
STACK_OF(OSSL_ECHSTORE_ENTRY) *entries;
OSSL_LIB_CTX *libctx;
char *propq;
};
/* ECH details associated with an SSL_CTX */
typedef struct ossl_ech_ctx_st {
/*
* 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" */
} OSSL_ECH_CTX;
/* ECH details associated with an SSL_CONNECTION */
typedef struct ossl_ech_conn_st {
/*
* 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;
unsigned char *alpn_outer;
size_t alpn_outer_len;
SSL_ech_cb_func cb; /* callback function for when ECH "done" */
/*
* If ECH fails, then we switch to verifying the cert for the
* outer_hostname, meanwhile we still want to be able to trace
* the value we tried as the inner SNI for debug purposes
*/
char *former_inner;
/* inner CH transcript buffer */
unsigned char *transbuf;
size_t transbuf_len;
/* inner ClientHello before ECH compression */
unsigned char *innerch;
size_t innerch_len;
/* encoded inner CH */
unsigned char *encoded_inner;
size_t encoded_inner_len;
/* lengths calculated early, used when encrypting at end of processing */
size_t clearlen;
size_t cipherlen;
/* location to put ciphertext, initially filled with zeros */
size_t cipher_offset;
/*
* Extensions are "outer-only" if the value is only sent in the
* outer CH and only the type is sent in the inner CH.
* We use this array to keep track of the extension types that
* have values only in the outer CH
* Currently, this is basically controlled at compile time, but
* in a way that could be varied, or, in future, put under
* run-time control, so having this isn't so much an overhead.
*/
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.
*/
int ext_ind;
/* ECH status vars */
int ch_depth; /* set during CH creation, 0: doing outer, 1: doing inner */
int attempted; /* 1 if ECH was or is being attempted, 0 otherwise */
int done; /* 1 if we've finished ECH calculations, 0 otherwise */
uint16_t attempted_type; /* ECH version used */
int attempted_cid; /* ECH config id sent/rx'd */
int backend; /* 1 if we're a server backend in split-mode, 0 otherwise */
/* When using a PSK stash the tick_identity from inner, for outer */
int tick_identity;
/*
* success is 1 if ECH succeeded, 0 otherwise, on the server this
* is known early, on the client we need to wait for the ECH confirm
* calculation based on the SH (or 2nd SH in case of HRR)
*/
int success;
/*
* we set this when we've gotten to the end of the handshake and
* the only thing that went wrong was ECH - in that case we're
* ok to provide the retry-configs to the client, otherwise better
* not.
*/
int retry_configs_ok;
int grease; /* 1 if we're GREASEing, 0 otherwise */
char *grease_suite; /* HPKE suite string for GREASEing */
unsigned char *sent; /* GREASEy ECH value sent, in case needed for re-tx */
size_t sent_len;
unsigned char *returned; /* binary ECHConfigList retry-configs value */
size_t returned_len;
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
* may need to put back what the server included so the transcript
* is correct when ECH acceptance failed.
*/
unsigned char *hrrsignal_p;
unsigned char hrrsignal[OSSL_ECH_SIGNAL_LEN];
/*
* Fields that differ on client between inner and outer that we need to
* keep and swap over IFF ECH has succeeded. Same names chosen as are
* used in SSL_CONNECTION
*/
EVP_PKEY *ks_pkey[OPENSSL_CLIENT_MAX_KEY_SHARES];
/* The IDs of the keyshare keys */
uint16_t ks_group_id[OPENSSL_CLIENT_MAX_KEY_SHARES];
size_t num_ks_pkey; /* how many keyshares are there */
unsigned char client_random[SSL3_RANDOM_SIZE]; /* CH random */
} OSSL_ECH_CONN;
/* Return values from ossl_ech_same_ext */
#define OSSL_ECH_SAME_EXT_ERR 0 /* bummer something wrong */
#define OSSL_ECH_SAME_EXT_DONE 1 /* proceed with same value in inner/outer */
#define OSSL_ECH_SAME_EXT_CONTINUE 2 /* generate a new value for outer CH */
/*
* During extension construction (in extensions_clnt.c, and surprisingly also in
* extensions.c), we need to handle inner/outer CH cloning - ossl_ech_same_ext
* will (depending on compile time handling options) copy the value from
* CH.inner to CH.outer or else processing will continue, for a 2nd call,
* likely generating a fresh value for the outer CH. The fresh value could well
* be the same as in the inner.
*
* This macro should be called in each _ctos_ function that doesn't explicitly
* have special ECH handling. There are some _ctos_ functions that are called
* from a server, but we don't want to do anything in such cases. We also
* screen out cases where the context is not handling the ClientHello.
*
* Note that the placement of this macro needs a bit of thought - it has to go
* after declarations (to keep the ansi-c compile happy) and also after any
* checks that result in the extension not being sent but before any relevant
* state changes that would affect a possible 2nd call to the constructor.
* Luckily, that's usually not too hard, but it's not mechanical.
*/
#define ECH_SAME_EXT(s, context, pkt) \
if (context == SSL_EXT_CLIENT_HELLO && !s->server \
&& s->ext.ech.es != NULL && s->ext.ech.grease == 0) { \
int ech_iosame_rv = ossl_ech_same_ext(s, pkt); \
\
if (ech_iosame_rv == OSSL_ECH_SAME_EXT_ERR) \
return EXT_RETURN_FAIL; \
if (ech_iosame_rv == OSSL_ECH_SAME_EXT_DONE) \
return EXT_RETURN_SENT; \
/* otherwise continue as normal */ \
}
/* Internal ECH APIs */
OSSL_ECHSTORE *ossl_echstore_dup(const OSSL_ECHSTORE *old);
void ossl_echstore_entry_free(OSSL_ECHSTORE_ENTRY *ee);
void ossl_ech_ctx_clear(OSSL_ECH_CTX *ce);
int ossl_ech_conn_init(SSL_CONNECTION *s, SSL_CTX *ctx,
const SSL_METHOD *method);
void ossl_ech_conn_clear(OSSL_ECH_CONN *ec);
void ossl_echext_free(OSSL_ECHEXT *e);
OSSL_ECHEXT *ossl_echext_dup(const OSSL_ECHEXT *src);
#ifdef OSSL_ECH_SUPERVERBOSE
void ossl_ech_pbuf(const char *msg,
const unsigned char *buf, const size_t blen);
#endif
int ossl_ech_get_retry_configs(SSL_CONNECTION *s, unsigned char **rcfgs,
size_t *rcfgslen);
int ossl_ech_send_grease(SSL_CONNECTION *s, WPACKET *pkt);
int ossl_ech_pick_matching_cfg(SSL_CONNECTION *s, OSSL_ECHSTORE_ENTRY **ee,
OSSL_HPKE_SUITE *suite);
int ossl_ech_encode_inner(SSL_CONNECTION *s, unsigned char **encoded,
size_t *encoded_len);
int ossl_ech_find_confirm(SSL_CONNECTION *s, int hrr,
unsigned char acbuf[OSSL_ECH_SIGNAL_LEN]);
int ossl_ech_reset_hs_buffer(SSL_CONNECTION *s, const unsigned char *buf,
size_t blen);
int ossl_ech_aad_and_encrypt(SSL_CONNECTION *s, WPACKET *pkt);
int ossl_ech_swaperoo(SSL_CONNECTION *s);
int ossl_ech_calc_confirm(SSL_CONNECTION *s, int for_hrr,
unsigned char acbuf[OSSL_ECH_SIGNAL_LEN],
const size_t shlen);
/* these are internal but located in ssl/statem/extensions.c */
int ossl_ech_same_ext(SSL_CONNECTION *s, WPACKET *pkt);
int ossl_ech_same_key_share(void);
int ossl_ech_2bcompressed(size_t ind);
int ossl_ech_copy_inner2outer(SSL_CONNECTION *s, uint16_t ext_type, int ind,
WPACKET *pkt);
int ossl_ech_get_ch_offsets(SSL_CONNECTION *s, PACKET *pkt, size_t *sessid,
size_t *exts, size_t *echoffset, uint16_t *echtype,
int *inner, size_t *snioffset);
int ossl_ech_early_decrypt(SSL_CONNECTION *s, PACKET *outerpkt, PACKET *newpkt);
void ossl_ech_status_print(BIO *out, SSL_CONNECTION *s, int selector);
int ossl_ech_intbuf_add(SSL_CONNECTION *s, const unsigned char *buf,
size_t blen, int hash_existing);
int ossl_ech_intbuf_fetch(SSL_CONNECTION *s, unsigned char **buf, size_t *blen);
size_t ossl_ech_calc_padding(SSL_CONNECTION *s, OSSL_ECHSTORE_ENTRY *ee,
size_t encoded_len);
int ossl_ech_stash_keyshares(SSL_CONNECTION *s);
int ossl_ech_unstash_keyshares(SSL_CONNECTION *s);
#endif
#endif