Merge pull request #437 from h2o/kazuho/ech
[ech] rewrite ESNI to ECH draft 15
diff --git a/CMakeLists.txt b/CMakeLists.txt
index 923ae3d..3af816d 100644
--- a/CMakeLists.txt
+++ b/CMakeLists.txt
@@ -127,8 +127,6 @@
TARGET_LINK_LIBRARIES(picotls-openssl ${OPENSSL_CRYPTO_LIBRARIES} picotls-core ${CMAKE_DL_LIBS})
ADD_EXECUTABLE(cli t/cli.c lib/pembase64.c)
TARGET_LINK_LIBRARIES(cli picotls-openssl picotls-core)
- ADD_EXECUTABLE(picotls-esni src/esni.c)
- TARGET_LINK_LIBRARIES(picotls-esni picotls-openssl picotls-core ${OPENSSL_CRYPTO_LIBRARIES} ${CMAKE_DL_LIBS})
ADD_EXECUTABLE(test-openssl.t
${MINICRYPTO_LIBRARY_FILES}
diff --git a/include/picotls.h b/include/picotls.h
index 83913f9..972bc9d 100644
--- a/include/picotls.h
+++ b/include/picotls.h
@@ -171,12 +171,6 @@
#define PTLS_HPKE_AEAD_AES_256_GCM 2
#define PTLS_HPKE_AEAD_CHACHA20POLY1305 3
-/* ESNI */
-#define PTLS_ESNI_VERSION_DRAFT03 0xff02
-
-#define PTLS_ESNI_RESPONSE_TYPE_ACCEPT 0
-#define PTLS_ESNI_RESPONSE_TYPE_RETRY_REQUEST 1
-
/* error classes and macros */
#define PTLS_ERROR_CLASS_SELF_ALERT 0
#define PTLS_ERROR_CLASS_PEER_ALERT 0x100
@@ -212,9 +206,11 @@
#define PTLS_ALERT_INTERNAL_ERROR 80
#define PTLS_ALERT_USER_CANCELED 90
#define PTLS_ALERT_MISSING_EXTENSION 109
+#define PTLS_ALERT_UNSUPPORTED_EXTENSION 110
#define PTLS_ALERT_UNRECOGNIZED_NAME 112
#define PTLS_ALERT_CERTIFICATE_REQUIRED 116
#define PTLS_ALERT_NO_APPLICATION_PROTOCOL 120
+#define PTLS_ALERT_ECH_REQUIRED 121
/* TLS 1.2 */
#define PTLS_TLS12_MASTER_SECRET_SIZE 48
@@ -233,7 +229,6 @@
#define PTLS_ERROR_STATELESS_RETRY (PTLS_ERROR_CLASS_INTERNAL + 6)
#define PTLS_ERROR_NOT_AVAILABLE (PTLS_ERROR_CLASS_INTERNAL + 7)
#define PTLS_ERROR_COMPRESSION_FAILURE (PTLS_ERROR_CLASS_INTERNAL + 8)
-#define PTLS_ERROR_ESNI_RETRY (PTLS_ERROR_CLASS_INTERNAL + 8)
#define PTLS_ERROR_REJECT_EARLY_DATA (PTLS_ERROR_CLASS_INTERNAL + 9)
#define PTLS_ERROR_DELEGATE (PTLS_ERROR_CLASS_INTERNAL + 10)
#define PTLS_ERROR_ASYNC_OPERATION (PTLS_ERROR_CLASS_INTERNAL + 11)
@@ -588,41 +583,6 @@
ptls_aead_algorithm_t *aead;
} ptls_hpke_cipher_suite_t;
-/**
- * holds ESNIKeys and the private key (instantiated by ptls_esni_parse, freed using ptls_esni_dispose)
- */
-typedef struct st_ptls_esni_context_t {
- ptls_key_exchange_context_t **key_exchanges;
- struct {
- ptls_cipher_suite_t *cipher_suite;
- uint8_t record_digest[PTLS_MAX_DIGEST_SIZE];
- } * cipher_suites;
- uint16_t padded_length;
- uint64_t not_before;
- uint64_t not_after;
- uint16_t version;
-} ptls_esni_context_t;
-
-/**
- * holds the ESNI secret, as exchanged during the handshake
- */
-
-#define PTLS_ESNI_NONCE_SIZE 16
-
-typedef struct st_ptls_esni_secret_t {
- ptls_iovec_t secret;
- uint8_t nonce[PTLS_ESNI_NONCE_SIZE];
- uint8_t esni_contents_hash[PTLS_MAX_DIGEST_SIZE];
- struct {
- ptls_key_exchange_algorithm_t *key_share;
- ptls_cipher_suite_t *cipher;
- ptls_iovec_t pubkey;
- uint8_t record_digest[PTLS_MAX_DIGEST_SIZE];
- uint16_t padded_length;
- } client;
- uint16_t version;
-} ptls_esni_secret_t;
-
#define PTLS_CALLBACK_TYPE0(ret, name) \
typedef struct st_ptls_##name##_t { \
ret (*cb)(struct st_ptls_##name##_t * self); \
@@ -669,10 +629,6 @@
size_t count;
} server_certificate_types;
/**
- * if ESNI was used
- */
- unsigned esni : 1;
- /**
* set to 1 if ClientHello is too old (or too new) to be handled by picotls
*/
unsigned incompatible_version : 1;
@@ -710,9 +666,11 @@
* callback to the invocation of the verify_sign callback, verify_sign is called with both data and sign set to an empty buffer.
* The implementor of the callback should use that as the opportunity to free any temporary data allocated for the verify_sign
* callback.
+ * The name of the server to be verified, if any, is provided explicitly as `server_name`. When ECH is offered by the client but
+ * the was rejected by the server, this value can be different from that being sent via `ptls_get_server_name`.
*/
typedef struct st_ptls_verify_certificate_t {
- int (*cb)(struct st_ptls_verify_certificate_t *self, ptls_t *tls,
+ int (*cb)(struct st_ptls_verify_certificate_t *self, ptls_t *tls, const char *server_name,
int (**verify_sign)(void *verify_ctx, uint16_t algo, ptls_iovec_t data, ptls_iovec_t sign), void **verify_data,
ptls_iovec_t *certs, size_t num_certs);
/**
@@ -766,10 +724,12 @@
ptls_iovec_t input);
} ptls_decompress_certificate_t;
/**
- * provides access to the ESNI shared secret (Zx). API is subject to change.
+ * ECH: creates the AEAD context to be used for "Open"-ing inner CH. Given `config_id`, the callback looks up the ECH config and the
+ * corresponding private key, invokes `ptls_hpke_setup_base_r` with provided `cipher`, `enc`, and `info_prefix` (which will be
+ * "tls ech" || 00).
*/
-PTLS_CALLBACK_TYPE(int, update_esni_key, ptls_t *tls, ptls_iovec_t secret, ptls_hash_algorithm_t *hash,
- const void *hashed_esni_contents);
+PTLS_CALLBACK_TYPE(ptls_aead_context_t *, ech_create_opener, ptls_hpke_kem_t **kem, ptls_hpke_cipher_suite_t **cipher, ptls_t *tls,
+ uint8_t config_id, ptls_hpke_cipher_suite_id_t cipher_id, ptls_iovec_t enc, ptls_iovec_t info_prefix);
/**
* the configuration
@@ -799,9 +759,30 @@
size_t count;
} certificates;
/**
- * list of ESNI data terminated by NULL
+ * ECH
*/
- ptls_esni_context_t **esni;
+ struct {
+ struct {
+ /**
+ * list of HPKE symmetric cipher-suites (set to NULL to disable ECH altogether)
+ */
+ ptls_hpke_cipher_suite_t **ciphers;
+ /**
+ * KEMs being supported
+ */
+ ptls_hpke_kem_t **kems;
+ } client;
+ struct {
+ /**
+ * callback that does ECDH key exchange and returns the AEAD context
+ */
+ ptls_ech_create_opener_t *create_opener;
+ /**
+ * ECHConfigList to be sent to the client when there is mismatch (or when the client sends a grease)
+ */
+ ptls_iovec_t retry_configs;
+ } server;
+ } ech;
/**
*
*/
@@ -900,10 +881,6 @@
/**
*
*/
- ptls_update_esni_key_t *update_esni_key;
- /**
- *
- */
ptls_on_extension_t *on_extension;
/**
* (optional) list of supported tls12 cipher-suites terminated by NULL
@@ -961,9 +938,18 @@
*/
unsigned negotiate_before_key_exchange : 1;
/**
- * ESNIKeys (the value of the TXT record, after being base64-"decoded")
+ * ECH
*/
- ptls_iovec_t esni_keys;
+ struct {
+ /**
+ * config offered by server e.g., by HTTPS RR
+ */
+ ptls_iovec_t configs;
+ /**
+ * slot to save the config obtained from server on mismatch; user must free the returned blob by calling `free`
+ */
+ ptls_iovec_t *retry_configs;
+ } ech;
} client;
struct {
/**
@@ -1159,7 +1145,7 @@
ptls_buffer_push(_buf, (type)); \
ptls_buffer_push_block(_buf, 3, block); \
if (_key_sched != NULL) \
- ptls__key_schedule_update_hash(_key_sched, _buf->base + mess_start, _buf->off - mess_start); \
+ ptls__key_schedule_update_hash(_key_sched, _buf->base + mess_start, _buf->off - mess_start, 0); \
} while (0)
#define ptls_push_message(emitter, key_sched, type, block) \
@@ -1482,6 +1468,10 @@
*/
int ptls_is_psk_handshake(ptls_t *tls);
/**
+ * return if a ECH handshake was performed, as well as optionally the kem and cipher-suite being used
+ */
+int ptls_is_ech_handshake(ptls_t *tls, ptls_hpke_kem_t **kem, ptls_hpke_cipher_suite_t **cipher);
+/**
* returns a pointer to user data pointer (client is reponsible for freeing the associated data prior to calling ptls_free)
*/
void **ptls_get_data_ptr(ptls_t *tls);
@@ -1683,7 +1673,7 @@
/**
* internal
*/
-void ptls__key_schedule_update_hash(ptls_key_schedule_t *sched, const uint8_t *msg, size_t msglen);
+void ptls__key_schedule_update_hash(ptls_key_schedule_t *sched, const uint8_t *msg, size_t msglen, int use_outer);
/**
* clears memory
*/
@@ -1697,6 +1687,11 @@
*/
int ptls_server_name_is_ipaddr(const char *name);
/**
+ * encodes one ECH Config
+ */
+int ptls_ech_encode_config(ptls_buffer_t *buf, uint8_t config_id, ptls_hpke_kem_t *kem, ptls_iovec_t public_key,
+ ptls_hpke_cipher_suite_t **ciphers, uint8_t max_name_length, const char *public_name);
+/**
* loads a certificate chain to ptls_context_t::certificates. `certificate.list` and each element of the list is allocated by
* malloc. It is the responsibility of the user to free them when discarding the TLS context.
*/
@@ -1716,19 +1711,6 @@
/**
*
*/
-int ptls_esni_init_context(ptls_context_t *ctx, ptls_esni_context_t *esni, ptls_iovec_t esni_keys,
- ptls_key_exchange_context_t **key_exchanges);
-/**
- *
- */
-void ptls_esni_dispose_context(ptls_esni_context_t *esni);
-/**
- * Obtain the ESNI secrets negotiated during the handshake.
- */
-ptls_esni_secret_t *ptls_get_esni_secret(ptls_t *ctx);
-/**
- *
- */
char *ptls_hexdump(char *dst, const void *src, size_t len);
/**
* Builds a JSON-safe string without double quotes. Supplied buffer MUST be at least 6x + 1 bytes larger than the input.
diff --git a/lib/openssl.c b/lib/openssl.c
index 3a64cb4..db3f98b 100644
--- a/lib/openssl.c
+++ b/lib/openssl.c
@@ -1420,7 +1420,7 @@
return ret;
}
-static int verify_cert(ptls_verify_certificate_t *_self, ptls_t *tls,
+static int verify_cert(ptls_verify_certificate_t *_self, ptls_t *tls, const char *server_name,
int (**verifier)(void *, uint16_t, ptls_iovec_t, ptls_iovec_t), void **verify_data, ptls_iovec_t *certs,
size_t num_certs)
{
@@ -1445,7 +1445,7 @@
}
sk_X509_push(chain, interm);
}
- ret = verify_cert_chain(self->cert_store, cert, chain, ptls_is_server(tls), ptls_get_server_name(tls), &ossl_x509_err);
+ ret = verify_cert_chain(self->cert_store, cert, chain, ptls_is_server(tls), server_name, &ossl_x509_err);
} else {
ret = PTLS_ALERT_CERTIFICATE_REQUIRED;
ossl_x509_err = 0;
@@ -1515,7 +1515,7 @@
return NULL;
}
-static int verify_raw_cert(ptls_verify_certificate_t *_self, ptls_t *tls,
+static int verify_raw_cert(ptls_verify_certificate_t *_self, ptls_t *tls, const char *server_name,
int (**verifier)(void *, uint16_t algo, ptls_iovec_t, ptls_iovec_t), void **verify_data,
ptls_iovec_t *certs, size_t num_certs)
{
diff --git a/lib/picotls.c b/lib/picotls.c
index eb77e67..976212f 100644
--- a/lib/picotls.c
+++ b/lib/picotls.c
@@ -68,10 +68,19 @@
#define PTLS_EXTENSION_TYPE_COOKIE 44
#define PTLS_EXTENSION_TYPE_PSK_KEY_EXCHANGE_MODES 45
#define PTLS_EXTENSION_TYPE_KEY_SHARE 51
-#define PTLS_EXTENSION_TYPE_ENCRYPTED_SERVER_NAME 0xffce
+#define PTLS_EXTENSION_TYPE_ECH_OUTER_EXTENSIONS 0xfd00
+#define PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO 0xfe0d
#define PTLS_SERVER_NAME_TYPE_HOSTNAME 0
+#define PTLS_ECH_CONFIG_VERSION 0xfe0d
+#define PTLS_ECH_CLIENT_HELLO_TYPE_OUTER 0
+#define PTLS_ECH_CLIENT_HELLO_TYPE_INNER 1
+
+#define PTLS_ECH_CONFIRM_LENGTH 8
+
+static const char ech_info_prefix[8] = "tls ech";
+
#define PTLS_SERVER_CERTIFICATE_VERIFY_CONTEXT_STRING "TLS 1.3, server CertificateVerify"
#define PTLS_CLIENT_CERTIFICATE_VERIFY_CONTEXT_STRING "TLS 1.3, client CertificateVerify"
#define PTLS_MAX_CERTIFICATE_VERIFY_SIGNDATA_SIZE \
@@ -148,6 +157,38 @@
struct st_ptls_signature_algorithms_t signature_algorithms;
};
+struct st_decoded_ech_config_t {
+ uint8_t id;
+ ptls_hpke_kem_t *kem;
+ ptls_iovec_t public_key;
+ ptls_hpke_cipher_suite_t *cipher;
+ uint8_t max_name_length;
+ ptls_iovec_t public_name;
+ ptls_iovec_t bytes;
+};
+
+/**
+ * Properties for ECH. Iff ECH is used and not rejected, `aead` is non-NULL.
+ */
+struct st_ptls_ech_t {
+ uint8_t offered : 1;
+ uint8_t accepted : 1;
+ uint8_t config_id;
+ ptls_hpke_kem_t *kem;
+ ptls_hpke_cipher_suite_t *cipher;
+ ptls_aead_context_t *aead;
+ uint8_t inner_client_random[PTLS_HELLO_RANDOM_SIZE];
+ struct {
+ ptls_iovec_t enc;
+ uint8_t max_name_length;
+ char *public_name;
+ /**
+ * retains a copy of entire ECH extension so that it can be replayed in the 2nd CH when ECH is rejected via HRR
+ */
+ ptls_iovec_t first_ech;
+ } client;
+};
+
struct st_ptls_t {
/**
* the context
@@ -212,20 +253,20 @@
*/
ptls_cipher_suite_t *cipher_suite;
/**
- * clienthello.random
+ * ClientHello.random that appears on the wire. When ECH is used, that of inner CH is retained separately.
*/
uint8_t client_random[PTLS_HELLO_RANDOM_SIZE];
/**
- * esni
- */
- ptls_esni_secret_t *esni;
- /**
* exporter master secret (either 0rtt or 1rtt)
*/
struct {
uint8_t *early;
uint8_t *one_rtt;
} exporter_master_secret;
+ /**
+ * ECH
+ */
+ struct st_ptls_ech_t ech;
/* flags */
unsigned is_server : 1;
unsigned is_psk_handshake : 1;
@@ -304,13 +345,6 @@
struct st_ptls_signature_algorithms_t signature_algorithms;
ptls_iovec_t server_name;
struct {
- ptls_cipher_suite_t *cipher; /* selected cipher-suite, or NULL if esni extension is not used */
- ptls_key_exchange_algorithm_t *key_share;
- ptls_iovec_t peer_key;
- const uint8_t *record_digest;
- ptls_iovec_t encrypted_sni;
- } esni;
- struct {
ptls_iovec_t list[16];
size_t count;
} alpn;
@@ -330,6 +364,21 @@
unsigned sent_key_share : 1;
} cookie;
struct {
+ uint8_t list[MAX_CERTIFICATE_TYPES];
+ size_t count;
+ } server_certificate_types;
+ unsigned status_request : 1;
+ /**
+ * ECH: payload.base != NULL indicates that the extension was received
+ */
+ struct {
+ uint8_t type;
+ uint8_t config_id;
+ ptls_hpke_cipher_suite_id_t cipher_suite;
+ ptls_iovec_t enc;
+ ptls_iovec_t payload;
+ } ech;
+ struct {
const uint8_t *hash_end;
struct {
struct st_ptls_client_hello_psk_t list[4];
@@ -339,12 +388,8 @@
unsigned early_data_indication : 1;
unsigned is_last_extension : 1;
} psk;
- struct {
- uint8_t list[MAX_CERTIFICATE_TYPES];
- size_t count;
- } server_certificate_types;
ptls_raw_extension_t unknown_extensions[MAX_UNKNOWN_EXTENSIONS + 1];
- unsigned status_request : 1;
+ size_t first_extension_at;
};
struct st_ptls_server_hello_t {
@@ -356,6 +401,7 @@
struct {
uint16_t selected_group;
ptls_iovec_t cookie;
+ const uint8_t *ech;
} retry_request;
};
};
@@ -366,7 +412,7 @@
size_t num_hashes;
struct {
ptls_hash_algorithm_t *algo;
- ptls_hash_context_t *ctx;
+ ptls_hash_context_t *ctx, *ctx_outer;
} hashes[1];
};
@@ -376,7 +422,7 @@
};
struct st_ptls_extension_bitmap_t {
- uint8_t bits[8]; /* only ids below 64 is tracked */
+ uint64_t bits;
};
static const uint8_t zeroes_of_max_digest_size[PTLS_MAX_DIGEST_SIZE] = {0};
@@ -395,27 +441,17 @@
return 0;
}
-static inline int extension_bitmap_is_set(struct st_ptls_extension_bitmap_t *bitmap, uint16_t id)
-{
- if (id < sizeof(bitmap->bits) * 8)
- return (bitmap->bits[id / 8] & (1 << (id % 8))) != 0;
- return 0;
-}
-
-static inline void extension_bitmap_set(struct st_ptls_extension_bitmap_t *bitmap, uint16_t id)
-{
- if (id < sizeof(bitmap->bits) * 8)
- bitmap->bits[id / 8] |= 1 << (id % 8);
-}
-
-static inline void init_extension_bitmap(struct st_ptls_extension_bitmap_t *bitmap, unsigned hstype)
+static int extension_bitmap_testandset(struct st_ptls_extension_bitmap_t *bitmap, int hstype, uint16_t extid)
{
#define HSTYPE_TO_BIT(hstype) ((uint64_t)1 << ((hstype) + 1)) /* min(hstype) is -1 (PSEUDO_HRR) */
#define DEFINE_BIT(abbrev, hstype) static const uint64_t abbrev = HSTYPE_TO_BIT(PTLS_HANDSHAKE_TYPE_##hstype)
-#define EXT(extid, allowed_bits) \
+#define EXT(candext, allowed_bits) \
do { \
- if (((allowed_bits)&HSTYPE_TO_BIT(hstype)) == 0) \
- extension_bitmap_set(bitmap, PTLS_EXTENSION_TYPE_##extid); \
+ if (PTLS_UNLIKELY(extid == PTLS_EXTENSION_TYPE_##candext)) { \
+ allowed_hs_bits = allowed_bits; \
+ goto Found; \
+ } \
+ ext_bitmap_mask <<= 1; \
} while (0)
DEFINE_BIT(CH, CLIENT_HELLO);
@@ -426,7 +462,7 @@
DEFINE_BIT(CT, CERTIFICATE);
DEFINE_BIT(NST, NEW_SESSION_TICKET);
- *bitmap = (struct st_ptls_extension_bitmap_t){{0}};
+ uint64_t allowed_hs_bits, ext_bitmap_mask = 1;
/* clang-format off */
/* RFC 8446 section 4.2: "The table below indicates the messages where a given extension may appear... If an implementation
@@ -449,9 +485,21 @@
EXT( COOKIE , CH + HRR );
EXT( SUPPORTED_VERSIONS , CH + SH + HRR );
EXT( COMPRESS_CERTIFICATE , CH + CR ); /* from RFC 8879 */
+ EXT( ENCRYPTED_CLIENT_HELLO , CH + HRR + EE ); /* from draft-ietf-tls-esni-15 */
+ EXT( ECH_OUTER_EXTENSIONS , 0 );
/* +-----------------------------------------+ */
/* clang-format on */
+ return 1;
+
+Found:
+ if ((allowed_hs_bits & HSTYPE_TO_BIT(hstype)) == 0)
+ return 0;
+ if ((bitmap->bits & ext_bitmap_mask) != 0)
+ return 0;
+ bitmap->bits |= ext_bitmap_mask;
+ return 1;
+
#undef HSTYPE_TO_BIT
#undef DEFINE_ABBREV
#undef EXT
@@ -492,6 +540,17 @@
dst[i] = (uint8_t)(v >> (56 - 8 * i));
}
+static char *duplicate_as_str(const void *src, size_t len)
+{
+ char *dst;
+
+ if ((dst = malloc(len + 1)) == NULL)
+ return NULL;
+ memcpy(dst, src, len);
+ dst[len] = '\0';
+ return dst;
+}
+
void ptls_buffer__release_memory(ptls_buffer_t *buf)
{
ptls_clear_memory(buf->base, buf->off);
@@ -818,17 +877,15 @@
#define decode_open_extensions(src, end, hstype, exttype, block) \
do { \
- struct st_ptls_extension_bitmap_t bitmap; \
- init_extension_bitmap(&bitmap, (hstype)); \
+ struct st_ptls_extension_bitmap_t bitmap = {0}; \
ptls_decode_open_block((src), end, 2, { \
while ((src) != end) { \
if ((ret = ptls_decode16((exttype), &(src), end)) != 0) \
goto Exit; \
- if (extension_bitmap_is_set(&bitmap, *(exttype)) != 0) { \
+ if (!extension_bitmap_testandset(&bitmap, (hstype), *(exttype))) { \
ret = PTLS_ALERT_ILLEGAL_PARAMETER; \
goto Exit; \
} \
- extension_bitmap_set(&bitmap, *(exttype)); \
ptls_decode_open_block((src), end, 2, block); \
} \
}); \
@@ -915,16 +972,242 @@
tls->ctx->log_event->cb(tls->ctx->log_event, tls, type, "%s", ptls_hexdump(hexbuf, secret.base, secret.len));
}
+/**
+ * This function preserves the flags and modes (e.g., `offered`, `accepted`, `cipher`), they can be used afterwards.
+ */
+static void clear_ech(struct st_ptls_ech_t *ech, int is_server)
+{
+ if (ech->aead != NULL) {
+ ptls_aead_free(ech->aead);
+ ech->aead = NULL;
+ }
+ ptls_clear_memory(ech->inner_client_random, PTLS_HELLO_RANDOM_SIZE);
+ if (!is_server) {
+ free(ech->client.enc.base);
+ ech->client.enc = ptls_iovec_init(NULL, 0);
+ if (ech->client.public_name != NULL) {
+ free(ech->client.public_name);
+ ech->client.public_name = NULL;
+ }
+ free(ech->client.first_ech.base);
+ ech->client.first_ech = ptls_iovec_init(NULL, 0);
+ }
+}
+
+/**
+ * Decodes one ECHConfigContents (tls-esni-15 section 4). `decoded->kem` and `cipher` may be NULL even when the function returns
+ * zero, if the corresponding entries are not found.
+ */
+static int decode_one_ech_config(ptls_hpke_kem_t **kems, ptls_hpke_cipher_suite_t **ciphers,
+ struct st_decoded_ech_config_t *decoded, const uint8_t **src, const uint8_t *const end)
+{
+ char *public_name_buf = NULL;
+ int ret;
+
+ *decoded = (struct st_decoded_ech_config_t){0};
+
+ if ((ret = ptls_decode8(&decoded->id, src, end)) != 0)
+ goto Exit;
+ uint16_t kem_id;
+ if ((ret = ptls_decode16(&kem_id, src, end)) != 0)
+ goto Exit;
+ for (size_t i = 0; kems[i] != NULL; ++i) {
+ if (kems[i]->id == kem_id) {
+ decoded->kem = kems[i];
+ break;
+ }
+ }
+ ptls_decode_open_block(*src, end, 2, {
+ if (*src == end) {
+ ret = PTLS_ALERT_DECODE_ERROR;
+ goto Exit;
+ }
+ decoded->public_key = ptls_iovec_init(*src, end - *src);
+ *src = end;
+ });
+ ptls_decode_open_block(*src, end, 2, {
+ do {
+ uint16_t kdf_id;
+ uint16_t aead_id;
+ if ((ret = ptls_decode16(&kdf_id, src, end)) != 0)
+ goto Exit;
+ if ((ret = ptls_decode16(&aead_id, src, end)) != 0)
+ goto Exit;
+ if (decoded->cipher == NULL) {
+ for (size_t i = 0; ciphers[i] != NULL; ++i) {
+ if (ciphers[i]->id.kdf == kdf_id && ciphers[i]->id.aead == aead_id) {
+ decoded->cipher = ciphers[i];
+ break;
+ }
+ }
+ }
+ } while (*src != end);
+ });
+ if ((ret = ptls_decode8(&decoded->max_name_length, src, end)) != 0)
+ goto Exit;
+
+#define SKIP_DECODED() \
+ do { \
+ decoded->kem = NULL; \
+ decoded->cipher = NULL; \
+ } while (0)
+
+ /* Decode public_name. The specification requires clients to ignore (upon parsing ESNIConfigList) or reject (upon handshake)
+ * public names that are not DNS names or IPv4 addresses. We ignore IPv4 and v6 addresses during parsing (IPv6 addresses never
+ * looks like DNS names), and delegate the responsibility of rejecting non-DNS names to the certificate verify callback. */
+ ptls_decode_open_block(*src, end, 1, {
+ if (*src == end) {
+ ret = PTLS_ALERT_DECODE_ERROR;
+ goto Exit;
+ }
+ if ((public_name_buf = duplicate_as_str(*src, end - *src)) == NULL) {
+ ret = PTLS_ERROR_NO_MEMORY;
+ goto Exit;
+ }
+ if (ptls_server_name_is_ipaddr(public_name_buf)) {
+ SKIP_DECODED();
+ } else {
+ decoded->public_name = ptls_iovec_init(*src, end - *src);
+ }
+ *src = end;
+ });
+
+ ptls_decode_block(*src, end, 2, {
+ while (*src < end) {
+ uint16_t type;
+ if ((ret = ptls_decode16(&type, src, end)) != 0)
+ goto Exit;
+ ptls_decode_open_block(*src, end, 2, { *src = end; });
+ /* if a critital extension is found, indicate that the config cannot be used */
+ if ((type & 0x8000) != 0)
+ SKIP_DECODED();
+ }
+ });
+
+#undef SKIP_DECODED
+
+Exit:
+ free(public_name_buf);
+ return ret;
+}
+
+static int client_decode_ech_config_list(ptls_context_t *ctx, struct st_decoded_ech_config_t *decoded, ptls_iovec_t config_list)
+{
+ const uint8_t *src = config_list.base, *const end = src + config_list.len;
+ int match_found = 0, ret;
+
+ *decoded = (struct st_decoded_ech_config_t){0};
+
+ ptls_decode_block(src, end, 2, {
+ do {
+ const uint8_t *config_start = src;
+ uint16_t version;
+ if ((ret = ptls_decode16(&version, &src, end)) != 0)
+ goto Exit;
+ ptls_decode_open_block(src, end, 2, {
+ /* If the block is the one that we recognize, parse it, then adopt if if possible. Otherwise, skip. */
+ if (version == PTLS_ECH_CONFIG_VERSION) {
+ struct st_decoded_ech_config_t thisconf;
+ if ((ret = decode_one_ech_config(ctx->ech.client.kems, ctx->ech.client.ciphers, &thisconf, &src, end)) != 0)
+ goto Exit;
+ if (!match_found && thisconf.kem != NULL && thisconf.cipher != NULL) {
+ *decoded = thisconf;
+ decoded->bytes = ptls_iovec_init(config_start, end - config_start);
+ match_found = 1;
+ }
+ } else {
+ src = end;
+ }
+ });
+ } while (src != end);
+ });
+ ret = 0;
+
+Exit:
+ if (ret != 0)
+ *decoded = (struct st_decoded_ech_config_t){0};
+ return ret;
+}
+
+static int client_setup_ech(struct st_ptls_ech_t *ech, struct st_decoded_ech_config_t *decoded,
+ void (*random_bytes)(void *, size_t))
+{
+ ptls_buffer_t infobuf;
+ uint8_t infobuf_smallbuf[256];
+ int ret;
+
+ /* setup `enc` and `aead` by running HPKE */
+ ptls_buffer_init(&infobuf, infobuf_smallbuf, sizeof(infobuf_smallbuf));
+ ptls_buffer_pushv(&infobuf, ech_info_prefix, sizeof(ech_info_prefix));
+ ptls_buffer_pushv(&infobuf, decoded->bytes.base, decoded->bytes.len);
+ if ((ret = ptls_hpke_setup_base_s(decoded->kem, decoded->cipher, &ech->client.enc, &ech->aead, decoded->public_key,
+ ptls_iovec_init(infobuf.base, infobuf.off))) != 0)
+ goto Exit;
+
+ /* setup the rest */
+ ech->config_id = decoded->id;
+ ech->kem = decoded->kem;
+ ech->cipher = decoded->cipher;
+ random_bytes(ech->inner_client_random, PTLS_HELLO_RANDOM_SIZE);
+ ech->client.max_name_length = decoded->max_name_length;
+ if ((ech->client.public_name = duplicate_as_str(decoded->public_name.base, decoded->public_name.len)) == NULL) {
+ ret = PTLS_ERROR_NO_MEMORY;
+ goto Exit;
+ }
+
+Exit:
+ if (ret != 0)
+ clear_ech(ech, 0);
+ return ret;
+}
+
+#define ECH_CONFIRMATION_SERVER_HELLO "ech accept confirmation"
+#define ECH_CONFIRMATION_HRR "hrr ech accept confirmation"
+static int ech_calc_confirmation(ptls_key_schedule_t *sched, void *dst, const uint8_t *inner_random, const char *label,
+ ptls_iovec_t message)
+{
+ ptls_hash_context_t *hash = NULL;
+ uint8_t secret[PTLS_MAX_DIGEST_SIZE], transcript_hash[PTLS_MAX_DIGEST_SIZE];
+ int ret;
+
+ /* calc transcript hash using the modified ServerHello / HRR */
+ if ((hash = sched->hashes[0].ctx->clone_(sched->hashes[0].ctx)) == NULL) {
+ ret = PTLS_ERROR_NO_MEMORY;
+ goto Exit;
+ }
+ hash->update(hash, message.base, message.len);
+ hash->final(hash, transcript_hash, PTLS_HASH_FINAL_MODE_FREE);
+ hash = NULL;
+
+ /* HKDF extract and expand */
+ if ((ret = ptls_hkdf_extract(sched->hashes[0].algo, secret, ptls_iovec_init(NULL, 0),
+ ptls_iovec_init(inner_random, PTLS_HELLO_RANDOM_SIZE))) != 0)
+ goto Exit;
+ if ((ret = ptls_hkdf_expand_label(sched->hashes[0].algo, dst, 8, ptls_iovec_init(secret, sched->hashes[0].algo->digest_size),
+ label, ptls_iovec_init(transcript_hash, sched->hashes[0].algo->digest_size), NULL)) != 0)
+ goto Exit;
+
+Exit:
+ ptls_clear_memory(secret, sizeof(secret));
+ ptls_clear_memory(transcript_hash, sizeof(transcript_hash));
+ if (hash != NULL)
+ hash->final(hash, NULL, PTLS_HASH_FINAL_MODE_FREE);
+ return ret;
+}
+
static void key_schedule_free(ptls_key_schedule_t *sched)
{
size_t i;
ptls_clear_memory(sched->secret, sizeof(sched->secret));
- for (i = 0; i != sched->num_hashes; ++i)
+ for (i = 0; i != sched->num_hashes; ++i) {
sched->hashes[i].ctx->final(sched->hashes[i].ctx, NULL, PTLS_HASH_FINAL_MODE_FREE);
+ if (sched->hashes[i].ctx_outer != NULL)
+ sched->hashes[i].ctx_outer->final(sched->hashes[i].ctx_outer, NULL, PTLS_HASH_FINAL_MODE_FREE);
+ }
free(sched);
}
-static ptls_key_schedule_t *key_schedule_new(ptls_cipher_suite_t *preferred, ptls_cipher_suite_t **offered)
+static ptls_key_schedule_t *key_schedule_new(ptls_cipher_suite_t *preferred, ptls_cipher_suite_t **offered, int use_outer)
{
#define FOREACH_HASH(block) \
do { \
@@ -962,6 +1245,12 @@
sched->hashes[sched->num_hashes].algo = cs->hash;
if ((sched->hashes[sched->num_hashes].ctx = cs->hash->create()) == NULL)
goto Fail;
+ if (use_outer) {
+ if ((sched->hashes[sched->num_hashes].ctx_outer = cs->hash->create()) == NULL)
+ goto Fail;
+ } else {
+ sched->hashes[sched->num_hashes].ctx_outer = NULL;
+ }
++sched->num_hashes;
});
@@ -994,7 +1283,7 @@
return ret;
}
-static int key_schedule_select_one(ptls_key_schedule_t *sched, ptls_cipher_suite_t *cs, int reset)
+static int key_schedule_select_cipher(ptls_key_schedule_t *sched, ptls_cipher_suite_t *cs, int reset)
{
size_t found_slot = SIZE_MAX, i;
int ret;
@@ -1008,6 +1297,8 @@
found_slot = i;
} else {
sched->hashes[i].ctx->final(sched->hashes[i].ctx, NULL, PTLS_HASH_FINAL_MODE_FREE);
+ if (sched->hashes[i].ctx_outer != NULL)
+ sched->hashes[i].ctx_outer->final(sched->hashes[i].ctx_outer, NULL, PTLS_HASH_FINAL_MODE_FREE);
}
}
if (found_slot != 0) {
@@ -1029,34 +1320,56 @@
return ret;
}
-void ptls__key_schedule_update_hash(ptls_key_schedule_t *sched, const uint8_t *msg, size_t msglen)
+static void key_schedule_select_outer(ptls_key_schedule_t *sched)
+{
+ /* This function is called when receiving a cleartext message (Server Hello), after the cipher-suite is determined (and hence
+ * the hash also), if ECH was offered */
+ assert(sched->generation == 1);
+ assert(sched->num_hashes == 1);
+ assert(sched->hashes[0].ctx_outer != NULL);
+
+ sched->hashes[0].ctx->final(sched->hashes[0].ctx, NULL, PTLS_HASH_FINAL_MODE_FREE);
+ sched->hashes[0].ctx = sched->hashes[0].ctx_outer;
+ sched->hashes[0].ctx_outer = NULL;
+}
+
+void ptls__key_schedule_update_hash(ptls_key_schedule_t *sched, const uint8_t *msg, size_t msglen, int use_outer)
{
size_t i;
PTLS_DEBUGF("%s:%zu\n", __FUNCTION__, msglen);
- for (i = 0; i != sched->num_hashes; ++i)
- sched->hashes[i].ctx->update(sched->hashes[i].ctx, msg, msglen);
+ for (i = 0; i != sched->num_hashes; ++i) {
+ ptls_hash_context_t *ctx = use_outer ? sched->hashes[i].ctx_outer : sched->hashes[i].ctx;
+ ctx->update(ctx, msg, msglen);
+ }
}
static void key_schedule_update_ch1hash_prefix(ptls_key_schedule_t *sched)
{
uint8_t prefix[4] = {PTLS_HANDSHAKE_TYPE_MESSAGE_HASH, 0, 0, (uint8_t)sched->hashes[0].algo->digest_size};
- ptls__key_schedule_update_hash(sched, prefix, sizeof(prefix));
+ ptls__key_schedule_update_hash(sched, prefix, sizeof(prefix), 0);
}
static void key_schedule_extract_ch1hash(ptls_key_schedule_t *sched, uint8_t *hash)
{
+ assert(sched->hashes[0].ctx_outer == NULL);
sched->hashes[0].ctx->final(sched->hashes[0].ctx, hash, PTLS_HASH_FINAL_MODE_RESET);
}
static void key_schedule_transform_post_ch1hash(ptls_key_schedule_t *sched)
{
+ size_t digest_size = sched->hashes[0].algo->digest_size;
+ ptls_hash_context_t *hashes[3] = {sched->hashes[0].ctx, sched->hashes[0].ctx_outer, NULL};
uint8_t ch1hash[PTLS_MAX_DIGEST_SIZE];
+ uint8_t prefix[4] = {PTLS_HANDSHAKE_TYPE_MESSAGE_HASH, 0, 0, (uint8_t)digest_size};
- key_schedule_extract_ch1hash(sched, ch1hash);
+ for (size_t i = 0; hashes[i] != NULL; ++i) {
+ hashes[i]->final(hashes[i], ch1hash, PTLS_HASH_FINAL_MODE_RESET);
+ hashes[i]->update(hashes[i], prefix, sizeof(prefix));
+ hashes[i]->update(hashes[i], ch1hash, digest_size);
+ }
- key_schedule_update_ch1hash_prefix(sched);
- ptls__key_schedule_update_hash(sched, ch1hash, sched->hashes[0].algo->digest_size);
+ ptls_clear_memory(ch1hash, sizeof(ch1hash));
}
static int derive_secret_with_hash(ptls_key_schedule_t *sched, void *secret, const char *label, const uint8_t *hash)
@@ -1623,21 +1936,6 @@
return ret;
}
-static ptls_hash_context_t *create_sha256_context(ptls_context_t *ctx)
-{
- ptls_cipher_suite_t **cs;
-
- for (cs = ctx->cipher_suites; *cs != NULL; ++cs) {
- switch ((*cs)->id) {
- case PTLS_CIPHER_SUITE_AES_128_GCM_SHA256:
- case PTLS_CIPHER_SUITE_CHACHA20_POLY1305_SHA256:
- return (*cs)->hash->create();
- }
- }
-
- return NULL;
-}
-
static int select_cipher(ptls_cipher_suite_t **selected, ptls_cipher_suite_t **candidates, const uint8_t *src,
const uint8_t *const end, int server_preference)
{
@@ -1752,265 +2050,227 @@
return ret;
}
-static int parse_esni_keys(ptls_context_t *ctx, uint16_t *esni_version, ptls_key_exchange_algorithm_t **selected_key_share,
- ptls_cipher_suite_t **selected_cipher, ptls_iovec_t *peer_key, uint16_t *padded_length,
- char **published_sni, ptls_iovec_t input)
+/**
+ * Within the outer ECH extension, returns the number of bytes that preceeds the AEAD-encrypted payload.
+ */
+static inline size_t outer_ech_header_size(size_t enc_size)
{
- const uint8_t *src = input.base, *const end = input.base + input.len;
- uint16_t version;
- uint64_t not_before, not_after, now;
- int ret = 0;
+ return 10 + enc_size;
+}
- /* version */
- if ((ret = ptls_decode16(&version, &src, end)) != 0)
- goto Exit;
- if (version != PTLS_ESNI_VERSION_DRAFT03) {
- ret = PTLS_ALERT_DECODE_ERROR;
- goto Exit;
- }
+/**
+ * Flag to indicate which of ClientHelloInner, EncodedClientHelloInner, ClientHelloOuter is to be generated. When ECH is inactive,
+ * only ClientHelloInner is used.
+ */
+enum encode_ch_mode { ENCODE_CH_MODE_INNER, ENCODE_CH_MODE_ENCODED_INNER, ENCODE_CH_MODE_OUTER };
- { /* verify checksum */
- ptls_hash_context_t *hctx;
- uint8_t digest[PTLS_SHA256_DIGEST_SIZE];
- if (end - src < 4) {
- ret = PTLS_ALERT_DECODE_ERROR;
- goto Exit;
- }
- if ((hctx = create_sha256_context(ctx)) == NULL) {
- ret = PTLS_ERROR_LIBRARY;
- goto Exit;
- }
- hctx->update(hctx, input.base, src - input.base);
- hctx->update(hctx, "\0\0\0\0", 4);
- hctx->update(hctx, src + 4, end - (src + 4));
- hctx->final(hctx, digest, PTLS_HASH_FINAL_MODE_FREE);
- if (memcmp(src, digest, 4) != 0) {
- ret = PTLS_ALERT_DECODE_ERROR;
- goto Exit;
- }
- src += 4;
- }
- *esni_version = version;
- /* published sni */
- ptls_decode_open_block(src, end, 2, {
- size_t len = end - src;
- *published_sni = malloc(len + 1);
- if (*published_sni == NULL) {
- ret = PTLS_ERROR_NO_MEMORY;
- goto Exit;
- }
- if (len > 0) {
- memcpy(*published_sni, src, len);
- }
- (*published_sni)[len] = 0;
- src = end;
- });
- /* key-shares */
- ptls_decode_open_block(src, end, 2, {
- if ((ret = select_key_share(selected_key_share, peer_key, ctx->key_exchanges, &src, end, 0)) != 0)
- goto Exit;
- });
- /* cipher-suite */
- ptls_decode_open_block(src, end, 2, {
- if ((ret = select_cipher(selected_cipher, ctx->cipher_suites, src, end, ctx->server_cipher_preference)) != 0)
- goto Exit;
- src = end;
- });
- /* padded-length */
- if ((ret = ptls_decode16(padded_length, &src, end)) != 0)
- goto Exit;
- if (padded_length == 0)
- goto Exit;
- /* not-before, not_after */
- if ((ret = ptls_decode64(¬_before, &src, end)) != 0 || (ret = ptls_decode64(¬_after, &src, end)) != 0)
- goto Exit;
- /* extensions */
- ptls_decode_block(src, end, 2, {
- while (src != end) {
- uint16_t id;
- if ((ret = ptls_decode16(&id, &src, end)) != 0)
+static int encode_client_hello(ptls_context_t *ctx, ptls_buffer_t *sendbuf, enum encode_ch_mode mode, int is_second_flight,
+ ptls_handshake_properties_t *properties, const void *client_random,
+ ptls_key_exchange_context_t *key_share_ctx, const char *sni_name, ptls_iovec_t legacy_session_id,
+ struct st_ptls_ech_t *ech, size_t *ech_size_offset, ptls_iovec_t ech_replay,
+ ptls_iovec_t resumption_secret, ptls_iovec_t resumption_ticket, uint32_t obfuscated_ticket_age,
+ size_t psk_binder_size, ptls_iovec_t *cookie, int using_early_data)
+{
+ int ret;
+
+ assert(mode == ENCODE_CH_MODE_INNER || ech != NULL);
+
+ ptls_buffer_push_message_body(sendbuf, NULL, PTLS_HANDSHAKE_TYPE_CLIENT_HELLO, {
+ /* legacy_version */
+ ptls_buffer_push16(sendbuf, 0x0303);
+ /* random_bytes */
+ ptls_buffer_pushv(sendbuf, client_random, PTLS_HELLO_RANDOM_SIZE);
+ /* lecagy_session_id */
+ ptls_buffer_push_block(sendbuf, 1, {
+ if (mode != ENCODE_CH_MODE_ENCODED_INNER)
+ ptls_buffer_pushv(sendbuf, legacy_session_id.base, legacy_session_id.len);
+ });
+ /* cipher_suites */
+ ptls_buffer_push_block(sendbuf, 2, {
+ ptls_cipher_suite_t **cs = ctx->cipher_suites;
+ for (; *cs != NULL; ++cs)
+ ptls_buffer_push16(sendbuf, (*cs)->id);
+ });
+ /* legacy_compression_methods */
+ ptls_buffer_push_block(sendbuf, 1, { ptls_buffer_push(sendbuf, 0); });
+ /* extensions */
+ ptls_buffer_push_block(sendbuf, 2, {
+ if (mode == ENCODE_CH_MODE_OUTER) {
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO, {
+ size_t ext_payload_from = sendbuf->off;
+ ptls_buffer_push(sendbuf, PTLS_ECH_CLIENT_HELLO_TYPE_OUTER);
+ ptls_buffer_push16(sendbuf, ech->cipher->id.kdf);
+ ptls_buffer_push16(sendbuf, ech->cipher->id.aead);
+ ptls_buffer_push(sendbuf, ech->config_id);
+ ptls_buffer_push_block(sendbuf, 2, {
+ if (!is_second_flight)
+ ptls_buffer_pushv(sendbuf, ech->client.enc.base, ech->client.enc.len);
+ });
+ ptls_buffer_push_block(sendbuf, 2, {
+ assert(sendbuf->off - ext_payload_from ==
+ outer_ech_header_size(is_second_flight ? 0 : ech->client.enc.len));
+ if ((ret = ptls_buffer_reserve(sendbuf, *ech_size_offset)) != 0)
+ goto Exit;
+ memset(sendbuf->base + sendbuf->off, 0, *ech_size_offset);
+ sendbuf->off += *ech_size_offset;
+ *ech_size_offset = sendbuf->off - *ech_size_offset;
+ });
+ });
+ } else if (ech->aead != NULL) {
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO,
+ { ptls_buffer_push(sendbuf, PTLS_ECH_CLIENT_HELLO_TYPE_INNER); });
+ } else if (ech_replay.base != NULL) {
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO,
+ { ptls_buffer_pushv(sendbuf, ech_replay.base, ech_replay.len); });
+ }
+ if (mode == ENCODE_CH_MODE_ENCODED_INNER) {
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ECH_OUTER_EXTENSIONS, {
+ ptls_buffer_push_block(sendbuf, 1, { ptls_buffer_push16(sendbuf, PTLS_EXTENSION_TYPE_KEY_SHARE); });
+ });
+ } else {
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_KEY_SHARE, {
+ ptls_buffer_push_block(sendbuf, 2, {
+ if (key_share_ctx != NULL &&
+ (ret = push_key_share_entry(sendbuf, key_share_ctx->algo->id, key_share_ctx->pubkey)) != 0)
+ goto Exit;
+ });
+ });
+ }
+ if (sni_name != NULL) {
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SERVER_NAME, {
+ if ((ret = emit_server_name_extension(sendbuf, sni_name)) != 0)
+ goto Exit;
+ });
+ }
+ if (properties != NULL && properties->client.negotiated_protocols.count != 0) {
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ALPN, {
+ ptls_buffer_push_block(sendbuf, 2, {
+ size_t i;
+ for (i = 0; i != properties->client.negotiated_protocols.count; ++i) {
+ ptls_buffer_push_block(sendbuf, 1, {
+ ptls_iovec_t p = properties->client.negotiated_protocols.list[i];
+ ptls_buffer_pushv(sendbuf, p.base, p.len);
+ });
+ }
+ });
+ });
+ }
+ if (ctx->decompress_certificate != NULL) {
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_COMPRESS_CERTIFICATE, {
+ ptls_buffer_push_block(sendbuf, 1, {
+ const uint16_t *algo = ctx->decompress_certificate->supported_algorithms;
+ assert(*algo != UINT16_MAX);
+ for (; *algo != UINT16_MAX; ++algo)
+ ptls_buffer_push16(sendbuf, *algo);
+ });
+ });
+ }
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SUPPORTED_VERSIONS, {
+ ptls_buffer_push_block(sendbuf, 1, {
+ size_t i;
+ for (i = 0; i != PTLS_ELEMENTSOF(supported_versions); ++i)
+ ptls_buffer_push16(sendbuf, supported_versions[i]);
+ });
+ });
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SIGNATURE_ALGORITHMS, {
+ if ((ret = push_signature_algorithms(ctx->verify_certificate, sendbuf)) != 0)
+ goto Exit;
+ });
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SUPPORTED_GROUPS, {
+ ptls_key_exchange_algorithm_t **algo = ctx->key_exchanges;
+ ptls_buffer_push_block(sendbuf, 2, {
+ for (; *algo != NULL; ++algo)
+ ptls_buffer_push16(sendbuf, (*algo)->id);
+ });
+ });
+ if (cookie != NULL && cookie->base != NULL) {
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_COOKIE, {
+ ptls_buffer_push_block(sendbuf, 2, { ptls_buffer_pushv(sendbuf, cookie->base, cookie->len); });
+ });
+ }
+ if (ctx->use_raw_public_keys) {
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SERVER_CERTIFICATE_TYPE, {
+ ptls_buffer_push_block(sendbuf, 1, { ptls_buffer_push(sendbuf, PTLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY); });
+ });
+ }
+ if ((ret = push_additional_extensions(properties, sendbuf)) != 0)
goto Exit;
- ptls_decode_open_block(src, end, 2, { src = end; });
- }
+ if (ctx->save_ticket != NULL || resumption_secret.base != NULL) {
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_PSK_KEY_EXCHANGE_MODES, {
+ ptls_buffer_push_block(sendbuf, 1, {
+ if (!ctx->require_dhe_on_psk)
+ ptls_buffer_push(sendbuf, PTLS_PSK_KE_MODE_PSK);
+ ptls_buffer_push(sendbuf, PTLS_PSK_KE_MODE_PSK_DHE);
+ });
+ });
+ }
+ if (resumption_secret.base != NULL) {
+ if (using_early_data && !is_second_flight)
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_EARLY_DATA, {});
+ /* pre-shared key "MUST be the last extension in the ClientHello" (draft-17 section 4.2.6) */
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_PRE_SHARED_KEY, {
+ ptls_buffer_push_block(sendbuf, 2, {
+ ptls_buffer_push_block(sendbuf, 2, {
+ if (mode == ENCODE_CH_MODE_OUTER) {
+ if ((ret = ptls_buffer_reserve(sendbuf, resumption_ticket.len)) != 0)
+ goto Exit;
+ ctx->random_bytes(sendbuf->base + sendbuf->off, resumption_ticket.len);
+ sendbuf->off += resumption_ticket.len;
+ } else {
+ ptls_buffer_pushv(sendbuf, resumption_ticket.base, resumption_ticket.len);
+ }
+ });
+ uint32_t age;
+ if (mode == ENCODE_CH_MODE_OUTER) {
+ ctx->random_bytes(&age, sizeof(age));
+ } else {
+ age = obfuscated_ticket_age;
+ }
+ ptls_buffer_push32(sendbuf, age);
+ });
+ /* allocate space for PSK binder. The space is filled initially filled by a random value (meeting the
+ * requirement of ClientHelloOuter), and later gets filled with the correct binder value if necessary. */
+ ptls_buffer_push_block(sendbuf, 2, {
+ ptls_buffer_push_block(sendbuf, 1, {
+ if ((ret = ptls_buffer_reserve(sendbuf, psk_binder_size)) != 0)
+ goto Exit;
+ ctx->random_bytes(sendbuf->base + sendbuf->off, psk_binder_size);
+ sendbuf->off += psk_binder_size;
+ });
+ });
+ });
+ }
+ });
});
- /* check validity period */
- now = ctx->get_time->cb(ctx->get_time);
- if (!(not_before * 1000 <= now && now <= not_after * 1000)) {
- ret = PTLS_ALERT_DECODE_ERROR;
- goto Exit;
- }
-
- ret = 0;
Exit:
return ret;
}
-static int create_esni_aead(ptls_aead_context_t **aead_ctx, int is_enc, ptls_cipher_suite_t *cipher, ptls_iovec_t ecdh_secret,
- const uint8_t *esni_contents_hash)
-{
- uint8_t aead_secret[PTLS_MAX_DIGEST_SIZE];
- int ret;
-
- if ((ret = ptls_hkdf_extract(cipher->hash, aead_secret, ptls_iovec_init(NULL, 0), ecdh_secret)) != 0)
- goto Exit;
- if ((*aead_ctx = new_aead(cipher->aead, cipher->hash, is_enc, aead_secret,
- ptls_iovec_init(esni_contents_hash, cipher->hash->digest_size), "tls13 esni ")) == NULL) {
- ret = PTLS_ERROR_NO_MEMORY;
- goto Exit;
- }
-
- ret = 0;
-Exit:
- ptls_clear_memory(aead_secret, sizeof(aead_secret));
- return ret;
-}
-
-static int build_esni_contents_hash(ptls_hash_algorithm_t *hash, uint8_t *digest, const uint8_t *record_digest, uint16_t group,
- ptls_iovec_t pubkey, const uint8_t *client_random)
-{
- ptls_buffer_t buf;
- uint8_t smallbuf[256];
- int ret;
-
- /* build ESNIContents */
- ptls_buffer_init(&buf, smallbuf, sizeof(smallbuf));
- ptls_buffer_push_block(&buf, 2, { ptls_buffer_pushv(&buf, record_digest, hash->digest_size); });
- if ((ret = push_key_share_entry(&buf, group, pubkey)) != 0)
- goto Exit;
- ptls_buffer_pushv(&buf, client_random, PTLS_HELLO_RANDOM_SIZE);
-
- /* calculate digest */
- if ((ret = ptls_calc_hash(hash, digest, buf.base, buf.off)) != 0)
- goto Exit;
-
- ret = 0;
-Exit:
- ptls_buffer_dispose(&buf);
- return ret;
-}
-
-static void free_esni_secret(ptls_esni_secret_t **esni, int is_server)
-{
- assert(*esni != NULL);
- if ((*esni)->secret.base != NULL) {
- ptls_clear_memory((*esni)->secret.base, (*esni)->secret.len);
- free((*esni)->secret.base);
- }
- if (!is_server)
- free((*esni)->client.pubkey.base);
- ptls_clear_memory((*esni), sizeof(**esni));
- free(*esni);
- *esni = NULL;
-}
-
-static int client_setup_esni(ptls_context_t *ctx, ptls_esni_secret_t **esni, ptls_iovec_t esni_keys, char **published_sni,
- const uint8_t *client_random)
-{
- ptls_iovec_t peer_key;
- int ret;
-
- if ((*esni = malloc(sizeof(**esni))) == NULL)
- return PTLS_ERROR_NO_MEMORY;
- memset(*esni, 0, sizeof(**esni));
-
- /* parse ESNI_Keys (and return success while keeping *esni NULL) */
- if (parse_esni_keys(ctx, &(*esni)->version, &(*esni)->client.key_share, &(*esni)->client.cipher, &peer_key,
- &(*esni)->client.padded_length, published_sni, esni_keys) != 0) {
- free(*esni);
- *esni = NULL;
- return 0;
- }
-
- ctx->random_bytes((*esni)->nonce, sizeof((*esni)->nonce));
-
- /* calc record digest */
- if ((ret = ptls_calc_hash((*esni)->client.cipher->hash, (*esni)->client.record_digest, esni_keys.base, esni_keys.len)) != 0)
- goto Exit;
- /* derive ECDH secret */
- if ((ret = (*esni)->client.key_share->exchange((*esni)->client.key_share, &(*esni)->client.pubkey, &(*esni)->secret,
- peer_key)) != 0)
- goto Exit;
- /* calc H(ESNIContents) */
- if ((ret = build_esni_contents_hash((*esni)->client.cipher->hash, (*esni)->esni_contents_hash, (*esni)->client.record_digest,
- (*esni)->client.key_share->id, (*esni)->client.pubkey, client_random)) != 0)
- goto Exit;
-
- ret = 0;
-Exit:
- if (ret != 0)
- free_esni_secret(esni, 0);
- return ret;
-}
-
-static int emit_esni_extension(ptls_esni_secret_t *esni, ptls_buffer_t *buf, ptls_iovec_t esni_keys, const char *server_name,
- size_t key_share_ch_off, size_t key_share_ch_len)
-{
- ptls_aead_context_t *aead = NULL;
- int ret;
-
- if ((ret = create_esni_aead(&aead, 1, esni->client.cipher, esni->secret, esni->esni_contents_hash)) != 0)
- goto Exit;
-
- /* cipher-suite id */
- ptls_buffer_push16(buf, esni->client.cipher->id);
- /* key-share */
- if ((ret = push_key_share_entry(buf, esni->client.key_share->id, esni->client.pubkey)) != 0)
- goto Exit;
- /* record-digest */
- ptls_buffer_push_block(buf, 2, { ptls_buffer_pushv(buf, esni->client.record_digest, esni->client.cipher->hash->digest_size); });
- /* encrypted sni */
- ptls_buffer_push_block(buf, 2, {
- size_t start_off = buf->off;
- /* nonce */
- ptls_buffer_pushv(buf, esni->nonce, PTLS_ESNI_NONCE_SIZE);
- /* emit server-name extension */
- if ((ret = emit_server_name_extension(buf, server_name)) != 0)
- goto Exit;
- /* pad */
- if (buf->off - start_off < (size_t)(esni->client.padded_length + PTLS_ESNI_NONCE_SIZE)) {
- size_t bytes_to_pad = esni->client.padded_length + PTLS_ESNI_NONCE_SIZE - (buf->off - start_off);
- if ((ret = ptls_buffer_reserve(buf, bytes_to_pad)) != 0)
- goto Exit;
- memset(buf->base + buf->off, 0, bytes_to_pad);
- buf->off += bytes_to_pad;
- }
- /* encrypt */
- if ((ret = ptls_buffer_reserve_aligned(buf, aead->algo->tag_size, aead->algo->align_bits)) != 0)
- goto Exit;
- ptls_aead_encrypt(aead, buf->base + start_off, buf->base + start_off, buf->off - start_off, 0, buf->base + key_share_ch_off,
- key_share_ch_len);
- buf->off += aead->algo->tag_size;
- });
-
- ret = 0;
-Exit:
- if (aead != NULL)
- ptls_aead_free(aead);
- return ret;
-}
-
static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_handshake_properties_t *properties,
ptls_iovec_t *cookie)
{
- ptls_iovec_t resumption_secret = {NULL}, resumption_ticket;
- char *published_sni = NULL;
+ ptls_iovec_t resumption_secret = {NULL}, resumption_ticket = {NULL};
uint32_t obfuscated_ticket_age = 0;
- size_t msghash_off;
+ const char *sni_name = NULL;
+ size_t mess_start, msghash_off;
uint8_t binder_key[PTLS_MAX_DIGEST_SIZE];
- int ret, is_second_flight = tls->key_schedule != NULL,
- send_sni = tls->server_name != NULL && !ptls_server_name_is_ipaddr(tls->server_name);
+ ptls_buffer_t encoded_ch_inner;
+ int ret, is_second_flight = tls->key_schedule != NULL;
+
+ ptls_buffer_init(&encoded_ch_inner, "", 0);
+
+ if (tls->server_name != NULL && !ptls_server_name_is_ipaddr(tls->server_name))
+ sni_name = tls->server_name;
if (properties != NULL) {
- /* try to use ESNI */
- if (!is_second_flight && send_sni && properties->client.esni_keys.base != NULL) {
- if ((ret = client_setup_esni(tls->ctx, &tls->esni, properties->client.esni_keys, &published_sni, tls->client_random)) !=
- 0) {
- goto Exit;
- }
- if (tls->ctx->update_esni_key != NULL) {
- if ((ret = tls->ctx->update_esni_key->cb(tls->ctx->update_esni_key, tls, tls->esni->secret,
- tls->esni->client.cipher->hash, tls->esni->esni_contents_hash)) != 0)
+ /* try to use ECH (ignore broken ECHConfigList; it is delivered insecurely) */
+ if (!is_second_flight && sni_name != NULL && tls->ctx->ech.client.ciphers != NULL &&
+ properties->client.ech.configs.len != 0) {
+ struct st_decoded_ech_config_t decoded;
+ client_decode_ech_config_list(tls->ctx, &decoded, properties->client.ech.configs);
+ if (decoded.kem != NULL && decoded.cipher != NULL) {
+ if ((ret = client_setup_ech(&tls->ech, &decoded, tls->ctx->random_bytes)) != 0)
goto Exit;
}
}
@@ -2048,164 +2308,111 @@
if (tls->key_share == NULL && !(properties != NULL && properties->client.negotiate_before_key_exchange))
tls->key_share = tls->ctx->key_exchanges[0];
+ /* instantiate key share context */
+ assert(tls->client.key_share_ctx == NULL);
+ if (tls->key_share != NULL) {
+ if ((ret = tls->key_share->create(tls->key_share, &tls->client.key_share_ctx)) != 0)
+ goto Exit;
+ }
+
+ /* initialize key schedule */
if (!is_second_flight) {
- tls->key_schedule = key_schedule_new(tls->cipher_suite, tls->ctx->cipher_suites);
+ tls->key_schedule = key_schedule_new(tls->cipher_suite, tls->ctx->cipher_suites, tls->ech.aead != NULL);
if ((ret = key_schedule_extract(tls->key_schedule, resumption_secret)) != 0)
goto Exit;
}
- msghash_off = emitter->buf->off + emitter->record_header_length;
- ptls_push_message(emitter, NULL, PTLS_HANDSHAKE_TYPE_CLIENT_HELLO, {
- ptls_buffer_t *sendbuf = emitter->buf;
- /* legacy_version */
- ptls_buffer_push16(sendbuf, 0x0303);
- /* random_bytes */
- ptls_buffer_pushv(sendbuf, tls->client_random, sizeof(tls->client_random));
- /* lecagy_session_id */
- ptls_buffer_push_block(
- sendbuf, 1, { ptls_buffer_pushv(sendbuf, tls->client.legacy_session_id.base, tls->client.legacy_session_id.len); });
- /* cipher_suites */
- ptls_buffer_push_block(sendbuf, 2, {
- ptls_cipher_suite_t **cs = tls->ctx->cipher_suites;
- for (; *cs != NULL; ++cs)
- ptls_buffer_push16(sendbuf, (*cs)->id);
- });
- /* legacy_compression_methods */
- ptls_buffer_push_block(sendbuf, 1, { ptls_buffer_push(sendbuf, 0); });
- /* extensions */
- ptls_buffer_push_block(sendbuf, 2, {
- struct {
- size_t off;
- size_t len;
- } key_share_client_hello;
- buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_KEY_SHARE, {
- key_share_client_hello.off = sendbuf->off;
- ptls_buffer_push_block(sendbuf, 2, {
- if (tls->key_share != NULL) {
- if ((ret = tls->key_share->create(tls->key_share, &tls->client.key_share_ctx)) != 0)
- goto Exit;
- if ((ret = push_key_share_entry(sendbuf, tls->key_share->id, tls->client.key_share_ctx->pubkey)) != 0)
- goto Exit;
- }
- });
- key_share_client_hello.len = sendbuf->off - key_share_client_hello.off;
- });
- if (send_sni) {
- if (tls->esni != NULL) {
- if (published_sni != NULL) {
- buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SERVER_NAME, {
- if ((ret = emit_server_name_extension(sendbuf, published_sni)) != 0)
- goto Exit;
- });
- }
- buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ENCRYPTED_SERVER_NAME, {
- if ((ret = emit_esni_extension(tls->esni, sendbuf, properties->client.esni_keys, tls->server_name,
- key_share_client_hello.off, key_share_client_hello.len)) != 0)
- goto Exit;
- });
- } else {
- buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SERVER_NAME, {
- if ((ret = emit_server_name_extension(sendbuf, tls->server_name)) != 0)
- goto Exit;
- });
- }
- }
- if (properties != NULL && properties->client.negotiated_protocols.count != 0) {
- buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ALPN, {
- ptls_buffer_push_block(sendbuf, 2, {
- size_t i;
- for (i = 0; i != properties->client.negotiated_protocols.count; ++i) {
- ptls_buffer_push_block(sendbuf, 1, {
- ptls_iovec_t p = properties->client.negotiated_protocols.list[i];
- ptls_buffer_pushv(sendbuf, p.base, p.len);
- });
- }
- });
- });
- }
- if (tls->ctx->decompress_certificate != NULL) {
- buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_COMPRESS_CERTIFICATE, {
- ptls_buffer_push_block(sendbuf, 1, {
- const uint16_t *algo = tls->ctx->decompress_certificate->supported_algorithms;
- assert(*algo != UINT16_MAX);
- for (; *algo != UINT16_MAX; ++algo)
- ptls_buffer_push16(sendbuf, *algo);
- });
- });
- }
- buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SUPPORTED_VERSIONS, {
- ptls_buffer_push_block(sendbuf, 1, {
- size_t i;
- for (i = 0; i != PTLS_ELEMENTSOF(supported_versions); ++i)
- ptls_buffer_push16(sendbuf, supported_versions[i]);
- });
- });
- buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SIGNATURE_ALGORITHMS, {
- if ((ret = push_signature_algorithms(tls->ctx->verify_certificate, sendbuf)) != 0)
- goto Exit;
- });
- buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SUPPORTED_GROUPS, {
- ptls_key_exchange_algorithm_t **algo = tls->ctx->key_exchanges;
- ptls_buffer_push_block(sendbuf, 2, {
- for (; *algo != NULL; ++algo)
- ptls_buffer_push16(sendbuf, (*algo)->id);
- });
- });
- if (cookie != NULL && cookie->base != NULL) {
- buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_COOKIE, {
- ptls_buffer_push_block(sendbuf, 2, { ptls_buffer_pushv(sendbuf, cookie->base, cookie->len); });
- });
- }
- if (tls->ctx->use_raw_public_keys) {
- buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SERVER_CERTIFICATE_TYPE, {
- ptls_buffer_push_block(sendbuf, 1, { ptls_buffer_push(sendbuf, PTLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY); });
- });
- }
- if ((ret = push_additional_extensions(properties, sendbuf)) != 0)
- goto Exit;
- if (tls->ctx->save_ticket != NULL || resumption_secret.base != NULL) {
- buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_PSK_KEY_EXCHANGE_MODES, {
- ptls_buffer_push_block(sendbuf, 1, {
- if (!tls->ctx->require_dhe_on_psk)
- ptls_buffer_push(sendbuf, PTLS_PSK_KE_MODE_PSK);
- ptls_buffer_push(sendbuf, PTLS_PSK_KE_MODE_PSK_DHE);
- });
- });
- }
- if (resumption_secret.base != NULL) {
- if (tls->client.using_early_data && !is_second_flight)
- buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_EARLY_DATA, {});
- /* pre-shared key "MUST be the last extension in the ClientHello" (draft-17 section 4.2.6) */
- buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_PRE_SHARED_KEY, {
- ptls_buffer_push_block(sendbuf, 2, {
- ptls_buffer_push_block(sendbuf, 2,
- { ptls_buffer_pushv(sendbuf, resumption_ticket.base, resumption_ticket.len); });
- ptls_buffer_push32(sendbuf, obfuscated_ticket_age);
- });
- /* allocate space for PSK binder. the space is filled at the bottom of the function */
- ptls_buffer_push_block(sendbuf, 2, {
- ptls_buffer_push_block(sendbuf, 1, {
- if ((ret = ptls_buffer_reserve(sendbuf, tls->key_schedule->hashes[0].algo->digest_size)) != 0)
- goto Exit;
- sendbuf->off += tls->key_schedule->hashes[0].algo->digest_size;
- });
- });
- });
- }
- });
- });
+ /* start generating CH */
+ if ((ret = emitter->begin_message(emitter)) != 0)
+ goto Exit;
+ mess_start = msghash_off = emitter->buf->off;
+
+ /* generate true (inner) CH */
+ if ((ret = encode_client_hello(tls->ctx, emitter->buf, ENCODE_CH_MODE_INNER, is_second_flight, properties,
+ tls->ech.aead != NULL ? tls->ech.inner_client_random : tls->client_random,
+ tls->client.key_share_ctx, sni_name, tls->client.legacy_session_id, &tls->ech, NULL,
+ tls->ech.client.first_ech, resumption_secret, resumption_ticket, obfuscated_ticket_age,
+ tls->key_schedule->hashes[0].algo->digest_size, cookie, tls->client.using_early_data)) != 0)
+ goto Exit;
/* update the message hash, filling in the PSK binder HMAC if necessary */
if (resumption_secret.base != NULL) {
size_t psk_binder_off = emitter->buf->off - (3 + tls->key_schedule->hashes[0].algo->digest_size);
if ((ret = derive_secret_with_empty_digest(tls->key_schedule, binder_key, "res binder")) != 0)
goto Exit;
- ptls__key_schedule_update_hash(tls->key_schedule, emitter->buf->base + msghash_off, psk_binder_off - msghash_off);
+ ptls__key_schedule_update_hash(tls->key_schedule, emitter->buf->base + msghash_off, psk_binder_off - msghash_off, 0);
msghash_off = psk_binder_off;
if ((ret = calc_verify_data(emitter->buf->base + psk_binder_off + 3, tls->key_schedule, binder_key)) != 0)
goto Exit;
}
- ptls__key_schedule_update_hash(tls->key_schedule, emitter->buf->base + msghash_off, emitter->buf->off - msghash_off);
+ ptls__key_schedule_update_hash(tls->key_schedule, emitter->buf->base + msghash_off, emitter->buf->off - msghash_off, 0);
+
+ /* ECH */
+ if (tls->ech.aead != NULL) {
+ /* build EncodedCHInner */
+ if ((ret = encode_client_hello(tls->ctx, &encoded_ch_inner, ENCODE_CH_MODE_ENCODED_INNER, is_second_flight, properties,
+ tls->ech.inner_client_random, tls->client.key_share_ctx, sni_name,
+ tls->client.legacy_session_id, &tls->ech, NULL, ptls_iovec_init(NULL, 0), resumption_secret,
+ resumption_ticket, obfuscated_ticket_age, tls->key_schedule->hashes[0].algo->digest_size,
+ cookie, tls->client.using_early_data)) != 0)
+ goto Exit;
+ if (resumption_secret.base != NULL)
+ memcpy(encoded_ch_inner.base + encoded_ch_inner.off - tls->key_schedule->hashes[0].algo->digest_size,
+ emitter->buf->base + emitter->buf->off - tls->key_schedule->hashes[0].algo->digest_size,
+ tls->key_schedule->hashes[0].algo->digest_size);
+ { /* pad EncodedCHInner (following draft-ietf-tls-esni-15 6.1.3) */
+ size_t padding_len;
+ if (sni_name != NULL) {
+ padding_len = strlen(sni_name);
+ if (padding_len < tls->ech.client.max_name_length)
+ padding_len = tls->ech.client.max_name_length;
+ } else {
+ padding_len = tls->ech.client.max_name_length + 9;
+ }
+ size_t final_len = encoded_ch_inner.off - PTLS_HANDSHAKE_HEADER_SIZE + padding_len;
+ final_len = (final_len + 31) / 32 * 32;
+ padding_len = final_len - (encoded_ch_inner.off - PTLS_HANDSHAKE_HEADER_SIZE);
+ if (padding_len != 0) {
+ if ((ret = ptls_buffer_reserve(&encoded_ch_inner, padding_len)) != 0)
+ goto Exit;
+ memset(encoded_ch_inner.base + encoded_ch_inner.off, 0, padding_len);
+ encoded_ch_inner.off += padding_len;
+ }
+ }
+ /* flush CHInner, build CHOuterAAD */
+ emitter->buf->off = mess_start;
+ size_t ech_payload_size = encoded_ch_inner.off - PTLS_HANDSHAKE_HEADER_SIZE + tls->ech.aead->algo->tag_size,
+ ech_size_offset = ech_payload_size;
+ if ((ret = encode_client_hello(tls->ctx, emitter->buf, ENCODE_CH_MODE_OUTER, is_second_flight, properties,
+ tls->client_random, tls->client.key_share_ctx, tls->ech.client.public_name,
+ tls->client.legacy_session_id, &tls->ech, &ech_size_offset, ptls_iovec_init(NULL, 0),
+ resumption_secret, resumption_ticket, obfuscated_ticket_age,
+ tls->key_schedule->hashes[0].algo->digest_size, cookie, tls->client.using_early_data)) != 0)
+ goto Exit;
+ /* overwrite ECH payload */
+ ptls_aead_encrypt(tls->ech.aead, emitter->buf->base + ech_size_offset, encoded_ch_inner.base + PTLS_HANDSHAKE_HEADER_SIZE,
+ encoded_ch_inner.off - PTLS_HANDSHAKE_HEADER_SIZE, is_second_flight,
+ emitter->buf->base + mess_start + PTLS_HANDSHAKE_HEADER_SIZE,
+ emitter->buf->off - (mess_start + PTLS_HANDSHAKE_HEADER_SIZE));
+ /* keep the copy of the 1st ECH extension so that we can send it again in 2nd CH in response to rejection with HRR */
+ if (!is_second_flight) {
+ size_t len = outer_ech_header_size(tls->ech.client.enc.len) + ech_payload_size;
+ if ((tls->ech.client.first_ech.base = malloc(len)) == NULL) {
+ ret = PTLS_ERROR_NO_MEMORY;
+ goto Exit;
+ }
+ memcpy(tls->ech.client.first_ech.base,
+ emitter->buf->base + ech_size_offset - outer_ech_header_size(tls->ech.client.enc.len), len);
+ tls->ech.client.first_ech.len = len;
+ tls->ech.offered = 1;
+ }
+ /* update hash */
+ ptls__key_schedule_update_hash(tls->key_schedule, emitter->buf->base + mess_start, emitter->buf->off - mess_start, 1);
+ }
+
+ /* commit CH to the record layer */
+ if ((ret = emitter->commit_message(emitter)) != 0)
+ goto Exit;
if (tls->client.using_early_data) {
assert(!is_second_flight);
@@ -2222,9 +2429,7 @@
ret = PTLS_ERROR_IN_PROGRESS;
Exit:
- if (published_sni != NULL) {
- free(published_sni);
- }
+ ptls_buffer_dispose(&encoded_ch_inner);
ptls_clear_memory(binder_key, sizeof(binder_key));
return ret;
}
@@ -2339,6 +2544,19 @@
if ((ret = ptls_decode16(&selected_psk_identity, &src, end)) != 0)
goto Exit;
break;
+ case PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO:
+ assert(sh->is_retry_request);
+ if (!tls->ech.offered) {
+ ret = PTLS_ALERT_UNSUPPORTED_EXTENSION;
+ goto Exit;
+ }
+ if (end - src != PTLS_ECH_CONFIRM_LENGTH) {
+ ret = PTLS_ALERT_DECODE_ERROR;
+ goto Exit;
+ }
+ sh->retry_request.ech = src;
+ src = end;
+ break;
default:
src = end;
break;
@@ -2410,14 +2628,41 @@
goto Exit;
}
- key_schedule_transform_post_ch1hash(tls->key_schedule);
- ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len);
ret = send_client_hello(tls, emitter, properties, &sh->retry_request.cookie);
Exit:
return ret;
}
+static int client_ech_select_hello(ptls_t *tls, ptls_iovec_t message, size_t confirm_hash_off, const char *label)
+{
+ uint8_t confirm_hash_delivered[PTLS_ECH_CONFIRM_LENGTH], confirm_hash_expected[PTLS_ECH_CONFIRM_LENGTH];
+ int ret = 0;
+
+ /* Determine if ECH has been accepted by checking the confirmation hash. `confirm_hash_off` set to zero indicates that HRR was
+ * received wo. ECH extension, which is an indication that ECH was rejected. */
+ if (confirm_hash_off != 0) {
+ memcpy(confirm_hash_delivered, message.base + confirm_hash_off, sizeof(confirm_hash_delivered));
+ memset(message.base + confirm_hash_off, 0, sizeof(confirm_hash_delivered));
+ if ((ret = ech_calc_confirmation(tls->key_schedule, confirm_hash_expected, tls->ech.inner_client_random, label, message)) !=
+ 0)
+ goto Exit;
+ tls->ech.accepted = ptls_mem_equal(confirm_hash_delivered, confirm_hash_expected, sizeof(confirm_hash_delivered));
+ memcpy(message.base + confirm_hash_off, confirm_hash_delivered, sizeof(confirm_hash_delivered));
+ if (tls->ech.accepted)
+ goto Exit;
+ }
+
+ /* dispose ECH AEAD state to indicate rejection, adopting outer CH for the rest of the handshake */
+ ptls_aead_free(tls->ech.aead);
+ tls->ech.aead = NULL;
+ key_schedule_select_outer(tls->key_schedule);
+
+Exit:
+ ptls_clear_memory(confirm_hash_expected, sizeof(confirm_hash_expected));
+ return ret;
+}
+
static int client_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_iovec_t message,
ptls_handshake_properties_t *properties)
{
@@ -2434,22 +2679,43 @@
}
if (sh.is_retry_request) {
- if ((ret = key_schedule_select_one(tls->key_schedule, tls->cipher_suite, 0)) != 0)
+ if ((ret = key_schedule_select_cipher(tls->key_schedule, tls->cipher_suite, 0)) != 0)
goto Exit;
+ key_schedule_transform_post_ch1hash(tls->key_schedule);
+ if (tls->ech.aead != NULL &&
+ (ret = client_ech_select_hello(tls, message, sh.retry_request.ech != NULL ? sh.retry_request.ech - message.base : 0,
+ ECH_CONFIRMATION_HRR)) != 0)
+ goto Exit;
+ ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len, 0);
return handle_hello_retry_request(tls, emitter, &sh, message, properties);
}
- if ((ret = key_schedule_select_one(tls->key_schedule, tls->cipher_suite, tls->client.offered_psk && !tls->is_psk_handshake)) !=
- 0)
+ if ((ret = key_schedule_select_cipher(tls->key_schedule, tls->cipher_suite,
+ tls->client.offered_psk && !tls->is_psk_handshake)) != 0)
goto Exit;
+ /* check if ECH is accepted */
+ static const size_t confirm_hash_off =
+ PTLS_HANDSHAKE_HEADER_SIZE + 2 /* legacy_version */ + PTLS_HELLO_RANDOM_SIZE - PTLS_ECH_CONFIRM_LENGTH;
+ if (tls->ech.aead != NULL) {
+ if ((ret = client_ech_select_hello(tls, message, confirm_hash_off, ECH_CONFIRMATION_SERVER_HELLO)) != 0)
+ goto Exit;
+ }
+
+ /* clear sensitive and space-consuming ECH state, now that are done with handling sending and decoding Hellos */
+ clear_ech(&tls->ech, 0);
+ if (tls->key_schedule->hashes[0].ctx_outer != NULL) {
+ tls->key_schedule->hashes[0].ctx_outer->final(tls->key_schedule->hashes[0].ctx_outer, NULL, PTLS_HASH_FINAL_MODE_FREE);
+ tls->key_schedule->hashes[0].ctx_outer = NULL;
+ }
+
+ ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len, 0);
+
if (sh.peerkey.base != NULL) {
if ((ret = tls->client.key_share_ctx->on_exchange(&tls->client.key_share_ctx, 1, &ecdh_secret, sh.peerkey)) != 0)
goto Exit;
}
- ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len);
-
if ((ret = key_schedule_extract(tls->key_schedule, ecdh_secret)) != 0)
goto Exit;
if ((ret = setup_traffic_protection(tls, 0, "s hs traffic", 2, 0)) != 0)
@@ -2514,7 +2780,7 @@
static int client_handle_encrypted_extensions(ptls_t *tls, ptls_iovec_t message, ptls_handshake_properties_t *properties)
{
- const uint8_t *src = message.base + PTLS_HANDSHAKE_HEADER_SIZE, *const end = message.base + message.len, *esni_nonce = NULL;
+ const uint8_t *src = message.base + PTLS_HANDSHAKE_HEADER_SIZE, *const end = message.base + message.len;
uint16_t type;
static const ptls_raw_extension_t no_unknown_extensions = {UINT16_MAX};
ptls_raw_extension_t *unknown_extensions = (ptls_raw_extension_t *)&no_unknown_extensions;
@@ -2537,19 +2803,6 @@
goto Exit;
}
break;
- case PTLS_EXTENSION_TYPE_ENCRYPTED_SERVER_NAME:
- if (*src == PTLS_ESNI_RESPONSE_TYPE_ACCEPT) {
- if (end - src != PTLS_ESNI_NONCE_SIZE + 1) {
- ret = PTLS_ALERT_ILLEGAL_PARAMETER;
- goto Exit;
- }
- esni_nonce = src + 1;
- } else {
- /* TODO: provide API to parse the RETRY REQUEST response */
- ret = PTLS_ERROR_ESNI_RETRY;
- goto Exit;
- }
- break;
case PTLS_EXTENSION_TYPE_ALPN:
ptls_decode_block(src, end, 2, {
ptls_decode_open_block(src, end, 1, {
@@ -2582,6 +2835,27 @@
server_offered_cert_type = *src;
src = end;
break;
+ case PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO: {
+ /* accept retry_configs only if we offered ECH but rejected */
+ if (!(tls->ech.offered && !ptls_is_ech_handshake(tls, NULL, NULL))) {
+ ret = PTLS_ALERT_UNSUPPORTED_EXTENSION;
+ goto Exit;
+ }
+ /* parse retry_config, and if it is applicable, provide that to the application */
+ struct st_decoded_ech_config_t decoded;
+ if ((ret = client_decode_ech_config_list(tls->ctx, &decoded, ptls_iovec_init(src, end - src))) != 0)
+ goto Exit;
+ if (decoded.kem != NULL && decoded.cipher != NULL && properties != NULL &&
+ properties->client.ech.retry_configs != NULL) {
+ if ((properties->client.ech.retry_configs->base = malloc(end - src)) == NULL) {
+ ret = PTLS_ERROR_NO_MEMORY;
+ goto Exit;
+ }
+ memcpy(properties->client.ech.retry_configs->base, src, end - src);
+ properties->client.ech.retry_configs->len = end - src;
+ }
+ src = end;
+ } break;
default:
if (should_collect_unknown_extension(tls, properties, type)) {
if (unknown_extensions == &no_unknown_extensions) {
@@ -2605,19 +2879,6 @@
goto Exit;
}
- if (tls->esni != NULL) {
- if (esni_nonce == NULL || !ptls_mem_equal(esni_nonce, tls->esni->nonce, PTLS_ESNI_NONCE_SIZE)) {
- ret = PTLS_ALERT_ILLEGAL_PARAMETER;
- goto Exit;
- }
- free_esni_secret(&tls->esni, 0);
- } else {
- if (esni_nonce != NULL) {
- ret = PTLS_ALERT_ILLEGAL_PARAMETER;
- goto Exit;
- }
- }
-
if (tls->client.using_early_data) {
if (skip_early_data)
tls->client.using_early_data = 0;
@@ -2627,7 +2888,7 @@
if ((ret = report_unknown_extensions(tls, properties, unknown_extensions)) != 0)
goto Exit;
- ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len);
+ ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len, 0);
tls->state =
tls->is_psk_handshake ? PTLS_STATE_CLIENT_EXPECT_FINISHED : PTLS_STATE_CLIENT_EXPECT_CERTIFICATE_REQUEST_OR_CERTIFICATE;
ret = PTLS_ERROR_IN_PROGRESS;
@@ -2805,6 +3066,8 @@
const uint8_t *src = message.base + PTLS_HANDSHAKE_HEADER_SIZE, *const end = message.base + message.len;
int ret = 0;
+ assert(!tls->is_psk_handshake && "state machine asserts that this message is never delivered when PSK is used");
+
if ((ret = decode_certificate_request(tls, &tls->client.certificate_request, src, end)) != 0)
return ret;
@@ -2813,7 +3076,7 @@
return PTLS_ALERT_ILLEGAL_PARAMETER;
tls->state = PTLS_STATE_CLIENT_EXPECT_CERTIFICATE;
- ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len);
+ ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len, 0);
return PTLS_ERROR_IN_PROGRESS;
}
@@ -2851,7 +3114,15 @@
});
if (tls->ctx->verify_certificate != NULL) {
- if ((ret = tls->ctx->verify_certificate->cb(tls->ctx->verify_certificate, tls, &tls->certificate_verify.cb,
+ const char *server_name = NULL;
+ if (!ptls_is_server(tls)) {
+ if (tls->ech.offered && !ptls_is_ech_handshake(tls, NULL, NULL)) {
+ server_name = tls->ech.client.public_name;
+ } else {
+ server_name = tls->server_name;
+ }
+ }
+ if ((ret = tls->ctx->verify_certificate->cb(tls->ctx->verify_certificate, tls, server_name, &tls->certificate_verify.cb,
&tls->certificate_verify.verify_ctx, certs, num_certs)) != 0)
goto Exit;
}
@@ -2881,7 +3152,7 @@
if ((ret = client_do_handle_certificate(tls, message.base + PTLS_HANDSHAKE_HEADER_SIZE, message.base + message.len)) != 0)
return ret;
- ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len);
+ ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len, 0);
tls->state = PTLS_STATE_CLIENT_EXPECT_CERTIFICATE_VERIFY;
return PTLS_ERROR_IN_PROGRESS;
@@ -2925,7 +3196,7 @@
if ((ret = client_do_handle_certificate(tls, uncompressed, uncompressed + uncompressed_size)) != 0)
goto Exit;
- ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len);
+ ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len, 0);
tls->state = PTLS_STATE_CLIENT_EXPECT_CERTIFICATE_VERIFY;
ret = PTLS_ERROR_IN_PROGRESS;
@@ -2941,7 +3212,7 @@
if ((ret = handle_certificate(tls, message.base + PTLS_HANDSHAKE_HEADER_SIZE, message.base + message.len, &got_certs)) != 0)
return ret;
- ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len);
+ ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len, 0);
if (got_certs) {
tls->state = PTLS_STATE_SERVER_EXPECT_CERTIFICATE_VERIFY;
@@ -2983,7 +3254,7 @@
goto Exit;
}
- ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len);
+ ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len, 0);
Exit:
return ret;
@@ -3016,11 +3287,11 @@
static int client_handle_finished(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_iovec_t message)
{
uint8_t send_secret[PTLS_MAX_DIGEST_SIZE];
- int ret;
+ int alert_ech_required = tls->ech.offered && !ptls_is_ech_handshake(tls, NULL, NULL), ret;
if ((ret = verify_finished(tls, message)) != 0)
goto Exit;
- ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len);
+ ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len, 0);
/* update traffic keys by using messages upto ServerFinished, but commission them after sending ClientFinished */
if ((ret = key_schedule_extract(tls->key_schedule, ptls_iovec_init(NULL, 0))) != 0)
@@ -3045,12 +3316,7 @@
if ((ret = push_change_cipher_spec(tls, emitter)) != 0)
goto Exit;
- if (tls->client.certificate_request.context.base != NULL) {
- /* If this is a resumed session, the server must not send the certificate request in the handshake */
- if (tls->is_psk_handshake) {
- ret = PTLS_ALERT_ILLEGAL_PARAMETER;
- goto Exit;
- }
+ if (!alert_ech_required && tls->client.certificate_request.context.base != NULL) {
if ((ret = send_certificate(tls, emitter, &tls->client.certificate_request.signature_algorithms,
tls->client.certificate_request.context, 0, NULL, 0)) == 0)
ret = send_certificate_verify(tls, emitter, &tls->client.certificate_request.signature_algorithms,
@@ -3069,6 +3335,10 @@
tls->state = PTLS_STATE_CLIENT_POST_HANDSHAKE;
+ /* if ECH was rejected, close the connection with ECH_REQUIRED alert after verifying messages up to Finished */
+ if (alert_ech_required)
+ ret = PTLS_ALERT_ECH_REQUIRED;
+
Exit:
ptls_clear_memory(send_secret, sizeof(send_secret));
return ret;
@@ -3146,110 +3416,6 @@
return ret;
}
-static int client_hello_decrypt_esni(ptls_context_t *ctx, ptls_iovec_t *server_name, ptls_esni_secret_t **secret,
- struct st_ptls_client_hello_t *ch)
-{
- ptls_esni_context_t **esni;
- ptls_key_exchange_context_t **key_share_ctx;
- uint8_t *decrypted = NULL;
- ptls_aead_context_t *aead = NULL;
- int ret;
-
- /* allocate secret */
- assert(*secret == NULL);
- if ((*secret = malloc(sizeof(**secret))) == NULL)
- return PTLS_ERROR_NO_MEMORY;
- memset(*secret, 0, sizeof(**secret));
-
- /* find the matching esni structure */
- for (esni = ctx->esni; *esni != NULL; ++esni) {
- size_t i;
- for (i = 0; (*esni)->cipher_suites[i].cipher_suite != NULL; ++i)
- if ((*esni)->cipher_suites[i].cipher_suite->id == ch->esni.cipher->id)
- break;
- if ((*esni)->cipher_suites[i].cipher_suite == NULL) {
- ret = PTLS_ALERT_ILLEGAL_PARAMETER;
- goto Exit;
- }
- if (memcmp((*esni)->cipher_suites[i].record_digest, ch->esni.record_digest, ch->esni.cipher->hash->digest_size) == 0) {
- (*secret)->version = (*esni)->version;
- break;
- }
- }
- if (*esni == NULL) {
- ret = PTLS_ALERT_ILLEGAL_PARAMETER;
- goto Exit;
- }
-
- /* find the matching private key for ESNI decryption */
- for (key_share_ctx = (*esni)->key_exchanges; *key_share_ctx != NULL; ++key_share_ctx)
- if ((*key_share_ctx)->algo->id == ch->esni.key_share->id)
- break;
- if (*key_share_ctx == NULL) {
- ret = PTLS_ALERT_ILLEGAL_PARAMETER;
- goto Exit;
- }
-
- /* calculate ESNIContents */
- if ((ret = build_esni_contents_hash(ch->esni.cipher->hash, (*secret)->esni_contents_hash, ch->esni.record_digest,
- ch->esni.key_share->id, ch->esni.peer_key, ch->random_bytes)) != 0)
- goto Exit;
- /* derive the shared secret */
- if ((ret = (*key_share_ctx)->on_exchange(key_share_ctx, 0, &(*secret)->secret, ch->esni.peer_key)) != 0)
- goto Exit;
- /* decrypt */
- if (ch->esni.encrypted_sni.len - ch->esni.cipher->aead->tag_size != (*esni)->padded_length + PTLS_ESNI_NONCE_SIZE) {
- ret = PTLS_ALERT_ILLEGAL_PARAMETER;
- goto Exit;
- }
- if ((decrypted = malloc((*esni)->padded_length + PTLS_ESNI_NONCE_SIZE)) == NULL) {
- ret = PTLS_ERROR_NO_MEMORY;
- goto Exit;
- }
- if ((ret = create_esni_aead(&aead, 0, ch->esni.cipher, (*secret)->secret, (*secret)->esni_contents_hash)) != 0)
- goto Exit;
- if (ptls_aead_decrypt(aead, decrypted, ch->esni.encrypted_sni.base, ch->esni.encrypted_sni.len, 0, ch->key_shares.base,
- ch->key_shares.len) != (*esni)->padded_length + PTLS_ESNI_NONCE_SIZE) {
- ret = PTLS_ALERT_DECRYPT_ERROR;
- goto Exit;
- }
- ptls_aead_free(aead);
- aead = NULL;
-
- { /* decode sni */
- const uint8_t *src = decrypted, *const end = src + (*esni)->padded_length;
- ptls_iovec_t found_name;
- if (end - src < PTLS_ESNI_NONCE_SIZE) {
- ret = PTLS_ALERT_ILLEGAL_PARAMETER;
- goto Exit;
- }
- memcpy((*secret)->nonce, src, PTLS_ESNI_NONCE_SIZE);
- src += PTLS_ESNI_NONCE_SIZE;
- if ((ret = client_hello_decode_server_name(&found_name, &src, end)) != 0)
- goto Exit;
- for (; src != end; ++src) {
- if (*src != '\0') {
- ret = PTLS_ALERT_ILLEGAL_PARAMETER;
- goto Exit;
- }
- }
- /* if successful, reuse memory allocated for padded_server_name for storing the found name (freed by the caller) */
- memmove(decrypted, found_name.base, found_name.len);
- *server_name = ptls_iovec_init(decrypted, found_name.len);
- decrypted = NULL;
- }
-
- ret = 0;
-Exit:
- if (decrypted != NULL)
- free(decrypted);
- if (aead != NULL)
- ptls_aead_free(aead);
- if (ret != 0 && *secret != NULL)
- free_esni_secret(secret, 1);
- return ret;
-}
-
static int select_negotiated_group(ptls_key_exchange_algorithm_t **selected, ptls_key_exchange_algorithm_t **candidates,
const uint8_t *src, const uint8_t *const end)
{
@@ -3276,9 +3442,10 @@
return ret;
}
-static int decode_client_hello(ptls_t *tls, struct st_ptls_client_hello_t *ch, const uint8_t *src, const uint8_t *const end,
- ptls_handshake_properties_t *properties)
+static int decode_client_hello(ptls_context_t *ctx, struct st_ptls_client_hello_t *ch, const uint8_t *src, const uint8_t *const end,
+ ptls_handshake_properties_t *properties, ptls_t *tls_cbarg)
{
+ const uint8_t *start = src;
uint16_t exttype = 0;
int ret;
@@ -3335,12 +3502,14 @@
src = end;
});
+ ch->first_extension_at = src - start + 2;
+
/* decode extensions */
decode_extensions(src, end, PTLS_HANDSHAKE_TYPE_CLIENT_HELLO, &exttype, {
ch->psk.is_last_extension = 0;
- if (tls->ctx->on_extension != NULL &&
- (ret = tls->ctx->on_extension->cb(tls->ctx->on_extension, tls, PTLS_HANDSHAKE_TYPE_CLIENT_HELLO, exttype,
- ptls_iovec_init(src, end - src)) != 0))
+ if (ctx->on_extension != NULL && tls_cbarg != NULL &&
+ (ret = ctx->on_extension->cb(ctx->on_extension, tls_cbarg, PTLS_HANDSHAKE_TYPE_CLIENT_HELLO, exttype,
+ ptls_iovec_init(src, end - src)) != 0))
goto Exit;
switch (exttype) {
case PTLS_EXTENSION_TYPE_SERVER_NAME:
@@ -3351,47 +3520,6 @@
goto Exit;
}
break;
- case PTLS_EXTENSION_TYPE_ENCRYPTED_SERVER_NAME: {
- ptls_cipher_suite_t **cipher;
- if (ch->esni.cipher != NULL) {
- ret = PTLS_ALERT_ILLEGAL_PARAMETER;
- goto Exit;
- }
- { /* cipher-suite */
- uint16_t csid;
- if ((ret = ptls_decode16(&csid, &src, end)) != 0)
- goto Exit;
- for (cipher = tls->ctx->cipher_suites; *cipher != NULL; ++cipher)
- if ((*cipher)->id == csid)
- break;
- if (*cipher == NULL) {
- ret = PTLS_ALERT_ILLEGAL_PARAMETER;
- goto Exit;
- }
- }
- /* key-share (including peer-key) */
- if ((ret = select_key_share(&ch->esni.key_share, &ch->esni.peer_key, tls->ctx->key_exchanges, &src, end, 1)) != 0)
- goto Exit;
- ptls_decode_open_block(src, end, 2, {
- size_t len = end - src;
- if (len != (*cipher)->hash->digest_size) {
- ret = PTLS_ALERT_ILLEGAL_PARAMETER;
- goto Exit;
- }
- ch->esni.record_digest = src;
- src += len;
- });
- ptls_decode_block(src, end, 2, {
- size_t len = end - src;
- if (len < (*cipher)->aead->tag_size) {
- ret = PTLS_ALERT_ILLEGAL_PARAMETER;
- goto Exit;
- }
- ch->esni.encrypted_sni = ptls_iovec_init(src, len);
- src += len;
- });
- ch->esni.cipher = *cipher; /* set only after successful parsing */
- } break;
case PTLS_EXTENSION_TYPE_ALPN:
ptls_decode_block(src, end, 2, {
do {
@@ -3551,9 +3679,45 @@
case PTLS_EXTENSION_TYPE_STATUS_REQUEST:
ch->status_request = 1;
break;
+ case PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO:
+ if ((ret = ptls_decode8(&ch->ech.type, &src, end)) != 0)
+ goto Exit;
+ switch (ch->ech.type) {
+ case PTLS_ECH_CLIENT_HELLO_TYPE_OUTER:
+ if ((ret = ptls_decode16(&ch->ech.cipher_suite.kdf, &src, end)) != 0 ||
+ (ret = ptls_decode16(&ch->ech.cipher_suite.aead, &src, end)) != 0)
+ goto Exit;
+ if ((ret = ptls_decode8(&ch->ech.config_id, &src, end)) != 0)
+ goto Exit;
+ ptls_decode_open_block(src, end, 2, {
+ ch->ech.enc = ptls_iovec_init(src, end - src);
+ src = end;
+ });
+ ptls_decode_open_block(src, end, 2, {
+ if (src == end) {
+ ret = PTLS_ALERT_DECODE_ERROR;
+ goto Exit;
+ }
+ ch->ech.payload = ptls_iovec_init(src, end - src);
+ src = end;
+ });
+ break;
+ case PTLS_ECH_CLIENT_HELLO_TYPE_INNER:
+ if (src != end) {
+ ret = PTLS_ALERT_DECODE_ERROR;
+ goto Exit;
+ }
+ ch->ech.payload = ptls_iovec_init("", 0); /* non-zero base indicates that the extension was received */
+ break;
+ default:
+ ret = PTLS_ALERT_ILLEGAL_PARAMETER;
+ goto Exit;
+ }
+ src = end;
+ break;
default:
- if (should_collect_unknown_extension(tls, properties, exttype)) {
- if ((ret = collect_unknown_extension(tls, exttype, src, end, ch->unknown_extensions)) != 0)
+ if (tls_cbarg != NULL && should_collect_unknown_extension(tls_cbarg, properties, exttype)) {
+ if ((ret = collect_unknown_extension(tls_cbarg, exttype, src, end, ch->unknown_extensions)) != 0)
goto Exit;
}
break;
@@ -3566,6 +3730,198 @@
return ret;
}
+static int rebuild_ch_inner_extensions(ptls_buffer_t *buf, const uint8_t **src, const uint8_t *const end, const uint8_t *outer_ext,
+ const uint8_t *outer_ext_end)
+{
+ int ret;
+
+ ptls_buffer_push_block(buf, 2, {
+ ptls_decode_open_block(*src, end, 2, {
+ while (*src != end) {
+ uint16_t exttype;
+ if ((ret = ptls_decode16(&exttype, src, end)) != 0)
+ goto Exit;
+ ptls_decode_open_block(*src, end, 2, {
+ if (exttype == PTLS_EXTENSION_TYPE_ECH_OUTER_EXTENSIONS) {
+ ptls_decode_open_block(*src, end, 1, {
+ do {
+ uint16_t reftype;
+ uint16_t outertype;
+ uint16_t outersize;
+ if ((ret = ptls_decode16(&reftype, src, end)) != 0)
+ goto Exit;
+ if (reftype == PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO) {
+ ret = PTLS_ALERT_ILLEGAL_PARAMETER;
+ goto Exit;
+ }
+ while (1) {
+ if (ptls_decode16(&outertype, &outer_ext, outer_ext_end) != 0 ||
+ ptls_decode16(&outersize, &outer_ext, outer_ext_end) != 0) {
+ ret = PTLS_ALERT_ILLEGAL_PARAMETER;
+ goto Exit;
+ }
+ assert(outer_ext_end - outer_ext >= outersize);
+ if (outertype == reftype)
+ break;
+ outer_ext += outersize;
+ }
+ buffer_push_extension(buf, reftype, {
+ ptls_buffer_pushv(buf, outer_ext, outersize);
+ outer_ext += outersize;
+ });
+ } while (*src != end);
+ });
+ } else {
+ buffer_push_extension(buf, exttype, {
+ ptls_buffer_pushv(buf, *src, end - *src);
+ *src = end;
+ });
+ }
+ });
+ }
+ });
+ });
+
+Exit:
+ return ret;
+}
+
+static int rebuild_ch_inner(ptls_buffer_t *buf, const uint8_t *src, const uint8_t *const end,
+ struct st_ptls_client_hello_t *outer_ch, const uint8_t *outer_ext, const uint8_t *outer_ext_end)
+{
+#define COPY_BLOCK(capacity) \
+ do { \
+ ptls_decode_open_block(src, end, (capacity), { \
+ ptls_buffer_push_block(buf, (capacity), { ptls_buffer_pushv(buf, src, end - src); }); \
+ src = end; \
+ }); \
+ } while (0)
+
+ int ret;
+
+ ptls_buffer_push_message_body(buf, NULL, PTLS_HANDSHAKE_TYPE_CLIENT_HELLO, {
+ { /* legacy_version */
+ uint16_t legacy_version;
+ if ((ret = ptls_decode16(&legacy_version, &src, end)) != 0)
+ goto Exit;
+ ptls_buffer_push16(buf, legacy_version);
+ }
+
+ /* hello random */
+ if (end - src < PTLS_HELLO_RANDOM_SIZE) {
+ ret = PTLS_ALERT_DECODE_ERROR;
+ goto Exit;
+ }
+ ptls_buffer_pushv(buf, src, PTLS_HELLO_RANDOM_SIZE);
+ src += PTLS_HELLO_RANDOM_SIZE;
+
+ ptls_decode_open_block(src, end, 1, {
+ if (src != end) {
+ ret = PTLS_ALERT_ILLEGAL_PARAMETER;
+ goto Exit;
+ }
+ });
+ ptls_buffer_push_block(buf, 1,
+ { ptls_buffer_pushv(buf, outer_ch->legacy_session_id.base, outer_ch->legacy_session_id.len); });
+
+ /* cipher-suites and legacy-compression-methods */
+ COPY_BLOCK(2);
+ COPY_BLOCK(1);
+
+ /* extensions */
+ if ((ret = rebuild_ch_inner_extensions(buf, &src, end, outer_ext, outer_ext_end)) != 0)
+ goto Exit;
+ });
+
+ /* padding must be all zero */
+ for (; src != end; ++src) {
+ if (*src != '\0') {
+ ret = PTLS_ALERT_ILLEGAL_PARAMETER;
+ goto Exit;
+ }
+ }
+
+Exit:
+ return ret;
+
+#undef COPY_BLOCK
+}
+
+/* Wrapper function for invoking the on_client_hello callback, taking an exhaustive list of parameters as arguments. The intention
+ * is to not miss setting them as we add new parameters to the struct. */
+static inline int call_on_client_hello_cb(ptls_t *tls, ptls_iovec_t server_name, ptls_iovec_t raw_message, ptls_iovec_t *alpns,
+ size_t num_alpns, const uint16_t *sig_algos, size_t num_sig_algos,
+ const uint16_t *cert_comp_algos, size_t num_cert_comp_algos,
+ const uint16_t *cipher_suites, size_t num_cipher_suites, const uint8_t *server_cert_types,
+ size_t num_server_cert_types, int incompatible_version)
+{
+ if (tls->ctx->on_client_hello == NULL)
+ return 0;
+
+ ptls_on_client_hello_parameters_t params = {server_name,
+ raw_message,
+ {alpns, num_alpns},
+ {sig_algos, num_sig_algos},
+ {cert_comp_algos, num_cert_comp_algos},
+ {cipher_suites, num_cipher_suites},
+ {server_cert_types, num_server_cert_types},
+ incompatible_version};
+ return tls->ctx->on_client_hello->cb(tls->ctx->on_client_hello, tls, ¶ms);
+}
+
+static int check_client_hello_constraints(ptls_context_t *ctx, struct st_ptls_client_hello_t *ch, const void *prev_random,
+ int ech_is_inner_ch, ptls_iovec_t raw_message, ptls_t *tls_cbarg)
+{
+ int is_second_flight = prev_random != 0;
+
+ /* The following check is necessary so that we would be able to track the connection in SSLKEYLOGFILE, even though it might not
+ * be for the safety of the protocol. */
+ if (is_second_flight && !ptls_mem_equal(ch->random_bytes, prev_random, PTLS_HELLO_RANDOM_SIZE))
+ return PTLS_ALERT_HANDSHAKE_FAILURE;
+
+ /* bail out if CH cannot be handled as TLS 1.3 */
+ if (!is_supported_version(ch->selected_version)) {
+ /* ECH: server MUST abort with an "illegal_parameter" alert if the client offers TLS 1.2 or below (draft-15 7.1) */
+ if (ech_is_inner_ch)
+ return PTLS_ALERT_ILLEGAL_PARAMETER;
+ /* fail with PROTOCOL_VERSION alert, after providing the applications the raw CH and SNI to help them fallback */
+ if (!is_second_flight) {
+ int ret;
+ if ((ret = call_on_client_hello_cb(tls_cbarg, ch->server_name, raw_message, ch->alpn.list, ch->alpn.count, NULL, 0,
+ NULL, 0, NULL, 0, NULL, 0, 1)) != 0)
+ return ret;
+ }
+ return PTLS_ALERT_PROTOCOL_VERSION;
+ }
+
+ /* Check TLS 1.3-specific constraints. Hereafter, we might exit without calling on_client_hello. That's fine because this CH is
+ * ought to be rejected. */
+ if (ch->legacy_version <= 0x0300) {
+ /* RFC 8446 Appendix D.5: any endpoint receiving a Hello message with legacy_version set to 0x0300 MUST abort the handshake
+ * with a "protocol_version" alert. */
+ return PTLS_ALERT_PROTOCOL_VERSION;
+ }
+ if (!(ch->compression_methods.count == 1 && ch->compression_methods.ids[0] == 0))
+ return PTLS_ALERT_ILLEGAL_PARAMETER;
+ /* pre-shared key */
+ if (ch->psk.hash_end != NULL) {
+ /* PSK must be the last extension */
+ if (!ch->psk.is_last_extension)
+ return PTLS_ALERT_ILLEGAL_PARAMETER;
+ } else {
+ if (ch->psk.early_data_indication)
+ return PTLS_ALERT_ILLEGAL_PARAMETER;
+ }
+
+ if (ech_is_inner_ch && ch->ech.payload.base == NULL)
+ return PTLS_ALERT_ILLEGAL_PARAMETER;
+ if (ch->ech.payload.base != NULL &&
+ ch->ech.type != (ech_is_inner_ch ? PTLS_ECH_CLIENT_HELLO_TYPE_INNER : PTLS_ECH_CLIENT_HELLO_TYPE_OUTER))
+ return PTLS_ALERT_ILLEGAL_PARAMETER;
+
+ return 0;
+}
+
static int vec_is_string(ptls_iovec_t x, const char *y)
{
return strncmp((const char *)x.base, y, x.len) == 0 && y[x.len] == '\0';
@@ -3665,7 +4021,7 @@
goto Exit;
if ((ret = derive_secret(tls->key_schedule, binder_key, "res binder")) != 0)
goto Exit;
- ptls__key_schedule_update_hash(tls->key_schedule, ch_trunc.base, ch_trunc.len);
+ ptls__key_schedule_update_hash(tls->key_schedule, ch_trunc.base, ch_trunc.len, 0);
if ((ret = calc_verify_data(binder_key /* to conserve space, reuse binder_key for storing verify_data */, tls->key_schedule,
binder_key)) != 0)
goto Exit;
@@ -3736,29 +4092,37 @@
static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_iovec_t message,
ptls_handshake_properties_t *properties)
{
-#define EMIT_SERVER_HELLO(sched, fill_rand, extensions) \
- ptls_push_message(emitter, (sched), PTLS_HANDSHAKE_TYPE_SERVER_HELLO, { \
- ptls_buffer_push16(emitter->buf, 0x0303 /* legacy version */); \
- if ((ret = ptls_buffer_reserve(emitter->buf, PTLS_HELLO_RANDOM_SIZE)) != 0) \
- goto Exit; \
- do { \
- fill_rand \
- } while (0); \
- emitter->buf->off += PTLS_HELLO_RANDOM_SIZE; \
- ptls_buffer_push_block(emitter->buf, 1, \
- { ptls_buffer_pushv(emitter->buf, ch->legacy_session_id.base, ch->legacy_session_id.len); }); \
- ptls_buffer_push16(emitter->buf, tls->cipher_suite->id); \
- ptls_buffer_push(emitter->buf, 0); \
- ptls_buffer_push_block(emitter->buf, 2, { \
- buffer_push_extension(emitter->buf, PTLS_EXTENSION_TYPE_SUPPORTED_VERSIONS, \
- { ptls_buffer_push16(emitter->buf, ch->selected_version); }); \
+#define EMIT_SERVER_HELLO(sched, fill_rand, extensions, post_action) \
+ do { \
+ size_t sh_start_off; \
+ ptls_push_message(emitter, NULL, PTLS_HANDSHAKE_TYPE_SERVER_HELLO, { \
+ sh_start_off = emitter->buf->off - PTLS_HANDSHAKE_HEADER_SIZE; \
+ ptls_buffer_push16(emitter->buf, 0x0303 /* legacy version */); \
+ if ((ret = ptls_buffer_reserve(emitter->buf, PTLS_HELLO_RANDOM_SIZE)) != 0) \
+ goto Exit; \
do { \
- extensions \
+ fill_rand \
} while (0); \
+ emitter->buf->off += PTLS_HELLO_RANDOM_SIZE; \
+ ptls_buffer_push_block(emitter->buf, 1, \
+ { ptls_buffer_pushv(emitter->buf, ch->legacy_session_id.base, ch->legacy_session_id.len); }); \
+ ptls_buffer_push16(emitter->buf, tls->cipher_suite->id); \
+ ptls_buffer_push(emitter->buf, 0); \
+ ptls_buffer_push_block(emitter->buf, 2, { \
+ buffer_push_extension(emitter->buf, PTLS_EXTENSION_TYPE_SUPPORTED_VERSIONS, \
+ { ptls_buffer_push16(emitter->buf, ch->selected_version); }); \
+ do { \
+ extensions \
+ } while (0); \
+ }); \
}); \
- });
+ do { \
+ post_action \
+ } while (0); \
+ ptls__key_schedule_update_hash((sched), emitter->buf->base + sh_start_off, emitter->buf->off - sh_start_off, 0); \
+ } while (0)
-#define EMIT_HELLO_RETRY_REQUEST(sched, negotiated_group, additional_extensions) \
+#define EMIT_HELLO_RETRY_REQUEST(sched, negotiated_group, additional_extensions, post_action) \
EMIT_SERVER_HELLO((sched), { memcpy(emitter->buf->base + emitter->buf->off, hello_retry_random, PTLS_HELLO_RANDOM_SIZE); }, \
{ \
ptls_key_exchange_algorithm_t *_negotiated_group = (negotiated_group); \
@@ -3769,121 +4133,131 @@
do { \
additional_extensions \
} while (0); \
- })
+ }, \
+ post_action)
struct st_ptls_client_hello_t *ch;
struct {
ptls_key_exchange_algorithm_t *algorithm;
ptls_iovec_t peer_key;
} key_share = {NULL};
+ struct {
+ uint8_t *encoded_ch_inner;
+ uint8_t *ch_outer_aad;
+ ptls_buffer_t ch_inner;
+ } ech = {NULL};
enum { HANDSHAKE_MODE_FULL, HANDSHAKE_MODE_PSK, HANDSHAKE_MODE_PSK_DHE } mode;
size_t psk_index = SIZE_MAX;
ptls_iovec_t pubkey = {0}, ecdh_secret = {0};
int accept_early_data = 0, is_second_flight = tls->state == PTLS_STATE_SERVER_EXPECT_SECOND_CLIENT_HELLO, ret;
+ ptls_buffer_init(&ech.ch_inner, "", 0);
+
if ((ch = malloc(sizeof(*ch))) == NULL) {
ret = PTLS_ERROR_NO_MEMORY;
goto Exit;
}
- *ch = (struct st_ptls_client_hello_t){0, NULL, {NULL}, {NULL}, 0, {NULL}, {NULL}, {NULL}, {{0}},
- {NULL}, {NULL}, {{{NULL}}}, {{0}}, {{0}}, {{NULL}}, {NULL}, {{0}}, {{UINT16_MAX}}};
+ *ch = (struct st_ptls_client_hello_t){.unknown_extensions = {{UINT16_MAX}}};
/* decode ClientHello */
- if ((ret = decode_client_hello(tls, ch, message.base + PTLS_HANDSHAKE_HEADER_SIZE, message.base + message.len, properties)) !=
- 0)
+ if ((ret = decode_client_hello(tls->ctx, ch, message.base + PTLS_HANDSHAKE_HEADER_SIZE, message.base + message.len, properties,
+ tls)) != 0)
goto Exit;
-
- /* bail out if CH cannot be handled as TLS 1.3, providing the application the raw CH and SNI, to help them fallback */
- if (!is_supported_version(ch->selected_version)) {
- if (!is_second_flight && tls->ctx->on_client_hello != NULL) {
- ptls_on_client_hello_parameters_t params = {
- .server_name = ch->server_name,
- .raw_message = message,
- .negotiated_protocols = {ch->alpn.list, ch->alpn.count},
- .incompatible_version = 1,
- };
- if ((ret = tls->ctx->on_client_hello->cb(tls->ctx->on_client_hello, tls, ¶ms)) != 0)
+ if ((ret = check_client_hello_constraints(tls->ctx, ch, is_second_flight ? tls->client_random : NULL, 0, message, tls)) != 0)
+ goto Exit;
+ if (!is_second_flight) {
+ memcpy(tls->client_random, ch->random_bytes, PTLS_HELLO_RANDOM_SIZE);
+ log_client_random(tls);
+ } else {
+ /* consistency check for ECH extension in response to HRR */
+ if (tls->ech.aead != NULL) {
+ if (ch->ech.payload.base == NULL) {
+ ret = PTLS_ALERT_MISSING_EXTENSION;
goto Exit;
+ }
+ if (!(ch->ech.config_id == tls->ech.config_id && ch->ech.cipher_suite.kdf == tls->ech.cipher->id.kdf &&
+ ch->ech.cipher_suite.aead == tls->ech.cipher->id.aead && ch->ech.enc.len == 0)) {
+ ret = PTLS_ALERT_ILLEGAL_PARAMETER;
+ goto Exit;
+ }
}
- ret = PTLS_ALERT_PROTOCOL_VERSION;
- goto Exit;
}
- /* Check TLS 1.3-specific constraints. Hereafter, we might exit without calling on_client_hello. That's fine because this CH is
- * ought to be rejected. */
- if (ch->legacy_version <= 0x0300) {
- /* RFC 8446 Appendix D.5: any endpoint receiving a Hello message with legacy_version set to 0x0300 MUST abort the handshake
- * with a "protocol_version" alert. */
- ret = PTLS_ALERT_PROTOCOL_VERSION;
- goto Exit;
- }
- if (!(ch->compression_methods.count == 1 && ch->compression_methods.ids[0] == 0)) {
+ /* ECH */
+ if (ch->ech.payload.base != NULL) {
+ if (ch->ech.type != PTLS_ECH_CLIENT_HELLO_TYPE_OUTER) {
+ ret = PTLS_ALERT_ILLEGAL_PARAMETER;
+ goto Exit;
+ }
+ if (!is_second_flight)
+ tls->ech.offered = 1;
+ /* obtain AEAD context for opening inner CH */
+ if (!is_second_flight && ch->ech.payload.base != NULL && tls->ctx->ech.server.create_opener != NULL) {
+ if ((tls->ech.aead = tls->ctx->ech.server.create_opener->cb(
+ tls->ctx->ech.server.create_opener, &tls->ech.kem, &tls->ech.cipher, tls, ch->ech.config_id,
+ ch->ech.cipher_suite, ch->ech.enc, ptls_iovec_init(ech_info_prefix, sizeof(ech_info_prefix)))) != NULL)
+ tls->ech.config_id = ch->ech.config_id;
+ }
+ if (tls->ech.aead != NULL) {
+ /* now that AEAD context is available, create AAD and decrypt inner CH */
+ if ((ech.encoded_ch_inner = malloc(ch->ech.payload.len - tls->ech.aead->algo->tag_size)) == NULL ||
+ (ech.ch_outer_aad = malloc(message.len - PTLS_HANDSHAKE_HEADER_SIZE)) == NULL) {
+ ret = PTLS_ERROR_NO_MEMORY;
+ goto Exit;
+ }
+ memcpy(ech.ch_outer_aad, message.base + PTLS_HANDSHAKE_HEADER_SIZE, message.len - PTLS_HANDSHAKE_HEADER_SIZE);
+ memset(ech.ch_outer_aad + (ch->ech.payload.base - (message.base + PTLS_HANDSHAKE_HEADER_SIZE)), 0, ch->ech.payload.len);
+ if (ptls_aead_decrypt(tls->ech.aead, ech.encoded_ch_inner, ch->ech.payload.base, ch->ech.payload.len, is_second_flight,
+ ech.ch_outer_aad, message.len - PTLS_HANDSHAKE_HEADER_SIZE) != SIZE_MAX) {
+ tls->ech.accepted = 1;
+ /* successfully decrypted EncodedCHInner, build CHInner */
+ if ((ret = rebuild_ch_inner(&ech.ch_inner, ech.encoded_ch_inner,
+ ech.encoded_ch_inner + ch->ech.payload.len - tls->ech.aead->algo->tag_size, ch,
+ message.base + PTLS_HANDSHAKE_HEADER_SIZE + ch->first_extension_at,
+ message.base + message.len)) != 0)
+ goto Exit;
+ /* treat inner ch as the message being received, re-decode it */
+ message = ptls_iovec_init(ech.ch_inner.base, ech.ch_inner.off);
+ *ch = (struct st_ptls_client_hello_t){.unknown_extensions = {{UINT16_MAX}}};
+ if ((ret = decode_client_hello(tls->ctx, ch, ech.ch_inner.base + PTLS_HANDSHAKE_HEADER_SIZE,
+ ech.ch_inner.base + ech.ch_inner.off, properties, tls)) != 0)
+ goto Exit;
+ if ((ret = check_client_hello_constraints(tls->ctx, ch, is_second_flight ? tls->ech.inner_client_random : NULL, 1,
+ message, tls)) != 0)
+ goto Exit;
+ if (!is_second_flight)
+ memcpy(tls->ech.inner_client_random, ch->random_bytes, PTLS_HELLO_RANDOM_SIZE);
+ } else if (is_second_flight) {
+ /* decryption failure of inner CH in 2nd CH is fatal */
+ ret = PTLS_ALERT_DECRYPT_ERROR;
+ goto Exit;
+ } else {
+ /* decryption failure of 1st CH indicates key mismatch; dispose of AEAD context to indicate adoption of outerCH */
+ ptls_aead_free(tls->ech.aead);
+ tls->ech.aead = NULL;
+ }
+ }
+ } else if (tls->ech.offered) {
+ assert(is_second_flight);
ret = PTLS_ALERT_ILLEGAL_PARAMETER;
goto Exit;
}
- /* esni */
- if (ch->esni.cipher != NULL) {
- if (ch->key_shares.base == NULL) {
- ret = PTLS_ALERT_ILLEGAL_PARAMETER;
- goto Exit;
- }
- }
- /* pre-shared key */
- if (ch->psk.hash_end != NULL) {
- /* PSK must be the last extension */
- if (!ch->psk.is_last_extension) {
- ret = PTLS_ALERT_ILLEGAL_PARAMETER;
- goto Exit;
- }
- } else {
- if (ch->psk.early_data_indication) {
- ret = PTLS_ALERT_ILLEGAL_PARAMETER;
- goto Exit;
- }
- }
if (tls->ctx->require_dhe_on_psk)
ch->psk.ke_modes &= ~(1u << PTLS_PSK_KE_MODE_PSK);
/* handle client_random, legacy_session_id, SNI, ESNI */
if (!is_second_flight) {
- memcpy(tls->client_random, ch->random_bytes, sizeof(tls->client_random));
- log_client_random(tls);
if (ch->legacy_session_id.len != 0)
tls->send_change_cipher_spec = 1;
ptls_iovec_t server_name = {NULL};
- int is_esni = 0;
- if (ch->esni.cipher != NULL && tls->ctx->esni != NULL) {
- if ((ret = client_hello_decrypt_esni(tls->ctx, &server_name, &tls->esni, ch)) != 0)
- goto Exit;
- if (tls->ctx->update_esni_key != NULL) {
- if ((ret = tls->ctx->update_esni_key->cb(tls->ctx->update_esni_key, tls, tls->esni->secret, ch->esni.cipher->hash,
- tls->esni->esni_contents_hash)) != 0)
- goto Exit;
- }
- is_esni = 1;
- } else if (ch->server_name.base != NULL) {
+ if (ch->server_name.base != NULL)
server_name = ch->server_name;
- }
- if (tls->ctx->on_client_hello != NULL) {
- ptls_on_client_hello_parameters_t params = {server_name,
- message,
- {ch->alpn.list, ch->alpn.count},
- {ch->signature_algorithms.list, ch->signature_algorithms.count},
- {ch->cert_compression_algos.list, ch->cert_compression_algos.count},
- {ch->client_ciphers.list, ch->client_ciphers.count},
- {ch->server_certificate_types.list, ch->server_certificate_types.count},
- is_esni};
- ret = tls->ctx->on_client_hello->cb(tls->ctx->on_client_hello, tls, ¶ms);
- } else {
- ret = 0;
- }
-
- if (is_esni)
- free(server_name.base);
- if (ret != 0)
+ if ((ret = call_on_client_hello_cb(tls, server_name, message, ch->alpn.list, ch->alpn.count, ch->signature_algorithms.list,
+ ch->signature_algorithms.count, ch->cert_compression_algos.list,
+ ch->cert_compression_algos.count, ch->client_ciphers.list, ch->client_ciphers.count,
+ ch->server_certificate_types.list, ch->server_certificate_types.count, 0)) != 0)
goto Exit;
-
if (!certificate_type_exists(ch->server_certificate_types.list, ch->server_certificate_types.count,
tls->ctx->use_raw_public_keys ? PTLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY
: PTLS_CERTIFICATE_TYPE_X509)) {
@@ -3895,12 +4269,6 @@
ret = PTLS_ALERT_DECODE_ERROR;
goto Exit;
}
- /* the following check is necessary so that we would be able to track the connection in SSLKEYLOGFILE, even though it
- * might not be for the safety of the protocol */
- if (!ptls_mem_equal(tls->client_random, ch->random_bytes, sizeof(tls->client_random))) {
- ret = PTLS_ALERT_HANDSHAKE_FAILURE;
- goto Exit;
- }
/* We compare SNI only when the value is saved by the on_client_hello callback. This should be OK because we are
* ignoring the value unless the callback saves the server-name. */
if (tls->server_name != NULL) {
@@ -3919,7 +4287,7 @@
goto Exit;
if (!is_second_flight) {
tls->cipher_suite = cs;
- tls->key_schedule = key_schedule_new(cs, NULL);
+ tls->key_schedule = key_schedule_new(cs, NULL, 0);
} else {
if (tls->cipher_suite != cs) {
ret = PTLS_ALERT_HANDSHAKE_FAILURE;
@@ -3951,14 +4319,17 @@
}
/* integrity check passed; update states */
key_schedule_update_ch1hash_prefix(tls->key_schedule);
- ptls__key_schedule_update_hash(tls->key_schedule, ch->cookie.ch1_hash.base, ch->cookie.ch1_hash.len);
+ ptls__key_schedule_update_hash(tls->key_schedule, ch->cookie.ch1_hash.base, ch->cookie.ch1_hash.len, 0);
key_schedule_extract(tls->key_schedule, ptls_iovec_init(NULL, 0));
/* ... reusing sendbuf to rebuild HRR for hash calculation */
size_t hrr_start = emitter->buf->off;
- EMIT_HELLO_RETRY_REQUEST(tls->key_schedule, ch->cookie.sent_key_share ? key_share.algorithm : NULL, {
- buffer_push_extension(emitter->buf, PTLS_EXTENSION_TYPE_COOKIE,
- { ptls_buffer_pushv(emitter->buf, ch->cookie.all.base, ch->cookie.all.len); });
- });
+ EMIT_HELLO_RETRY_REQUEST(tls->key_schedule, ch->cookie.sent_key_share ? key_share.algorithm : NULL,
+ {
+ buffer_push_extension(emitter->buf, PTLS_EXTENSION_TYPE_COOKIE, {
+ ptls_buffer_pushv(emitter->buf, ch->cookie.all.base, ch->cookie.all.len);
+ });
+ },
+ {});
emitter->buf->off = hrr_start;
is_second_flight = 1;
@@ -3973,55 +4344,78 @@
if ((ret = select_negotiated_group(&negotiated_group, tls->ctx->key_exchanges, ch->negotiated_groups.base,
ch->negotiated_groups.base + ch->negotiated_groups.len)) != 0)
goto Exit;
- ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len);
+ ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len, 0);
assert(tls->key_schedule->generation == 0);
- if (properties != NULL && properties->server.retry_uses_cookie) {
- /* emit HRR with cookie (note: we MUST omit KeyShare if the client has specified the correct one; see 46554f0)
- */
- EMIT_HELLO_RETRY_REQUEST(NULL, key_share.algorithm != NULL ? NULL : negotiated_group, {
+
+ /* Either send a stateless retry (w. cookies) or a stateful one. When sending the latter, run the state machine. At the
+ * moment, stateless retry is disabled when ECH is used (do we need to support it?). */
+ int retry_uses_cookie =
+ properties != NULL && properties->server.retry_uses_cookie && !ptls_is_ech_handshake(tls, NULL, NULL);
+ if (!retry_uses_cookie) {
+ key_schedule_transform_post_ch1hash(tls->key_schedule);
+ key_schedule_extract(tls->key_schedule, ptls_iovec_init(NULL, 0));
+ }
+ size_t ech_confirm_off = 0;
+ EMIT_HELLO_RETRY_REQUEST(
+ tls->key_schedule, key_share.algorithm != NULL ? NULL : negotiated_group,
+ {
ptls_buffer_t *sendbuf = emitter->buf;
- buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_COOKIE, {
- ptls_buffer_push_block(sendbuf, 2, {
- /* push to-be-signed data */
- size_t tbs_start = sendbuf->off;
+ if (ptls_is_ech_handshake(tls, NULL, NULL)) {
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO, {
+ if ((ret = ptls_buffer_reserve(sendbuf, PTLS_ECH_CONFIRM_LENGTH)) != 0)
+ goto Exit;
+ memset(sendbuf->base + sendbuf->off, 0, PTLS_ECH_CONFIRM_LENGTH);
+ ech_confirm_off = sendbuf->off;
+ sendbuf->off += PTLS_ECH_CONFIRM_LENGTH;
+ });
+ }
+ if (retry_uses_cookie) {
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_COOKIE, {
ptls_buffer_push_block(sendbuf, 2, {
- /* first block of the cookie data is the hash(ch1) */
+ /* push to-be-signed data */
+ size_t tbs_start = sendbuf->off;
+ ptls_buffer_push_block(sendbuf, 2, {
+ /* first block of the cookie data is the hash(ch1) */
+ ptls_buffer_push_block(sendbuf, 1, {
+ size_t sz = tls->cipher_suite->hash->digest_size;
+ if ((ret = ptls_buffer_reserve(sendbuf, sz)) != 0)
+ goto Exit;
+ key_schedule_extract_ch1hash(tls->key_schedule, sendbuf->base + sendbuf->off);
+ sendbuf->off += sz;
+ });
+ /* second is if we have sent key_share extension */
+ ptls_buffer_push(sendbuf, key_share.algorithm == NULL);
+ /* we can add more data here */
+ });
+ size_t tbs_len = sendbuf->off - tbs_start;
+ /* push the signature */
ptls_buffer_push_block(sendbuf, 1, {
- size_t sz = tls->cipher_suite->hash->digest_size;
+ size_t sz = tls->ctx->cipher_suites[0]->hash->digest_size;
if ((ret = ptls_buffer_reserve(sendbuf, sz)) != 0)
goto Exit;
- key_schedule_extract_ch1hash(tls->key_schedule, sendbuf->base + sendbuf->off);
+ if ((ret = calc_cookie_signature(tls, properties, negotiated_group,
+ ptls_iovec_init(sendbuf->base + tbs_start, tbs_len),
+ sendbuf->base + sendbuf->off)) != 0)
+ goto Exit;
sendbuf->off += sz;
});
- /* second is if we have sent key_share extension */
- ptls_buffer_push(sendbuf, key_share.algorithm == NULL);
- /* we can add more data here */
- });
- size_t tbs_len = sendbuf->off - tbs_start;
- /* push the signature */
- ptls_buffer_push_block(sendbuf, 1, {
- size_t sz = tls->ctx->cipher_suites[0]->hash->digest_size;
- if ((ret = ptls_buffer_reserve(sendbuf, sz)) != 0)
- goto Exit;
- if ((ret = calc_cookie_signature(tls, properties, negotiated_group,
- ptls_iovec_init(sendbuf->base + tbs_start, tbs_len),
- sendbuf->base + sendbuf->off)) != 0)
- goto Exit;
- sendbuf->off += sz;
});
});
- });
+ }
+ },
+ {
+ if (ech_confirm_off != 0 &&
+ (ret = ech_calc_confirmation(
+ tls->key_schedule, emitter->buf->base + ech_confirm_off, tls->ech.inner_client_random,
+ ECH_CONFIRMATION_HRR,
+ ptls_iovec_init(emitter->buf->base + sh_start_off, emitter->buf->off - sh_start_off))) != 0)
+ goto Exit;
});
+ if (retry_uses_cookie) {
if ((ret = push_change_cipher_spec(tls, emitter)) != 0)
goto Exit;
ret = PTLS_ERROR_STATELESS_RETRY;
} else {
- /* invoking stateful retry; roll the key schedule and emit HRR */
- key_schedule_transform_post_ch1hash(tls->key_schedule);
- key_schedule_extract(tls->key_schedule, ptls_iovec_init(NULL, 0));
- EMIT_HELLO_RETRY_REQUEST(tls->key_schedule, key_share.algorithm != NULL ? NULL : negotiated_group, {});
- if ((ret = push_change_cipher_spec(tls, emitter)) != 0)
- goto Exit;
tls->state = PTLS_STATE_SERVER_EXPECT_SECOND_CLIENT_HELLO;
if (ch->psk.early_data_indication)
tls->server.early_data_skipped_bytes = 0;
@@ -4052,7 +4446,7 @@
* adjust key_schedule, determine handshake mode
*/
if (psk_index == SIZE_MAX || tls->ctx->require_client_authentication) {
- ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len);
+ ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len, 0);
if (!is_second_flight) {
assert(tls->key_schedule->generation == 0);
key_schedule_extract(tls->key_schedule, ptls_iovec_init(NULL, 0));
@@ -4061,7 +4455,7 @@
if (properties != NULL)
properties->server.selected_psk_binder.len = 0;
} else {
- ptls__key_schedule_update_hash(tls->key_schedule, ch->psk.hash_end, message.base + message.len - ch->psk.hash_end);
+ ptls__key_schedule_update_hash(tls->key_schedule, ch->psk.hash_end, message.base + message.len - ch->psk.hash_end, 0);
if ((ch->psk.ke_modes & (1u << PTLS_PSK_KE_MODE_PSK)) != 0) {
mode = HANDSHAKE_MODE_PSK;
} else {
@@ -4099,22 +4493,44 @@
tls->key_share = key_share.algorithm;
}
- /* send ServerHello */
- EMIT_SERVER_HELLO(
- tls->key_schedule, { tls->ctx->random_bytes(emitter->buf->base + emitter->buf->off, PTLS_HELLO_RANDOM_SIZE); },
- {
- ptls_buffer_t *sendbuf = emitter->buf;
- if (mode != HANDSHAKE_MODE_PSK) {
- buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_KEY_SHARE, {
- ptls_buffer_push16(sendbuf, key_share.algorithm->id);
- ptls_buffer_push_block(sendbuf, 2, { ptls_buffer_pushv(sendbuf, pubkey.base, pubkey.len); });
- });
- }
- if (mode != HANDSHAKE_MODE_FULL) {
- buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_PRE_SHARED_KEY,
- { ptls_buffer_push16(sendbuf, (uint16_t)psk_index); });
- }
- });
+ { /* send ServerHello */
+ size_t ech_confirm_off = 0;
+ EMIT_SERVER_HELLO(
+ tls->key_schedule,
+ {
+ tls->ctx->random_bytes(emitter->buf->base + emitter->buf->off, PTLS_HELLO_RANDOM_SIZE);
+ /* when accepting CHInner, last 8 byte of SH.random is zero for the handshake transcript */
+ if (ptls_is_ech_handshake(tls, NULL, NULL)) {
+ ech_confirm_off = emitter->buf->off + PTLS_HELLO_RANDOM_SIZE - PTLS_ECH_CONFIRM_LENGTH;
+ memset(emitter->buf->base + ech_confirm_off, 0, PTLS_ECH_CONFIRM_LENGTH);
+ }
+ },
+ {
+ ptls_buffer_t *sendbuf = emitter->buf;
+ if (mode != HANDSHAKE_MODE_PSK) {
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_KEY_SHARE, {
+ ptls_buffer_push16(sendbuf, key_share.algorithm->id);
+ ptls_buffer_push_block(sendbuf, 2, { ptls_buffer_pushv(sendbuf, pubkey.base, pubkey.len); });
+ });
+ }
+ if (mode != HANDSHAKE_MODE_FULL) {
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_PRE_SHARED_KEY,
+ { ptls_buffer_push16(sendbuf, (uint16_t)psk_index); });
+ }
+ },
+ {
+ if (ech_confirm_off != 0 &&
+ (ret = ech_calc_confirmation(
+ tls->key_schedule, emitter->buf->base + ech_confirm_off, tls->ech.inner_client_random,
+ ECH_CONFIRMATION_SERVER_HELLO,
+ ptls_iovec_init(emitter->buf->base + sh_start_off, emitter->buf->off - sh_start_off))) != 0)
+ goto Exit;
+ });
+ }
+
+ /* processing of ECH is complete; dispose state */
+ clear_ech(&tls->ech, 1);
+
if ((ret = push_change_cipher_spec(tls, emitter)) != 0)
goto Exit;
@@ -4140,16 +4556,7 @@
ptls_push_message(emitter, tls->key_schedule, PTLS_HANDSHAKE_TYPE_ENCRYPTED_EXTENSIONS, {
ptls_buffer_t *sendbuf = emitter->buf;
ptls_buffer_push_block(sendbuf, 2, {
- if (tls->esni != NULL) {
- /* the extension is sent even if the application does not handle server name, because otherwise the handshake
- * would fail (FIXME ch->esni.nonce will be zero on HRR) */
- buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ENCRYPTED_SERVER_NAME, {
- uint8_t response_type = PTLS_ESNI_RESPONSE_TYPE_ACCEPT;
- ptls_buffer_pushv(sendbuf, &response_type, 1);
- ptls_buffer_pushv(sendbuf, tls->esni->nonce, PTLS_ESNI_NONCE_SIZE);
- });
- free_esni_secret(&tls->esni, 1);
- } else if (tls->server_name != NULL) {
+ if (tls->server_name != NULL) {
/* In this event, the server SHALL include an extension of type "server_name" in the (extended) server hello.
* The "extension_data" field of this extension SHALL be empty. (RFC 6066 section 3) */
buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SERVER_NAME, {});
@@ -4169,6 +4576,12 @@
}
if (tls->pending_handshake_secret != NULL)
buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_EARLY_DATA, {});
+ /* send ECH retry_configs, if ECH was offered by rejected, even though we (the server) could have accepted ECH */
+ if (tls->ech.offered && !ptls_is_ech_handshake(tls, NULL, NULL) && tls->ctx->ech.server.create_opener != NULL &&
+ tls->ctx->ech.server.retry_configs.len != 0)
+ buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO, {
+ ptls_buffer_pushv(sendbuf, tls->ctx->ech.server.retry_configs.base, tls->ctx->ech.server.retry_configs.len);
+ });
if ((ret = push_additional_extensions(properties, sendbuf)) != 0)
goto Exit;
});
@@ -4216,6 +4629,9 @@
ptls_clear_memory(ecdh_secret.base, ecdh_secret.len);
free(ecdh_secret.base);
}
+ free(ech.encoded_ch_inner);
+ free(ech.ch_outer_aad);
+ ptls_buffer_dispose(&ech.ch_inner);
free(ch);
return ret;
@@ -4288,7 +4704,7 @@
if ((ret = commission_handshake_secret(tls)) != 0)
goto Exit;
- ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len);
+ ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len, 0);
tls->state = PTLS_STATE_SERVER_EXPECT_FINISHED;
ret = PTLS_ERROR_IN_PROGRESS;
@@ -4308,7 +4724,7 @@
if ((ret = setup_traffic_protection(tls, 0, NULL, 3, 0)) != 0)
return ret;
- ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len);
+ ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len, 0);
tls->state = PTLS_STATE_SERVER_POST_HANDSHAKE;
return 0;
@@ -4603,6 +5019,7 @@
*tls = NULL;
+ /* TODO handle flags like psk_handshake, ech_handshake as we add support for TLS/1.3 import */
ptls_decode_block(src, end, 2, {
/* instantiate, based on the is_server flag */
if (end - src < 2) {
@@ -4687,8 +5104,6 @@
ptls_buffer_dispose(&tls->recvbuf.mess);
free_exporter_master_secret(tls, 1);
free_exporter_master_secret(tls, 0);
- if (tls->esni != NULL)
- free_esni_secret(&tls->esni, tls->is_server);
if (tls->key_schedule != NULL)
key_schedule_free(tls->key_schedule);
if (tls->traffic_protection.dec.aead != NULL)
@@ -4697,6 +5112,7 @@
ptls_aead_free(tls->traffic_protection.enc.aead);
free(tls->server_name);
free(tls->negotiated_protocol);
+ clear_ech(&tls->ech, tls->is_server);
if (tls->is_server) {
if (tls->server.async_job != NULL)
tls->server.async_job->destroy_(tls->server.async_job);
@@ -4773,14 +5189,9 @@
{
char *duped = NULL;
- if (server_name != NULL) {
- if (server_name_len == 0)
- server_name_len = strlen(server_name);
- if ((duped = malloc(server_name_len + 1)) == NULL)
- return PTLS_ERROR_NO_MEMORY;
- memcpy(duped, server_name, server_name_len);
- duped[server_name_len] = '\0';
- }
+ if (server_name != NULL &&
+ (duped = duplicate_as_str(server_name, server_name_len != 0 ? server_name_len : strlen(server_name))) == NULL)
+ return PTLS_ERROR_NO_MEMORY;
free(tls->server_name);
tls->server_name = duped;
@@ -4797,14 +5208,8 @@
{
char *duped = NULL;
- if (protocol != NULL) {
- if (protocol_len == 0)
- protocol_len = strlen(protocol);
- if ((duped = malloc(protocol_len + 1)) == NULL)
- return PTLS_ERROR_NO_MEMORY;
- memcpy(duped, protocol, protocol_len);
- duped[protocol_len] = '\0';
- }
+ if (protocol != NULL && (duped = duplicate_as_str(protocol, protocol_len != 0 ? protocol_len : strlen(protocol))) == NULL)
+ return PTLS_ERROR_NO_MEMORY;
free(tls->negotiated_protocol);
tls->negotiated_protocol = duped;
@@ -4822,6 +5227,18 @@
return tls->is_psk_handshake;
}
+int ptls_is_ech_handshake(ptls_t *tls, ptls_hpke_kem_t **kem, ptls_hpke_cipher_suite_t **cipher)
+{
+ if (tls->ech.accepted) {
+ if (kem != NULL)
+ *kem = tls->ech.kem;
+ if (cipher != NULL)
+ *cipher = tls->ech.cipher;
+ return 1;
+ }
+ return 0;
+}
+
void **ptls_get_data_ptr(ptls_t *tls)
{
return &tls->data_ptr;
@@ -5286,9 +5703,12 @@
case PTLS_ERROR_ASYNC_OPERATION:
break;
default:
- /* flush partially written response */
- ptls_clear_memory(emitter.super.buf->base + sendbuf_orig_off, emitter.super.buf->off - sendbuf_orig_off);
- emitter.super.buf->off = sendbuf_orig_off;
+ /* Flush handshake messages that have been written partially. ECH_REQUIRED sticks out because it is a message sent
+ * post-handshake compared to other alerts that are generating *during* the handshake. */
+ if (ret != PTLS_ALERT_ECH_REQUIRED) {
+ ptls_clear_memory(emitter.super.buf->base + sendbuf_orig_off, emitter.super.buf->off - sendbuf_orig_off);
+ emitter.super.buf->off = sendbuf_orig_off;
+ }
/* send alert immediately */
if (PTLS_ERROR_GET_CLASS(ret) != PTLS_ERROR_CLASS_PEER_ALERT)
if (ptls_send_alert(tls, emitter.super.buf, PTLS_ALERT_LEVEL_FATAL,
@@ -5886,134 +6306,6 @@
return handle_handshake_record(tls, handle_server_handshake_message, &emitter.super, &rec, properties);
}
-int ptls_esni_init_context(ptls_context_t *ctx, ptls_esni_context_t *esni, ptls_iovec_t esni_keys,
- ptls_key_exchange_context_t **key_exchanges)
-{
- const uint8_t *src = esni_keys.base, *const end = src + esni_keys.len;
- size_t num_key_exchanges, num_cipher_suites = 0;
- int ret;
-
- for (num_key_exchanges = 0; key_exchanges[num_key_exchanges] != NULL; ++num_key_exchanges)
- ;
-
- memset(esni, 0, sizeof(*esni));
- if ((esni->key_exchanges = malloc(sizeof(*esni->key_exchanges) * (num_key_exchanges + 1))) == NULL) {
- ret = PTLS_ERROR_NO_MEMORY;
- goto Exit;
- }
- memcpy(esni->key_exchanges, key_exchanges, sizeof(*esni->key_exchanges) * (num_key_exchanges + 1));
-
- /* ESNIKeys */
- if ((ret = ptls_decode16(&esni->version, &src, end)) != 0)
- goto Exit;
- /* Skip checksum fields */
- if (end - src < 4) {
- ret = PTLS_ALERT_DECRYPT_ERROR;
- goto Exit;
- }
- src += 4;
- /* Published SNI field */
- ptls_decode_open_block(src, end, 2, { src = end; });
-
- /* Process the list of KeyShareEntries, verify for each of them that the ciphersuite is supported. */
- ptls_decode_open_block(src, end, 2, {
- do {
- /* parse */
- uint16_t id;
- if ((ret = ptls_decode16(&id, &src, end)) != 0)
- goto Exit;
- ptls_decode_open_block(src, end, 2, { src = end; });
- /* check that matching key-share exists */
- ptls_key_exchange_context_t **found;
- for (found = key_exchanges; *found != NULL; ++found)
- if ((*found)->algo->id == id)
- break;
- if (found == NULL) {
- ret = PTLS_ERROR_INCOMPATIBLE_KEY;
- goto Exit;
- }
- } while (src != end);
- });
- /* Process the list of cipher_suites. If they are supported, store in esni context */
- ptls_decode_open_block(src, end, 2, {
- void *newp;
- do {
- uint16_t id;
- if ((ret = ptls_decode16(&id, &src, end)) != 0)
- goto Exit;
- size_t i;
- for (i = 0; ctx->cipher_suites[i] != NULL; ++i)
- if (ctx->cipher_suites[i]->id == id)
- break;
- if (ctx->cipher_suites[i] != NULL) {
- if ((newp = realloc(esni->cipher_suites, sizeof(*esni->cipher_suites) * (num_cipher_suites + 1))) == NULL) {
- ret = PTLS_ERROR_NO_MEMORY;
- goto Exit;
- }
- esni->cipher_suites = newp;
- esni->cipher_suites[num_cipher_suites++].cipher_suite = ctx->cipher_suites[i];
- }
- } while (src != end);
- if ((newp = realloc(esni->cipher_suites, sizeof(*esni->cipher_suites) * (num_cipher_suites + 1))) == NULL) {
- ret = PTLS_ERROR_NO_MEMORY;
- goto Exit;
- }
- esni->cipher_suites = newp;
- esni->cipher_suites[num_cipher_suites].cipher_suite = NULL;
- });
- /* Parse the padded length, not before, not after parameters */
- if ((ret = ptls_decode16(&esni->padded_length, &src, end)) != 0)
- goto Exit;
- if ((ret = ptls_decode64(&esni->not_before, &src, end)) != 0)
- goto Exit;
- if ((ret = ptls_decode64(&esni->not_after, &src, end)) != 0)
- goto Exit;
- /* Skip the extension fields */
- ptls_decode_block(src, end, 2, {
- while (src != end) {
- uint16_t ext_type;
- if ((ret = ptls_decode16(&ext_type, &src, end)) != 0)
- goto Exit;
- ptls_decode_open_block(src, end, 2, { src = end; });
- }
- });
-
- { /* calculate digests for every cipher-suite */
- size_t i;
- for (i = 0; esni->cipher_suites[i].cipher_suite != NULL; ++i) {
- if ((ret = ptls_calc_hash(esni->cipher_suites[i].cipher_suite->hash, esni->cipher_suites[i].record_digest,
- esni_keys.base, esni_keys.len)) != 0)
- goto Exit;
- }
- }
-
- ret = 0;
-Exit:
- if (ret != 0)
- ptls_esni_dispose_context(esni);
- return ret;
-}
-
-void ptls_esni_dispose_context(ptls_esni_context_t *esni)
-{
- size_t i;
-
- if (esni->key_exchanges != NULL) {
- for (i = 0; esni->key_exchanges[i] != NULL; ++i)
- esni->key_exchanges[i]->on_exchange(esni->key_exchanges + i, 1, NULL, ptls_iovec_init(NULL, 0));
- free(esni->key_exchanges);
- }
- free(esni->cipher_suites);
-}
-
-/**
- * Obtain the ESNI secrets negotiated during the handshake.
- */
-ptls_esni_secret_t *ptls_get_esni_secret(ptls_t *ctx)
-{
- return ctx->esni;
-}
-
/**
* checks if given name looks like an IP address
*/
@@ -6032,6 +6324,31 @@
return 0;
}
+int ptls_ech_encode_config(ptls_buffer_t *buf, uint8_t config_id, ptls_hpke_kem_t *kem, ptls_iovec_t public_key,
+ ptls_hpke_cipher_suite_t **ciphers, uint8_t max_name_length, const char *public_name)
+{
+ int ret;
+
+ ptls_buffer_push16(buf, PTLS_ECH_CONFIG_VERSION);
+ ptls_buffer_push_block(buf, 2, {
+ ptls_buffer_push(buf, config_id);
+ ptls_buffer_push16(buf, kem->id);
+ ptls_buffer_push_block(buf, 2, { ptls_buffer_pushv(buf, public_key.base, public_key.len); });
+ ptls_buffer_push_block(buf, 2, {
+ for (size_t i = 0; ciphers[i] != NULL; ++i) {
+ ptls_buffer_push16(buf, ciphers[i]->id.kdf);
+ ptls_buffer_push16(buf, ciphers[i]->id.aead);
+ }
+ });
+ ptls_buffer_push(buf, max_name_length);
+ ptls_buffer_push_block(buf, 1, { ptls_buffer_pushv(buf, public_name, strlen(public_name)); });
+ ptls_buffer_push_block(buf, 2, {/* extensions */});
+ });
+
+Exit:
+ return ret;
+}
+
static char *byte_to_hex(char *dst, uint8_t v)
{
*dst++ = "0123456789abcdef"[v >> 4];
diff --git a/picotls.xcodeproj/project.pbxproj b/picotls.xcodeproj/project.pbxproj
index 92b5954..ef65447 100644
--- a/picotls.xcodeproj/project.pbxproj
+++ b/picotls.xcodeproj/project.pbxproj
@@ -105,9 +105,6 @@
E9925A162354C3DF00CA2082 /* chacha20.c in Sources */ = {isa = PBXBuildFile; fileRef = E9F20BE422E34B340018D260 /* chacha20.c */; };
E9925A172354C3E200CA2082 /* random.c in Sources */ = {isa = PBXBuildFile; fileRef = E9F20BF922E34C110018D260 /* random.c */; };
E9925A182354C3E500CA2082 /* x25519.c in Sources */ = {isa = PBXBuildFile; fileRef = E9F20BE122E34B340018D260 /* x25519.c */; };
- E992F7A320E99A7C0008154D /* libpicotls-openssl.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 1059008C1DC8E1A300FB4085 /* libpicotls-openssl.a */; };
- E992F7A420E99A7C0008154D /* libpicotls-core.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 106530DA1D9B3E6F005B2C60 /* libpicotls-core.a */; };
- E992F7AA20E99AA10008154D /* esni.c in Sources */ = {isa = PBXBuildFile; fileRef = E992F79A20E99A6B0008154D /* esni.c */; };
E99B75E01F5CDDB500CF503E /* asn1.c in Sources */ = {isa = PBXBuildFile; fileRef = E99B75DE1F5CDDB500CF503E /* asn1.c */; };
E99B75E11F5CDDB500CF503E /* pembase64.c in Sources */ = {isa = PBXBuildFile; fileRef = E99B75DF1F5CDDB500CF503E /* pembase64.c */; };
E99B75E21F5CE54D00CF503E /* asn1.c in Sources */ = {isa = PBXBuildFile; fileRef = E99B75DE1F5CDDB500CF503E /* asn1.c */; };
@@ -159,20 +156,6 @@
remoteGlobalIDString = 106530D91D9B3E6F005B2C60;
remoteInfo = "picotls-core";
};
- E992F79D20E99A7C0008154D /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 106530AA1D9985E0005B2C60 /* Project object */;
- proxyType = 1;
- remoteGlobalIDString = 106530D91D9B3E6F005B2C60;
- remoteInfo = "picotls-core";
- };
- E992F79F20E99A7C0008154D /* PBXContainerItemProxy */ = {
- isa = PBXContainerItemProxy;
- containerPortal = 106530AA1D9985E0005B2C60 /* Project object */;
- proxyType = 1;
- remoteGlobalIDString = 105900701DC8E1A300FB4085;
- remoteInfo = "picotls-openssl";
- };
/* End PBXContainerItemProxy section */
/* Begin PBXCopyFilesBuildPhase section */
@@ -203,15 +186,6 @@
);
runOnlyForDeploymentPostprocessing = 1;
};
- E992F7A520E99A7C0008154D /* CopyFiles */ = {
- isa = PBXCopyFilesBuildPhase;
- buildActionMask = 2147483647;
- dstPath = /usr/share/man/man1/;
- dstSubfolderSpec = 0;
- files = (
- );
- runOnlyForDeploymentPostprocessing = 1;
- };
E9B43DDD24619D5100824E51 /* CopyFiles */ = {
isa = PBXCopyFilesBuildPhase;
buildActionMask = 2147483647;
@@ -287,8 +261,6 @@
E97577002212405300D1EF74 /* ffx.h */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.h; path = ffx.h; sourceTree = "<group>"; };
E97577022212405D00D1EF74 /* ffx.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = ffx.c; sourceTree = "<group>"; };
E97577072213148800D1EF74 /* e2e.t */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.perl; path = e2e.t; sourceTree = "<group>"; };
- E992F79A20E99A6B0008154D /* esni.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = esni.c; sourceTree = "<group>"; };
- E992F7A920E99A7C0008154D /* picotls-esni */ = {isa = PBXFileReference; explicitFileType = "compiled.mach-o.executable"; includeInIndex = 0; path = "picotls-esni"; sourceTree = BUILT_PRODUCTS_DIR; };
E99B75DE1F5CDDB500CF503E /* asn1.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = asn1.c; sourceTree = "<group>"; };
E99B75DF1F5CDDB500CF503E /* pembase64.c */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.c.c; path = pembase64.c; sourceTree = "<group>"; };
E9B43DBF24619D1700824E51 /* fusion.c */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.c; path = fusion.c; sourceTree = "<group>"; };
@@ -357,15 +329,6 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
- E992F7A220E99A7C0008154D /* Frameworks */ = {
- isa = PBXFrameworksBuildPhase;
- buildActionMask = 2147483647;
- files = (
- E992F7A320E99A7C0008154D /* libpicotls-openssl.a in Frameworks */,
- E992F7A420E99A7C0008154D /* libpicotls-core.a in Frameworks */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
E9B43DDC24619D5100824E51 /* Frameworks */ = {
isa = PBXFrameworksBuildPhase;
buildActionMask = 2147483647;
@@ -450,7 +413,6 @@
1059004B1DC8D57000FB4085 /* test-minicrypto */,
1059008C1DC8E1A300FB4085 /* libpicotls-openssl.a */,
10EACB171DCEAF0F00CA0341 /* libpicotls-minicrypto.a */,
- E992F7A920E99A7C0008154D /* picotls-esni */,
E9B43DE124619D5100824E51 /* test-fusion */,
);
name = Products;
@@ -571,7 +533,6 @@
E992F79920E99A080008154D /* src */ = {
isa = PBXGroup;
children = (
- E992F79A20E99A6B0008154D /* esni.c */,
);
path = src;
sourceTree = "<group>";
@@ -759,25 +720,6 @@
productReference = 10EACB171DCEAF0F00CA0341 /* libpicotls-minicrypto.a */;
productType = "com.apple.product-type.library.static";
};
- E992F79B20E99A7C0008154D /* picotls-esni */ = {
- isa = PBXNativeTarget;
- buildConfigurationList = E992F7A620E99A7C0008154D /* Build configuration list for PBXNativeTarget "picotls-esni" */;
- buildPhases = (
- E992F7A020E99A7C0008154D /* Sources */,
- E992F7A220E99A7C0008154D /* Frameworks */,
- E992F7A520E99A7C0008154D /* CopyFiles */,
- );
- buildRules = (
- );
- dependencies = (
- E992F79C20E99A7C0008154D /* PBXTargetDependency */,
- E992F79E20E99A7C0008154D /* PBXTargetDependency */,
- );
- name = "picotls-esni";
- productName = "test-crypto-openssl";
- productReference = E992F7A920E99A7C0008154D /* picotls-esni */;
- productType = "com.apple.product-type.tool";
- };
E9B43DC024619D5100824E51 /* test-fusion */ = {
isa = PBXNativeTarget;
buildConfigurationList = E9B43DDE24619D5100824E51 /* Build configuration list for PBXNativeTarget "test-fusion" */;
@@ -831,7 +773,6 @@
106530F11DAD8985005B2C60 /* cli */,
106530CB1D9B3D45005B2C60 /* test-openssl */,
105900411DC8D57000FB4085 /* test-minicrypto */,
- E992F79B20E99A7C0008154D /* picotls-esni */,
E9B43DC024619D5100824E51 /* test-fusion */,
);
};
@@ -966,14 +907,6 @@
);
runOnlyForDeploymentPostprocessing = 0;
};
- E992F7A020E99A7C0008154D /* Sources */ = {
- isa = PBXSourcesBuildPhase;
- buildActionMask = 2147483647;
- files = (
- E992F7AA20E99AA10008154D /* esni.c in Sources */,
- );
- runOnlyForDeploymentPostprocessing = 0;
- };
E9B43DC124619D5100824E51 /* Sources */ = {
isa = PBXSourcesBuildPhase;
buildActionMask = 2147483647;
@@ -998,16 +931,6 @@
target = 106530D91D9B3E6F005B2C60 /* picotls-core */;
targetProxy = 10EACB181DCEAF4A00CA0341 /* PBXContainerItemProxy */;
};
- E992F79C20E99A7C0008154D /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- target = 106530D91D9B3E6F005B2C60 /* picotls-core */;
- targetProxy = E992F79D20E99A7C0008154D /* PBXContainerItemProxy */;
- };
- E992F79E20E99A7C0008154D /* PBXTargetDependency */ = {
- isa = PBXTargetDependency;
- target = 105900701DC8E1A300FB4085 /* picotls-openssl */;
- targetProxy = E992F79F20E99A7C0008154D /* PBXContainerItemProxy */;
- };
/* End PBXTargetDependency section */
/* Begin XCBuildConfiguration section */
@@ -1270,34 +1193,6 @@
};
name = Release;
};
- E992F7A720E99A7C0008154D /* Debug */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
- HEADER_SEARCH_PATHS = (
- /usr/local/opt/openssl/include,
- include,
- );
- LIBRARY_SEARCH_PATHS = /usr/local/opt/openssl/lib;
- OTHER_LDFLAGS = "-lcrypto";
- PRODUCT_NAME = "$(TARGET_NAME)";
- };
- name = Debug;
- };
- E992F7A820E99A7C0008154D /* Release */ = {
- isa = XCBuildConfiguration;
- buildSettings = {
- GCC_PREPROCESSOR_DEFINITIONS = "$(inherited)";
- HEADER_SEARCH_PATHS = (
- /usr/local/opt/openssl/include,
- include,
- );
- LIBRARY_SEARCH_PATHS = /usr/local/opt/openssl/lib;
- OTHER_LDFLAGS = "-lcrypto";
- PRODUCT_NAME = "$(TARGET_NAME)";
- };
- name = Release;
- };
E9B43DDF24619D5100824E51 /* Debug */ = {
isa = XCBuildConfiguration;
buildSettings = {
@@ -1392,15 +1287,6 @@
defaultConfigurationIsVisible = 0;
defaultConfigurationName = Release;
};
- E992F7A620E99A7C0008154D /* Build configuration list for PBXNativeTarget "picotls-esni" */ = {
- isa = XCConfigurationList;
- buildConfigurations = (
- E992F7A720E99A7C0008154D /* Debug */,
- E992F7A820E99A7C0008154D /* Release */,
- );
- defaultConfigurationIsVisible = 0;
- defaultConfigurationName = Release;
- };
E9B43DDE24619D5100824E51 /* Build configuration list for PBXNativeTarget "test-fusion" */ = {
isa = XCConfigurationList;
buildConfigurations = (
diff --git a/picotlsvs/picotlsvs.sln b/picotlsvs/picotlsvs.sln
index e6140c4..0322568 100644
--- a/picotlsvs/picotlsvs.sln
+++ b/picotlsvs/picotlsvs.sln
@@ -30,14 +30,6 @@
{497433FE-B252-4985-A504-54EB791F57F4} = {497433FE-B252-4985-A504-54EB791F57F4}
EndProjectSection
EndProject
-Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "picotls-esni", "picotls-esni\picotls-esni.vcxproj", "{592127C5-DD8C-47ED-8EBA-026B5848C374}"
- ProjectSection(ProjectDependencies) = postProject
- {559AC085-1BEF-450A-A62D-0D370561D596} = {559AC085-1BEF-450A-A62D-0D370561D596}
- {499B82B3-F5A5-4C2E-91EF-A2F77CBC33F5} = {499B82B3-F5A5-4C2E-91EF-A2F77CBC33F5}
- {56C264BF-822B-4F29-B512-5B26157CA2EC} = {56C264BF-822B-4F29-B512-5B26157CA2EC}
- {497433FE-B252-4985-A504-54EB791F57F4} = {497433FE-B252-4985-A504-54EB791F57F4}
- EndProjectSection
-EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "picotls-core", "picotls-core\picotls-core.vcxproj", "{497433FE-B252-4985-A504-54EB791F57F4}"
EndProject
Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "picotls-minicrypto", "picotls-minicrypto\picotls-minicrypto.vcxproj", "{559AC085-1BEF-450A-A62D-0D370561D596}"
@@ -98,14 +90,6 @@
{15D7D32F-3B62-4B10-A06A-BA1ADD591D7F}.Release|x64.Build.0 = Release|x64
{15D7D32F-3B62-4B10-A06A-BA1ADD591D7F}.Release|x86.ActiveCfg = Release|Win32
{15D7D32F-3B62-4B10-A06A-BA1ADD591D7F}.Release|x86.Build.0 = Release|Win32
- {592127C5-DD8C-47ED-8EBA-026B5848C374}.Debug|x64.ActiveCfg = Debug|x64
- {592127C5-DD8C-47ED-8EBA-026B5848C374}.Debug|x64.Build.0 = Debug|x64
- {592127C5-DD8C-47ED-8EBA-026B5848C374}.Debug|x86.ActiveCfg = Debug|Win32
- {592127C5-DD8C-47ED-8EBA-026B5848C374}.Debug|x86.Build.0 = Debug|Win32
- {592127C5-DD8C-47ED-8EBA-026B5848C374}.Release|x64.ActiveCfg = Release|x64
- {592127C5-DD8C-47ED-8EBA-026B5848C374}.Release|x64.Build.0 = Release|x64
- {592127C5-DD8C-47ED-8EBA-026B5848C374}.Release|x86.ActiveCfg = Release|Win32
- {592127C5-DD8C-47ED-8EBA-026B5848C374}.Release|x86.Build.0 = Release|Win32
{497433FE-B252-4985-A504-54EB791F57F4}.Debug|x64.ActiveCfg = Debug|x64
{497433FE-B252-4985-A504-54EB791F57F4}.Debug|x64.Build.0 = Debug|x64
{497433FE-B252-4985-A504-54EB791F57F4}.Debug|x86.ActiveCfg = Debug|Win32
diff --git a/src/esni.c b/src/esni.c
deleted file mode 100644
index 3bd0bf0..0000000
--- a/src/esni.c
+++ /dev/null
@@ -1,246 +0,0 @@
-/*
- * Copyright (c) 2018 Fastly, Kazuho Oku
- *
- * Permission is hereby granted, free of charge, to any person obtaining a copy
- * of this software and associated documentation files (the "Software"), to
- * deal in the Software without restriction, including without limitation the
- * rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
- * sell copies of the Software, and to permit persons to whom the Software is
- * furnished to do so, subject to the following conditions:
- *
- * The above copyright notice and this permission notice shall be included in
- * all copies or substantial portions of the Software.
- *
- * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
- * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
- * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
- * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
- * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
- * FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
- * IN THE SOFTWARE.
- */
-#include <assert.h>
-#include <getopt.h>
-#include <inttypes.h>
-#include <stdio.h>
-#include <string.h>
-#ifdef _WINDOWS
-#include "..\picotls\wincompat.h"
-#ifndef _CRT_SECURE_NO_WARNINGS
-#define _CRT_SECURE_NO_WARNINGS
-#endif
-#pragma warning(disable : 4996)
-#else
-#include <strings.h>
-#endif
-#include <time.h>
-#define OPENSSL_API_COMPAT 0x00908000L
-#include <openssl/err.h>
-#if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x30000000L
-#include <openssl/provider.h>
-#endif
-#include <openssl/engine.h>
-#include <openssl/pem.h>
-#include "picotls.h"
-#include "picotls/pembase64.h"
-#include "picotls/openssl.h"
-
-static int emit_esni(ptls_key_exchange_context_t **key_exchanges, ptls_cipher_suite_t **cipher_suites, uint16_t padded_length,
- uint64_t not_before, uint64_t lifetime, char const *published_sni, char const *file_output)
-{
- ptls_buffer_t buf;
- ptls_key_exchange_context_t *ctx[256] = {NULL};
- int ret;
-
- ptls_buffer_init(&buf, "", 0);
-
- ptls_buffer_push16(&buf, PTLS_ESNI_VERSION_DRAFT03);
- ptls_buffer_push(&buf, 0, 0, 0, 0); /* checksum, filled later */
- if (published_sni != NULL) {
- ptls_buffer_push_block(&buf, 2, { ptls_buffer_pushv(&buf, published_sni, strlen(published_sni)); });
- } else {
- ptls_buffer_push16(&buf, 0);
- }
- ptls_buffer_push_block(&buf, 2, {
- size_t i;
- for (i = 0; key_exchanges[i] != NULL; ++i) {
- ptls_buffer_push16(&buf, key_exchanges[i]->algo->id);
- ptls_buffer_push_block(&buf, 2,
- { ptls_buffer_pushv(&buf, key_exchanges[i]->pubkey.base, key_exchanges[i]->pubkey.len); });
- }
- });
- ptls_buffer_push_block(&buf, 2, {
- size_t i;
- for (i = 0; cipher_suites[i] != NULL; ++i)
- ptls_buffer_push16(&buf, cipher_suites[i]->id);
- });
- ptls_buffer_push16(&buf, padded_length);
- ptls_buffer_push64(&buf, not_before);
- ptls_buffer_push64(&buf, not_before + lifetime - 1);
- ptls_buffer_push_block(&buf, 2, {});
- { /* fill checksum */
- uint8_t d[PTLS_SHA256_DIGEST_SIZE];
- ptls_calc_hash(&ptls_openssl_sha256, d, buf.base, buf.off);
- memcpy(buf.base + 2, d, 4);
- }
-
- if (file_output != NULL) {
- FILE *fo = fopen(file_output, "wb");
- if (fo == NULL) {
- fprintf(stderr, "failed to open file:%s:%s\n", optarg, strerror(errno));
- goto Exit;
- } else {
- fwrite(buf.base, 1, buf.off, fo);
- fclose(fo);
- }
- } else {
- /* emit the structure to stdout */
- fwrite(buf.base, 1, buf.off, stdout);
- fflush(stdout);
- }
-
- ret = 0;
-Exit : {
- size_t i;
- for (i = 0; ctx[i] != NULL; ++i)
- ctx[i]->on_exchange(ctx + i, 1, NULL, ptls_iovec_init(NULL, 0));
-}
- ptls_buffer_dispose(&buf);
- return ret;
-}
-
-static void usage(const char *cmd, int status)
-{
- printf("picotls-esni - generates an ESNI Resource Record\n"
- "\n"
- "Usage: %s [options]\n"
- "Options:\n"
- " -n <published-sni> published sni value\n"
- " -K <key-file> private key files (repeat the option to include multiple\n"
- " keys)\n"
- " -c <cipher-suite> aes128-gcm, chacha20-poly1305, ...\n"
- " -d <days> number of days until expiration (default: 90)\n"
- " -p <padded-length> padded length (default: 260)\n"
- " -o <output-file> write output to specified file instead of stdout\n"
- " (use on Windows as stdout is not binary there)\n"
- " -h prints this help\n"
- "\n"
- "-c and -x can be used multiple times.\n"
- "\n",
- cmd);
- exit(status);
-}
-
-int main(int argc, char **argv)
-{
- char const *published_sni = NULL;
- char const *file_output = NULL;
- ERR_load_crypto_strings();
- OpenSSL_add_all_algorithms();
-#if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x30000000L
- (void)OSSL_PROVIDER_load(NULL, "default");
-#elif !defined(OPENSSL_NO_ENGINE)
- /* Load all compiled-in ENGINEs */
- ENGINE_load_builtin_engines();
- ENGINE_register_all_ciphers();
- ENGINE_register_all_digests();
-#endif
-
- struct {
- ptls_key_exchange_context_t *elements[256];
- size_t count;
- } key_exchanges = {{NULL}, 0};
- struct {
- ptls_cipher_suite_t *elements[256];
- size_t count;
- } cipher_suites = {{NULL}, 0};
- uint16_t padded_length = 260;
- uint64_t lifetime = 90 * 86400;
-
- int ch;
-
- while ((ch = getopt(argc, argv, "n:K:c:d:p:o:h")) != -1) {
- switch (ch) {
- case 'n':
- published_sni = optarg;
- break;
- case 'K': {
- FILE *fp;
- EVP_PKEY *pkey;
-
- if ((fp = fopen(optarg, "rt")) == NULL) {
- fprintf(stderr, "failed to open file:%s:%s\n", optarg, strerror(errno));
- exit(1);
- }
-
- if ((pkey = PEM_read_PrivateKey(fp, NULL, NULL, NULL)) == NULL) {
- fprintf(stderr, "failed to read private key from file:%s:%s\n", optarg, strerror(errno));
- exit(1);
- }
- fclose(fp);
- if (ptls_openssl_create_key_exchange(key_exchanges.elements + key_exchanges.count++, pkey) != 0) {
- fprintf(stderr, "unknown type of private key found in file:%s\n", optarg);
- exit(1);
- }
- EVP_PKEY_free(pkey);
- } break;
- case 'c': {
- size_t i;
- for (i = 0; ptls_openssl_cipher_suites[i] != NULL; ++i)
- if (strcasecmp(ptls_openssl_cipher_suites[i]->aead->name, optarg) == 0)
- break;
- if (ptls_openssl_cipher_suites[i] == NULL) {
- fprintf(stderr, "unknown cipher-suite: %s\n", optarg);
- exit(1);
- }
- cipher_suites.elements[cipher_suites.count++] = ptls_openssl_cipher_suites[i];
- } break;
- case 'd':
- if (sscanf(optarg, "%" SCNu64, &lifetime) != 1 || lifetime == 0) {
- fprintf(stderr, "lifetime must be a positive integer\n");
- exit(1);
- }
- lifetime *= 86400; /* convert to seconds */
- break;
- case 'p':
-#ifdef _WINDOWS
- if (sscanf_s(optarg, "%" SCNu16, &padded_length) != 1 || padded_length == 0) {
- fprintf(stderr, "padded length must be a positive integer\n");
- exit(1);
- }
-#else
- if (sscanf(optarg, "%" SCNu16, &padded_length) != 1 || padded_length == 0) {
- fprintf(stderr, "padded length must be a positive integer\n");
- exit(1);
- }
-#endif
- break;
- case 'o':
- file_output = optarg;
- break;
- case 'h':
- usage(argv[0], 0);
- break;
- default:
- usage(argv[0], 1);
- break;
- }
- }
- if (cipher_suites.count == 0)
- cipher_suites.elements[cipher_suites.count++] = &ptls_openssl_aes128gcmsha256;
- if (key_exchanges.count == 0) {
- fprintf(stderr, "no private key specified\n");
- exit(1);
- }
-
- argc -= optind;
- argv += optind;
-
- if (emit_esni(key_exchanges.elements, cipher_suites.elements, padded_length, time(NULL), lifetime, published_sni,
- file_output) != 0) {
- fprintf(stderr, "failed to generate ESNI private structure.\n");
- exit(1);
- }
-
- return 0;
-}
diff --git a/t/cli.c b/t/cli.c
index a9a65c7..18bba34 100644
--- a/t/cli.c
+++ b/t/cli.c
@@ -57,6 +57,90 @@
/* sentinels indicating that the endpoint is in benchmark mode */
static const char input_file_is_benchmark[] = "is:benchmark";
+static struct {
+ ptls_iovec_t config_list;
+ struct {
+ struct {
+ ptls_hpke_kem_t *kem;
+ ptls_key_exchange_context_t *ctx;
+ } list[16];
+ size_t count;
+ } keyex;
+ struct {
+ ptls_iovec_t configs;
+ char *fn;
+ } retry;
+} ech;
+
+static ptls_hpke_kem_t *find_kem(ptls_key_exchange_algorithm_t *algo)
+{
+ for (size_t i = 0; ptls_openssl_hpke_kems[i] != NULL; ++i)
+ if (ptls_openssl_hpke_kems[i]->keyex == algo)
+ return ptls_openssl_hpke_kems[i];
+
+ fprintf(stderr, "HPKE KEM not found for %s\n", algo->name);
+ return NULL;
+}
+
+static ptls_aead_context_t *create_ech_opener(ptls_ech_create_opener_t *self, ptls_hpke_kem_t **kem,
+ ptls_hpke_cipher_suite_t **cipher, ptls_t *tls, uint8_t config_id,
+ ptls_hpke_cipher_suite_id_t cipher_id, ptls_iovec_t enc, ptls_iovec_t info_prefix)
+{
+ const uint8_t *src = ech.config_list.base, *const end = src + ech.config_list.len;
+ size_t index = 0;
+ int ret = 0;
+
+ /* look for the cipher implementation; this should better be specific to each ECHConfig (as each of them may advertise different
+ * set of values) */
+ *cipher = NULL;
+ for (size_t i = 0; ptls_openssl_hpke_cipher_suites[i] != NULL; ++i) {
+ if (ptls_openssl_hpke_cipher_suites[i]->id.kdf == cipher_id.kdf &&
+ ptls_openssl_hpke_cipher_suites[i]->id.aead == cipher_id.aead) {
+ *cipher = ptls_openssl_hpke_cipher_suites[i];
+ break;
+ }
+ }
+ if (*cipher == NULL)
+ goto Exit;
+
+ ptls_decode_open_block(src, end, 2, {
+ uint16_t version;
+ if ((ret = ptls_decode16(&version, &src, end)) != 0)
+ goto Exit;
+ do {
+ ptls_decode_open_block(src, end, 2, {
+ if (src == end) {
+ ret = PTLS_ALERT_DECODE_ERROR;
+ goto Exit;
+ }
+ if (*src == config_id) {
+ /* this is the ECHConfig that we have been looking for */
+ if (index >= ech.keyex.count) {
+ fprintf(stderr, "ECH key missing for config %zu\n", index);
+ return NULL;
+ }
+ uint8_t *info = malloc(info_prefix.len + end - (src - 4));
+ memcpy(info, info_prefix.base, info_prefix.len);
+ memcpy(info + info_prefix.len, src - 4, end - (src - 4));
+ ptls_aead_context_t *aead;
+ ptls_hpke_setup_base_r(ech.keyex.list[index].kem, *cipher, ech.keyex.list[index].ctx, &aead, enc,
+ ptls_iovec_init(info, info_prefix.len + end - (src - 4)));
+ free(info);
+ *kem = ech.keyex.list[index].kem;
+ return aead;
+ }
+ ++index;
+ src = end;
+ });
+ } while (src != end);
+ });
+
+Exit:
+ if (ret != 0)
+ fprintf(stderr, "ECH decode error:%d\n", ret);
+ return NULL;
+}
+
static void shift_buffer(ptls_buffer_t *buf, size_t delta)
{
if (delta != 0) {
@@ -167,6 +251,18 @@
} else if (ret == PTLS_ERROR_IN_PROGRESS) {
/* ok */
} else {
+ if (ret == PTLS_ALERT_ECH_REQUIRED) {
+ assert(!ptls_is_server(tls));
+ if (ech.retry.configs.base != NULL) {
+ FILE *fp;
+ if ((fp = fopen(ech.retry.fn, "wt")) == NULL) {
+ fprintf(stderr, "failed to write to ECH config file:%s:%s\n", ech.retry.fn, strerror(errno));
+ exit(1);
+ }
+ fwrite(ech.retry.configs.base, 1, ech.retry.configs.len, fp);
+ fclose(fp);
+ }
+ }
if (encbuf.off != 0)
repeat_while_eintr(write(sockfd, encbuf.base, encbuf.off), { break; });
fprintf(stderr, "ptls_handshake:%d\n", ret);
@@ -332,8 +428,6 @@
{
int fd;
- hsprop->client.esni_keys = resolve_esni_keys(server_name);
-
if ((fd = socket(sa->sa_family, SOCK_STREAM, 0)) == 1) {
perror("socket(2) failed");
return 1;
@@ -344,7 +438,6 @@
}
int ret = handle_connection(fd, ctx, server_name, input_file, hsprop, request_key_update, keep_sender_open);
- free(hsprop->client.esni_keys.base);
return ret;
}
@@ -366,12 +459,14 @@
" -I keep send side open after sending all data (client-only)\n"
" -j log-file file to log probe events in JSON-Lines\n"
" -k key-file specifies the credentials for signing the certificate\n"
+ " -K key-file ECH private key for each ECH config provided by -E\n"
" -l log-file file to log events (incl. traffic secrets)\n"
" -n negotiates the key exchange method (i.e. wait for HRR)\n"
" -N named-group named group to be used (default: secp256r1)\n"
" -s session-file file to read/write the session ticket\n"
" -S require public key exchange when resuming a session\n"
- " -E esni-file file that stores ESNI data generated by picotls-esni\n"
+ " -E echconfiglist file that contains ECHConfigList; overwritten when\n"
+ " receiving retry_configs from the server\n"
" -e when resuming a session, send first 8,192 bytes of input\n"
" as early data\n"
" -r public-key-file use raw public keys (RFC 7250). When set and running as a\n"
@@ -425,13 +520,16 @@
ptls_key_exchange_algorithm_t *key_exchanges[128] = {NULL};
ptls_cipher_suite_t *cipher_suites[128] = {NULL};
- ptls_context_t ctx = {ptls_openssl_random_bytes, &ptls_get_time, key_exchanges, cipher_suites};
+ ptls_ech_create_opener_t ech_opener = {.cb = create_ech_opener};
+ ptls_context_t ctx = {
+ .random_bytes = ptls_openssl_random_bytes,
+ .get_time = &ptls_get_time,
+ .key_exchanges = key_exchanges,
+ .cipher_suites = cipher_suites,
+ .ech = {.client = {ptls_openssl_hpke_cipher_suites, ptls_openssl_hpke_kems}, .server = {NULL /* activated by -K option */}},
+ };
ptls_handshake_properties_t hsprop = {{{{NULL}}}};
- const char *host, *port, *input_file = NULL, *esni_file = NULL;
- struct {
- ptls_key_exchange_context_t *elements[16];
- size_t count;
- } esni_key_exchanges;
+ const char *host, *port, *input_file = NULL;
int is_server = 0, use_early_data = 0, request_key_update = 0, keep_sender_open = 0, ch;
struct sockaddr_storage sa;
socklen_t salen;
@@ -496,27 +594,41 @@
case 'S':
ctx.require_dhe_on_psk = 1;
break;
- case 'E':
- esni_file = optarg;
- break;
+ case 'E': {
+ FILE *fp;
+ if ((fp = fopen(optarg, "rt")) == NULL) {
+ fprintf(stderr, "failed to open ECHConfigList file:%s:%s\n", optarg, strerror(errno));
+ return 1;
+ }
+ ech.config_list.base = malloc(65536);
+ if ((ech.config_list.len = fread(ech.config_list.base, 1, 65536, fp)) == 65536) {
+ fprintf(stderr, "ECHConfigList is too large:%s\n", optarg);
+ return 1;
+ }
+ fclose(fp);
+ ech.retry.fn = optarg;
+ } break;
case 'K': {
FILE *fp;
EVP_PKEY *pkey;
int ret;
if ((fp = fopen(optarg, "rt")) == NULL) {
- fprintf(stderr, "failed to open ESNI private key file:%s:%s\n", optarg, strerror(errno));
+ fprintf(stderr, "failed to open ECH private key file:%s:%s\n", optarg, strerror(errno));
return 1;
}
if ((pkey = PEM_read_PrivateKey(fp, NULL, NULL, NULL)) == NULL) {
fprintf(stderr, "failed to load private key from file:%s\n", optarg);
return 1;
}
- if ((ret = ptls_openssl_create_key_exchange(esni_key_exchanges.elements + esni_key_exchanges.count++, pkey)) != 0) {
+ if ((ret = ptls_openssl_create_key_exchange(&ech.keyex.list[ech.keyex.count].ctx, pkey)) != 0) {
fprintf(stderr, "failed to load private key from file:%s:picotls-error:%d", optarg, ret);
return 1;
}
+ ech.keyex.list[ech.keyex.count].kem = find_kem(ech.keyex.list[ech.keyex.count].ctx->algo);
+ ++ech.keyex.count;
EVP_PKEY_free(pkey);
fclose(fp);
+ ctx.ech.server.create_opener = &ech_opener;
} break;
case 'l':
setup_log_event(&ctx, optarg);
@@ -636,6 +748,8 @@
hsprop.client.max_early_data_size = &max_early_data_size;
}
ctx.send_change_cipher_spec = 1;
+ hsprop.client.ech.configs = ech.config_list;
+ hsprop.client.ech.retry_configs = &ech.retry.configs;
}
if (key_exchanges[0] == NULL)
key_exchanges[0] = &ptls_openssl_secp256r1;
@@ -644,13 +758,6 @@
for (i = 0; ptls_openssl_cipher_suites[i] != NULL; ++i)
cipher_suites[i] = ptls_openssl_cipher_suites[i];
}
- if (esni_file != NULL) {
- if (esni_key_exchanges.count == 0) {
- fprintf(stderr, "-E must be used together with -K\n");
- return 1;
- }
- setup_esni(&ctx, esni_file, esni_key_exchanges.elements);
- }
if (argc != 2) {
fprintf(stderr, "missing host and port\n");
return 1;
diff --git a/t/hpke.c b/t/hpke.c
index 0330064..e2cb010 100644
--- a/t/hpke.c
+++ b/t/hpke.c
@@ -169,7 +169,8 @@
for (ptls_hpke_kem_t **kem = all_kems; *kem != NULL; ++kem) {
for (ptls_hpke_cipher_suite_t **cipher = all_ciphers; *cipher != NULL; ++cipher) {
char namebuf[64];
- sprintf(namebuf, "%s-%s/%s-%s", (*kem)->keyex->name, (*kem)->hash->name, (*cipher)->hash->name, (*cipher)->aead->name);
+ snprintf(namebuf, sizeof(namebuf), "%s-%s/%s-%s", (*kem)->keyex->name, (*kem)->hash->name, (*cipher)->hash->name,
+ (*cipher)->aead->name);
test_kem = *kem;
test_cipher = *cipher;
subtest(namebuf, test_one_hpke);
diff --git a/t/minicrypto.c b/t/minicrypto.c
index bf09040..30395cf 100644
--- a/t/minicrypto.c
+++ b/t/minicrypto.c
@@ -155,7 +155,7 @@
ptls_minicrypto_key_exchanges,
ptls_minicrypto_cipher_suites,
{&cert, 1},
- NULL,
+ {NULL},
NULL,
NULL,
&sign_certificate.super};
diff --git a/t/openssl.c b/t/openssl.c
index 32984ac..8e79b15 100644
--- a/t/openssl.c
+++ b/t/openssl.c
@@ -335,6 +335,43 @@
test_hpke(ptls_openssl_hpke_kems, ptls_openssl_hpke_cipher_suites);
}
+static ptls_aead_context_t *create_ech_opener(ptls_ech_create_opener_t *self, ptls_hpke_kem_t **kem,
+ ptls_hpke_cipher_suite_t **cipher, ptls_t *tls, uint8_t config_id,
+ ptls_hpke_cipher_suite_id_t cipher_id, ptls_iovec_t enc, ptls_iovec_t info_prefix)
+{
+ static ptls_key_exchange_context_t *pem = NULL;
+ if (pem == NULL) {
+ pem = key_from_pem(ECH_PRIVATE_KEY);
+ assert(pem != NULL);
+ }
+
+ *cipher = NULL;
+ for (size_t i = 0; ptls_openssl_hpke_cipher_suites[i] != NULL; ++i) {
+ if (ptls_openssl_hpke_cipher_suites[i]->id.kdf == cipher_id.kdf &&
+ ptls_openssl_hpke_cipher_suites[i]->id.aead == cipher_id.aead) {
+ *cipher = ptls_openssl_hpke_cipher_suites[i];
+ break;
+ }
+ }
+ if (*cipher == NULL)
+ return NULL;
+
+ ptls_aead_context_t *aead = NULL;
+ ptls_buffer_t infobuf;
+ int ret;
+
+ ptls_buffer_init(&infobuf, "", 0);
+ ptls_buffer_pushv(&infobuf, info_prefix.base, info_prefix.len);
+ ptls_buffer_pushv(&infobuf, (const uint8_t *)ECH_CONFIG_LIST + 2,
+ sizeof(ECH_CONFIG_LIST) - 3); /* choose the only ECHConfig from the list */
+ ret = ptls_hpke_setup_base_r(&ptls_openssl_hpke_kem_p256sha256, *cipher, pem, &aead, enc,
+ ptls_iovec_init(infobuf.base, infobuf.off));
+
+Exit:
+ ptls_buffer_dispose(&infobuf);
+ return aead;
+}
+
#if ASYNC_TESTS
static ENGINE *load_engine(const char *name)
@@ -486,6 +523,7 @@
{
ptls_openssl_sign_certificate_t openssl_sign_certificate;
ptls_openssl_verify_certificate_t openssl_verify_certificate;
+ ptls_ech_create_opener_t ech_create_opener = {.cb = create_ech_opener};
ERR_load_crypto_strings();
OpenSSL_add_all_algorithms();
@@ -518,14 +556,15 @@
.cipher_suites = ptls_openssl_cipher_suites,
.tls12_cipher_suites = ptls_openssl_tls12_cipher_suites,
.certificates = {&cert, 1},
+ .ech = {.client = {.ciphers = ptls_openssl_hpke_cipher_suites, .kems = ptls_openssl_hpke_kems},
+ .server = {.create_opener = &ech_create_opener,
+ .retry_configs = {(uint8_t *)ECH_CONFIG_LIST, sizeof(ECH_CONFIG_LIST) - 1}}},
.sign_certificate = &openssl_sign_certificate.super};
assert(openssl_ctx.cipher_suites[0]->hash->digest_size == 48); /* sha384 */
ptls_context_t openssl_ctx_sha256only = openssl_ctx;
++openssl_ctx_sha256only.cipher_suites;
assert(openssl_ctx_sha256only.cipher_suites[0]->hash->digest_size == 32); /* sha256 */
- ptls_key_exchange_context_t *esni_private_keys[2] = {key_from_pem(ESNI_SECP256R1KEY), NULL};
-
ctx = ctx_peer = &openssl_ctx;
verify_certificate = &openssl_verify_certificate.super;
ADD_FFX_AES128_ALGORITHMS(openssl);
@@ -539,7 +578,6 @@
subtest("ed25519-sign", test_ed25519_sign);
subtest("cert-verify", test_cert_verify);
subtest("picotls", test_picotls);
- test_picotls_esni(esni_private_keys);
ctx = ctx_peer = &openssl_ctx_sha256only;
subtest("picotls", test_picotls);
@@ -561,7 +599,7 @@
ptls_minicrypto_key_exchanges,
ptls_minicrypto_cipher_suites,
{&minicrypto_certificate, 1},
- NULL,
+ {NULL},
NULL,
NULL,
&minicrypto_sign_certificate.super};
@@ -573,6 +611,8 @@
ctx_peer = &openssl_ctx;
subtest("minicrypto vs.", test_picotls);
+ subtest("hpke", test_all_hpke);
+
#if ASYNC_TESTS
// switch to x25519 / aes128gcmsha256 as we run benchmarks
static ptls_key_exchange_algorithm_t *fast_keyex[] = {&ptls_openssl_x25519, NULL}; // use x25519 for speed
@@ -599,10 +639,6 @@
}
#endif
- esni_private_keys[0]->on_exchange(esni_private_keys, 1, NULL, ptls_iovec_init(NULL, 0));
-
- subtest("hpke", test_all_hpke);
-
int ret = done_testing();
#if !defined(LIBRESSL_VERSION_NUMBER) && OPENSSL_VERSION_NUMBER >= 0x30000000L
OSSL_PROVIDER_unload(dflt);
diff --git a/t/picotls.c b/t/picotls.c
index a4a802b..bc68ab9 100644
--- a/t/picotls.c
+++ b/t/picotls.c
@@ -44,6 +44,18 @@
ok(ptls_server_name_is_ipaddr("2001:db8::2:1"));
}
+static void test_extension_bitmap(void)
+{
+ struct st_ptls_extension_bitmap_t bitmap = {0};
+
+ /* disallowed extension is rejected */
+ ok(!extension_bitmap_testandset(&bitmap, PTLS_HANDSHAKE_TYPE_SERVER_HELLO, PTLS_EXTENSION_TYPE_COOKIE));
+
+ /* allowed extension is accepted first, rejected upon repetition */
+ ok(extension_bitmap_testandset(&bitmap, PTLS_HANDSHAKE_TYPE_SERVER_HELLO, PTLS_EXTENSION_TYPE_KEY_SHARE));
+ ok(!extension_bitmap_testandset(&bitmap, PTLS_HANDSHAKE_TYPE_SERVER_HELLO, PTLS_EXTENSION_TYPE_KEY_SHARE));
+}
+
static void test_select_cipher(void)
{
#define C(x) ((x) >> 8) & 0xff, (x)&0xff
@@ -498,6 +510,96 @@
ptls_buffer_dispose(&buf);
}
+static void test_ech_decode_config(void)
+{
+ static ptls_hpke_kem_t p256 = {PTLS_HPKE_KEM_P256_SHA256}, *kems[] = {&p256, NULL};
+ static ptls_hpke_cipher_suite_t aes128gcmsha256 = {{PTLS_HPKE_HKDF_SHA256, PTLS_HPKE_AEAD_AES_128_GCM}},
+ *ciphers[] = {&aes128gcmsha256, NULL};
+ struct st_decoded_ech_config_t decoded;
+
+ { /* broken list */
+ const uint8_t *src = (const uint8_t *)"a", *end = src + 1;
+ int ret = decode_one_ech_config(kems, ciphers, &decoded, &src, end);
+ ok(ret == PTLS_ALERT_DECODE_ERROR);
+ }
+
+ {
+ ptls_iovec_t input = ptls_iovec_init(ECH_CONFIG_LIST, sizeof(ECH_CONFIG_LIST) - 1);
+ const uint8_t *src = input.base + 6 /* dive into ECHConfigContents */, *const end = input.base + input.len;
+ int ret = decode_one_ech_config(kems, ciphers, &decoded, &src, end);
+ ok(ret == 0);
+ ok(decoded.id == 0x12);
+ ok(decoded.kem == &p256);
+ ok(decoded.public_key.len == 65);
+ ok(decoded.public_key.base == input.base + 11);
+ ok(decoded.cipher == &aes128gcmsha256);
+ ok(decoded.max_name_length == 64);
+ ok(decoded.public_name.len == sizeof("example.com") - 1);
+ ok(memcmp(decoded.public_name.base, "example.com", sizeof("example.com") - 1) == 0);
+ }
+}
+
+static void test_rebuild_ch_inner(void)
+{
+ ptls_buffer_t buf;
+ ptls_buffer_init(&buf, "", 0);
+
+#define TEST(_expected_err) \
+ do { \
+ const uint8_t *src = encoded_inner; \
+ buf.off = 0; \
+ ok(rebuild_ch_inner_extensions(&buf, &src, encoded_inner + sizeof(encoded_inner), outer, outer + sizeof(outer)) == \
+ _expected_err); \
+ if (_expected_err == 0) { \
+ ok(src == encoded_inner + sizeof(encoded_inner)); \
+ ok(buf.off == sizeof(expected)); \
+ ok(memcmp(buf.base, expected, sizeof(expected)) == 0); \
+ } \
+ } while (0)
+
+ { /* replace none */
+ static const uint8_t encoded_inner[] = {0x00, 0x09, 0x12, 0x34, 0x00, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f},
+ outer[] = {0xde, 0xad},
+ expected[] = {0x00, 0x09, 0x12, 0x34, 0x00, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f};
+ TEST(0);
+ }
+
+ { /* replace one */
+ static const uint8_t encoded_inner[] = {0x00, 0x07, 0xfd, 0x00, 0x00, 0x03, 0x02, 0x00, 0x01},
+ outer[] = {0x00, 0x01, 0x00, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f},
+ expected[] = {0x00, 0x09, 0x00, 0x01, 0x00, 0x05, 0x68, 0x65, 0x6c, 0x6c, 0x6f};
+ TEST(0);
+ }
+
+ { /* replace multi */
+ static const uint8_t encoded_inner[] = {0x00, 0x13, 0x00, 0x01, 0x00, 0x01, 0x31, 0xfd, 0x00, 0x00, 0x05,
+ 0x04, 0x00, 0x02, 0x00, 0x04, 0x00, 0x05, 0x00, 0x01, 0x35},
+ outer[] = {0x00, 0x01, 0x00, 0x01, 0x41, 0x00, 0x02, 0x00, 0x01, 0x42, 0x00, 0x03, 0x00,
+ 0x01, 0x43, 0x00, 0x04, 0x00, 0x01, 0x44, 0x00, 0x05, 0x00, 0x01, 0x45},
+ expected[] = {0x00, 0x14, 0x00, 0x01, 0x00, 0x01, 0x31, 0x00, 0x02, 0x00, 0x01,
+ 0x42, 0x00, 0x04, 0x00, 0x01, 0x44, 0x00, 0x05, 0x00, 0x01, 0x35};
+ TEST(0);
+ }
+
+ { /* outer extension not found */
+ static const uint8_t encoded_inner[] = {0x00, 0x13, 0x00, 0x01, 0x00, 0x01, 0x31, 0xfd, 0x00, 0x00, 0x05,
+ 0x04, 0x00, 0x02, 0x00, 0x04, 0x00, 0x05, 0x00, 0x01, 0x35},
+ outer[] = {0x00, 0x01, 0x00, 0x01, 0x41, 0x00, 0x02, 0x00, 0x01, 0x42, 0x00, 0x03, 0x00, 0x01, 0x43},
+ expected[] = {0x00, 0x14, 0x00, 0x01, 0x00, 0x01, 0x31, 0x00, 0x02, 0x00, 0x01,
+ 0x42, 0x00, 0x04, 0x00, 0x01, 0x44, 0x00, 0x05, 0x00, 0x01, 0x35};
+ TEST(PTLS_ALERT_ILLEGAL_PARAMETER);
+ }
+
+#undef TEST
+ ptls_buffer_dispose(&buf);
+}
+
+static void test_ech(void)
+{
+ subtest("decode-config", test_ech_decode_config);
+ subtest("rebuild_ch_inner", test_rebuild_ch_inner);
+}
+
static struct {
struct {
uint8_t buf[32];
@@ -605,16 +707,12 @@
#undef SET_RECORD
}
-static int was_esni;
-
static int save_client_hello(ptls_on_client_hello_t *self, ptls_t *tls, ptls_on_client_hello_parameters_t *params)
{
ptls_set_server_name(tls, (const char *)params->server_name.base, params->server_name.len);
if (params->negotiated_protocols.count != 0)
ptls_set_negotiated_protocol(tls, (const char *)params->negotiated_protocols.list[0].base,
params->negotiated_protocols.list[0].len);
- if (params->esni)
- ++was_esni;
return 0;
}
@@ -633,6 +731,15 @@
return 0;
}
+static int can_ech(ptls_context_t *ctx, int is_server)
+{
+ if (is_server) {
+ return ctx->ech.server.create_opener != NULL;
+ } else {
+ return ctx->ech.client.ciphers != NULL;
+ }
+}
+
static void test_handshake(ptls_iovec_t ticket, int mode, int expect_ticket, int check_ch, int require_client_authentication)
{
ptls_t *client, *server;
@@ -666,17 +773,17 @@
ptls_set_server_name(client, "test.example.com", 0);
}
+ if (can_ech(ctx, 0)) {
+ ptls_set_server_name(client, "test.example.com", 0);
+ client_hs_prop.client.ech.configs = ptls_iovec_init(ECH_CONFIG_LIST, sizeof(ECH_CONFIG_LIST) - 1);
+ }
+
static ptls_on_extension_t cb = {on_extension_cb};
ctx_peer->on_extension = &cb;
if (require_client_authentication)
ctx_peer->require_client_authentication = 1;
- if (ctx_peer->esni != NULL) {
- was_esni = 0;
- client_hs_prop.client.esni_keys = ptls_iovec_init(ESNIKEYS, sizeof(ESNIKEYS) - 1);
- }
-
switch (mode) {
case TEST_HANDSHAKE_HRR:
client_hs_prop.client.negotiate_before_key_exchange = 1;
@@ -742,10 +849,14 @@
ok(sbuf.off != 0);
if (check_ch) {
ok(ptls_get_server_name(server) != NULL);
- ok(strcmp(ptls_get_server_name(server), "test.example.com") == 0);
+ if (can_ech(ctx, 0) && !can_ech(ctx_peer, 1)) {
+ /* server should be using CHouter.sni that includes the public name of the ECH extension */
+ ok(strcmp(ptls_get_server_name(server), "example.com") == 0);
+ } else {
+ ok(strcmp(ptls_get_server_name(server), "test.example.com") == 0);
+ }
ok(ptls_get_negotiated_protocol(server) != NULL);
ok(strcmp(ptls_get_negotiated_protocol(server), "h2") == 0);
- ok(was_esni == (ctx_peer->esni != NULL));
} else {
ok(ptls_get_server_name(server) == NULL);
ok(ptls_get_negotiated_protocol(server) == NULL);
@@ -890,6 +1001,14 @@
decbuf.off = 0;
}
+ if (can_ech(ctx_peer, 1) && can_ech(ctx, 0)) {
+ ok(ptls_is_ech_handshake(client, NULL, NULL));
+ ok(ptls_is_ech_handshake(server, NULL, NULL));
+ } else {
+ ok(!ptls_is_ech_handshake(client, NULL, NULL));
+ ok(!ptls_is_ech_handshake(server, NULL, NULL));
+ }
+
ptls_buffer_dispose(&cbuf);
ptls_buffer_dispose(&sbuf);
ptls_buffer_dispose(&decbuf);
@@ -1260,6 +1379,63 @@
ptls_buffer_dispose(&sbuf);
}
+static void test_ech_config_mismatch(void)
+{
+ ptls_t *client, *server;
+ ptls_buffer_t cbuf, sbuf, decryptbuf;
+ size_t consumed;
+ int ret;
+ ptls_iovec_t retry_configs = {NULL};
+ ptls_handshake_properties_t client_hs_prop = {
+ .client.ech = {
+ .configs = ptls_iovec_init((void *)ECH_ALTERNATIVE_CONFIG_LIST, sizeof(ECH_ALTERNATIVE_CONFIG_LIST) - 1),
+ .retry_configs = &retry_configs,
+ }};
+
+ client = ptls_new(ctx, 0);
+ ptls_set_server_name(client, "test.example.com", 0);
+ server = ptls_new(ctx_peer, 1);
+ ptls_buffer_init(&cbuf, "", 0);
+ ptls_buffer_init(&sbuf, "", 0);
+ ptls_buffer_init(&decryptbuf, "", 0);
+
+ ret = ptls_handshake(client, &cbuf, NULL, NULL, &client_hs_prop);
+ ok(ret == PTLS_ERROR_IN_PROGRESS);
+
+ consumed = cbuf.off;
+ ret = ptls_handshake(server, &sbuf, cbuf.base, &consumed, NULL);
+ ok(ret == 0);
+ ok(cbuf.off == consumed);
+ cbuf.off = 0;
+
+ consumed = sbuf.off;
+ ret = ptls_handshake(client, &cbuf, sbuf.base, &consumed, &client_hs_prop);
+ ok(ret == PTLS_ALERT_ECH_REQUIRED);
+ ok(sbuf.off == consumed);
+ ok(retry_configs.len == sizeof(ECH_CONFIG_LIST) - 1);
+ ok(memcmp(retry_configs.base, ECH_CONFIG_LIST, retry_configs.len) == 0);
+ sbuf.off = 0;
+
+ consumed = cbuf.off;
+ ret = ptls_handshake(server, &sbuf, cbuf.base, &consumed, NULL);
+ ok(ret == 0);
+ ok(consumed < cbuf.off);
+ memmove(cbuf.base, cbuf.base + consumed, cbuf.off - consumed);
+ cbuf.off -= consumed;
+
+ consumed = cbuf.off;
+ ret = ptls_receive(server, &decryptbuf, cbuf.base, &consumed);
+ ok(ret == PTLS_ALERT_TO_PEER_ERROR(PTLS_ALERT_ECH_REQUIRED));
+ ok(cbuf.off == consumed);
+
+ ptls_free(client);
+ ptls_free(server);
+ ptls_buffer_dispose(&cbuf);
+ ptls_buffer_dispose(&sbuf);
+ ptls_buffer_dispose(&decryptbuf);
+ free(retry_configs.base);
+}
+
typedef uint8_t traffic_secrets_t[2 /* is_enc */][4 /* epoch */][PTLS_MAX_DIGEST_SIZE /* octets */];
static int on_update_traffic_key(ptls_update_traffic_key_t *self, ptls_t *tls, int is_enc, size_t epoch, const void *secret)
@@ -1545,6 +1721,28 @@
ctx_peer->max_early_data_size = 0;
}
+static void test_all_handshakes_core(void)
+{
+ subtest("full-handshake", test_full_handshake);
+ subtest("full-handshake+client-auth", test_full_handshake_with_client_authentication);
+ subtest("hrr-handshake", test_hrr_handshake);
+ /* resumption does not work when the client offers ECH but the server does not recognize that */
+ if (!(can_ech(ctx, 0) && !can_ech(ctx_peer, 1))) {
+ subtest("resumption", test_resumption);
+ subtest("resumption-different-preferred-key-share", test_resumption_different_preferred_key_share);
+ subtest("resumption-with-client-authentication", test_resumption_with_client_authentication);
+ }
+ subtest("async-sign-certificate", test_async_sign_certificate);
+ subtest("enforce-retry-stateful", test_enforce_retry_stateful);
+ if (!(can_ech(ctx_peer, 1) && can_ech(ctx, 0))) {
+ subtest("hrr-stateless-handshake", test_hrr_stateless_handshake);
+ subtest("enforce-retry-stateless", test_enforce_retry_stateless);
+ subtest("stateless-hrr-aad-change", test_stateless_hrr_aad_change);
+ }
+ subtest("key-update", test_key_update);
+ subtest("handshake-api", test_handshake_api);
+}
+
static void test_all_handshakes(void)
{
ptls_sign_certificate_t server_sc = {sign_certificate};
@@ -1557,24 +1755,27 @@
ctx->sign_certificate = &client_sc;
}
- subtest("full-handshake", test_full_handshake);
- subtest("full-handshake-with-client-authentication", test_full_handshake_with_client_authentication);
- subtest("hrr-handshake", test_hrr_handshake);
- subtest("hrr-stateless-handshake", test_hrr_stateless_handshake);
- subtest("resumption", test_resumption);
- subtest("resumption-different-preferred-key-share", test_resumption_different_preferred_key_share);
- subtest("resumption-with-client-authentication", test_resumption_with_client_authentication);
+ struct {
+ ptls_ech_create_opener_t *create_opener;
+ ptls_hpke_cipher_suite_t **client_ciphers;
+ } orig_ech = {ctx_peer->ech.server.create_opener, ctx->ech.client.ciphers};
- subtest("async-sign-certificate", test_async_sign_certificate);
+ /* first run tests wo. ECH */
+ ctx_peer->ech.server.create_opener = NULL;
+ ctx->ech.client.ciphers = NULL;
+ subtest("no-ech", test_all_handshakes_core);
+ ctx_peer->ech.server.create_opener = orig_ech.create_opener;
+ ctx->ech.client.ciphers = orig_ech.client_ciphers;
- subtest("enforce-retry-stateful", test_enforce_retry_stateful);
- subtest("enforce-retry-stateless", test_enforce_retry_stateless);
-
- subtest("stateless-hrr-aad-change", test_stateless_hrr_aad_change);
-
- subtest("key-update", test_key_update);
-
- subtest("handshake-api", test_handshake_api);
+ if (can_ech(ctx_peer, 1) && can_ech(ctx, 0)) {
+ subtest("ech", test_all_handshakes_core);
+ if (ctx != ctx_peer) {
+ ctx->ech.client.ciphers = NULL;
+ subtest("ech (server-only)", test_all_handshakes_core);
+ ctx->ech.client.ciphers = orig_ech.client_ciphers;
+ }
+ subtest("ech-config-mismatch", test_ech_config_mismatch);
+ }
ctx_peer->sign_certificate = sc_orig;
@@ -1760,7 +1961,8 @@
void test_picotls(void)
{
subtest("is_ipaddr", test_is_ipaddr);
- subtest("select_cypher", test_select_cipher);
+ subtest("extension_bitmap", test_extension_bitmap);
+ subtest("select_cipher", test_select_cipher);
subtest("sha256", test_sha256);
subtest("sha384", test_sha384);
subtest("hmac-sha256", test_hmac_sha256);
@@ -1774,6 +1976,7 @@
subtest("chacha20", test_chacha20);
subtest("ffx", test_ffx);
subtest("base64-decode", test_base64_decode);
+ subtest("ech", test_ech);
subtest("fragmented-message", test_fragmented_message);
subtest("handshake", test_all_handshakes);
subtest("quic", test_quic);
@@ -1781,17 +1984,6 @@
subtest("ptls_escape_json_unsafe_string", test_escape_json_unsafe_string);
}
-void test_picotls_esni(ptls_key_exchange_context_t **keys)
-{
- ptls_esni_context_t esni, *esni_list[] = {&esni, NULL};
- ptls_esni_init_context(ctx_peer, &esni, ptls_iovec_init(ESNIKEYS, sizeof(ESNIKEYS) - 1), keys);
- ctx_peer->esni = esni_list;
-
- subtest("esni-handshake", test_picotls);
-
- ctx_peer->esni = NULL;
-}
-
void test_key_exchange(ptls_key_exchange_algorithm_t *client, ptls_key_exchange_algorithm_t *server)
{
ptls_key_exchange_context_t *ctx;
diff --git a/t/test.h b/t/test.h
index 0d2ecbb..e19f629 100644
--- a/t/test.h
+++ b/t/test.h
@@ -51,18 +51,24 @@
"\x1d\x99\x42\xe0\xa2\xb7\x75\xbb\x14\x03\x79\x9a\xf6\x07\xd8\xa5\xab\x2b\x3a\x70\x8b\x77\x85\x70\x8a\x98\x38\x9b\x35\x09\xf6" \
"\x62\x6b\x29\x4a\xa7\xa7\xf9\x3b\xde\xd8\xc8\x90\x57\xf2\x76\x2a\x23\x0b\x01\x68\xc6\x9a\xf2"
-/* secp256r1 key that lasts until 2028 */
-#define ESNIKEYS \
- "\xff\x02\xcf\x27\xde\x17\x00\x0b\x65\x78\x61\x6d\x70\x6c\x65\x2e\x63\x6f\x6d\x00\x45" \
- "\x00\x17\x00\x41\x04\x3e\xee\xf7\x10\xe3\x75\x07\xa8\xfb\x3e\xfc\x62\x50\x24\x95\xa0" \
- "\x61\x6e\xff\x6b\x63\x0f\xa3\xfd\xcc\x33\x36\xd0\xb1\x2d\x55\xba\xb0\x06\xbd\xb4\x29" \
- "\x82\xc6\xd9\xee\x66\x84\xa9\x63\x94\x44\xbe\x04\xe7\xee\xcf\xab\xc2\xc9\xdd\x40\xe6" \
- "\xc8\x89\x88\xed\x94\x86\x00\x02\x13\x01\x01\x04\x00\x00\x00\x00\x5d\x1c\xc0\x63\x00" \
- "\x00\x4e\x94\xee\x6b\xc0\x62\x00\x00"
-#define ESNI_SECP256R1KEY \
- "-----BEGIN EC PARAMETERS-----\nBggqhkjOPQMBBw==\n-----END EC PARAMETERS-----\n-----BEGIN EC PRIVATE " \
- "KEY-----\nMHcCAQEEIGrRVTfTXuOVewLt/g+Ugvg9XW/g4lGXrkZ8fdYaYuJCoAoGCCqGSM49\nAwEHoUQDQgAEPu73EON1B6j7PvxiUCSVoGFu/" \
- "2tjD6P9zDM20LEtVbqwBr20KYLG\n2e5mhKljlES+BOfuz6vCyd1A5siJiO2Uhg==\n-----END EC PRIVATE KEY-----\n"
+/* test vector using RFC 9180 A.3 */
+#define ECH_CONFIG_LIST \
+ "\x00\x63\xfe\x0d\x00\x5f\x12\x00\x10\x00\x41\x04\xfe\x8c\x19\xce\x09\x05\x19\x1e\xbc\x29\x8a\x92\x45\x79\x25\x31\xf2\x6f\x0c" \
+ "\xec\xe2\x46\x06\x39\xe8\xbc\x39\xcb\x7f\x70\x6a\x82\x6a\x77\x9b\x4c\xf9\x69\xb8\xa0\xe5\x39\xc7\xf6\x2f\xb3\xd3\x0a\xd6\xaa" \
+ "\x8f\x80\xe3\x0f\x1d\x12\x8a\xaf\xd6\x8a\x2c\xe7\x2e\xa0\x00\x08\x00\x02\x00\x02\x00\x01\x00\x01\x40\x0b\x65\x78\x61\x6d\x70" \
+ "\x6c\x65\x2e\x63\x6f\x6d\x00\x00"
+/* another config using different ID and public key */
+#define ECH_ALTERNATIVE_CONFIG_LIST \
+ "\x00\x63\xfe\x0d\x00\x5f\x13\x00\x10\x00\x41\x04\x39\xd2\xc8\xfb\x6f\xcc\x79\x72\xb2\x28\x20\x33\xad\xc4\x97\x01\xff\xd6\x91" \
+ "\x76\xaa\x1a\x11\xd9\x36\x51\xb1\xb1\x29\xd9\x0e\xe0\x96\x1f\x75\xfa\x19\xff\xec\xe2\xd7\x91\xab\xf5\x29\x39\x35\x66\x90\xbf" \
+ "\xf3\x56\x73\xcf\xc1\x42\xc1\x6e\x99\x25\xd2\xab\xdb\xb6\x00\x08\x00\x02\x00\x02\x00\x01\x00\x01\x40\x0b\x65\x78\x61\x6d\x70" \
+ "\x6c\x65\x2e\x63\x6f\x6d\x00\x00"
+#define ECH_PRIVATE_KEY \
+ "-----BEGIN PRIVATE KEY-----\n" \
+ "MIGHAgEAMBMGByqGSM49AgEGCCqGSM49AwEHBG0wawIBAQQg885/2uV+GjENh/Hr\n" \
+ "vebzKL4Kmc28rfTWWJzyneS4/9KhRANCAAT+jBnOCQUZHrwpipJFeSUx8m8M7OJG\n" \
+ "BjnovDnLf3Bqgmp3m0z5abig5TnH9i+z0wrWqo+A4w8dEoqv1oos5y6g\n" \
+ "-----END PRIVATE KEY-----\n"
extern ptls_context_t *ctx, *ctx_peer;
extern ptls_verify_certificate_t *verify_certificate;
@@ -102,7 +108,6 @@
void test_key_exchange(ptls_key_exchange_algorithm_t *client, ptls_key_exchange_algorithm_t *server);
void test_picotls(void);
-void test_picotls_esni(ptls_key_exchange_context_t **keys);
void test_hpke(ptls_hpke_kem_t **all_kems, ptls_hpke_cipher_suite_t **all_ciphers);
diff --git a/t/util.h b/t/util.h
index 6c1a9bc..3c59243 100644
--- a/t/util.h
+++ b/t/util.h
@@ -159,38 +159,6 @@
ctx->verify_certificate = &vc.super;
}
-static inline void setup_esni(ptls_context_t *ctx, const char *esni_fn, ptls_key_exchange_context_t **key_exchanges)
-{
- uint8_t esnikeys[65536];
- size_t esnikeys_len;
- int ret = 0;
-
- { /* read esnikeys */
- FILE *fp;
- if ((fp = fopen(esni_fn, "rb")) == NULL) {
- fprintf(stderr, "failed to open file:%s:%s\n", esni_fn, strerror(errno));
- exit(1);
- }
- esnikeys_len = fread(esnikeys, 1, sizeof(esnikeys), fp);
- if (esnikeys_len == 0 || !feof(fp)) {
- fprintf(stderr, "failed to load ESNI data from file:%s\n", esni_fn);
- exit(1);
- }
- fclose(fp);
- }
-
- if ((ctx->esni = (ptls_esni_context_t **)malloc(sizeof(*ctx->esni) * 2)) == NULL ||
- (*ctx->esni = (ptls_esni_context_t *)malloc(sizeof(**ctx->esni))) == NULL) {
- fprintf(stderr, "no memory\n");
- exit(1);
- }
-
- if ((ret = ptls_esni_init_context(ctx, ctx->esni[0], ptls_iovec_init(esnikeys, esnikeys_len), key_exchanges)) != 0) {
- fprintf(stderr, "failed to parse ESNI data of file:%s:error=%d\n", esni_fn, ret);
- exit(1);
- }
-}
-
struct st_util_log_event_t {
ptls_log_event_t super;
FILE *fp;
@@ -327,44 +295,6 @@
return 1;
}
-static inline ptls_iovec_t resolve_esni_keys(const char *server_name)
-{
- char esni_name[256], *base64;
- uint8_t answer[1024];
- ns_msg msg;
- ns_rr rr;
- ptls_buffer_t decode_buf;
- ptls_base64_decode_state_t ds;
- int answer_len;
-
- char *buf = "";
- ptls_buffer_init(&decode_buf, buf, 0);
-
- if (snprintf(esni_name, sizeof(esni_name), "_esni.%s", server_name) > sizeof(esni_name) - 1)
- goto Error;
- if ((answer_len = res_query(esni_name, ns_c_in, ns_t_txt, answer, sizeof(answer))) <= 0)
- goto Error;
- if (ns_initparse(answer, answer_len, &msg) != 0)
- goto Error;
- if (ns_msg_count(msg, ns_s_an) < 1)
- goto Error;
- if (ns_parserr(&msg, ns_s_an, 0, &rr) != 0)
- goto Error;
- base64 = (char *)ns_rr_rdata(rr);
- if (!normalize_txt((uint8_t *)base64, ns_rr_rdlen(rr)))
- goto Error;
-
- ptls_base64_decode_init(&ds);
- if (ptls_base64_decode(base64, &ds, &decode_buf) != 0)
- goto Error;
- assert(decode_buf.is_allocated);
-
- return ptls_iovec_init(decode_buf.base, decode_buf.off);
-Error:
- ptls_buffer_dispose(&decode_buf);
- return ptls_iovec_init(NULL, 0);
-}
-
/* The ptls_repeat_while_eintr macro will repeat a function call (block) if it is interrupted (EINTR) before completion. If failing
* for other reason, the macro executes the exit block, such as either { break; } or { goto Fail; }.
*/