reflect the fact that the supported set of HPKE cipher-suites can be different between ECHConfigs
diff --git a/include/picotls.h b/include/picotls.h
index 641fd2d..ecd4620 100644
--- a/include/picotls.h
+++ b/include/picotls.h
@@ -728,8 +728,8 @@
* 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(ptls_aead_context_t *, ech_create_opener, ptls_hpke_kem_t **kem, ptls_t *tls, uint8_t config_id,
- ptls_hpke_cipher_suite_t *cipher, ptls_iovec_t enc, ptls_iovec_t info_prefix);
+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
@@ -762,22 +762,26 @@
* ECH
*/
struct {
- /**
- * list of HPKE symmetric cipher-suites (set to NULL to disable ECH altogether)
- */
- ptls_hpke_cipher_suite_t **ciphers;
- /**
- * client-only: KEMs being supported
- */
- ptls_hpke_kem_t **kems;
- /**
- * server-only: 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;
+ 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;
/**
*
diff --git a/lib/picotls.c b/lib/picotls.c
index ad0c40f..9a0d983 100644
--- a/lib/picotls.c
+++ b/lib/picotls.c
@@ -374,7 +374,7 @@
struct {
uint8_t type;
uint8_t config_id;
- ptls_hpke_cipher_suite_t *cipher;
+ ptls_hpke_cipher_suite_id_t cipher_suite;
ptls_iovec_t enc;
ptls_iovec_t payload;
} ech;
@@ -1091,7 +1091,7 @@
return ret;
}
-static int decode_ech_config_list(ptls_context_t *ctx, struct st_decoded_ech_config_t *decoded, ptls_iovec_t config_list)
+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;
@@ -1108,7 +1108,7 @@
/* 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.kems, ctx->ech.ciphers, &thisconf, &src, end)) != 0)
+ 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;
@@ -2265,10 +2265,10 @@
if (properties != NULL) {
/* try to use ECH (ignore broken ECHConfigList; it is delivered insecurely) */
- if (!is_second_flight && sni_name != NULL && tls->ctx->ech.ciphers != NULL && tls->ctx->ech.kems != NULL &&
+ 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;
- decode_ech_config_list(tls->ctx, &decoded, properties->client.ech.configs);
+ 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;
@@ -2843,7 +2843,7 @@
}
/* parse retry_config, and if it is applicable, provide that to the application */
struct st_decoded_ech_config_t decoded;
- if ((ret = decode_ech_config_list(tls->ctx, &decoded, ptls_iovec_init(src, end - src))) != 0)
+ 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) {
@@ -3686,19 +3686,10 @@
if ((ret = ptls_decode8(&ch->ech.type, &src, end)) != 0)
goto Exit;
switch (ch->ech.type) {
- case PTLS_ECH_CLIENT_HELLO_TYPE_OUTER: {
- ptls_hpke_cipher_suite_id_t cipher_id;
- if ((ret = ptls_decode16(&cipher_id.kdf, &src, end)) != 0 || (ret = ptls_decode16(&cipher_id.aead, &src, end)) != 0)
+ 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;
- /* find corresponding cipher-suite; if not found, the field is left NULL */
- if (ctx->ech.ciphers != NULL) {
- for (size_t i = 0; ctx->ech.ciphers[i] != NULL; ++i) {
- if (ctx->ech.ciphers[i]->id.kdf == cipher_id.kdf && ctx->ech.ciphers[i]->id.aead == cipher_id.aead) {
- ch->ech.cipher = ctx->ech.ciphers[i];
- break;
- }
- }
- }
if ((ret = ptls_decode8(&ch->ech.config_id, &src, end)) != 0)
goto Exit;
ptls_decode_open_block(src, end, 2, {
@@ -3713,7 +3704,7 @@
ch->ech.payload = ptls_iovec_init(src, end - src);
src = end;
});
- } break;
+ break;
case PTLS_ECH_CLIENT_HELLO_TYPE_INNER:
if (src != end) {
ret = PTLS_ALERT_DECODE_ERROR;
@@ -4187,7 +4178,8 @@
ret = PTLS_ALERT_MISSING_EXTENSION;
goto Exit;
}
- if (!(ch->ech.config_id == tls->ech.config_id && ch->ech.cipher == tls->ech.cipher && ch->ech.enc.len == 0)) {
+ 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;
}
@@ -4203,14 +4195,11 @@
if (!is_second_flight)
tls->ech.offered = 1;
/* obtain AEAD context for opening inner CH */
- if (!is_second_flight && ch->ech.cipher != NULL && ch->ech.payload.len > ch->ech.cipher->aead->tag_size &&
- tls->ctx->ech.create_opener != NULL) {
- if ((tls->ech.aead = tls->ctx->ech.create_opener->cb(
- tls->ctx->ech.create_opener, &tls->ech.kem, tls, ch->ech.config_id, ch->ech.cipher, ch->ech.enc,
- ptls_iovec_init(ech_info_prefix, sizeof(ech_info_prefix)))) != NULL) {
+ 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;
- tls->ech.cipher = ch->ech.cipher;
- }
}
if (tls->ech.aead != NULL) {
/* now that AEAD context is available, create AAD and decrypt inner CH */
@@ -4591,10 +4580,10 @@
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.ciphers != NULL &&
- tls->ctx->ech.create_opener != NULL && tls->ctx->ech.retry_configs.len != 0)
+ 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.retry_configs.base, tls->ctx->ech.retry_configs.len);
+ 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;
diff --git a/t/cli.c b/t/cli.c
index 9ae3cd9..c696bda 100644
--- a/t/cli.c
+++ b/t/cli.c
@@ -78,38 +78,57 @@
return NULL;
}
-static ptls_aead_context_t *create_ech_opener(ptls_ech_create_opener_t *self, ptls_hpke_kem_t **kem, ptls_t *tls, uint8_t config_id,
- ptls_hpke_cipher_suite_t *cipher, ptls_iovec_t enc, ptls_iovec_t info_prefix)
+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;
+ 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;
- ptls_decode_open_block(src, end, 2, {
- if (src == end)
- 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;
+ do {
+ ptls_decode_open_block(src, end, 2, {
+ if (src == end) {
+ ret = PTLS_ALERT_DECODE_ERROR;
+ goto Exit;
}
- 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;
- });
+ 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:
@@ -490,7 +509,7 @@
.get_time = &ptls_get_time,
.key_exchanges = key_exchanges,
.cipher_suites = cipher_suites,
- .ech = {ptls_openssl_hpke_cipher_suites, ptls_openssl_hpke_kems, NULL /* activated by -K option */},
+ .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;
@@ -591,7 +610,7 @@
++ech.keyex.count;
EVP_PKEY_free(pkey);
fclose(fp);
- ctx.ech.create_opener = &ech_opener;
+ ctx.ech.server.create_opener = &ech_opener;
} break;
case 'l':
setup_log_event(&ctx, optarg);
diff --git a/t/openssl.c b/t/openssl.c
index 73ae997..8e79b15 100644
--- a/t/openssl.c
+++ b/t/openssl.c
@@ -335,8 +335,9 @@
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_t *tls, uint8_t config_id,
- ptls_hpke_cipher_suite_t *cipher, ptls_iovec_t enc, ptls_iovec_t info_prefix)
+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) {
@@ -344,6 +345,17 @@
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;
@@ -352,7 +364,7 @@
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,
+ ret = ptls_hpke_setup_base_r(&ptls_openssl_hpke_kem_p256sha256, *cipher, pem, &aead, enc,
ptls_iovec_init(infobuf.base, infobuf.off));
Exit:
@@ -544,10 +556,9 @@
.cipher_suites = ptls_openssl_cipher_suites,
.tls12_cipher_suites = ptls_openssl_tls12_cipher_suites,
.certificates = {&cert, 1},
- .ech = {.ciphers = ptls_openssl_hpke_cipher_suites,
- .kems = ptls_openssl_hpke_kems,
- .create_opener = &ech_create_opener,
- .retry_configs = {(uint8_t *)ECH_CONFIG_LIST, sizeof(ECH_CONFIG_LIST) - 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;
diff --git a/t/picotls.c b/t/picotls.c
index 96fd3f9..58d9b81 100644
--- a/t/picotls.c
+++ b/t/picotls.c
@@ -733,11 +733,11 @@
static int can_ech(ptls_context_t *ctx, int is_server)
{
- if (ctx->ech.ciphers == NULL)
- return 0;
- if (is_server && ctx->ech.create_opener == NULL)
- return 0;
- return 1;
+ 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)
@@ -1747,22 +1747,23 @@
}
struct {
- ptls_hpke_cipher_suite_t **server, **client;
- } orig_ech_ciphers = {ctx_peer->ech.ciphers, ctx->ech.ciphers};
+ 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};
/* first run tests wo. ECH */
- ctx_peer->ech.ciphers = NULL;
- ctx->ech.ciphers = NULL;
+ ctx_peer->ech.server.create_opener = NULL;
+ ctx->ech.client.ciphers = NULL;
subtest("no-ech", test_all_handshakes_core);
- ctx_peer->ech.ciphers = orig_ech_ciphers.server;
- ctx->ech.ciphers = orig_ech_ciphers.client;
+ ctx_peer->ech.server.create_opener = orig_ech.create_opener;
+ ctx->ech.client.ciphers = orig_ech.client_ciphers;
if (can_ech(ctx_peer, 1) && can_ech(ctx, 0)) {
subtest("ech", test_all_handshakes_core);
if (ctx != ctx_peer) {
- ctx->ech.ciphers = NULL;
+ ctx->ech.client.ciphers = NULL;
subtest("ech (server-only)", test_all_handshakes_core);
- ctx->ech.ciphers = orig_ech_ciphers.client;
+ ctx->ech.client.ciphers = orig_ech.client_ciphers;
}
subtest("ech-config-mismatch", test_ech_config_mismatch);
}