From 9592b5bb4b23efeb084eb5c60e013dfd63819331 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Tue, 4 Aug 2020 09:10:38 +0900 Subject: [PATCH 01/61] zero-length identity is not permitted --- lib/picotls.c | 4 ++++ 1 file changed, 4 insertions(+) diff --git a/lib/picotls.c b/lib/picotls.c index 14ea227da..02c4673df 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -3705,6 +3705,10 @@ static int decode_client_hello(ptls_context_t *ctx, struct st_ptls_client_hello_ do { struct st_ptls_client_hello_psk_t psk = {{NULL}}; ptls_decode_open_block(src, end, 2, { + if (end - src < 1) { + ret = PTLS_ALERT_DECODE_ERROR; + goto Exit; + } psk.identity = ptls_iovec_init(src, end - src); src = end; }); From 1c385833fc6dd96b13783a46508aa573bfe049f7 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Tue, 4 Aug 2020 09:21:20 +0900 Subject: [PATCH 02/61] it works --- include/picotls.h | 7 ++++ lib/picotls.c | 76 ++++++++++++++++++++++++++------------ t/picotls.c | 93 +++++++++++++++++++++++++++++++++++++++++++++++ 3 files changed, 153 insertions(+), 23 deletions(-) diff --git a/include/picotls.h b/include/picotls.h index 0ad6f872c..74cfc1d32 100644 --- a/include/picotls.h +++ b/include/picotls.h @@ -1016,6 +1016,13 @@ typedef struct st_ptls_handshake_properties_t { * an optional callback that reports the extensions being collected */ int (*collected_extensions)(ptls_t *tls, struct st_ptls_handshake_properties_t *properties, ptls_raw_extension_t *extensions); + /** + * [optional] if set, the endpoints negotiate using the provided pre-shared key only + */ + struct { + ptls_iovec_t identity; + ptls_iovec_t key; + } pre_shared_key; } ptls_handshake_properties_t; #ifdef _WINDOWS #pragma warning(pop) diff --git a/lib/picotls.c b/lib/picotls.c index 02c4673df..ffb92d860 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -2223,13 +2223,15 @@ static int encode_client_hello(ptls_context_t *ctx, ptls_buffer_t *sendbuf, enum 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 (tls->ctx->key_exchanges != NULL) { + 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); }); @@ -2312,7 +2314,7 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ if (properties != NULL) { /* try to use ECH (ignore broken ECHConfigList; it is delivered insecurely) */ - if (!is_second_flight && sni_name != NULL && tls->ctx->ech.client.ciphers != NULL) { + if (!is_second_flight && sni_name != NULL && tls->ctx->ech.client.ciphers != NULL && tls->ctx->key_exchanges != NULL) { if (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); @@ -2327,7 +2329,7 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ } } /* setup resumption-related data. If successful, resumption_secret becomes a non-zero value. */ - if (properties->client.session_ticket.base != NULL) { + if (properties->client.session_ticket.base != NULL && tls->ctx->key_exchanges != NULL) { ptls_key_exchange_algorithm_t *key_share = NULL; ptls_cipher_suite_t *cipher_suite = NULL; uint32_t max_early_data_size; @@ -2346,6 +2348,15 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ } else { resumption_secret = ptls_iovec_init(NULL, 0); } + } else if (properties->pre_shared_key.identity.base != NULL) { + tls->client.offered_psk = 1; + tls->cipher_suite = tls->ctx->cipher_suites[0]; + resumption_secret = properties->pre_shared_key.key; + resumption_ticket = properties->pre_shared_key.identity; + if (!is_second_flight && properties->client.max_early_data_size != NULL) { + tls->client.using_early_data = 1; + *properties->client.max_early_data_size = SIZE_MAX; + } } if (tls->client.using_early_data) { properties->client.early_data_acceptance = PTLS_EARLY_DATA_ACCEPTANCE_UNKNOWN; @@ -2357,7 +2368,8 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ } /* use the default key share if still not undetermined */ - if (tls->key_share == NULL && !(properties != NULL && properties->client.negotiate_before_key_exchange)) + if (tls->key_share == NULL && tls->ctx->key_exchanges != NULL && + !(properties != NULL && properties->client.negotiate_before_key_exchange)) tls->key_share = tls->ctx->key_exchanges[0]; /* instantiate key share context */ @@ -2567,6 +2579,10 @@ static int decode_server_hello(ptls_t *tls, struct st_ptls_server_hello_t *sh, c goto Exit; break; case PTLS_EXTENSION_TYPE_KEY_SHARE: + if (tls->ctx->key_exchanges == NULL) { + ret = PTLS_ALERT_ILLEGAL_PARAMETER; + goto Exit; + } if (sh->is_retry_request) { if ((ret = ptls_decode16(&sh->retry_request.selected_group, &src, end)) != 0) goto Exit; @@ -4003,7 +4019,7 @@ static int vec_is_string(ptls_iovec_t x, const char *y) } static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_data, struct st_ptls_client_hello_t *ch, - ptls_iovec_t ch_trunc) + ptls_iovec_t ch_trunc, ptls_handshake_properties_t *properties) { ptls_buffer_t decbuf; ptls_iovec_t ticket_psk, ticket_server_name, ticket_negotiated_protocol; @@ -4017,7 +4033,19 @@ static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_d for (*psk_index = 0; *psk_index < ch->psk.identities.count; ++*psk_index) { struct st_ptls_client_hello_psk_t *identity = ch->psk.identities.list + *psk_index; - /* decrypt and decode */ + /* negotiate using fixed pre-shared key */ + if (properties != NULL && properties->pre_shared_key.identity.base != NULL) { + if (identity->identity.len == properties->pre_shared_key.identity.len && + memcmp(identity->identity.base, properties->pre_shared_key.identity.base, identity->identity.len) == 0) { + *accept_early_data = 1; + tls->key_share = NULL; + ticket_psk = properties->pre_shared_key.key; + goto Found; + } + } + /* decrypt ticket and decode */ + if (tls->ctx->encrypt_ticket == NULL || tls->ctx->key_exchanges == NULL) + continue; int can_accept_early_data = 1; decbuf.off = 0; switch (tls->ctx->encrypt_ticket->cb(tls->ctx->encrypt_ticket, tls, 0, &decbuf, identity->identity)) { @@ -4140,7 +4168,7 @@ static int calc_cookie_signature(ptls_t *tls, ptls_handshake_properties_t *prope UPDATE_BLOCK(tls->client_random, sizeof(tls->client_random)); UPDATE_BLOCK(tls->server_name, tls->server_name != NULL ? strlen(tls->server_name) : 0); UPDATE16(tls->cipher_suite->id); - UPDATE16(negotiated_group->id); + UPDATE16(negotiated_group != NULL ? negotiated_group->id : 0); UPDATE_BLOCK(properties->server.cookie.additional_data.base, properties->server.cookie.additional_data.len); UPDATE_BLOCK(tbs.base, tbs.len); @@ -4376,7 +4404,7 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl } /* select key_share */ - if (key_share.algorithm == NULL && ch->key_shares.base != NULL) { + if (key_share.algorithm == NULL && ch->key_shares.base != NULL && tls->ctx->key_exchanges != NULL) { const uint8_t *src = ch->key_shares.base, *const end = src + ch->key_shares.len; ptls_decode_block(src, end, 2, { if ((ret = select_key_share(&key_share.algorithm, &key_share.peer_key, tls->ctx->key_exchanges, &src, end, 0)) != 0) @@ -4416,14 +4444,16 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl } else if (key_share.algorithm == NULL || (properties != NULL && properties->server.enforce_retry)) { /* send HelloRetryRequest */ - if (ch->negotiated_groups.base == NULL) { - ret = PTLS_ALERT_MISSING_EXTENSION; - goto Exit; + ptls_key_exchange_algorithm_t *negotiated_group = NULL; + if (tls->ctx->key_exchanges != NULL) { + if (ch->negotiated_groups.base == NULL) { + ret = PTLS_ALERT_MISSING_EXTENSION; + goto Exit; + } + 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_exchange_algorithm_t *negotiated_group; - 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, 0); assert(tls->key_schedule->generation == 0); @@ -4512,9 +4542,10 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl /* try psk handshake */ if (!is_second_flight && ch->psk.hash_end != 0 && (ch->psk.ke_modes & ((1u << PTLS_PSK_KE_MODE_PSK) | (1u << PTLS_PSK_KE_MODE_PSK_DHE))) != 0 && - tls->ctx->encrypt_ticket != NULL && !tls->ctx->require_client_authentication) { + (tls->ctx->encrypt_ticket != NULL || properties->pre_shared_key.identity.base != NULL) && + !tls->ctx->require_client_authentication) { if ((ret = try_psk_handshake(tls, &psk_index, &accept_early_data, ch, - ptls_iovec_init(message.base, ch->psk.hash_end - message.base))) != 0) { + ptls_iovec_init(message.base, ch->psk.hash_end - message.base), properties)) != 0) { goto Exit; } } @@ -5770,7 +5801,6 @@ int ptls_handshake(ptls_t *tls, ptls_buffer_t *_sendbuf, const void *input, size switch (tls->state) { case PTLS_STATE_CLIENT_HANDSHAKE_START: { assert(input == NULL || *inlen == 0); - assert(tls->ctx->key_exchanges[0] != NULL); return send_client_hello(tls, &emitter.super, properties, NULL); } case PTLS_STATE_SERVER_GENERATING_CERTIFICATE_VERIFY: diff --git a/t/picotls.c b/t/picotls.c index 6116de02b..0288c183b 100644 --- a/t/picotls.c +++ b/t/picotls.c @@ -1530,6 +1530,98 @@ static void test_ech_config_mismatch(void) free(retry_configs.base); } +static void test_pre_shared_key(void) +{ + uint32_t max_early_data_size_backup = ctx->max_early_data_size; + ctx->max_early_data_size = 16384; + + ptls_t *client = ptls_new(ctx, 0), *server = ptls_new(ctx, 1); + ptls_buffer_t cbuf, sbuf, decbuf; + ptls_buffer_init(&cbuf, "", 0); + ptls_buffer_init(&sbuf, "", 0); + ptls_buffer_init(&decbuf, "", 0); + + ptls_handshake_properties_t client_prop = {}; + size_t client_max_early_data_size = 0; + client_prop.pre_shared_key.identity = ptls_iovec_init("", 1); + client_prop.pre_shared_key.key = ptls_iovec_init("hello world", 11); + client_prop.client.max_early_data_size = &client_max_early_data_size; + + ptls_handshake_properties_t server_prop = {.pre_shared_key = client_prop.pre_shared_key}; + + /* [client] send CH and early data */ + int ret = ptls_handshake(client, &cbuf, NULL, NULL, &client_prop); + ok(ret == PTLS_ERROR_IN_PROGRESS); + ok(client_prop.client.early_data_acceptance == PTLS_EARLY_DATA_ACCEPTANCE_UNKNOWN); + ok(client_max_early_data_size == SIZE_MAX); + ret = ptls_send(client, &cbuf, "hello", 5); + ok(ret == 0); + + /* [server] read CH and generate up to ServerFinished */ + size_t consumed = cbuf.off; + ret = ptls_handshake(server, &sbuf, cbuf.base, &consumed, &server_prop); + ok(ret == 0); + ok(consumed < cbuf.off); + memmove(cbuf.base, cbuf.base + consumed, cbuf.off - consumed); + cbuf.off -= consumed; + + /* [server] read early data */ + consumed = cbuf.off; + ret = ptls_receive(server, &decbuf, cbuf.base, &consumed); + ok(ret == 0); + ok(consumed == cbuf.off); + cbuf.off = 0; + ok(decbuf.off == 5); + ok(memcmp(decbuf.base, "hello", 5) == 0); + decbuf.off = 0; + + /* [server] write 0.5-RTT data */ + ret = ptls_send(server, &sbuf, "hi", 2); + ok(ret == 0); + + /* [client] read up to ServerFinished */ + consumed = sbuf.off; + ret = ptls_handshake(client, &cbuf, sbuf.base, &consumed, &client_prop); + ok(ret == 0); + ok(client_prop.client.early_data_acceptance == PTLS_EARLY_DATA_ACCEPTED); + ok(consumed < sbuf.off); + memmove(sbuf.base, sbuf.base + consumed, sbuf.off - consumed); + sbuf.off -= consumed; + + /* [client] read 0.5-RTT data */ + consumed = sbuf.off; + ret = ptls_receive(client, &decbuf, sbuf.base, &consumed); + ok(ret == 0); + ok(consumed == sbuf.off); + sbuf.off = 0; + ok(decbuf.off == 2); + ok(memcmp(decbuf.base, "hi", 2) == 0); + decbuf.off = 0; + + /* [client] write 1-RTT data */ + ret = ptls_send(client, &cbuf, "bye", 3); + ok(ret == 0); + + /* [server] read ClientFinished and 1-RTT data */ + ok(!ptls_handshake_is_complete(server)); + consumed = cbuf.off; + ret = ptls_receive(server, &decbuf, cbuf.base, &consumed); + ok(ret == 0); + ok(ptls_handshake_is_complete(server)); + ok(consumed == cbuf.off); + cbuf.off = 0; + ok(decbuf.off == 3); + ok(memcmp(decbuf.base, "bye", 3) == 0); + + ptls_buffer_dispose(&cbuf); + ptls_buffer_dispose(&sbuf); + ptls_buffer_dispose(&decbuf); + ptls_free(client); + ptls_free(server); + + ctx->max_early_data_size = max_early_data_size_backup; +} + 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) @@ -1834,6 +1926,7 @@ static void test_all_handshakes_core(void) subtest("stateless-hrr-aad-change", test_stateless_hrr_aad_change); } subtest("key-update", test_key_update); + subtest("pre-shared-key", test_pre_shared_key); subtest("handshake-api", test_handshake_api); } From c2ddeeec5f612f5345f67e228eb3454ef60bd1b7 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Tue, 4 Aug 2020 16:40:11 +0900 Subject: [PATCH 03/61] but not if `ptls_context_t::key_exchanges` is set to NULL --- t/picotls.c | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/t/picotls.c b/t/picotls.c index 0288c183b..456f0c2f7 100644 --- a/t/picotls.c +++ b/t/picotls.c @@ -1530,9 +1530,14 @@ static void test_ech_config_mismatch(void) free(retry_configs.base); } -static void test_pre_shared_key(void) +static void do_test_pre_shared_key(int clear_ke) { - uint32_t max_early_data_size_backup = ctx->max_early_data_size; + struct { + ptls_key_exchange_algorithm_t **key_exchanges; + uint32_t max_early_data_size; + } backup = {ctx->key_exchanges, ctx->max_early_data_size}; + if (clear_ke) + ctx->key_exchanges = NULL; ctx->max_early_data_size = 16384; ptls_t *client = ptls_new(ctx, 0), *server = ptls_new(ctx, 1); @@ -1619,7 +1624,14 @@ static void test_pre_shared_key(void) ptls_free(client); ptls_free(server); - ctx->max_early_data_size = max_early_data_size_backup; + ctx->key_exchanges = backup.key_exchanges; + ctx->max_early_data_size = backup.max_early_data_size; +} + +static void test_pre_shared_key(void) +{ + do_test_pre_shared_key(0); + do_test_pre_shared_key(1); } typedef uint8_t traffic_secrets_t[2 /* is_enc */][4 /* epoch */][PTLS_MAX_DIGEST_SIZE /* octets */]; From db5af12c286df46bf1f94a400b48d9b4017d7b2f Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Tue, 4 Aug 2020 17:08:41 +0900 Subject: [PATCH 04/61] Never send a Retry if handshake using external PSK is possible. Design of picotls is based on the assumption that PSK-based handshake will not be used together with HRR. --- include/picotls.h | 2 +- lib/picotls.c | 260 +++++++++++++++++++++++++--------------------- 2 files changed, 145 insertions(+), 117 deletions(-) diff --git a/include/picotls.h b/include/picotls.h index 74cfc1d32..5d4cb7bfd 100644 --- a/include/picotls.h +++ b/include/picotls.h @@ -1019,7 +1019,7 @@ typedef struct st_ptls_handshake_properties_t { /** * [optional] if set, the endpoints negotiate using the provided pre-shared key only */ - struct { + struct st_ptls_external_psk_t { ptls_iovec_t identity; ptls_iovec_t key; } pre_shared_key; diff --git a/lib/picotls.c b/lib/picotls.c index ffb92d860..a9d8cb1fe 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -4018,8 +4018,12 @@ 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'; } +/** + * Looks for a PSK identity that can be used, and if found, updates the handshake state and returns the necessary variables. If + * external_psk is set, only tries handshake using those keys provided. Otherwise, tries resumption. + */ static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_data, struct st_ptls_client_hello_t *ch, - ptls_iovec_t ch_trunc, ptls_handshake_properties_t *properties) + ptls_iovec_t ch_trunc, struct st_ptls_external_psk_t *external_psk) { ptls_buffer_t decbuf; ptls_iovec_t ticket_psk, ticket_server_name, ticket_negotiated_protocol; @@ -4034,14 +4038,15 @@ static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_d for (*psk_index = 0; *psk_index < ch->psk.identities.count; ++*psk_index) { struct st_ptls_client_hello_psk_t *identity = ch->psk.identities.list + *psk_index; /* negotiate using fixed pre-shared key */ - if (properties != NULL && properties->pre_shared_key.identity.base != NULL) { - if (identity->identity.len == properties->pre_shared_key.identity.len && - memcmp(identity->identity.base, properties->pre_shared_key.identity.base, identity->identity.len) == 0) { + if (external_psk != NULL) { + if (identity->identity.len == external_psk->identity.len && + memcmp(identity->identity.base, external_psk->identity.base, identity->identity.len) == 0) { *accept_early_data = 1; tls->key_share = NULL; - ticket_psk = properties->pre_shared_key.key; + ticket_psk = external_psk->key; goto Found; } + continue; } /* decrypt ticket and decode */ if (tls->ctx->encrypt_ticket == NULL || tls->ctx->key_exchanges == NULL) @@ -4412,127 +4417,151 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl }); } - if (!is_second_flight) { - if (ch->cookie.all.len != 0 && key_share.algorithm != NULL) { + /* use cookie to check the integrity of the handshake, and update the context */ + if (!is_second_flight && ch->cookie.all.len != 0 && key_share.algorithm != NULL) { + { + uint8_t sig[PTLS_MAX_DIGEST_SIZE]; + size_t sigsize = tls->ctx->cipher_suites[0]->hash->digest_size; + if ((ret = calc_cookie_signature(tls, properties, key_share.algorithm, ch->cookie.tbs, sig)) != 0) + goto Exit; + if (!(ch->cookie.signature.len == sigsize && ptls_mem_equal(ch->cookie.signature.base, sig, sigsize))) { + ret = PTLS_ALERT_HANDSHAKE_FAILURE; + goto Exit; + } + } + /* 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, 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); + }); + }, + {}); + emitter->buf->off = hrr_start; + is_second_flight = 1; + } + + /* try external psk handshake */ + if (!is_second_flight && ch->psk.hash_end != 0 && + (ch->psk.ke_modes & ((1u << PTLS_PSK_KE_MODE_PSK) | (1u << PTLS_PSK_KE_MODE_PSK_DHE))) != 0 && properties != NULL && + properties->pre_shared_key.identity.base != NULL && !tls->ctx->require_client_authentication) { + if ((ret = try_psk_handshake(tls, &psk_index, &accept_early_data, ch, + ptls_iovec_init(message.base, ch->psk.hash_end - message.base), + &properties->pre_shared_key)) != 0) + goto Exit; + } - { /* use cookie to check the integrity of the handshake, and update the context */ - uint8_t sig[PTLS_MAX_DIGEST_SIZE]; - size_t sigsize = tls->ctx->cipher_suites[0]->hash->digest_size; - if ((ret = calc_cookie_signature(tls, properties, key_share.algorithm, ch->cookie.tbs, sig)) != 0) - goto Exit; - if (!(ch->cookie.signature.len == sigsize && ptls_mem_equal(ch->cookie.signature.base, sig, sigsize))) { - ret = PTLS_ALERT_HANDSHAKE_FAILURE; - goto Exit; - } + /* send HelloRetryRequest if enforced by config or upon key-share mismatch, unless PSK has already been selected */ + if (!is_second_flight && psk_index == SIZE_MAX && + (key_share.algorithm == NULL || (properties != NULL && properties->server.enforce_retry))) { + ptls_key_exchange_algorithm_t *negotiated_group = NULL; + if (tls->ctx->key_exchanges != NULL) { + if (ch->negotiated_groups.base == NULL) { + ret = PTLS_ALERT_MISSING_EXTENSION; + goto Exit; } - /* 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, 0); + 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, 0); + assert(tls->key_schedule->generation == 0); + + /* 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, NULL); + if (!retry_uses_cookie) { + key_schedule_transform_post_ch1hash(tls->key_schedule); 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); - }); - }, - {}); - emitter->buf->off = hrr_start; - is_second_flight = 1; - - } else if (key_share.algorithm == NULL || (properties != NULL && properties->server.enforce_retry)) { - - /* send HelloRetryRequest */ - ptls_key_exchange_algorithm_t *negotiated_group = NULL; - if (tls->ctx->key_exchanges != NULL) { - if (ch->negotiated_groups.base == NULL) { - ret = PTLS_ALERT_MISSING_EXTENSION; - goto Exit; + } + 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; + if (ptls_is_ech_handshake(tls, NULL, 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 ((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, 0); - assert(tls->key_schedule->generation == 0); - - /* 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, 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; - if (ptls_is_ech_handshake(tls, NULL, 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, { + if (retry_uses_cookie) { + 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; ptls_buffer_push_block(sendbuf, 2, { - /* 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 */ + /* first block of the cookie data is the hash(ch1) */ ptls_buffer_push_block(sendbuf, 1, { - size_t sz = tls->ctx->cipher_suites[0]->hash->digest_size; + size_t sz = tls->cipher_suite->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; + 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->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) + /* 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; - ret = PTLS_ERROR_STATELESS_RETRY; - } else { - tls->state = PTLS_STATE_SERVER_EXPECT_SECOND_CLIENT_HELLO; - if (ch->psk.early_data_indication) - tls->server.early_data_skipped_bytes = 0; - ret = PTLS_ERROR_IN_PROGRESS; - } - goto Exit; + }); + if (retry_uses_cookie) { + if ((ret = push_change_cipher_spec(tls, emitter)) != 0) + goto Exit; + ret = PTLS_ERROR_STATELESS_RETRY; + } else { + tls->state = PTLS_STATE_SERVER_EXPECT_SECOND_CLIENT_HELLO; + if (ch->psk.early_data_indication) + tls->server.early_data_skipped_bytes = 0; + ret = PTLS_ERROR_IN_PROGRESS; } + goto Exit; } /* handle unknown extensions */ @@ -4540,12 +4569,11 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl goto Exit; /* try psk handshake */ - if (!is_second_flight && ch->psk.hash_end != 0 && + if (!is_second_flight && psk_index == SIZE_MAX && ch->psk.hash_end != 0 && (ch->psk.ke_modes & ((1u << PTLS_PSK_KE_MODE_PSK) | (1u << PTLS_PSK_KE_MODE_PSK_DHE))) != 0 && - (tls->ctx->encrypt_ticket != NULL || properties->pre_shared_key.identity.base != NULL) && - !tls->ctx->require_client_authentication) { + tls->ctx->encrypt_ticket != NULL && !tls->ctx->require_client_authentication) { if ((ret = try_psk_handshake(tls, &psk_index, &accept_early_data, ch, - ptls_iovec_init(message.base, ch->psk.hash_end - message.base), properties)) != 0) { + ptls_iovec_init(message.base, ch->psk.hash_end - message.base), NULL)) != 0) { goto Exit; } } From 4031113b67d6f3ac76b3864e1269315e1a6d5268 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Tue, 4 Aug 2020 17:43:24 +0900 Subject: [PATCH 05/61] remove "only"; client can offer (ec)dhe + external_psk. server can accept (ec)dhe + external_psk + resumption --- include/picotls.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/picotls.h b/include/picotls.h index 5d4cb7bfd..e23107669 100644 --- a/include/picotls.h +++ b/include/picotls.h @@ -1017,7 +1017,7 @@ typedef struct st_ptls_handshake_properties_t { */ int (*collected_extensions)(ptls_t *tls, struct st_ptls_handshake_properties_t *properties, ptls_raw_extension_t *extensions); /** - * [optional] if set, the endpoints negotiate using the provided pre-shared key only + * [optional] if set, the endpoints negotiate using the provided pre-shared key */ struct st_ptls_external_psk_t { ptls_iovec_t identity; From 75f7dfaf8c615483d581a2c4eb847a6ec222b7e6 Mon Sep 17 00:00:00 2001 From: Phillip Hellewell Date: Tue, 18 Jul 2023 11:48:01 -0600 Subject: [PATCH 06/61] Fix mistake during rebase --- lib/picotls.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/picotls.c b/lib/picotls.c index a9d8cb1fe..8f2aaa25b 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -2223,7 +2223,7 @@ static int encode_client_hello(ptls_context_t *ctx, ptls_buffer_t *sendbuf, enum if ((ret = push_signature_algorithms(ctx->verify_certificate, sendbuf)) != 0) goto Exit; }); - if (tls->ctx->key_exchanges != NULL) { + if (ctx->key_exchanges != NULL) { buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_SUPPORTED_GROUPS, { ptls_key_exchange_algorithm_t **algo = ctx->key_exchanges; ptls_buffer_push_block(sendbuf, 2, { From d0451d34d68b19341e4ed93607dbf324510b1e3d Mon Sep 17 00:00:00 2001 From: Phillip Hellewell Date: Tue, 18 Jul 2023 11:57:58 -0600 Subject: [PATCH 07/61] Fix mistake during rebase --- lib/picotls.c | 15 --------------- 1 file changed, 15 deletions(-) diff --git a/lib/picotls.c b/lib/picotls.c index 8f2aaa25b..3214ed3c6 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -4525,21 +4525,6 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl 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; }); } }, From 92d9832523c4a32313defd9ca8b9b80fa5456ebb Mon Sep 17 00:00:00 2001 From: Phillip Hellewell Date: Tue, 25 Jul 2023 10:14:00 -0600 Subject: [PATCH 08/61] Add a todo comment about renaming --- lib/picotls.c | 8 ++++++++ 1 file changed, 8 insertions(+) diff --git a/lib/picotls.c b/lib/picotls.c index 3214ed3c6..06ebda1ac 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -2111,6 +2111,10 @@ static inline size_t outer_ech_header_size(size_t enc_size) */ enum encode_ch_mode { ENCODE_CH_MODE_INNER, ENCODE_CH_MODE_ENCODED_INNER, ENCODE_CH_MODE_OUTER }; +/** + * TODO: resumption_secret and resumption_ticket should be renamed (e.g., to psk and psk_identity) + * because when the PSK is an external PSK, it is not for resumption. + */ 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, @@ -2299,6 +2303,10 @@ static int encode_client_hello(ptls_context_t *ctx, ptls_buffer_t *sendbuf, enum static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_handshake_properties_t *properties, ptls_iovec_t *cookie) { + /** + * TODO: resumption_secret and resumption_ticket should be renamed (e.g., to psk and psk_identity) + * because when the PSK is an external PSK, it is not for resumption. + */ ptls_iovec_t resumption_secret = {NULL}, resumption_ticket = {NULL}; uint32_t obfuscated_ticket_age = 0; const char *sni_name = NULL; From fba3d962d1fb49870b50be0bede48a34fd06ed9a Mon Sep 17 00:00:00 2001 From: Phillip Hellewell Date: Tue, 25 Jul 2023 17:45:31 -0600 Subject: [PATCH 09/61] Use correct binder key label on client --- lib/picotls.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/lib/picotls.c b/lib/picotls.c index 06ebda1ac..76d253d58 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -2311,6 +2311,7 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ uint32_t obfuscated_ticket_age = 0; const char *sni_name = NULL; size_t mess_start, msghash_off; + const char *binder_key_label = "res binder"; uint8_t binder_key[PTLS_MAX_DIGEST_SIZE]; ptls_buffer_t encoded_ch_inner; int ret, is_second_flight = tls->key_schedule != NULL; @@ -2361,6 +2362,7 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ tls->cipher_suite = tls->ctx->cipher_suites[0]; resumption_secret = properties->pre_shared_key.key; resumption_ticket = properties->pre_shared_key.identity; + binder_key_label = "ext binder"; if (!is_second_flight && properties->client.max_early_data_size != NULL) { tls->client.using_early_data = 1; *properties->client.max_early_data_size = SIZE_MAX; @@ -2410,7 +2412,7 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ /* 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) + if ((ret = derive_secret_with_empty_digest(tls->key_schedule, binder_key, binder_key_label)) != 0) goto Exit; ptls__key_schedule_update_hash(tls->key_schedule, emitter->buf->base + msghash_off, psk_binder_off - msghash_off, 0); msghash_off = psk_binder_off; From b6b3c04b809b988db54d57bfcaabae12b257f136 Mon Sep 17 00:00:00 2001 From: Phillip Hellewell Date: Tue, 25 Jul 2023 17:46:13 -0600 Subject: [PATCH 10/61] Default to SHA-256 with external PSK From RFC 8446 section 4.2.11: For externally established PSKs, the Hash algorithm MUST be set when the PSK is established or default to SHA-256 if no such algorithm is defined. --- lib/picotls.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/picotls.c b/lib/picotls.c index 76d253d58..35b1ca769 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -2359,7 +2359,7 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ } } else if (properties->pre_shared_key.identity.base != NULL) { tls->client.offered_psk = 1; - tls->cipher_suite = tls->ctx->cipher_suites[0]; + tls->cipher_suite = ptls_find_cipher_suite(tls->ctx->cipher_suites, PTLS_CIPHER_SUITE_AES_128_GCM_SHA256); resumption_secret = properties->pre_shared_key.key; resumption_ticket = properties->pre_shared_key.identity; binder_key_label = "ext binder"; From b6e55b8734f71a45f9b802599f6bdb57222870c1 Mon Sep 17 00:00:00 2001 From: Phillip Hellewell Date: Wed, 26 Jul 2023 11:45:11 -0600 Subject: [PATCH 11/61] Make cipher suite to use with PSK configurable Still default to SHA-256 for the hash, but make it configurable. Be sure to use the same cipher suite on both the client and the server (note technically the client only uses the hash alg of it, unless there is early data). --- include/picotls.h | 1 + lib/picotls.c | 3 ++- 2 files changed, 3 insertions(+), 1 deletion(-) diff --git a/include/picotls.h b/include/picotls.h index e23107669..f8a8a2655 100644 --- a/include/picotls.h +++ b/include/picotls.h @@ -1022,6 +1022,7 @@ typedef struct st_ptls_handshake_properties_t { struct st_ptls_external_psk_t { ptls_iovec_t identity; ptls_iovec_t key; + uint16_t csid; /* PTLS_CIPHER_SUITE_XXX; leave 0 to default to one of the SHA256 ones */ } pre_shared_key; } ptls_handshake_properties_t; #ifdef _WINDOWS diff --git a/lib/picotls.c b/lib/picotls.c index 35b1ca769..335a7abdf 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -2358,8 +2358,9 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ resumption_secret = ptls_iovec_init(NULL, 0); } } else if (properties->pre_shared_key.identity.base != NULL) { + uint16_t csid = properties->pre_shared_key.csid; tls->client.offered_psk = 1; - tls->cipher_suite = ptls_find_cipher_suite(tls->ctx->cipher_suites, PTLS_CIPHER_SUITE_AES_128_GCM_SHA256); + tls->cipher_suite = ptls_find_cipher_suite(tls->ctx->cipher_suites, csid == 0 ? PTLS_CIPHER_SUITE_AES_128_GCM_SHA256 : csid); resumption_secret = properties->pre_shared_key.key; resumption_ticket = properties->pre_shared_key.identity; binder_key_label = "ext binder"; From 9912af6af5d9ebf749fbd4f46b363bc221717189 Mon Sep 17 00:00:00 2001 From: Phillip Hellewell Date: Wed, 26 Jul 2023 12:20:32 -0600 Subject: [PATCH 12/61] Use correct binder key label on server --- lib/picotls.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/picotls.c b/lib/picotls.c index 335a7abdf..a8547b5fe 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -4138,7 +4138,7 @@ static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_d Found: if ((ret = key_schedule_extract(tls->key_schedule, ticket_psk)) != 0) goto Exit; - if ((ret = derive_secret(tls->key_schedule, binder_key, "res binder")) != 0) + if ((ret = derive_secret(tls->key_schedule, binder_key, external_psk ? "ext binder" : "res binder")) != 0) goto Exit; 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, From 7782e0a28956ab56a97ea4ff5517df905e43b6e5 Mon Sep 17 00:00:00 2001 From: Phillip Hellewell Date: Wed, 26 Jul 2023 13:43:54 -0600 Subject: [PATCH 13/61] Fix bug where server was sending early data indicator Server should not send early data indicator unless the client sent an early data indicator. --- lib/picotls.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/picotls.c b/lib/picotls.c index a8547b5fe..a4855b15f 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -4052,7 +4052,7 @@ static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_d if (external_psk != NULL) { if (identity->identity.len == external_psk->identity.len && memcmp(identity->identity.base, external_psk->identity.base, identity->identity.len) == 0) { - *accept_early_data = 1; + *accept_early_data = ch->psk.early_data_indication; tls->key_share = NULL; ticket_psk = external_psk->key; goto Found; From ea44c61659d84b0a19e9dc9f17d7491d09194972 Mon Sep 17 00:00:00 2001 From: Phillip Hellewell Date: Wed, 26 Jul 2023 16:53:03 -0600 Subject: [PATCH 14/61] Use PSK compatiable cipher suite on server Use configured cipher, or default to a SHA-256 one. Note: here we've hard-coded PTLS_CIPHER_SUITE_AES_128_GCM_SHA256 for the default, but any SHA256 one could be chosen (unless the client is sending early data, but in that case both endpoints need to specify it explicitly, not rely on default). --- lib/picotls.c | 41 ++++++++++++++++++++++++++++++++++------- t/picotls.c | 32 ++++++++++++++++---------------- 2 files changed, 50 insertions(+), 23 deletions(-) diff --git a/lib/picotls.c b/lib/picotls.c index a4855b15f..06314527a 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -1979,7 +1979,7 @@ static int decode_signature_algorithms(struct st_ptls_signature_algorithms_t *sa } 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, int server_chacha_priority) + const uint8_t *const end, int server_preference, int server_chacha_priority, uint16_t csid_for_psk) { size_t found_index = SIZE_MAX; int ret; @@ -1989,7 +1989,7 @@ static int select_cipher(ptls_cipher_suite_t **selected, ptls_cipher_suite_t **c if ((ret = ptls_decode16(&id, &src, end)) != 0) goto Exit; for (size_t i = 0; candidates[i] != NULL; ++i) { - if (candidates[i]->id == id) { + if (candidates[i]->id == id && (csid_for_psk == 0 || id == csid_for_psk)) { if (server_preference && !(server_chacha_priority && id == PTLS_CIPHER_SUITE_CHACHA20_POLY1305_SHA256)) { /* preserve smallest matching index, and proceed to the next input */ if (i < found_index) { @@ -4029,6 +4029,22 @@ 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'; } +/** + * Will one of the PSKs provided by the client match our external PSK? + * FIXME: Find a good way to avoid duplicate code and having to match the PSK here and in try_psk_handshake. + */ +static int will_match_external_psk(const struct st_ptls_client_hello_t *ch, const struct st_ptls_external_psk_t *external_psk) +{ + for (size_t psk_index = 0; psk_index < ch->psk.identities.count; ++psk_index) { + struct st_ptls_client_hello_psk_t *identity = ch->psk.identities.list + psk_index; + if (identity->identity.len == external_psk->identity.len && + memcmp(identity->identity.base, external_psk->identity.base, identity->identity.len) == 0) { + return 1; + } + } + return 0; +} + /** * Looks for a PSK identity that can be used, and if found, updates the handshake state and returns the necessary variables. If * external_psk is set, only tries handshake using those keys provided. Otherwise, tries resumption. @@ -4268,6 +4284,7 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl 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; + int can_try_external_psk = 0; ptls_buffer_init(&ech.ch_inner, "", 0); @@ -4403,10 +4420,22 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl } } - { /* select (or check) cipher-suite, create key_schedule */ + /* can we try external psk handshake below? */ + can_try_external_psk = (!is_second_flight && ch->psk.hash_end != 0 && + (ch->psk.ke_modes & ((1u << PTLS_PSK_KE_MODE_PSK) | (1u << PTLS_PSK_KE_MODE_PSK_DHE))) != 0 && properties != NULL && + properties->pre_shared_key.identity.base != NULL && properties->pre_shared_key.key.base != NULL && !tls->ctx->require_client_authentication); + + /* select (or check) cipher-suite, create key_schedule */ + { + /* for external PSK, determine a compatible cipher suite; if none specified, it needs to default to a SHA256 one */ + /* TODO: for resumption PSK, can we get the ticket_csid sooner so we could use that here? */ + uint16_t csid_for_psk = 0; + if (can_try_external_psk && will_match_external_psk(ch, &properties->pre_shared_key)) + csid_for_psk = properties->pre_shared_key.csid == 0 ? PTLS_CIPHER_SUITE_AES_128_GCM_SHA256 : properties->pre_shared_key.csid; + ptls_cipher_suite_t *cs; if ((ret = select_cipher(&cs, tls->ctx->cipher_suites, ch->cipher_suites.base, - ch->cipher_suites.base + ch->cipher_suites.len, tls->ctx->server_cipher_preference, tls->ctx->server_cipher_chacha_priority)) != 0) + ch->cipher_suites.base + ch->cipher_suites.len, tls->ctx->server_cipher_preference, tls->ctx->server_cipher_chacha_priority, csid_for_psk)) != 0) goto Exit; if (!is_second_flight) { tls->cipher_suite = cs; @@ -4458,9 +4487,7 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl } /* try external psk handshake */ - if (!is_second_flight && ch->psk.hash_end != 0 && - (ch->psk.ke_modes & ((1u << PTLS_PSK_KE_MODE_PSK) | (1u << PTLS_PSK_KE_MODE_PSK_DHE))) != 0 && properties != NULL && - properties->pre_shared_key.identity.base != NULL && !tls->ctx->require_client_authentication) { + if (can_try_external_psk) { if ((ret = try_psk_handshake(tls, &psk_index, &accept_early_data, ch, ptls_iovec_init(message.base, ch->psk.hash_end - message.base), &properties->pre_shared_key)) != 0) diff --git a/t/picotls.c b/t/picotls.c index 456f0c2f7..703a66292 100644 --- a/t/picotls.c +++ b/t/picotls.c @@ -65,66 +65,66 @@ static void test_select_cipher(void) { ptls_cipher_suite_t *candidates[] = {&ptls_minicrypto_chacha20poly1305sha256, &ptls_minicrypto_aes128gcmsha256, NULL}; static const uint8_t input; /* `input[0]` is preferable, but prohibited by MSVC */ - ok(select_cipher(&selected, candidates, &input, &input, 0, 0) == PTLS_ALERT_HANDSHAKE_FAILURE); + ok(select_cipher(&selected, candidates, &input, &input, 0, 0, 0) == PTLS_ALERT_HANDSHAKE_FAILURE); } { ptls_cipher_suite_t *candidates[] = {&ptls_minicrypto_chacha20poly1305sha256, &ptls_minicrypto_aes128gcmsha256, NULL}; static const uint8_t input[] = {C(PTLS_CIPHER_SUITE_AES_128_GCM_SHA256), C(PTLS_CIPHER_SUITE_CHACHA20_POLY1305_SHA256)}; - ok(select_cipher(&selected, candidates, input, input + sizeof(input), 0, 0) == 0); + ok(select_cipher(&selected, candidates, input, input + sizeof(input), 0, 0, 0) == 0); ok(selected == &ptls_minicrypto_aes128gcmsha256); - ok(select_cipher(&selected, candidates, input, input + sizeof(input), 1, 0) == 0); + ok(select_cipher(&selected, candidates, input, input + sizeof(input), 1, 0, 0) == 0); ok(selected == &ptls_minicrypto_chacha20poly1305sha256); } { ptls_cipher_suite_t *candidates[] = {&ptls_minicrypto_chacha20poly1305sha256, &ptls_minicrypto_aes128gcmsha256, NULL}; static const uint8_t input[] = {C(PTLS_CIPHER_SUITE_AES_256_GCM_SHA384), C(PTLS_CIPHER_SUITE_AES_128_GCM_SHA256)}; - ok(select_cipher(&selected, candidates, input, input + sizeof(input), 0, 0) == 0); + ok(select_cipher(&selected, candidates, input, input + sizeof(input), 0, 0, 0) == 0); ok(selected == &ptls_minicrypto_aes128gcmsha256); - ok(select_cipher(&selected, candidates, input, input + sizeof(input), 1, 0) == 0); + ok(select_cipher(&selected, candidates, input, input + sizeof(input), 1, 0, 0) == 0); ok(selected == &ptls_minicrypto_aes128gcmsha256); } { ptls_cipher_suite_t *candidates[] = {&ptls_minicrypto_aes128gcmsha256, &ptls_minicrypto_aes256gcmsha384, &ptls_minicrypto_chacha20poly1305sha256, NULL}; static const uint8_t input[] = {C(PTLS_CIPHER_SUITE_CHACHA20_POLY1305_SHA256), C(PTLS_CIPHER_SUITE_AES_128_GCM_SHA256)}; - ok(select_cipher(&selected, candidates, input, input + sizeof(input), 1, 0) == 0); + ok(select_cipher(&selected, candidates, input, input + sizeof(input), 1, 0, 0) == 0); ok(selected == &ptls_minicrypto_aes128gcmsha256); - ok(select_cipher(&selected, candidates, input, input + sizeof(input), 1, 1) == 0); + ok(select_cipher(&selected, candidates, input, input + sizeof(input), 1, 1, 0) == 0); ok(selected == &ptls_minicrypto_chacha20poly1305sha256); } { ptls_cipher_suite_t *candidates[] = {&ptls_minicrypto_aes256gcmsha384, &ptls_minicrypto_chacha20poly1305sha256, &ptls_minicrypto_aes128gcmsha256, NULL}; static const uint8_t input[] = {C(PTLS_CIPHER_SUITE_CHACHA20_POLY1305_SHA256), C(PTLS_CIPHER_SUITE_AES_128_GCM_SHA256), C(PTLS_CIPHER_SUITE_AES_256_GCM_SHA384)}; - ok(select_cipher(&selected, candidates, input, input + sizeof(input), 1, 0) == 0); + ok(select_cipher(&selected, candidates, input, input + sizeof(input), 1, 0, 0) == 0); ok(selected == &ptls_minicrypto_aes256gcmsha384); - ok(select_cipher(&selected, candidates, input, input + sizeof(input), 1, 1) == 0); + ok(select_cipher(&selected, candidates, input, input + sizeof(input), 1, 1, 0) == 0); ok(selected == &ptls_minicrypto_chacha20poly1305sha256); - ok(select_cipher(&selected, candidates, input, input + sizeof(input), 0, 1) == 0); + ok(select_cipher(&selected, candidates, input, input + sizeof(input), 0, 1, 0) == 0); ok(selected == &ptls_minicrypto_chacha20poly1305sha256); } { ptls_cipher_suite_t *candidates[] = {&ptls_minicrypto_aes256gcmsha384, &ptls_minicrypto_chacha20poly1305sha256, &ptls_minicrypto_aes128gcmsha256, NULL}; static const uint8_t input[] = {C(PTLS_CIPHER_SUITE_AES_128_GCM_SHA256), C(PTLS_CIPHER_SUITE_CHACHA20_POLY1305_SHA256), C(PTLS_CIPHER_SUITE_AES_256_GCM_SHA384)}; - ok(select_cipher(&selected, candidates, input, input + sizeof(input), 1, 1) == 0); + ok(select_cipher(&selected, candidates, input, input + sizeof(input), 1, 1, 0) == 0); ok(selected == &ptls_minicrypto_aes256gcmsha384); - ok(select_cipher(&selected, candidates, input, input + sizeof(input), 1, 1) == 0); + ok(select_cipher(&selected, candidates, input, input + sizeof(input), 1, 1, 0) == 0); ok(selected == &ptls_minicrypto_aes256gcmsha384); } { ptls_cipher_suite_t *candidates[] = {&ptls_minicrypto_aes256gcmsha384, &ptls_minicrypto_aes128gcmsha256, NULL}; static const uint8_t input[] = {C(PTLS_CIPHER_SUITE_CHACHA20_POLY1305_SHA256), C(PTLS_CIPHER_SUITE_AES_128_GCM_SHA256), C(PTLS_CIPHER_SUITE_AES_256_GCM_SHA384)}; - ok(select_cipher(&selected, candidates, input, input + sizeof(input), 1, 0) == 0); + ok(select_cipher(&selected, candidates, input, input + sizeof(input), 1, 0, 0) == 0); ok(selected == &ptls_minicrypto_aes256gcmsha384); - ok(select_cipher(&selected, candidates, input, input + sizeof(input), 1, 1) == 0); + ok(select_cipher(&selected, candidates, input, input + sizeof(input), 1, 1, 0) == 0); ok(selected == &ptls_minicrypto_aes256gcmsha384); - ok(select_cipher(&selected, candidates, input, input + sizeof(input), 0, 0) == 0); + ok(select_cipher(&selected, candidates, input, input + sizeof(input), 0, 0, 0) == 0); ok(selected == &ptls_minicrypto_aes128gcmsha256); - ok(select_cipher(&selected, candidates, input, input + sizeof(input), 0, 1) == 0); + ok(select_cipher(&selected, candidates, input, input + sizeof(input), 0, 1, 0) == 0); ok(selected == &ptls_minicrypto_aes128gcmsha256); } From 415f5c4ebf0095686bf925cae9109d8e6cec7db2 Mon Sep 17 00:00:00 2001 From: Phillip Hellewell Date: Wed, 26 Jul 2023 17:11:51 -0600 Subject: [PATCH 15/61] Only accept early data if using the first PSK From TLS 1.3 RFC 8446 section 4.2.10: The PSK used to encrypt the early data MUST be the first PSK listed in the client's "pre_shared_key" extension. I noticed that later on in the code it only sets up the tls->pending_handshake_secret when accept_early_data && tls->ctx->max_early_data_size != 0 && psk_index == 0, so perhaps we don't need to do a check here, but I think it is still good to check it in the psk handshake. --- lib/picotls.c | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/lib/picotls.c b/lib/picotls.c index 06314527a..85f384ac5 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -4068,7 +4068,7 @@ static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_d if (external_psk != NULL) { if (identity->identity.len == external_psk->identity.len && memcmp(identity->identity.base, external_psk->identity.base, identity->identity.len) == 0) { - *accept_early_data = ch->psk.early_data_indication; + *accept_early_data = ch->psk.early_data_indication && *psk_index == 0; tls->key_share = NULL; ticket_psk = external_psk->key; goto Found; @@ -4078,7 +4078,7 @@ static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_d /* decrypt ticket and decode */ if (tls->ctx->encrypt_ticket == NULL || tls->ctx->key_exchanges == NULL) continue; - int can_accept_early_data = 1; + int can_accept_early_data = *psk_index == 0; decbuf.off = 0; switch (tls->ctx->encrypt_ticket->cb(tls->ctx->encrypt_ticket, tls, 0, &decbuf, identity->identity)) { case 0: /* decrypted */ From 6e37a7eb51b0fb96c186dd0d4e10e13cd5d8f841 Mon Sep 17 00:00:00 2001 From: Phillip Hellewell Date: Wed, 26 Jul 2023 17:15:27 -0600 Subject: [PATCH 16/61] Fix warning --- lib/picotls.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/picotls.c b/lib/picotls.c index 85f384ac5..41dd8b12e 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -4036,7 +4036,7 @@ static int vec_is_string(ptls_iovec_t x, const char *y) static int will_match_external_psk(const struct st_ptls_client_hello_t *ch, const struct st_ptls_external_psk_t *external_psk) { for (size_t psk_index = 0; psk_index < ch->psk.identities.count; ++psk_index) { - struct st_ptls_client_hello_psk_t *identity = ch->psk.identities.list + psk_index; + const struct st_ptls_client_hello_psk_t *identity = ch->psk.identities.list + psk_index; if (identity->identity.len == external_psk->identity.len && memcmp(identity->identity.base, external_psk->identity.base, identity->identity.len) == 0) { return 1; From 46cbf7a8c1b5fe29df70b92df3b768e90a7547d4 Mon Sep 17 00:00:00 2001 From: Phillip Hellewell Date: Wed, 26 Jul 2023 17:17:02 -0600 Subject: [PATCH 17/61] Fix warning --- lib/picotls.c | 2 ++ 1 file changed, 2 insertions(+) diff --git a/lib/picotls.c b/lib/picotls.c index 41dd8b12e..1fc8e50f0 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -1668,7 +1668,9 @@ static int commission_handshake_secret(ptls_t *tls) static void log_client_random(ptls_t *tls) { +#if PICOTLS_USE_DTRACE char buf[sizeof(tls->client_random) * 2 + 1]; +#endif PTLS_PROBE(CLIENT_RANDOM, tls, ptls_hexdump(buf, tls->client_random, sizeof(tls->client_random))); PTLS_LOG_CONN(client_random, tls, { PTLS_LOG_ELEMENT_HEXDUMP(bytes, tls->client_random, sizeof(tls->client_random)); }); From 291b3291445db779d0830450b64b01ed904fcf5a Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Sat, 5 Aug 2023 14:05:32 +0900 Subject: [PATCH 18/61] identity should be part of `ptls_context_t` --- include/picotls.h | 16 ++++++------- lib/picotls.c | 57 +++++++++++++++++++++++++++-------------------- t/picotls.c | 11 ++++----- 3 files changed, 47 insertions(+), 37 deletions(-) diff --git a/include/picotls.h b/include/picotls.h index dcf335c0b..2999ec159 100644 --- a/include/picotls.h +++ b/include/picotls.h @@ -773,6 +773,14 @@ struct st_ptls_context_t { ptls_iovec_t *list; size_t count; } certificates; + /** + * if set, the endpoints negotiate using the provided pre-shared key + */ + struct st_ptls_external_psk_t { + ptls_iovec_t identity; + ptls_iovec_t key; + uint16_t csid; /* PTLS_CIPHER_SUITE_XXX; leave 0 to default to one of the SHA256 ones */ + } pre_shared_key; /** * ECH */ @@ -1016,14 +1024,6 @@ typedef struct st_ptls_handshake_properties_t { * an optional callback that reports the extensions being collected */ int (*collected_extensions)(ptls_t *tls, struct st_ptls_handshake_properties_t *properties, ptls_raw_extension_t *extensions); - /** - * [optional] if set, the endpoints negotiate using the provided pre-shared key - */ - struct st_ptls_external_psk_t { - ptls_iovec_t identity; - ptls_iovec_t key; - uint16_t csid; /* PTLS_CIPHER_SUITE_XXX; leave 0 to default to one of the SHA256 ones */ - } pre_shared_key; } ptls_handshake_properties_t; #ifdef _WINDOWS #pragma warning(pop) diff --git a/lib/picotls.c b/lib/picotls.c index 1fc8e50f0..827c69c5f 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -2340,7 +2340,8 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ } } /* setup resumption-related data. If successful, resumption_secret becomes a non-zero value. */ - if (properties->client.session_ticket.base != NULL && tls->ctx->key_exchanges != NULL) { + if (properties->client.session_ticket.base != NULL && tls->ctx->key_exchanges != NULL && + tls->ctx->pre_shared_key.identity.base == NULL) { ptls_key_exchange_algorithm_t *key_share = NULL; ptls_cipher_suite_t *cipher_suite = NULL; uint32_t max_early_data_size; @@ -2359,27 +2360,33 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ } else { resumption_secret = ptls_iovec_init(NULL, 0); } - } else if (properties->pre_shared_key.identity.base != NULL) { - uint16_t csid = properties->pre_shared_key.csid; - tls->client.offered_psk = 1; - tls->cipher_suite = ptls_find_cipher_suite(tls->ctx->cipher_suites, csid == 0 ? PTLS_CIPHER_SUITE_AES_128_GCM_SHA256 : csid); - resumption_secret = properties->pre_shared_key.key; - resumption_ticket = properties->pre_shared_key.identity; - binder_key_label = "ext binder"; - if (!is_second_flight && properties->client.max_early_data_size != NULL) { - tls->client.using_early_data = 1; - *properties->client.max_early_data_size = SIZE_MAX; - } } - if (tls->client.using_early_data) { - properties->client.early_data_acceptance = PTLS_EARLY_DATA_ACCEPTANCE_UNKNOWN; - } else { - if (properties->client.max_early_data_size != NULL) - *properties->client.max_early_data_size = 0; - properties->client.early_data_acceptance = PTLS_EARLY_DATA_REJECTED; + } + + /* try PSK unless we are trying to resume */ + if (tls->ctx->pre_shared_key.identity.base != NULL) { + assert(resumption_secret.base == NULL); + uint16_t csid = tls->ctx->pre_shared_key.csid; + tls->client.offered_psk = 1; + tls->cipher_suite = ptls_find_cipher_suite(tls->ctx->cipher_suites, csid == 0 ? PTLS_CIPHER_SUITE_AES_128_GCM_SHA256 : csid); + resumption_secret = tls->ctx->pre_shared_key.key; + resumption_ticket = tls->ctx->pre_shared_key.identity; + binder_key_label = "ext binder"; + if (!is_second_flight && properties->client.max_early_data_size != NULL) { + tls->client.using_early_data = 1; + *properties->client.max_early_data_size = SIZE_MAX; } } + /* send 0-RTT related signals back to the client */ + if (tls->client.using_early_data) { + properties->client.early_data_acceptance = PTLS_EARLY_DATA_ACCEPTANCE_UNKNOWN; + } else { + if (properties->client.max_early_data_size != NULL) + *properties->client.max_early_data_size = 0; + properties->client.early_data_acceptance = PTLS_EARLY_DATA_REJECTED; + } + /* use the default key share if still not undetermined */ if (tls->key_share == NULL && tls->ctx->key_exchanges != NULL && !(properties != NULL && properties->client.negotiate_before_key_exchange)) @@ -4052,7 +4059,7 @@ static int will_match_external_psk(const struct st_ptls_client_hello_t *ch, cons * external_psk is set, only tries handshake using those keys provided. Otherwise, tries resumption. */ static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_data, struct st_ptls_client_hello_t *ch, - ptls_iovec_t ch_trunc, struct st_ptls_external_psk_t *external_psk) + ptls_iovec_t ch_trunc, const struct st_ptls_external_psk_t *external_psk) { ptls_buffer_t decbuf; ptls_iovec_t ticket_psk, ticket_server_name, ticket_negotiated_protocol; @@ -4424,16 +4431,18 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl /* can we try external psk handshake below? */ can_try_external_psk = (!is_second_flight && ch->psk.hash_end != 0 && - (ch->psk.ke_modes & ((1u << PTLS_PSK_KE_MODE_PSK) | (1u << PTLS_PSK_KE_MODE_PSK_DHE))) != 0 && properties != NULL && - properties->pre_shared_key.identity.base != NULL && properties->pre_shared_key.key.base != NULL && !tls->ctx->require_client_authentication); + (ch->psk.ke_modes & ((1u << PTLS_PSK_KE_MODE_PSK) | (1u << PTLS_PSK_KE_MODE_PSK_DHE))) != 0 && + properties != NULL && tls->ctx->pre_shared_key.identity.base != NULL && + tls->ctx->pre_shared_key.key.base != NULL && !tls->ctx->require_client_authentication); /* select (or check) cipher-suite, create key_schedule */ { /* for external PSK, determine a compatible cipher suite; if none specified, it needs to default to a SHA256 one */ /* TODO: for resumption PSK, can we get the ticket_csid sooner so we could use that here? */ uint16_t csid_for_psk = 0; - if (can_try_external_psk && will_match_external_psk(ch, &properties->pre_shared_key)) - csid_for_psk = properties->pre_shared_key.csid == 0 ? PTLS_CIPHER_SUITE_AES_128_GCM_SHA256 : properties->pre_shared_key.csid; + if (can_try_external_psk && will_match_external_psk(ch, &tls->ctx->pre_shared_key)) + csid_for_psk = + tls->ctx->pre_shared_key.csid == 0 ? PTLS_CIPHER_SUITE_AES_128_GCM_SHA256 : tls->ctx->pre_shared_key.csid; ptls_cipher_suite_t *cs; if ((ret = select_cipher(&cs, tls->ctx->cipher_suites, ch->cipher_suites.base, @@ -4492,7 +4501,7 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl if (can_try_external_psk) { if ((ret = try_psk_handshake(tls, &psk_index, &accept_early_data, ch, ptls_iovec_init(message.base, ch->psk.hash_end - message.base), - &properties->pre_shared_key)) != 0) + &tls->ctx->pre_shared_key)) != 0) goto Exit; } diff --git a/t/picotls.c b/t/picotls.c index 703a66292..f9896bc8f 100644 --- a/t/picotls.c +++ b/t/picotls.c @@ -1539,6 +1539,9 @@ static void do_test_pre_shared_key(int clear_ke) if (clear_ke) ctx->key_exchanges = NULL; ctx->max_early_data_size = 16384; + assert(ctx->pre_shared_key.identity.len == 0 && ctx->pre_shared_key.key.len == 0); + ctx->pre_shared_key.identity = ptls_iovec_init("", 1); + ctx->pre_shared_key.key = ptls_iovec_init("hello world", 11); ptls_t *client = ptls_new(ctx, 0), *server = ptls_new(ctx, 1); ptls_buffer_t cbuf, sbuf, decbuf; @@ -1548,12 +1551,8 @@ static void do_test_pre_shared_key(int clear_ke) ptls_handshake_properties_t client_prop = {}; size_t client_max_early_data_size = 0; - client_prop.pre_shared_key.identity = ptls_iovec_init("", 1); - client_prop.pre_shared_key.key = ptls_iovec_init("hello world", 11); client_prop.client.max_early_data_size = &client_max_early_data_size; - ptls_handshake_properties_t server_prop = {.pre_shared_key = client_prop.pre_shared_key}; - /* [client] send CH and early data */ int ret = ptls_handshake(client, &cbuf, NULL, NULL, &client_prop); ok(ret == PTLS_ERROR_IN_PROGRESS); @@ -1564,7 +1563,7 @@ static void do_test_pre_shared_key(int clear_ke) /* [server] read CH and generate up to ServerFinished */ size_t consumed = cbuf.off; - ret = ptls_handshake(server, &sbuf, cbuf.base, &consumed, &server_prop); + 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); @@ -1626,6 +1625,8 @@ static void do_test_pre_shared_key(int clear_ke) ctx->key_exchanges = backup.key_exchanges; ctx->max_early_data_size = backup.max_early_data_size; + ctx->pre_shared_key.identity = ptls_iovec_init(NULL, 0); + ctx->pre_shared_key.key = ptls_iovec_init(NULL, 0); } static void test_pre_shared_key(void) From a98430819e98d120d685fc5081c769f3c4d0e37b Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Sat, 5 Aug 2023 14:08:13 +0900 Subject: [PATCH 19/61] ECH is orthogonal to PSK --- lib/picotls.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/picotls.c b/lib/picotls.c index 827c69c5f..6386fc1ed 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -2325,7 +2325,7 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ if (properties != NULL) { /* try to use ECH (ignore broken ECHConfigList; it is delivered insecurely) */ - if (!is_second_flight && sni_name != NULL && tls->ctx->ech.client.ciphers != NULL && tls->ctx->key_exchanges != NULL) { + if (!is_second_flight && sni_name != NULL && tls->ctx->ech.client.ciphers != NULL) { if (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); From 38c37a08c37cceee81a6e412d505c0bb0acbe465 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Sat, 5 Aug 2023 14:11:18 +0900 Subject: [PATCH 20/61] rename --- lib/picotls.c | 79 +++++++++++++++++++++++---------------------------- 1 file changed, 36 insertions(+), 43 deletions(-) diff --git a/lib/picotls.c b/lib/picotls.c index 6386fc1ed..8f3ab324f 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -2113,16 +2113,12 @@ static inline size_t outer_ech_header_size(size_t enc_size) */ enum encode_ch_mode { ENCODE_CH_MODE_INNER, ENCODE_CH_MODE_ENCODED_INNER, ENCODE_CH_MODE_OUTER }; -/** - * TODO: resumption_secret and resumption_ticket should be renamed (e.g., to psk and psk_identity) - * because when the PSK is an external PSK, it is not for resumption. - */ 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) + struct st_ptls_ech_t *ech, size_t *ech_size_offset, ptls_iovec_t ech_replay, ptls_iovec_t psk_secret, + ptls_iovec_t psk_identity, uint32_t obfuscated_ticket_age, size_t psk_binder_size, + ptls_iovec_t *cookie, int using_early_data) { int ret; @@ -2250,7 +2246,7 @@ static int encode_client_hello(ptls_context_t *ctx, ptls_buffer_t *sendbuf, enum } if ((ret = push_additional_extensions(properties, sendbuf)) != 0) goto Exit; - if (ctx->save_ticket != NULL || resumption_secret.base != NULL) { + if (ctx->save_ticket != NULL || psk_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) @@ -2259,7 +2255,7 @@ static int encode_client_hello(ptls_context_t *ctx, ptls_buffer_t *sendbuf, enum }); }); } - if (resumption_secret.base != NULL) { + if (psk_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) */ @@ -2267,12 +2263,12 @@ static int encode_client_hello(ptls_context_t *ctx, ptls_buffer_t *sendbuf, enum 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) + if ((ret = ptls_buffer_reserve(sendbuf, psk_identity.len)) != 0) goto Exit; - ctx->random_bytes(sendbuf->base + sendbuf->off, resumption_ticket.len); - sendbuf->off += resumption_ticket.len; + ctx->random_bytes(sendbuf->base + sendbuf->off, psk_identity.len); + sendbuf->off += psk_identity.len; } else { - ptls_buffer_pushv(sendbuf, resumption_ticket.base, resumption_ticket.len); + ptls_buffer_pushv(sendbuf, psk_identity.base, psk_identity.len); } }); uint32_t age; @@ -2305,11 +2301,7 @@ static int encode_client_hello(ptls_context_t *ctx, ptls_buffer_t *sendbuf, enum static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_handshake_properties_t *properties, ptls_iovec_t *cookie) { - /** - * TODO: resumption_secret and resumption_ticket should be renamed (e.g., to psk and psk_identity) - * because when the PSK is an external PSK, it is not for resumption. - */ - ptls_iovec_t resumption_secret = {NULL}, resumption_ticket = {NULL}; + ptls_iovec_t psk_secret = {NULL}, psk_identity = {NULL}; uint32_t obfuscated_ticket_age = 0; const char *sni_name = NULL; size_t mess_start, msghash_off; @@ -2345,8 +2337,8 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ ptls_key_exchange_algorithm_t *key_share = NULL; ptls_cipher_suite_t *cipher_suite = NULL; uint32_t max_early_data_size; - if (decode_stored_session_ticket(tls, &key_share, &cipher_suite, &resumption_secret, &obfuscated_ticket_age, - &resumption_ticket, &max_early_data_size, properties->client.session_ticket.base, + if (decode_stored_session_ticket(tls, &key_share, &cipher_suite, &psk_secret, &obfuscated_ticket_age, &psk_identity, + &max_early_data_size, properties->client.session_ticket.base, properties->client.session_ticket.base + properties->client.session_ticket.len) == 0) { tls->client.offered_psk = 1; /* key-share selected by HRR should not be overridden */ @@ -2358,19 +2350,20 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ *properties->client.max_early_data_size = max_early_data_size; } } else { - resumption_secret = ptls_iovec_init(NULL, 0); + psk_secret = ptls_iovec_init(NULL, 0); } } } /* try PSK unless we are trying to resume */ if (tls->ctx->pre_shared_key.identity.base != NULL) { - assert(resumption_secret.base == NULL); + assert(psk_secret.base == NULL); uint16_t csid = tls->ctx->pre_shared_key.csid; tls->client.offered_psk = 1; - tls->cipher_suite = ptls_find_cipher_suite(tls->ctx->cipher_suites, csid == 0 ? PTLS_CIPHER_SUITE_AES_128_GCM_SHA256 : csid); - resumption_secret = tls->ctx->pre_shared_key.key; - resumption_ticket = tls->ctx->pre_shared_key.identity; + tls->cipher_suite = + ptls_find_cipher_suite(tls->ctx->cipher_suites, csid == 0 ? PTLS_CIPHER_SUITE_AES_128_GCM_SHA256 : csid); + psk_secret = tls->ctx->pre_shared_key.key; + psk_identity = tls->ctx->pre_shared_key.identity; binder_key_label = "ext binder"; if (!is_second_flight && properties->client.max_early_data_size != NULL) { tls->client.using_early_data = 1; @@ -2402,7 +2395,7 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ /* initialize key schedule */ if (!is_second_flight) { 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) + if ((ret = key_schedule_extract(tls->key_schedule, psk_secret)) != 0) goto Exit; } @@ -2415,12 +2408,12 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ 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->ech.client.first_ech, psk_secret, psk_identity, 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) { + if (psk_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, binder_key_label)) != 0) goto Exit; @@ -2436,11 +2429,11 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ /* 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) + tls->client.legacy_session_id, &tls->ech, NULL, ptls_iovec_init(NULL, 0), psk_secret, + psk_identity, 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) + if (psk_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); @@ -2470,7 +2463,7 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ 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, + psk_secret, psk_identity, obfuscated_ticket_age, tls->key_schedule->hashes[0].algo->digest_size, cookie, tls->client.using_early_data)) != 0) goto Exit; /* overwrite ECH payload */ @@ -2509,7 +2502,7 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ if ((ret = push_change_cipher_spec(tls, emitter)) != 0) goto Exit; } - if (resumption_secret.base != NULL && !is_second_flight) { + if (psk_secret.base != NULL && !is_second_flight) { if ((ret = derive_exporter_secret(tls, 1)) != 0) goto Exit; } @@ -4445,8 +4438,9 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl tls->ctx->pre_shared_key.csid == 0 ? PTLS_CIPHER_SUITE_AES_128_GCM_SHA256 : tls->ctx->pre_shared_key.csid; ptls_cipher_suite_t *cs; - if ((ret = select_cipher(&cs, tls->ctx->cipher_suites, ch->cipher_suites.base, - ch->cipher_suites.base + ch->cipher_suites.len, tls->ctx->server_cipher_preference, tls->ctx->server_cipher_chacha_priority, csid_for_psk)) != 0) + if ((ret = + select_cipher(&cs, tls->ctx->cipher_suites, ch->cipher_suites.base, ch->cipher_suites.base + ch->cipher_suites.len, + tls->ctx->server_cipher_preference, tls->ctx->server_cipher_chacha_priority, csid_for_psk)) != 0) goto Exit; if (!is_second_flight) { tls->cipher_suite = cs; @@ -4500,8 +4494,8 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl /* try external psk handshake */ if (can_try_external_psk) { if ((ret = try_psk_handshake(tls, &psk_index, &accept_early_data, ch, - ptls_iovec_init(message.base, ch->psk.hash_end - message.base), - &tls->ctx->pre_shared_key)) != 0) + ptls_iovec_init(message.base, ch->psk.hash_end - message.base), &tls->ctx->pre_shared_key)) != + 0) goto Exit; } @@ -4578,11 +4572,10 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl } }, { - 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) + 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) { From 402214268e9313adbe79870e8f741de25daa7d9d Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Sat, 5 Aug 2023 15:16:02 +0900 Subject: [PATCH 21/61] let psk be a composition of identity and secret (rather than key) --- include/picotls.h | 2 +- lib/picotls.c | 14 +++++++------- t/picotls.c | 6 +++--- 3 files changed, 11 insertions(+), 11 deletions(-) diff --git a/include/picotls.h b/include/picotls.h index 2999ec159..024025cfd 100644 --- a/include/picotls.h +++ b/include/picotls.h @@ -778,7 +778,7 @@ struct st_ptls_context_t { */ struct st_ptls_external_psk_t { ptls_iovec_t identity; - ptls_iovec_t key; + ptls_iovec_t secret; uint16_t csid; /* PTLS_CIPHER_SUITE_XXX; leave 0 to default to one of the SHA256 ones */ } pre_shared_key; /** diff --git a/lib/picotls.c b/lib/picotls.c index 8f3ab324f..54fd80925 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -2362,7 +2362,7 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ tls->client.offered_psk = 1; tls->cipher_suite = ptls_find_cipher_suite(tls->ctx->cipher_suites, csid == 0 ? PTLS_CIPHER_SUITE_AES_128_GCM_SHA256 : csid); - psk_secret = tls->ctx->pre_shared_key.key; + psk_secret = tls->ctx->pre_shared_key.secret; psk_identity = tls->ctx->pre_shared_key.identity; binder_key_label = "ext binder"; if (!is_second_flight && properties->client.max_early_data_size != NULL) { @@ -4055,7 +4055,7 @@ static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_d ptls_iovec_t ch_trunc, const struct st_ptls_external_psk_t *external_psk) { ptls_buffer_t decbuf; - ptls_iovec_t ticket_psk, ticket_server_name, ticket_negotiated_protocol; + ptls_iovec_t secret, ticket_server_name, ticket_negotiated_protocol; uint64_t issue_at, now = tls->ctx->get_time->cb(tls->ctx->get_time); uint32_t age_add; uint16_t ticket_key_exchange_id, ticket_csid; @@ -4072,7 +4072,7 @@ static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_d memcmp(identity->identity.base, external_psk->identity.base, identity->identity.len) == 0) { *accept_early_data = ch->psk.early_data_indication && *psk_index == 0; tls->key_share = NULL; - ticket_psk = external_psk->key; + secret = external_psk->secret; goto Found; } continue; @@ -4091,7 +4091,7 @@ static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_d default: /* decryption failure */ continue; } - if (decode_session_identifier(&issue_at, &ticket_psk, &age_add, &ticket_server_name, &ticket_key_exchange_id, &ticket_csid, + if (decode_session_identifier(&issue_at, &secret, &age_add, &ticket_server_name, &ticket_key_exchange_id, &ticket_csid, &ticket_negotiated_protocol, decbuf.base, decbuf.base + decbuf.off) != 0) continue; /* check age */ @@ -4137,7 +4137,7 @@ static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_d continue; } /* check the length of the decrypted psk and the PSK binder */ - if (ticket_psk.len != tls->key_schedule->hashes[0].algo->digest_size) + if (secret.len != tls->key_schedule->hashes[0].algo->digest_size) continue; if (ch->psk.identities.list[*psk_index].binder.len != tls->key_schedule->hashes[0].algo->digest_size) continue; @@ -4154,7 +4154,7 @@ static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_d goto Exit; Found: - if ((ret = key_schedule_extract(tls->key_schedule, ticket_psk)) != 0) + if ((ret = key_schedule_extract(tls->key_schedule, secret)) != 0) goto Exit; if ((ret = derive_secret(tls->key_schedule, binder_key, external_psk ? "ext binder" : "res binder")) != 0) goto Exit; @@ -4426,7 +4426,7 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl can_try_external_psk = (!is_second_flight && ch->psk.hash_end != 0 && (ch->psk.ke_modes & ((1u << PTLS_PSK_KE_MODE_PSK) | (1u << PTLS_PSK_KE_MODE_PSK_DHE))) != 0 && properties != NULL && tls->ctx->pre_shared_key.identity.base != NULL && - tls->ctx->pre_shared_key.key.base != NULL && !tls->ctx->require_client_authentication); + tls->ctx->pre_shared_key.secret.base != NULL && !tls->ctx->require_client_authentication); /* select (or check) cipher-suite, create key_schedule */ { diff --git a/t/picotls.c b/t/picotls.c index f9896bc8f..655a43cf2 100644 --- a/t/picotls.c +++ b/t/picotls.c @@ -1539,9 +1539,9 @@ static void do_test_pre_shared_key(int clear_ke) if (clear_ke) ctx->key_exchanges = NULL; ctx->max_early_data_size = 16384; - assert(ctx->pre_shared_key.identity.len == 0 && ctx->pre_shared_key.key.len == 0); + assert(ctx->pre_shared_key.identity.len == 0 && ctx->pre_shared_key.secret.len == 0); ctx->pre_shared_key.identity = ptls_iovec_init("", 1); - ctx->pre_shared_key.key = ptls_iovec_init("hello world", 11); + ctx->pre_shared_key.secret = ptls_iovec_init("hello world", 11); ptls_t *client = ptls_new(ctx, 0), *server = ptls_new(ctx, 1); ptls_buffer_t cbuf, sbuf, decbuf; @@ -1626,7 +1626,7 @@ static void do_test_pre_shared_key(int clear_ke) ctx->key_exchanges = backup.key_exchanges; ctx->max_early_data_size = backup.max_early_data_size; ctx->pre_shared_key.identity = ptls_iovec_init(NULL, 0); - ctx->pre_shared_key.key = ptls_iovec_init(NULL, 0); + ctx->pre_shared_key.secret = ptls_iovec_init(NULL, 0); } static void test_pre_shared_key(void) From 35f648abe28603c27b911fbc5ac17162f108bb5e Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Sat, 5 Aug 2023 16:04:43 +0900 Subject: [PATCH 22/61] [cli] -p option to specify PSK identity --- t/cli.c | 21 ++++++++++++++------- t/util.h | 21 +++++++++++++++------ 2 files changed, 29 insertions(+), 13 deletions(-) diff --git a/t/cli.c b/t/cli.c index ef3c1ed81..21e18a9b3 100644 --- a/t/cli.c +++ b/t/cli.c @@ -383,6 +383,8 @@ static void usage(const char *cmd) " client, the argument specifies the public keys that the\n" " server is expected to use. When running as a server, the\n" " argument is ignored.\n" + " -p psk-identity name of the PSK key; if set, -c and -C specify the\n" + " pre-shared secret\n" " -u update the traffic key when handshake is complete\n" " -v verify peer using the default certificates\n" " -V CA-root-file verify peer using the CA Root File\n" @@ -445,7 +447,7 @@ int main(int argc, char **argv) int family = 0; const char *raw_pub_key_file = NULL, *cert_location = NULL; - while ((ch = getopt(argc, argv, "46abBC:c:i:Ij:k:nN:es:Sr:E:K:l:y:vV:h")) != -1) { + while ((ch = getopt(argc, argv, "46abBC:c:i:Ij:k:nN:es:Sr:p:E:K:l:y:vV:h")) != -1) { switch (ch) { case '4': family = AF_INET; @@ -497,6 +499,9 @@ int main(int argc, char **argv) case 'r': raw_pub_key_file = optarg; break; + case 'p': + ctx.pre_shared_key.identity = ptls_iovec_init(optarg, strlen(optarg)); + break; case 's': setup_session_file(&ctx, &hsprop, optarg); break; @@ -593,8 +598,14 @@ int main(int argc, char **argv) EVP_PKEY_free(pubkey); } ctx.use_raw_public_keys = 1; + } else if (ctx.pre_shared_key.identity.base != NULL) { + if (cert_location == NULL) { + fprintf(stderr, "-p must be used with -C or -c\n"); + return 1; + } + ctx.pre_shared_key.secret = load_file(cert_location); } else { - if (cert_location) + if (cert_location != NULL) load_certificate_chain(&ctx, cert_location); } @@ -604,12 +615,8 @@ int main(int argc, char **argv) } if (is_server) { - if (ctx.certificates.count == 0) { - fprintf(stderr, "-c and -k options must be set\n"); - return 1; - } #if PICOTLS_USE_BROTLI - if (ctx.decompress_certificate != NULL) { + if (ctx.certificates.count != 0 && ctx.decompress_certificate != NULL) { static ptls_emit_compressed_certificate_t ecc; if (ptls_init_compressed_certificate(&ecc, ctx.certificates.list, ctx.certificates.count, ptls_iovec_init(NULL, 0)) != 0) { diff --git a/t/util.h b/t/util.h index 0ead3f724..0a299099d 100644 --- a/t/util.h +++ b/t/util.h @@ -339,20 +339,29 @@ static void ech_save_retry_configs(void) fclose(fp); } -static void ech_setup_configs(const char *fn) +static ptls_iovec_t load_file(const char *fn) { FILE *fp; + ptls_iovec_t buf; if ((fp = fopen(fn, "rt")) == NULL) { - fprintf(stderr, "failed to open ECHConfigList file:%s:%s\n", fn, strerror(errno)); + fprintf(stderr, "failed to open file:%s:%s\n", fn, strerror(errno)); exit(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", fn); - exit(1); + buf.len = 65536; + if ((buf.base = malloc(buf.len)) == NULL) { + fprintf(stderr, "no memory\n"); + abort(); } + buf.len = fread(buf.base, 1, buf.len, fp); fclose(fp); + + return buf; +} + +static void ech_setup_configs(const char *fn) +{ + ech.config_list = load_file(fn); ech.retry.fn = strdup(fn); } From bf31de46c30ba43189ca32fe942e18e4ec85777f Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Mon, 7 Aug 2023 10:38:22 +0900 Subject: [PATCH 23/61] send session ticket only when ECDHE is used --- lib/picotls.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/picotls.c b/lib/picotls.c index 54fd80925..09452a294 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -1872,9 +1872,10 @@ static int send_session_ticket(ptls_t *tls, ptls_message_emitter_t *emitter) tls->ctx->random_bytes(&ticket_age_add, sizeof(ticket_age_add)); /* build the raw nsk */ - ret = encode_session_identifier(tls->ctx, &session_id, ticket_age_add, ptls_iovec_init(NULL, 0), tls->key_schedule, - tls->server_name, tls->key_share->id, tls->cipher_suite->id, tls->negotiated_protocol); - if (ret != 0) + if (tls->key_share != NULL && + (ret = encode_session_identifier(tls->ctx, &session_id, ticket_age_add, ptls_iovec_init(NULL, 0), tls->key_schedule, + tls->server_name, tls->key_share->id, tls->cipher_suite->id, + tls->negotiated_protocol)) != 0) goto Exit; /* encrypt and send */ From cba24484f254956a84a388f5200d2620f33cecd1 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Mon, 7 Aug 2023 14:04:12 +0900 Subject: [PATCH 24/61] restore guard --- lib/picotls.c | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/lib/picotls.c b/lib/picotls.c index 09452a294..6682d477f 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -2373,12 +2373,14 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ } /* send 0-RTT related signals back to the client */ - if (tls->client.using_early_data) { - properties->client.early_data_acceptance = PTLS_EARLY_DATA_ACCEPTANCE_UNKNOWN; - } else { - if (properties->client.max_early_data_size != NULL) - *properties->client.max_early_data_size = 0; - properties->client.early_data_acceptance = PTLS_EARLY_DATA_REJECTED; + if (properties != NULL) { + if (tls->client.using_early_data) { + properties->client.early_data_acceptance = PTLS_EARLY_DATA_ACCEPTANCE_UNKNOWN; + } else { + if (properties->client.max_early_data_size != NULL) + *properties->client.max_early_data_size = 0; + properties->client.early_data_acceptance = PTLS_EARLY_DATA_REJECTED; + } } /* use the default key share if still not undetermined */ From 374fe534639bc1bc935af495eddef82196c5bd84 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Mon, 7 Aug 2023 14:25:27 +0900 Subject: [PATCH 25/61] retain the pointer to hash algorithm in psk object --- include/picotls.h | 6 +++++- lib/picotls.c | 33 ++++++++++++++++++--------------- t/picotls.c | 8 ++++++++ 3 files changed, 31 insertions(+), 16 deletions(-) diff --git a/include/picotls.h b/include/picotls.h index 024025cfd..af08dc924 100644 --- a/include/picotls.h +++ b/include/picotls.h @@ -779,7 +779,11 @@ struct st_ptls_context_t { struct st_ptls_external_psk_t { ptls_iovec_t identity; ptls_iovec_t secret; - uint16_t csid; /* PTLS_CIPHER_SUITE_XXX; leave 0 to default to one of the SHA256 ones */ + /** + * (mandatory) hash algorithm associated to the PSK; cipher-suites not sharing the same `ptls_hash_algorithm_t` will be + * ignored + */ + const ptls_hash_algorithm_t *hash; } pre_shared_key; /** * ECH diff --git a/lib/picotls.c b/lib/picotls.c index 6682d477f..a96b19345 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -1981,8 +1981,11 @@ static int decode_signature_algorithms(struct st_ptls_signature_algorithms_t *sa return ret; } +/** + * @param hash optional argument for restricting the underlying hash algorithm + */ 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, int server_chacha_priority, uint16_t csid_for_psk) + const uint8_t *const end, int server_preference, int server_chacha_priority, ptls_hash_algorithm_t *hash) { size_t found_index = SIZE_MAX; int ret; @@ -1992,7 +1995,7 @@ static int select_cipher(ptls_cipher_suite_t **selected, ptls_cipher_suite_t **c if ((ret = ptls_decode16(&id, &src, end)) != 0) goto Exit; for (size_t i = 0; candidates[i] != NULL; ++i) { - if (candidates[i]->id == id && (csid_for_psk == 0 || id == csid_for_psk)) { + if (candidates[i]->id == id && (hash == NULL || candidates[i]->hash == hash)) { if (server_preference && !(server_chacha_priority && id == PTLS_CIPHER_SUITE_CHACHA20_POLY1305_SHA256)) { /* preserve smallest matching index, and proceed to the next input */ if (i < found_index) { @@ -2359,10 +2362,15 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ /* try PSK unless we are trying to resume */ if (tls->ctx->pre_shared_key.identity.base != NULL) { assert(psk_secret.base == NULL); - uint16_t csid = tls->ctx->pre_shared_key.csid; + assert(tls->ctx->pre_shared_key.hash != NULL); tls->client.offered_psk = 1; - tls->cipher_suite = - ptls_find_cipher_suite(tls->ctx->cipher_suites, csid == 0 ? PTLS_CIPHER_SUITE_AES_128_GCM_SHA256 : csid); + for (size_t i = 0; tls->ctx->cipher_suites[i] != NULL; ++i) { + if (tls->ctx->cipher_suites[i]->hash == tls->ctx->pre_shared_key.hash) { + tls->cipher_suite = tls->ctx->cipher_suites[i]; + break; + } + } + assert(tls->cipher_suite != NULL && "no compatible cipher-suite provided that matches psk.hash"); psk_secret = tls->ctx->pre_shared_key.secret; psk_identity = tls->ctx->pre_shared_key.identity; binder_key_label = "ext binder"; @@ -4428,22 +4436,17 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl /* can we try external psk handshake below? */ can_try_external_psk = (!is_second_flight && ch->psk.hash_end != 0 && (ch->psk.ke_modes & ((1u << PTLS_PSK_KE_MODE_PSK) | (1u << PTLS_PSK_KE_MODE_PSK_DHE))) != 0 && - properties != NULL && tls->ctx->pre_shared_key.identity.base != NULL && - tls->ctx->pre_shared_key.secret.base != NULL && !tls->ctx->require_client_authentication); + tls->ctx->pre_shared_key.identity.base != NULL && tls->ctx->pre_shared_key.secret.base != NULL && + !tls->ctx->require_client_authentication); /* select (or check) cipher-suite, create key_schedule */ { /* for external PSK, determine a compatible cipher suite; if none specified, it needs to default to a SHA256 one */ /* TODO: for resumption PSK, can we get the ticket_csid sooner so we could use that here? */ - uint16_t csid_for_psk = 0; - if (can_try_external_psk && will_match_external_psk(ch, &tls->ctx->pre_shared_key)) - csid_for_psk = - tls->ctx->pre_shared_key.csid == 0 ? PTLS_CIPHER_SUITE_AES_128_GCM_SHA256 : tls->ctx->pre_shared_key.csid; - ptls_cipher_suite_t *cs; - if ((ret = - select_cipher(&cs, tls->ctx->cipher_suites, ch->cipher_suites.base, ch->cipher_suites.base + ch->cipher_suites.len, - tls->ctx->server_cipher_preference, tls->ctx->server_cipher_chacha_priority, csid_for_psk)) != 0) + if ((ret = select_cipher(&cs, tls->ctx->cipher_suites, ch->cipher_suites.base, + ch->cipher_suites.base + ch->cipher_suites.len, tls->ctx->server_cipher_preference, + tls->ctx->server_cipher_chacha_priority, tls->ctx->pre_shared_key.hash)) != 0) goto Exit; if (!is_second_flight) { tls->cipher_suite = cs; diff --git a/t/picotls.c b/t/picotls.c index 655a43cf2..30ef177b6 100644 --- a/t/picotls.c +++ b/t/picotls.c @@ -1542,6 +1542,13 @@ static void do_test_pre_shared_key(int clear_ke) assert(ctx->pre_shared_key.identity.len == 0 && ctx->pre_shared_key.secret.len == 0); ctx->pre_shared_key.identity = ptls_iovec_init("", 1); ctx->pre_shared_key.secret = ptls_iovec_init("hello world", 11); + for (size_t i = 0; ctx->cipher_suites[i] != NULL; ++i) { + if (strcmp(ctx->cipher_suites[i]->hash->name, "sha256") == 0) { + ctx->pre_shared_key.hash = ctx->cipher_suites[i]->hash; + break; + } + } + assert(ctx->pre_shared_key.hash != NULL); ptls_t *client = ptls_new(ctx, 0), *server = ptls_new(ctx, 1); ptls_buffer_t cbuf, sbuf, decbuf; @@ -1627,6 +1634,7 @@ static void do_test_pre_shared_key(int clear_ke) ctx->max_early_data_size = backup.max_early_data_size; ctx->pre_shared_key.identity = ptls_iovec_init(NULL, 0); ctx->pre_shared_key.secret = ptls_iovec_init(NULL, 0); + ctx->pre_shared_key.hash = NULL; } static void test_pre_shared_key(void) From 65e77c931e7e289d487e7c95358eb67c653d1c0e Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Mon, 7 Aug 2023 15:54:44 +0900 Subject: [PATCH 26/61] adjust now that slot for `psk` has been added --- t/openssl.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/t/openssl.c b/t/openssl.c index 4ca49aa38..4c8215e7d 100644 --- a/t/openssl.c +++ b/t/openssl.c @@ -596,15 +596,12 @@ int main(int argc, char **argv) ptls_iovec_t minicrypto_certificate = ptls_iovec_init(SECP256R1_CERTIFICATE, sizeof(SECP256R1_CERTIFICATE) - 1); ptls_minicrypto_init_secp256r1sha256_sign_certificate( &minicrypto_sign_certificate, ptls_iovec_init(SECP256R1_PRIVATE_KEY, sizeof(SECP256R1_PRIVATE_KEY) - 1)); - ptls_context_t minicrypto_ctx = {ptls_minicrypto_random_bytes, - &ptls_get_time, - ptls_minicrypto_key_exchanges, - ptls_minicrypto_cipher_suites, - {&minicrypto_certificate, 1}, - {{NULL}}, - NULL, - NULL, - &minicrypto_sign_certificate.super}; + ptls_context_t minicrypto_ctx = {.random_bytes = ptls_minicrypto_random_bytes, + .get_time = &ptls_get_time, + .key_exchanges = ptls_minicrypto_key_exchanges, + .cipher_suites = ptls_minicrypto_cipher_suites, + .certificates = {&minicrypto_certificate, 1}, + .sign_certificate = &minicrypto_sign_certificate.super}; ctx = &openssl_ctx; ctx_peer = &minicrypto_ctx; subtest("vs. minicrypto", test_picotls); From ff788d7292ff64b7ded31b6e5ed6ac06b646cd27 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Mon, 7 Aug 2023 16:07:02 +0900 Subject: [PATCH 27/61] clients can send KeyShare extension, we are not accepting them --- lib/picotls.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/picotls.c b/lib/picotls.c index a96b19345..a9bbf1c86 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -2604,7 +2604,7 @@ static int decode_server_hello(ptls_t *tls, struct st_ptls_server_hello_t *sh, c break; case PTLS_EXTENSION_TYPE_KEY_SHARE: if (tls->ctx->key_exchanges == NULL) { - ret = PTLS_ALERT_ILLEGAL_PARAMETER; + ret = PTLS_ALERT_HANDSHAKE_FAILURE; goto Exit; } if (sh->is_retry_request) { From fc41e2f3e11b31462aab4e9ccbd57b0a4d7dd5d0 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Mon, 7 Aug 2023 16:09:57 +0900 Subject: [PATCH 28/61] remove function no longer used (amends 374fe53) --- lib/picotls.c | 16 ---------------- 1 file changed, 16 deletions(-) diff --git a/lib/picotls.c b/lib/picotls.c index a9bbf1c86..fc71c5bce 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -4042,22 +4042,6 @@ 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'; } -/** - * Will one of the PSKs provided by the client match our external PSK? - * FIXME: Find a good way to avoid duplicate code and having to match the PSK here and in try_psk_handshake. - */ -static int will_match_external_psk(const struct st_ptls_client_hello_t *ch, const struct st_ptls_external_psk_t *external_psk) -{ - for (size_t psk_index = 0; psk_index < ch->psk.identities.count; ++psk_index) { - const struct st_ptls_client_hello_psk_t *identity = ch->psk.identities.list + psk_index; - if (identity->identity.len == external_psk->identity.len && - memcmp(identity->identity.base, external_psk->identity.base, identity->identity.len) == 0) { - return 1; - } - } - return 0; -} - /** * Looks for a PSK identity that can be used, and if found, updates the handshake state and returns the necessary variables. If * external_psk is set, only tries handshake using those keys provided. Otherwise, tries resumption. From fce9b09e911c7039a433e0be8b03fa82ea7ed409 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Mon, 7 Aug 2023 16:12:45 +0900 Subject: [PATCH 29/61] revert unneccesary change --- lib/picotls.c | 6 ++---- 1 file changed, 2 insertions(+), 4 deletions(-) diff --git a/lib/picotls.c b/lib/picotls.c index fc71c5bce..8a8b3076b 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -4423,10 +4423,8 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl tls->ctx->pre_shared_key.identity.base != NULL && tls->ctx->pre_shared_key.secret.base != NULL && !tls->ctx->require_client_authentication); - /* select (or check) cipher-suite, create key_schedule */ - { - /* for external PSK, determine a compatible cipher suite; if none specified, it needs to default to a SHA256 one */ - /* TODO: for resumption PSK, can we get the ticket_csid sooner so we could use that here? */ + + { /* select (or check) cipher-suite, create key_schedule */ ptls_cipher_suite_t *cs; if ((ret = select_cipher(&cs, tls->ctx->cipher_suites, ch->cipher_suites.base, ch->cipher_suites.base + ch->cipher_suites.len, tls->ctx->server_cipher_preference, From dd99f6f74b2ebf7611def433925770a3a6376f1c Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Mon, 7 Aug 2023 17:14:27 +0900 Subject: [PATCH 30/61] no need to cache the value --- lib/picotls.c | 13 ++++--------- 1 file changed, 4 insertions(+), 9 deletions(-) diff --git a/lib/picotls.c b/lib/picotls.c index 8a8b3076b..b751d5110 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -4281,7 +4281,6 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl 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; - int can_try_external_psk = 0; ptls_buffer_init(&ech.ch_inner, "", 0); @@ -4417,13 +4416,6 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl } } - /* can we try external psk handshake below? */ - can_try_external_psk = (!is_second_flight && ch->psk.hash_end != 0 && - (ch->psk.ke_modes & ((1u << PTLS_PSK_KE_MODE_PSK) | (1u << PTLS_PSK_KE_MODE_PSK_DHE))) != 0 && - tls->ctx->pre_shared_key.identity.base != NULL && tls->ctx->pre_shared_key.secret.base != NULL && - !tls->ctx->require_client_authentication); - - { /* select (or check) cipher-suite, create key_schedule */ ptls_cipher_suite_t *cs; if ((ret = select_cipher(&cs, tls->ctx->cipher_suites, ch->cipher_suites.base, @@ -4480,7 +4472,10 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl } /* try external psk handshake */ - if (can_try_external_psk) { + if (!is_second_flight && ch->psk.hash_end != 0 && + (ch->psk.ke_modes & ((1u << PTLS_PSK_KE_MODE_PSK) | (1u << PTLS_PSK_KE_MODE_PSK_DHE))) != 0 && + tls->ctx->pre_shared_key.identity.base != NULL && tls->ctx->pre_shared_key.secret.base != NULL && + !tls->ctx->require_client_authentication) { if ((ret = try_psk_handshake(tls, &psk_index, &accept_early_data, ch, ptls_iovec_init(message.base, ch->psk.hash_end - message.base), &tls->ctx->pre_shared_key)) != 0) From 63ba5e4a9ffc0eb84b9b78e199ea1badcaf1296e Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Mon, 7 Aug 2023 17:19:53 +0900 Subject: [PATCH 31/61] external PSK to be used only when not resuming --- lib/picotls.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/picotls.c b/lib/picotls.c index b751d5110..2913e24e4 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -2335,7 +2335,7 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ sni_name); } } - /* setup resumption-related data. If successful, resumption_secret becomes a non-zero value. */ + /* setup resumption-related data. If successful, psk_secret becomes a non-zero value. */ if (properties->client.session_ticket.base != NULL && tls->ctx->key_exchanges != NULL && tls->ctx->pre_shared_key.identity.base == NULL) { ptls_key_exchange_algorithm_t *key_share = NULL; @@ -2359,9 +2359,8 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ } } - /* try PSK unless we are trying to resume */ - if (tls->ctx->pre_shared_key.identity.base != NULL) { - assert(psk_secret.base == NULL); + /* use external PSK if provided and resumption is not to be used */ + if (psk_secret.base == NULL && tls->ctx->pre_shared_key.identity.base != NULL) { assert(tls->ctx->pre_shared_key.hash != NULL); tls->client.offered_psk = 1; for (size_t i = 0; tls->ctx->cipher_suites[i] != NULL; ++i) { From 06709869cacb70b7fc1aac7556bd7ed573bc0329 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Tue, 8 Aug 2023 11:02:35 +0900 Subject: [PATCH 32/61] amends 65e77c9 --- t/minicrypto.c | 15 ++++++--------- 1 file changed, 6 insertions(+), 9 deletions(-) diff --git a/t/minicrypto.c b/t/minicrypto.c index 17a05d26f..e485089f5 100644 --- a/t/minicrypto.c +++ b/t/minicrypto.c @@ -150,15 +150,12 @@ int main(int argc, char **argv) ptls_minicrypto_init_secp256r1sha256_sign_certificate(&sign_certificate, ptls_iovec_init(SECP256R1_PRIVATE_KEY, SECP256R1_PRIVATE_KEY_SIZE)); - ptls_context_t ctxbuf = {ptls_minicrypto_random_bytes, - &ptls_get_time, - ptls_minicrypto_key_exchanges, - ptls_minicrypto_cipher_suites, - {&cert, 1}, - {{NULL}}, - NULL, - NULL, - &sign_certificate.super}; + ptls_context_t ctxbuf = {.random_bytes = ptls_minicrypto_random_bytes, + .get_time = &ptls_get_time, + .key_exchanges = ptls_minicrypto_key_exchanges, + .cipher_suites = ptls_minicrypto_cipher_suites, + .certificates = {&cert, 1}, + .sign_certificate = &sign_certificate.super}; ctx = ctx_peer = &ctxbuf; ADD_FFX_AES128_ALGORITHMS(minicrypto); ADD_FFX_CHACHA20_ALGORITHMS(minicrypto); From e3e11fc0f8f903784e9d63d92a6e6f10034694ce Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Tue, 8 Aug 2023 11:08:28 +0900 Subject: [PATCH 33/61] external PSK can be obtained via `tls->ctx` --- lib/picotls.c | 21 ++++++++++++--------- 1 file changed, 12 insertions(+), 9 deletions(-) diff --git a/lib/picotls.c b/lib/picotls.c index 2913e24e4..4a5044a22 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -4046,7 +4046,7 @@ static int vec_is_string(ptls_iovec_t x, const char *y) * external_psk is set, only tries handshake using those keys provided. Otherwise, tries resumption. */ static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_data, struct st_ptls_client_hello_t *ch, - ptls_iovec_t ch_trunc, const struct st_ptls_external_psk_t *external_psk) + ptls_iovec_t ch_trunc) { ptls_buffer_t decbuf; ptls_iovec_t secret, ticket_server_name, ticket_negotiated_protocol; @@ -4054,6 +4054,7 @@ static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_d uint32_t age_add; uint16_t ticket_key_exchange_id, ticket_csid; uint8_t binder_key[PTLS_MAX_DIGEST_SIZE]; + const char *binder_label = "res binder"; int ret; ptls_buffer_init(&decbuf, "", 0); @@ -4061,12 +4062,15 @@ static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_d for (*psk_index = 0; *psk_index < ch->psk.identities.count; ++*psk_index) { struct st_ptls_client_hello_psk_t *identity = ch->psk.identities.list + *psk_index; /* negotiate using fixed pre-shared key */ - if (external_psk != NULL) { - if (identity->identity.len == external_psk->identity.len && - memcmp(identity->identity.base, external_psk->identity.base, identity->identity.len) == 0) { + if (tls->ctx->pre_shared_key.secret.base != NULL) { + assert(tls->ctx->pre_shared_key.secret.len != 0 && tls->ctx->pre_shared_key.identity.len != 0 && + tls->ctx->pre_shared_key.hash != NULL && "`ptls_context_t::pre_shared_key` in incosistent state"); + if (identity->identity.len == tls->ctx->pre_shared_key.identity.len && + memcmp(identity->identity.base, tls->ctx->pre_shared_key.identity.base, identity->identity.len) == 0) { *accept_early_data = ch->psk.early_data_indication && *psk_index == 0; tls->key_share = NULL; - secret = external_psk->secret; + secret = tls->ctx->pre_shared_key.secret; + binder_label = "ext binder"; goto Found; } continue; @@ -4150,7 +4154,7 @@ static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_d Found: if ((ret = key_schedule_extract(tls->key_schedule, secret)) != 0) goto Exit; - if ((ret = derive_secret(tls->key_schedule, binder_key, external_psk ? "ext binder" : "res binder")) != 0) + if ((ret = derive_secret(tls->key_schedule, binder_key, binder_label)) != 0) goto Exit; 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, @@ -4476,8 +4480,7 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl tls->ctx->pre_shared_key.identity.base != NULL && tls->ctx->pre_shared_key.secret.base != NULL && !tls->ctx->require_client_authentication) { if ((ret = try_psk_handshake(tls, &psk_index, &accept_early_data, ch, - ptls_iovec_init(message.base, ch->psk.hash_end - message.base), &tls->ctx->pre_shared_key)) != - 0) + ptls_iovec_init(message.base, ch->psk.hash_end - message.base))) != 0) goto Exit; } @@ -4582,7 +4585,7 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl (ch->psk.ke_modes & ((1u << PTLS_PSK_KE_MODE_PSK) | (1u << PTLS_PSK_KE_MODE_PSK_DHE))) != 0 && tls->ctx->encrypt_ticket != NULL && !tls->ctx->require_client_authentication) { if ((ret = try_psk_handshake(tls, &psk_index, &accept_early_data, ch, - ptls_iovec_init(message.base, ch->psk.hash_end - message.base), NULL)) != 0) { + ptls_iovec_init(message.base, ch->psk.hash_end - message.base))) != 0) { goto Exit; } } From 135a2c865450bcee17f01c0e29d7183523ec1d66 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Tue, 8 Aug 2023 11:09:02 +0900 Subject: [PATCH 34/61] clang-format --- lib/picotls.c | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/lib/picotls.c b/lib/picotls.c index 4a5044a22..a947e4e85 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -1872,10 +1872,9 @@ static int send_session_ticket(ptls_t *tls, ptls_message_emitter_t *emitter) tls->ctx->random_bytes(&ticket_age_add, sizeof(ticket_age_add)); /* build the raw nsk */ - if (tls->key_share != NULL && - (ret = encode_session_identifier(tls->ctx, &session_id, ticket_age_add, ptls_iovec_init(NULL, 0), tls->key_schedule, - tls->server_name, tls->key_share->id, tls->cipher_suite->id, - tls->negotiated_protocol)) != 0) + if (tls->key_share != NULL && (ret = encode_session_identifier(tls->ctx, &session_id, ticket_age_add, ptls_iovec_init(NULL, 0), + tls->key_schedule, tls->server_name, tls->key_share->id, + tls->cipher_suite->id, tls->negotiated_protocol)) != 0) goto Exit; /* encrypt and send */ From b331c219daf2132a79d1ab173be5863ef62208cc Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Tue, 8 Aug 2023 11:18:50 +0900 Subject: [PATCH 35/61] `ptls_hash_algorithm_t` is const --- include/picotls.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/picotls.h b/include/picotls.h index af08dc924..79b2acce4 100644 --- a/include/picotls.h +++ b/include/picotls.h @@ -783,7 +783,7 @@ struct st_ptls_context_t { * (mandatory) hash algorithm associated to the PSK; cipher-suites not sharing the same `ptls_hash_algorithm_t` will be * ignored */ - const ptls_hash_algorithm_t *hash; + ptls_hash_algorithm_t *hash; } pre_shared_key; /** * ECH From eb696e1745eb78f5287bda1c98f7a8dd01b57476 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Tue, 8 Aug 2023 11:28:45 +0900 Subject: [PATCH 36/61] msvc doesn't like `= {}` --- t/picotls.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/picotls.c b/t/picotls.c index 30ef177b6..ed9b07c7e 100644 --- a/t/picotls.c +++ b/t/picotls.c @@ -1556,7 +1556,7 @@ static void do_test_pre_shared_key(int clear_ke) ptls_buffer_init(&sbuf, "", 0); ptls_buffer_init(&decbuf, "", 0); - ptls_handshake_properties_t client_prop = {}; + ptls_handshake_properties_t client_prop = {{{{NULL}}}}; size_t client_max_early_data_size = 0; client_prop.client.max_early_data_size = &client_max_early_data_size; From 1a1ecc8aa81a92194835559f7d06781928a9d341 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Tue, 17 Oct 2023 11:57:19 +0900 Subject: [PATCH 37/61] if the client offered PSK but the server did not use it, call it a handshake failure --- lib/picotls.c | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/lib/picotls.c b/lib/picotls.c index 77e96c60b..0e4d84d9d 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -2821,6 +2821,12 @@ static int client_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl tls->key_schedule->hashes[0].ctx_outer = NULL; } + /* if we (the client) offered PSK but the server did not use that, we call it a handshake failure */ + if (tls->ctx->pre_shared_key.identity.base != NULL && !tls->is_psk_handshake) { + ret = PTLS_ALERT_HANDSHAKE_FAILURE; + goto Exit; + } + ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len, 0); if (sh.peerkey.base != NULL) { From 3aed2aeec508351ae8e2e74b59316e0adf1e2993 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Tue, 17 Oct 2023 12:13:17 +0900 Subject: [PATCH 38/61] ditto on server side --- include/picotls.h | 1 + lib/picotls.c | 7 +++++++ 2 files changed, 8 insertions(+) diff --git a/include/picotls.h b/include/picotls.h index 5ed5ca994..1f7ebc15c 100644 --- a/include/picotls.h +++ b/include/picotls.h @@ -224,6 +224,7 @@ extern "C" { #define PTLS_ALERT_MISSING_EXTENSION 109 #define PTLS_ALERT_UNSUPPORTED_EXTENSION 110 #define PTLS_ALERT_UNRECOGNIZED_NAME 112 +#define PTLS_ALERT_UNKNOWN_PSK_IDENTITY 115 #define PTLS_ALERT_CERTIFICATE_REQUIRED 116 #define PTLS_ALERT_NO_APPLICATION_PROTOCOL 120 #define PTLS_ALERT_ECH_REQUIRED 121 diff --git a/lib/picotls.c b/lib/picotls.c index 0e4d84d9d..5b64e0c56 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -4599,6 +4599,13 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl } } + /* If the server was setup to use an external PSK but failed to agree, abort the handshake. Because external PSK is a form of + * mutual authentication, we should abort continue the handshake upon negotiation failure, at least by default. */ + if (tls->ctx->pre_shared_key.identity.base != NULL && tls->ctx->require_client_authentication && psk_index == SIZE_MAX) { + ret = PTLS_ALERT_UNKNOWN_PSK_IDENTITY; + goto Exit; + } + /* If client authentication is enabled, we always force a full handshake. * TODO: Check for `post_handshake_auth` extension and if that is present, do not force full handshake! * Remove also the check `!require_client_authentication` above. From 651a06032465441143708d1795bc31e09c56f516 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Tue, 17 Oct 2023 16:28:19 +0900 Subject: [PATCH 39/61] [on_client_hello_cb] pass list of PSK identities offerred by client, so that the server can swap ptls_context_t based on the availability of an external PSK --- include/picotls.h | 10 ++++++++++ lib/picotls.c | 21 +++++++++------------ 2 files changed, 19 insertions(+), 12 deletions(-) diff --git a/include/picotls.h b/include/picotls.h index 1f7ebc15c..53b21a1cf 100644 --- a/include/picotls.h +++ b/include/picotls.h @@ -612,6 +612,12 @@ typedef const struct st_ptls_hpke_cipher_suite_t { ret (*cb)(struct st_ptls_##name##_t * self, __VA_ARGS__); \ } ptls_##name##_t +typedef struct st_ptls_client_hello_psk_identity_t { + ptls_iovec_t identity; + uint32_t obfuscated_ticket_age; + ptls_iovec_t binder; +} ptls_client_hello_psk_identity_t; + /** * arguments passsed to the on_client_hello callback */ @@ -647,6 +653,10 @@ typedef struct st_ptls_on_client_hello_parameters_t { const uint8_t *list; size_t count; } server_certificate_types; + struct { + const ptls_client_hello_psk_identity_t *list; + size_t count; + } psk_identities; /** * set to 1 if ClientHello is too old (or too new) to be handled by picotls */ diff --git a/lib/picotls.c b/lib/picotls.c index 5b64e0c56..a9f454d7d 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -325,12 +325,6 @@ struct st_ptls_record_t { const uint8_t *fragment; }; -struct st_ptls_client_hello_psk_t { - ptls_iovec_t identity; - uint32_t obfuscated_ticket_age; - ptls_iovec_t binder; -}; - #define MAX_UNKNOWN_EXTENSIONS 16 #define MAX_CERTIFICATE_TYPES 8 @@ -381,7 +375,7 @@ struct st_ptls_client_hello_t { struct { const uint8_t *hash_end; struct { - struct st_ptls_client_hello_psk_t list[4]; + ptls_client_hello_psk_identity_t list[4]; size_t count; } identities; unsigned ke_modes; @@ -3751,7 +3745,7 @@ static int decode_client_hello(ptls_context_t *ctx, struct st_ptls_client_hello_ size_t num_identities = 0; ptls_decode_open_block(src, end, 2, { do { - struct st_ptls_client_hello_psk_t psk = {{NULL}}; + ptls_client_hello_psk_identity_t psk = {{NULL}}; ptls_decode_open_block(src, end, 2, { if (end - src < 1) { ret = PTLS_ALERT_DECODE_ERROR; @@ -3976,7 +3970,8 @@ static inline int call_on_client_hello_cb(ptls_t *tls, ptls_iovec_t server_name, ptls_iovec_t cipher_suites, 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 uint8_t *server_cert_types, - size_t num_server_cert_types, int incompatible_version) + size_t num_server_cert_types, const ptls_client_hello_psk_identity_t *psk_identities, + size_t num_psk_identities, int incompatible_version) { if (tls->ctx->on_client_hello == NULL) return 0; @@ -3988,6 +3983,7 @@ static inline int call_on_client_hello_cb(ptls_t *tls, ptls_iovec_t server_name, {sig_algos, num_sig_algos}, {cert_comp_algos, num_cert_comp_algos}, {server_cert_types, num_server_cert_types}, + {psk_identities, num_psk_identities}, incompatible_version}; return tls->ctx->on_client_hello->cb(tls->ctx->on_client_hello, tls, ¶ms); } @@ -4011,7 +4007,7 @@ static int check_client_hello_constraints(ptls_context_t *ctx, struct st_ptls_cl if (!is_second_flight) { int ret; if ((ret = call_on_client_hello_cb(tls_cbarg, ch->server_name, raw_message, ch->cipher_suites, ch->alpn.list, - ch->alpn.count, NULL, 0, NULL, 0, NULL, 0, 1)) != 0) + ch->alpn.count, NULL, 0, NULL, 0, NULL, 0, NULL, 0, 1)) != 0) return ret; } return PTLS_ALERT_PROTOCOL_VERSION; @@ -4069,7 +4065,7 @@ static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_d ptls_buffer_init(&decbuf, "", 0); for (*psk_index = 0; *psk_index < ch->psk.identities.count; ++*psk_index) { - struct st_ptls_client_hello_psk_t *identity = ch->psk.identities.list + *psk_index; + ptls_client_hello_psk_identity_t *identity = ch->psk.identities.list + *psk_index; /* negotiate using fixed pre-shared key */ if (tls->ctx->pre_shared_key.secret.base != NULL) { assert(tls->ctx->pre_shared_key.secret.len != 0 && tls->ctx->pre_shared_key.identity.len != 0 && @@ -4404,7 +4400,8 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl if ((ret = call_on_client_hello_cb(tls, server_name, message, ch->cipher_suites, 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->server_certificate_types.list, ch->server_certificate_types.count, 0)) != 0) + ch->server_certificate_types.list, ch->server_certificate_types.count, + ch->psk.identities.list, ch->psk.identities.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 From e220cdab7f16aeb0ffd7d04036e497c4e56f8124 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Thu, 19 Oct 2023 15:27:26 +0900 Subject: [PATCH 40/61] external PSK mode is orthogonal to X.509 client auth --- lib/picotls.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/lib/picotls.c b/lib/picotls.c index a9f454d7d..985f69a5c 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -4598,7 +4598,7 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl /* If the server was setup to use an external PSK but failed to agree, abort the handshake. Because external PSK is a form of * mutual authentication, we should abort continue the handshake upon negotiation failure, at least by default. */ - if (tls->ctx->pre_shared_key.identity.base != NULL && tls->ctx->require_client_authentication && psk_index == SIZE_MAX) { + if (tls->ctx->pre_shared_key.identity.base != NULL && psk_index == SIZE_MAX) { ret = PTLS_ALERT_UNKNOWN_PSK_IDENTITY; goto Exit; } From 48ffce50a063f9ae30fb30994c8e3b69168c8141 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Mon, 1 Apr 2024 14:40:40 +0900 Subject: [PATCH 41/61] follow the changes made to other backend tests --- t/mbedtls.c | 30 ++++++++++++------------------ 1 file changed, 12 insertions(+), 18 deletions(-) diff --git a/t/mbedtls.c b/t/mbedtls.c index d3651de14..af3ec95fc 100644 --- a/t/mbedtls.c +++ b/t/mbedtls.c @@ -112,30 +112,24 @@ int main(int argc, char **argv) ptls_minicrypto_secp256r1sha256_sign_certificate_t minicrypto_sign_certificate; ptls_minicrypto_init_secp256r1sha256_sign_certificate( &minicrypto_sign_certificate, ptls_iovec_init(SECP256R1_PRIVATE_KEY, sizeof(SECP256R1_PRIVATE_KEY) - 1)); - ptls_context_t minicrypto_ctx = {ptls_minicrypto_random_bytes, - &ptls_get_time, - ptls_minicrypto_key_exchanges, - ptls_minicrypto_cipher_suites, - {&secp256r1_certificate, 1}, - {{NULL}}, - NULL, - NULL, - &minicrypto_sign_certificate.super}; + ptls_context_t minicrypto_ctx = {.random_bytes = ptls_minicrypto_random_bytes, + .get_time = &ptls_get_time, + .key_exchanges = ptls_minicrypto_key_exchanges, + .cipher_suites = ptls_minicrypto_cipher_suites, + .certificates = {&secp256r1_certificate, 1}, + .sign_certificate = &minicrypto_sign_certificate.super}; /* context using mbedtls as backend; minicrypto is used for signing certificate as the mbedtls backend does not (yet) have the * capability */ ptls_minicrypto_secp256r1sha256_sign_certificate_t mbedtls_sign_certificate; ptls_minicrypto_init_secp256r1sha256_sign_certificate( &mbedtls_sign_certificate, ptls_iovec_init(SECP256R1_PRIVATE_KEY, sizeof(SECP256R1_PRIVATE_KEY) - 1)); - ptls_context_t mbedtls_ctx = {ptls_mbedtls_random_bytes, - &ptls_get_time, - ptls_mbedtls_key_exchanges, - ptls_mbedtls_cipher_suites, - {&secp256r1_certificate, 1}, - {{NULL}}, - NULL, - NULL, - &mbedtls_sign_certificate.super}; + ptls_context_t mbedtls_ctx = {.random_bytes = ptls_mbedtls_random_bytes, + .get_time = &ptls_get_time, + .key_exchanges = ptls_mbedtls_key_exchanges, + .cipher_suites = ptls_mbedtls_cipher_suites, + .certificates = {&secp256r1_certificate, 1}, + .sign_certificate = &mbedtls_sign_certificate.super}; ctx = &mbedtls_ctx; ctx_peer = &mbedtls_ctx; From c7a825a7367adb9a55d78304350dee085e6f8520 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Mon, 1 Apr 2024 16:06:08 +0900 Subject: [PATCH 42/61] simplify, reducing diff from master --- lib/picotls.c | 224 ++++++++++++++++++++++++-------------------------- 1 file changed, 108 insertions(+), 116 deletions(-) diff --git a/lib/picotls.c b/lib/picotls.c index 279d2eb2f..e795bfdd9 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -4437,9 +4437,9 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl { /* select (or check) cipher-suite, create key_schedule */ ptls_cipher_suite_t *cs; - if ((ret = - select_cipher(&cs, tls->ctx->cipher_suites, ch->cipher_suites.base, ch->cipher_suites.base + ch->cipher_suites.len, - tls->ctx->server_cipher_preference, tls->ctx->server_cipher_chacha_priority, tls->ctx->pre_shared_key.hash)) != 0) + if ((ret = select_cipher(&cs, tls->ctx->cipher_suites, ch->cipher_suites.base, + ch->cipher_suites.base + ch->cipher_suites.len, tls->ctx->server_cipher_preference, + tls->ctx->server_cipher_chacha_priority, tls->ctx->pre_shared_key.hash)) != 0) goto Exit; if (!is_second_flight) { tls->cipher_suite = cs; @@ -4464,135 +4464,126 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl }); } - /* use cookie to check the integrity of the handshake, and update the context */ - if (!is_second_flight && ch->cookie.all.len != 0 && key_share.algorithm != NULL) { - { - uint8_t sig[PTLS_MAX_DIGEST_SIZE]; - size_t sigsize = tls->ctx->cipher_suites[0]->hash->digest_size; - if ((ret = calc_cookie_signature(tls, properties, key_share.algorithm, ch->cookie.tbs, sig)) != 0) - goto Exit; - if (!(ch->cookie.signature.len == sigsize && ptls_mem_equal(ch->cookie.signature.base, sig, sigsize))) { - ret = PTLS_ALERT_HANDSHAKE_FAILURE; - goto Exit; - } - } - /* 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, 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); - }); - }, - {}); - emitter->buf->off = hrr_start; - is_second_flight = 1; - } - - /* try external psk handshake */ - if (!is_second_flight && ch->psk.hash_end != 0 && - (ch->psk.ke_modes & ((1u << PTLS_PSK_KE_MODE_PSK) | (1u << PTLS_PSK_KE_MODE_PSK_DHE))) != 0 && - tls->ctx->pre_shared_key.identity.base != NULL && tls->ctx->pre_shared_key.secret.base != NULL && - !tls->ctx->require_client_authentication) { - if ((ret = try_psk_handshake(tls, &psk_index, &accept_early_data, ch, - ptls_iovec_init(message.base, ch->psk.hash_end - message.base))) != 0) - goto Exit; - } + if (!is_second_flight) { + if (ch->cookie.all.len != 0 && key_share.algorithm != NULL) { - /* send HelloRetryRequest if enforced by config or upon key-share mismatch, unless PSK has already been selected */ - if (!is_second_flight && psk_index == SIZE_MAX && - (key_share.algorithm == NULL || (properties != NULL && properties->server.enforce_retry))) { - ptls_key_exchange_algorithm_t *negotiated_group = NULL; - if (tls->ctx->key_exchanges != NULL) { + /* use cookie to check the integrity of the handshake, and update the context */ + { + uint8_t sig[PTLS_MAX_DIGEST_SIZE]; + size_t sigsize = tls->ctx->cipher_suites[0]->hash->digest_size; + if ((ret = calc_cookie_signature(tls, properties, key_share.algorithm, ch->cookie.tbs, sig)) != 0) + goto Exit; + if (!(ch->cookie.signature.len == sigsize && ptls_mem_equal(ch->cookie.signature.base, sig, sigsize))) { + ret = PTLS_ALERT_HANDSHAKE_FAILURE; + goto Exit; + } + } + /* 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, 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); + }); + }, + {}); + emitter->buf->off = hrr_start; + is_second_flight = 1; + + } else if (ch->key_shares.base != NULL && tls->ctx->key_exchanges != NULL && + (key_share.algorithm == NULL || (properties != NULL && properties->server.enforce_retry))) { + /* send HelloRetryRequest, when trying to negotiate the key share but enforced by config or upon key-share mismatch */ if (ch->negotiated_groups.base == NULL) { ret = PTLS_ALERT_MISSING_EXTENSION; goto Exit; } + ptls_key_exchange_algorithm_t *negotiated_group; 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, 0); - assert(tls->key_schedule->generation == 0); - - /* 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, 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; - if (ptls_is_ech_handshake(tls, NULL, 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, { - /* push to-be-signed data */ - size_t tbs_start = sendbuf->off; + ptls__key_schedule_update_hash(tls->key_schedule, message.base, message.len, 0); + assert(tls->key_schedule->generation == 0); + + /* 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, 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; + if (ptls_is_ech_handshake(tls, NULL, 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) + } + }, + { + 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; - }); - if (retry_uses_cookie) { - if ((ret = push_change_cipher_spec(tls, emitter)) != 0) - goto Exit; - ret = PTLS_ERROR_STATELESS_RETRY; - } else { - tls->state = PTLS_STATE_SERVER_EXPECT_SECOND_CLIENT_HELLO; - if (ch->psk.early_data_indication) - tls->server.early_data_skipped_bytes = 0; - ret = PTLS_ERROR_IN_PROGRESS; + ret = PTLS_ERROR_STATELESS_RETRY; + } else { + tls->state = PTLS_STATE_SERVER_EXPECT_SECOND_CLIENT_HELLO; + if (ch->psk.early_data_indication) + tls->server.early_data_skipped_bytes = 0; + ret = PTLS_ERROR_IN_PROGRESS; + } + goto Exit; } - goto Exit; } /* handle unknown extensions */ @@ -4600,9 +4591,10 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl goto Exit; /* try psk handshake */ - if (!is_second_flight && psk_index == SIZE_MAX && ch->psk.hash_end != 0 && + if (!is_second_flight && ch->psk.hash_end != 0 && (ch->psk.ke_modes & ((1u << PTLS_PSK_KE_MODE_PSK) | (1u << PTLS_PSK_KE_MODE_PSK_DHE))) != 0 && - tls->ctx->encrypt_ticket != NULL && !tls->ctx->require_client_authentication) { + (tls->ctx->encrypt_ticket != NULL || tls->ctx->pre_shared_key.identity.base != NULL) && + !tls->ctx->require_client_authentication) { if ((ret = try_psk_handshake(tls, &psk_index, &accept_early_data, ch, ptls_iovec_init(message.base, ch->psk.hash_end - message.base))) != 0) { goto Exit; From 859ba9db4ff5696b2c1e681124202e1567a48553 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Mon, 1 Apr 2024 16:21:00 +0900 Subject: [PATCH 43/61] update picotest --- deps/picotest | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/deps/picotest b/deps/picotest index f390562fd..a99858e0c 160000 --- a/deps/picotest +++ b/deps/picotest @@ -1 +1 @@ -Subproject commit f390562fd4d6919807441721ec05b08f6d8c8d9c +Subproject commit a99858e0c33b97b24cd09ceae729f2be33ec01e1 From 12b2453154501272e6b1ac3adf7d7fb069d03cee Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Mon, 1 Apr 2024 16:21:04 +0900 Subject: [PATCH 44/61] refactor --- t/picotls.c | 16 +++++----------- 1 file changed, 5 insertions(+), 11 deletions(-) diff --git a/t/picotls.c b/t/picotls.c index 79df2e2d1..5572d9933 100644 --- a/t/picotls.c +++ b/t/picotls.c @@ -1621,10 +1621,8 @@ static void test_ech_config_mismatch(void) static void do_test_pre_shared_key(int clear_ke) { - struct { - ptls_key_exchange_algorithm_t **key_exchanges; - uint32_t max_early_data_size; - } backup = {ctx->key_exchanges, ctx->max_early_data_size}; + ptls_context_t ctx_backup = *ctx; + if (clear_ke) ctx->key_exchanges = NULL; ctx->max_early_data_size = 16384; @@ -1719,17 +1717,13 @@ static void do_test_pre_shared_key(int clear_ke) ptls_free(client); ptls_free(server); - ctx->key_exchanges = backup.key_exchanges; - ctx->max_early_data_size = backup.max_early_data_size; - ctx->pre_shared_key.identity = ptls_iovec_init(NULL, 0); - ctx->pre_shared_key.secret = ptls_iovec_init(NULL, 0); - ctx->pre_shared_key.hash = NULL; + *ctx = ctx_backup; } static void test_pre_shared_key(void) { - do_test_pre_shared_key(0); - do_test_pre_shared_key(1); + subtest("without key-share", do_test_pre_shared_key, 0); + subtest("with key-share", do_test_pre_shared_key, 1); } typedef uint8_t traffic_secrets_t[2 /* is_enc */][4 /* epoch */][PTLS_MAX_DIGEST_SIZE /* octets */]; From ddb2121598ea1973040e51863c3682a53e2f12f9 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Tue, 2 Apr 2024 13:32:38 +0900 Subject: [PATCH 45/61] test various forms of psk handshakes (incl. retry) --- t/picotls.c | 77 +++++++++++++++++++++++++++++++++++++++-------------- 1 file changed, 57 insertions(+), 20 deletions(-) diff --git a/t/picotls.c b/t/picotls.c index 5572d9933..cb94a538d 100644 --- a/t/picotls.c +++ b/t/picotls.c @@ -1619,12 +1619,36 @@ static void test_ech_config_mismatch(void) free(retry_configs.base); } -static void do_test_pre_shared_key(int clear_ke) +static void do_test_pre_shared_key(int mode) { ptls_context_t ctx_backup = *ctx; + ptls_key_exchange_algorithm_t *alternate_keyex[3]; + size_t client_max_early_data_size = 0; + ptls_handshake_properties_t client_prop = {.client.max_early_data_size = &client_max_early_data_size}; - if (clear_ke) + switch (mode) { + case 0: /* no keyex */ ctx->key_exchanges = NULL; + break; + case 1: /* keyex match */ + break; + case 2: /* keyex mismatch */ + if (!(ctx->key_exchanges[0] != NULL && ctx->key_exchanges[1] != NULL)) { + note("keyex mismatch test requires two key exchange algorithms"); + return; + } + alternate_keyex[0] = ctx->key_exchanges[1]; + alternate_keyex[1] = ctx->key_exchanges[0]; + alternate_keyex[2] = NULL; + ctx->key_exchanges = alternate_keyex; + break; + case 3: /* negotiate */ + client_prop.client.negotiate_before_key_exchange = 1; + break; + default: + assert(!"FIXME"); + } + ctx->max_early_data_size = 16384; assert(ctx->pre_shared_key.identity.len == 0 && ctx->pre_shared_key.secret.len == 0); ctx->pre_shared_key.identity = ptls_iovec_init("", 1); @@ -1643,10 +1667,6 @@ static void do_test_pre_shared_key(int clear_ke) ptls_buffer_init(&sbuf, "", 0); ptls_buffer_init(&decbuf, "", 0); - ptls_handshake_properties_t client_prop = {{{{NULL}}}}; - size_t client_max_early_data_size = 0; - client_prop.client.max_early_data_size = &client_max_early_data_size; - /* [client] send CH and early data */ int ret = ptls_handshake(client, &cbuf, NULL, NULL, &client_prop); ok(ret == PTLS_ERROR_IN_PROGRESS); @@ -1658,20 +1678,35 @@ static void do_test_pre_shared_key(int clear_ke) /* [server] read CH and generate up to ServerFinished */ size_t consumed = cbuf.off; ret = ptls_handshake(server, &sbuf, cbuf.base, &consumed, NULL); - ok(ret == 0); - ok(consumed < cbuf.off); + ok(consumed <= cbuf.off); memmove(cbuf.base, cbuf.base + consumed, cbuf.off - consumed); cbuf.off -= consumed; - - /* [server] read early data */ - consumed = cbuf.off; - ret = ptls_receive(server, &decbuf, cbuf.base, &consumed); - ok(ret == 0); - ok(consumed == cbuf.off); - cbuf.off = 0; - ok(decbuf.off == 5); - ok(memcmp(decbuf.base, "hello", 5) == 0); - decbuf.off = 0; + if (mode >= 2) { + ok(ret == PTLS_ERROR_IN_PROGRESS); + ok(cbuf.off == 0); + consumed = sbuf.off; + ret = ptls_handshake(client, &cbuf, sbuf.base, &consumed, &client_prop); + ok(ret == PTLS_ERROR_IN_PROGRESS); + ok(client_prop.client.early_data_acceptance == PTLS_EARLY_DATA_REJECTED); + ok(consumed == sbuf.off); + sbuf.off = 0; + consumed = cbuf.off; + ret = ptls_handshake(server, &sbuf, cbuf.base, &consumed, NULL); + ok(ret == 0); + ok(consumed == cbuf.off); + cbuf.off = 0; + } else { + ok(ret == 0); + /* [server] read early data */ + consumed = cbuf.off; + ret = ptls_receive(server, &decbuf, cbuf.base, &consumed); + ok(ret == 0); + ok(consumed == cbuf.off); + cbuf.off = 0; + ok(decbuf.off == 5); + ok(memcmp(decbuf.base, "hello", 5) == 0); + decbuf.off = 0; + } /* [server] write 0.5-RTT data */ ret = ptls_send(server, &sbuf, "hi", 2); @@ -1722,8 +1757,10 @@ static void do_test_pre_shared_key(int clear_ke) static void test_pre_shared_key(void) { - subtest("without key-share", do_test_pre_shared_key, 0); - subtest("with key-share", do_test_pre_shared_key, 1); + subtest("key-share:no", do_test_pre_shared_key, 0); + subtest("key-share:yes", do_test_pre_shared_key, 1); + subtest("key-share:mismatch", do_test_pre_shared_key, 2); + subtest("key-share:negotiate", do_test_pre_shared_key, 3); } typedef uint8_t traffic_secrets_t[2 /* is_enc */][4 /* epoch */][PTLS_MAX_DIGEST_SIZE /* octets */]; From a522cd99c695b11af64573bf562c3ddf83163f13 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Tue, 2 Apr 2024 13:33:13 +0900 Subject: [PATCH 46/61] [cli] add -P option for setting the PSK hash --- t/cli.c | 22 ++++++++++++++++++---- 1 file changed, 18 insertions(+), 4 deletions(-) diff --git a/t/cli.c b/t/cli.c index ca40c376b..aa0483976 100644 --- a/t/cli.c +++ b/t/cli.c @@ -385,6 +385,7 @@ static void usage(const char *cmd) " argument is ignored.\n" " -p psk-identity name of the PSK key; if set, -c and -C specify the\n" " pre-shared secret\n" + " -P psk-hash hash function associated to the PSK (default: sha256)\n" " -u update the traffic key when handshake is complete\n" " -v verify peer using the default certificates\n" " -V CA-root-file verify peer using the CA Root File\n" @@ -446,14 +447,14 @@ int main(int argc, char **argv) .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; + const char *host, *port, *input_file = NULL, *psk_hash = "sha256"; int is_server = 0, use_early_data = 0, request_key_update = 0, keep_sender_open = 0, ch; struct sockaddr_storage sa; socklen_t salen; int family = 0; const char *raw_pub_key_file = NULL, *cert_location = NULL; - while ((ch = getopt(argc, argv, "46abBC:c:i:Ij:k:nN:es:Sr:p:E:K:l:y:vV:h")) != -1) { + while ((ch = getopt(argc, argv, "46abBC:c:i:Ij:k:nN:es:Sr:p:P:E:K:l:y:vV:h")) != -1) { switch (ch) { case '4': family = AF_INET; @@ -508,6 +509,9 @@ int main(int argc, char **argv) case 'p': ctx.pre_shared_key.identity = ptls_iovec_init(optarg, strlen(optarg)); break; + case 'P': + psk_hash = optarg; + break; case 's': setup_session_file(&ctx, &hsprop, optarg); break; @@ -651,10 +655,20 @@ int main(int argc, char **argv) if (key_exchanges[0] == NULL) key_exchanges[0] = &ptls_openssl_secp256r1; if (cipher_suites[0] == NULL) { - size_t i; - for (i = 0; ptls_openssl_cipher_suites[i] != NULL; ++i) + for (size_t i = 0; ptls_openssl_cipher_suites[i] != NULL; ++i) cipher_suites[i] = ptls_openssl_cipher_suites[i]; } + if (ctx.pre_shared_key.identity.base != NULL) { + size_t i; + for (i = 0; cipher_suites[i] != NULL; ++i) + if (strcmp(cipher_suites[i]->hash->name, psk_hash) == 0) + break; + if (cipher_suites[i] == NULL) { + fprintf(stderr, "no compatible cipher-suite for psk hash: %s\n", psk_hash); + exit(1); + } + ctx.pre_shared_key.hash = cipher_suites[i]->hash; + } if (argc != 2) { fprintf(stderr, "missing host and port\n"); return 1; From de44a18b335c5f9b9a0feb2b92eff9ef7bdb4c89 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Tue, 2 Apr 2024 13:33:35 +0900 Subject: [PATCH 47/61] PSK+HRR works on the client side --- lib/picotls.c | 90 +++++++++++++++++++++++++++------------------------ 1 file changed, 47 insertions(+), 43 deletions(-) diff --git a/lib/picotls.c b/lib/picotls.c index e795bfdd9..e12b5ee6b 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -1318,7 +1318,7 @@ static int key_schedule_extract(ptls_key_schedule_t *sched, ptls_iovec_t ikm) return ret; } -static int key_schedule_select_cipher(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, ptls_iovec_t reset_ikm) { size_t found_slot = SIZE_MAX, i; int ret; @@ -1346,7 +1346,7 @@ static int key_schedule_select_cipher(ptls_key_schedule_t *sched, ptls_cipher_su if (reset) { --sched->generation; memset(sched->secret, 0, sizeof(sched->secret)); - if ((ret = key_schedule_extract(sched, ptls_iovec_init(NULL, 0))) != 0) + if ((ret = key_schedule_extract(sched, reset_ikm)) != 0) goto Exit; } @@ -2316,8 +2316,8 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ if (tls->server_name != NULL && !ptls_server_name_is_ipaddr(tls->server_name)) sni_name = tls->server_name; + /* try to use ECH (ignore broken ECHConfigList; it is delivered insecurely) */ if (properties != NULL) { - /* try to use ECH (ignore broken ECHConfigList; it is delivered insecurely) */ if (!is_second_flight && sni_name != NULL && tls->ctx->ech.client.ciphers != NULL) { if (properties->client.ech.configs.len != 0) { struct st_decoded_ech_config_t decoded; @@ -2332,47 +2332,52 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ sni_name); } } - /* setup resumption-related data. If successful, psk_secret becomes a non-zero value. */ - if (properties->client.session_ticket.base != NULL && tls->ctx->key_exchanges != NULL && - tls->ctx->pre_shared_key.identity.base == NULL) { - ptls_key_exchange_algorithm_t *key_share = NULL; - ptls_cipher_suite_t *cipher_suite = NULL; - uint32_t max_early_data_size; - if (decode_stored_session_ticket(tls, &key_share, &cipher_suite, &psk_secret, &obfuscated_ticket_age, &psk_identity, - &max_early_data_size, properties->client.session_ticket.base, - properties->client.session_ticket.base + properties->client.session_ticket.len) == 0) { - tls->client.offered_psk = 1; - /* key-share selected by HRR should not be overridden */ - if (tls->key_share == NULL) - tls->key_share = key_share; - tls->cipher_suite = cipher_suite; - if (!is_second_flight && max_early_data_size != 0 && properties->client.max_early_data_size != NULL) { - tls->client.using_early_data = 1; - *properties->client.max_early_data_size = max_early_data_size; - } - } else { - psk_secret = ptls_iovec_init(NULL, 0); - } - } } - /* use external PSK if provided and resumption is not to be used */ - if (psk_secret.base == NULL && tls->ctx->pre_shared_key.identity.base != NULL) { - assert(tls->ctx->pre_shared_key.hash != NULL); - tls->client.offered_psk = 1; - for (size_t i = 0; tls->ctx->cipher_suites[i] != NULL; ++i) { - if (tls->ctx->cipher_suites[i]->hash == tls->ctx->pre_shared_key.hash) { - tls->cipher_suite = tls->ctx->cipher_suites[i]; - break; + /* use external PSK if provided */ + if (tls->ctx->pre_shared_key.identity.base != NULL) { + if (!is_second_flight) { + assert(tls->ctx->pre_shared_key.hash != NULL); + tls->client.offered_psk = 1; + for (size_t i = 0; tls->ctx->cipher_suites[i] != NULL; ++i) { + if (tls->ctx->cipher_suites[i]->hash == tls->ctx->pre_shared_key.hash) { + tls->cipher_suite = tls->ctx->cipher_suites[i]; + break; + } + } + assert(tls->cipher_suite != NULL && "no compatible cipher-suite provided that matches psk.hash"); + if (properties->client.max_early_data_size != NULL) { + tls->client.using_early_data = 1; + *properties->client.max_early_data_size = SIZE_MAX; } + } else { + assert(tls->cipher_suite != NULL && tls->cipher_suite->hash == tls->ctx->pre_shared_key.hash); } - assert(tls->cipher_suite != NULL && "no compatible cipher-suite provided that matches psk.hash"); psk_secret = tls->ctx->pre_shared_key.secret; psk_identity = tls->ctx->pre_shared_key.identity; binder_key_label = "ext binder"; - if (!is_second_flight && properties->client.max_early_data_size != NULL) { - tls->client.using_early_data = 1; - *properties->client.max_early_data_size = SIZE_MAX; + } + + /* try to setup resumption-related data, unless external PSK is used */ + if (psk_secret.base == NULL && properties != NULL && properties->client.session_ticket.base != NULL && + tls->ctx->key_exchanges != NULL) { + ptls_key_exchange_algorithm_t *key_share = NULL; + ptls_cipher_suite_t *cipher_suite = NULL; + uint32_t max_early_data_size; + if (decode_stored_session_ticket(tls, &key_share, &cipher_suite, &psk_secret, &obfuscated_ticket_age, &psk_identity, + &max_early_data_size, properties->client.session_ticket.base, + properties->client.session_ticket.base + properties->client.session_ticket.len) == 0) { + tls->client.offered_psk = 1; + /* key-share selected by HRR should not be overridden */ + if (tls->key_share == NULL) + tls->key_share = key_share; + tls->cipher_suite = cipher_suite; + if (!is_second_flight && max_early_data_size != 0 && properties->client.max_early_data_size != NULL) { + tls->client.using_early_data = 1; + *properties->client.max_early_data_size = max_early_data_size; + } + } else { + psk_secret = ptls_iovec_init(NULL, 0); } } @@ -2776,7 +2781,7 @@ static int client_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl } if (sh.is_retry_request) { - if ((ret = key_schedule_select_cipher(tls->key_schedule, tls->cipher_suite, 0)) != 0) + if ((ret = key_schedule_select_cipher(tls->key_schedule, tls->cipher_suite, 0, tls->ctx->pre_shared_key.secret)) != 0) goto Exit; key_schedule_transform_post_ch1hash(tls->key_schedule); if (tls->ech.aead != NULL) { @@ -2795,7 +2800,7 @@ static int client_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl } if ((ret = key_schedule_select_cipher(tls->key_schedule, tls->cipher_suite, - tls->client.offered_psk && !tls->is_psk_handshake)) != 0) + tls->client.offered_psk && !tls->is_psk_handshake, ptls_iovec_init(NULL, 0))) != 0) goto Exit; /* check if ECH is accepted */ @@ -4591,10 +4596,9 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl goto Exit; /* try psk handshake */ - if (!is_second_flight && ch->psk.hash_end != 0 && - (ch->psk.ke_modes & ((1u << PTLS_PSK_KE_MODE_PSK) | (1u << PTLS_PSK_KE_MODE_PSK_DHE))) != 0 && - (tls->ctx->encrypt_ticket != NULL || tls->ctx->pre_shared_key.identity.base != NULL) && - !tls->ctx->require_client_authentication) { + if (ch->psk.hash_end != 0 && (ch->psk.ke_modes & ((1u << PTLS_PSK_KE_MODE_PSK) | (1u << PTLS_PSK_KE_MODE_PSK_DHE))) != 0 && + !tls->ctx->require_client_authentication && + ((!is_second_flight && tls->ctx->encrypt_ticket != NULL) || tls->ctx->pre_shared_key.identity.base != NULL)) { if ((ret = try_psk_handshake(tls, &psk_index, &accept_early_data, ch, ptls_iovec_init(message.base, ch->psk.hash_end - message.base))) != 0) { goto Exit; From 5655ee1d951b858d3a7cd48b7d11e274ee9656e5 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Tue, 2 Apr 2024 13:56:59 +0900 Subject: [PATCH 48/61] [cli] add option to enforce PSK handshake rather than PSK-DHE --- t/cli.c | 33 +++++++++++++++++++-------------- 1 file changed, 19 insertions(+), 14 deletions(-) diff --git a/t/cli.c b/t/cli.c index aa0483976..13c1ee882 100644 --- a/t/cli.c +++ b/t/cli.c @@ -533,31 +533,36 @@ int main(int argc, char **argv) case 'V': setup_verify_certificate(&ctx, optarg); break; - case 'N': { - ptls_key_exchange_algorithm_t *algo = NULL; + case 'N': + if (strcasecmp(optarg, "null") == 0) { + /* disable use of key exchanges entirely */ + ctx.key_exchanges = NULL; + } else { + ptls_key_exchange_algorithm_t *algo = NULL; #define MATCH(name) \ if (algo == NULL && strcasecmp(optarg, #name) == 0) \ algo = (&ptls_openssl_##name) - MATCH(secp256r1); + MATCH(secp256r1); #if PTLS_OPENSSL_HAVE_SECP384R1 - MATCH(secp384r1); + MATCH(secp384r1); #endif #if PTLS_OPENSSL_HAVE_SECP521R1 - MATCH(secp521r1); + MATCH(secp521r1); #endif #if PTLS_OPENSSL_HAVE_X25519 - MATCH(x25519); + MATCH(x25519); #endif #undef MATCH - if (algo == NULL) { - fprintf(stderr, "could not find key exchange: %s\n", optarg); - return 1; + if (algo == NULL) { + fprintf(stderr, "could not find key exchange: %s\n", optarg); + return 1; + } + size_t i; + for (i = 0; key_exchanges[i] != NULL; ++i) + ; + key_exchanges[i++] = algo; } - size_t i; - for (i = 0; key_exchanges[i] != NULL; ++i) - ; - key_exchanges[i++] = algo; - } break; + break; case 'u': request_key_update = 1; break; From dfaa338787920b2f165236546b6096fd278d4464 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Tue, 2 Apr 2024 15:49:27 +0900 Subject: [PATCH 49/61] fix compile error when PTLS_DEBUG is set --- lib/picotls.c | 11 +++++++++-- 1 file changed, 9 insertions(+), 2 deletions(-) diff --git a/lib/picotls.c b/lib/picotls.c index e12b5ee6b..b061bb2f6 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -1641,8 +1641,15 @@ static int setup_traffic_protection(ptls_t *tls, int is_enc, const char *secret_ return PTLS_ERROR_NO_MEMORY; /* TODO obtain error from ptls_aead_new */ ctx->seq = seq; - PTLS_DEBUGF("[%s] %02x%02x,%02x%02x\n", log_labels[ptls_is_server(tls)][epoch], (unsigned)ctx->secret[0], - (unsigned)ctx->secret[1], (unsigned)ctx->aead->static_iv[0], (unsigned)ctx->aead->static_iv[1]); +#if defined(PTLS_DEBUG) && PTLS_DEBUG + { + uint8_t static_iv[PTLS_MAX_IV_SIZE]; + ptls_aead_get_iv(ctx->aead, static_iv); + PTLS_DEBUGF("[%s] %02x%02x,%02x%02x\n", log_labels[ptls_is_server(tls)][epoch], (unsigned)ctx->secret[0], + (unsigned)ctx->secret[1], static_iv[0], static_iv[1]); + } +#endif + return 0; } From 9547f8042dc6ec793306f95432cda4c5dc42603d Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Tue, 2 Apr 2024 16:29:55 +0900 Subject: [PATCH 50/61] [psk] when HRR is involved at the server side, supply PSK to the key schedule --- lib/picotls.c | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/lib/picotls.c b/lib/picotls.c index b061bb2f6..6711eb518 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -4477,6 +4477,9 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl } if (!is_second_flight) { + /* the only ikm we adopt alongside HRR is the external PSK; session tickets are disregarded when sending HRR */ + ptls_iovec_t ikm_of_1st_ch = tls->ctx->pre_shared_key.secret; + if (ch->cookie.all.len != 0 && key_share.algorithm != NULL) { /* use cookie to check the integrity of the handshake, and update the context */ @@ -4493,7 +4496,7 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl /* 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, 0); - key_schedule_extract(tls->key_schedule, ptls_iovec_init(NULL, 0)); + key_schedule_extract(tls->key_schedule, ikm_of_1st_ch); /* ... 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, @@ -4526,7 +4529,7 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl properties != NULL && properties->server.retry_uses_cookie && !ptls_is_ech_handshake(tls, NULL, 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)); + key_schedule_extract(tls->key_schedule, ikm_of_1st_ch); } size_t ech_confirm_off = 0; EMIT_HELLO_RETRY_REQUEST( From 694dd69779eddc3a270de794956ac43b63ab9f34 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Tue, 2 Apr 2024 16:58:19 +0900 Subject: [PATCH 51/61] more logging --- lib/picotls.c | 9 ++++++++- 1 file changed, 8 insertions(+), 1 deletion(-) diff --git a/lib/picotls.c b/lib/picotls.c index 6711eb518..f39ecc8c6 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -1372,10 +1372,17 @@ void ptls__key_schedule_update_hash(ptls_key_schedule_t *sched, const uint8_t *m { size_t i; - PTLS_DEBUGF("%s:%zu\n", __FUNCTION__, msglen); + PTLS_DEBUGF("%s:%p:len=%zu\n", __FUNCTION__, sched, 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); +#if defined(PTLS_DEBUG) && PTLS_DEBUG + { + uint8_t digest[PTLS_MAX_DIGEST_SIZE]; + ctx->final(ctx, digest, PTLS_HASH_FINAL_MODE_SNAPSHOT); + PTLS_DEBUGF(" %zu: %02x%02x%02x%02x\n", i, digest[0], digest[1], digest[2], digest[3]); + } +#endif } } From a8e1af5480988c47a5f5522ee934c9760e46f22b Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Fri, 5 Apr 2024 15:16:02 +0900 Subject: [PATCH 52/61] when HRR is involved, make sure called HKDF-Extract(PSK) only once per handshake, and binder_key is derived from null message --- lib/picotls.c | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/lib/picotls.c b/lib/picotls.c index f39ecc8c6..7ff9058e2 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -4073,7 +4073,7 @@ static int vec_is_string(ptls_iovec_t x, const char *y) * external_psk is set, only tries handshake using those keys provided. Otherwise, tries resumption. */ static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_data, struct st_ptls_client_hello_t *ch, - ptls_iovec_t ch_trunc) + ptls_iovec_t ch_trunc, int is_second_flight) { ptls_buffer_t decbuf; ptls_iovec_t secret, ticket_ctx, ticket_negotiated_protocol; @@ -4186,9 +4186,9 @@ static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_d goto Exit; Found: - if ((ret = key_schedule_extract(tls->key_schedule, secret)) != 0) + if (!is_second_flight && (ret = key_schedule_extract(tls->key_schedule, secret)) != 0) goto Exit; - if ((ret = derive_secret(tls->key_schedule, binder_key, binder_label)) != 0) + if ((ret = derive_secret_with_empty_digest(tls->key_schedule, binder_key, binder_label)) != 0) goto Exit; 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, @@ -4617,7 +4617,7 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl !tls->ctx->require_client_authentication && ((!is_second_flight && tls->ctx->encrypt_ticket != NULL) || tls->ctx->pre_shared_key.identity.base != NULL)) { if ((ret = try_psk_handshake(tls, &psk_index, &accept_early_data, ch, - ptls_iovec_init(message.base, ch->psk.hash_end - message.base))) != 0) { + ptls_iovec_init(message.base, ch->psk.hash_end - message.base), is_second_flight)) != 0) { goto Exit; } } From 436e433edddb997488e36cddb6cc50d80f37ab40 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Fri, 5 Apr 2024 15:16:10 +0900 Subject: [PATCH 53/61] fix test --- t/picotls.c | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/t/picotls.c b/t/picotls.c index cb94a538d..e5129bf8e 100644 --- a/t/picotls.c +++ b/t/picotls.c @@ -1716,7 +1716,7 @@ static void do_test_pre_shared_key(int mode) consumed = sbuf.off; ret = ptls_handshake(client, &cbuf, sbuf.base, &consumed, &client_prop); ok(ret == 0); - ok(client_prop.client.early_data_acceptance == PTLS_EARLY_DATA_ACCEPTED); + ok(client_prop.client.early_data_acceptance == (mode < 2 ? PTLS_EARLY_DATA_ACCEPTED : PTLS_EARLY_DATA_REJECTED)); ok(consumed < sbuf.off); memmove(sbuf.base, sbuf.base + consumed, sbuf.off - consumed); sbuf.off -= consumed; From 388b26ea1fb1204ac250c09fe08dfa0b1a2176ad Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Fri, 5 Apr 2024 15:26:46 +0900 Subject: [PATCH 54/61] for readability, use one struct and initialize the variables consistently --- lib/picotls.c | 38 +++++++++++++++++++++----------------- 1 file changed, 21 insertions(+), 17 deletions(-) diff --git a/lib/picotls.c b/lib/picotls.c index 7ff9058e2..69c8d568d 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -2316,11 +2316,14 @@ static int encode_client_hello(ptls_context_t *ctx, ptls_buffer_t *sendbuf, enum 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 psk_secret = {NULL}, psk_identity = {NULL}; + struct { + ptls_iovec_t secret; + ptls_iovec_t identity; + const char *label; + } psk = {{NULL}}; uint32_t obfuscated_ticket_age = 0; const char *sni_name = NULL; size_t mess_start, msghash_off; - const char *binder_key_label = "res binder"; uint8_t binder_key[PTLS_MAX_DIGEST_SIZE]; ptls_buffer_t encoded_ch_inner; int ret, is_second_flight = tls->key_schedule != NULL; @@ -2367,20 +2370,21 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ } else { assert(tls->cipher_suite != NULL && tls->cipher_suite->hash == tls->ctx->pre_shared_key.hash); } - psk_secret = tls->ctx->pre_shared_key.secret; - psk_identity = tls->ctx->pre_shared_key.identity; - binder_key_label = "ext binder"; + psk.secret = tls->ctx->pre_shared_key.secret; + psk.identity = tls->ctx->pre_shared_key.identity; + psk.label = "ext binder"; } /* try to setup resumption-related data, unless external PSK is used */ - if (psk_secret.base == NULL && properties != NULL && properties->client.session_ticket.base != NULL && + if (psk.secret.base == NULL && properties != NULL && properties->client.session_ticket.base != NULL && tls->ctx->key_exchanges != NULL) { ptls_key_exchange_algorithm_t *key_share = NULL; ptls_cipher_suite_t *cipher_suite = NULL; uint32_t max_early_data_size; - if (decode_stored_session_ticket(tls, &key_share, &cipher_suite, &psk_secret, &obfuscated_ticket_age, &psk_identity, + if (decode_stored_session_ticket(tls, &key_share, &cipher_suite, &psk.secret, &obfuscated_ticket_age, &psk.identity, &max_early_data_size, properties->client.session_ticket.base, properties->client.session_ticket.base + properties->client.session_ticket.len) == 0) { + psk.label = "res binder"; tls->client.offered_psk = 1; /* key-share selected by HRR should not be overridden */ if (tls->key_share == NULL) @@ -2391,7 +2395,7 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ *properties->client.max_early_data_size = max_early_data_size; } } else { - psk_secret = ptls_iovec_init(NULL, 0); + psk.secret = ptls_iovec_init(NULL, 0); } } @@ -2424,7 +2428,7 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ ret = PTLS_ERROR_NO_MEMORY; goto Exit; } - if ((ret = key_schedule_extract(tls->key_schedule, psk_secret)) != 0) + if ((ret = key_schedule_extract(tls->key_schedule, psk.secret)) != 0) goto Exit; } @@ -2437,14 +2441,14 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ 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, psk_secret, psk_identity, obfuscated_ticket_age, + tls->ech.client.first_ech, psk.secret, psk.identity, 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 (psk_secret.base != NULL) { + if (psk.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, binder_key_label)) != 0) + if ((ret = derive_secret_with_empty_digest(tls->key_schedule, binder_key, psk.label)) != 0) goto Exit; ptls__key_schedule_update_hash(tls->key_schedule, emitter->buf->base + msghash_off, psk_binder_off - msghash_off, 0); msghash_off = psk_binder_off; @@ -2458,11 +2462,11 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ /* 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), psk_secret, - psk_identity, obfuscated_ticket_age, tls->key_schedule->hashes[0].algo->digest_size, cookie, + tls->client.legacy_session_id, &tls->ech, NULL, ptls_iovec_init(NULL, 0), psk.secret, + psk.identity, obfuscated_ticket_age, tls->key_schedule->hashes[0].algo->digest_size, cookie, tls->client.using_early_data)) != 0) goto Exit; - if (psk_secret.base != NULL) + if (psk.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); @@ -2492,7 +2496,7 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ 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), - psk_secret, psk_identity, obfuscated_ticket_age, + psk.secret, psk.identity, obfuscated_ticket_age, tls->key_schedule->hashes[0].algo->digest_size, cookie, tls->client.using_early_data)) != 0) goto Exit; /* overwrite ECH payload */ @@ -2531,7 +2535,7 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ if ((ret = push_change_cipher_spec(tls, emitter)) != 0) goto Exit; } - if (psk_secret.base != NULL && !is_second_flight) { + if (psk.secret.base != NULL && !is_second_flight) { if ((ret = derive_exporter_secret(tls, 1)) != 0) goto Exit; } From 1fb276fce57a12fad8047ff788bdc37b329304d9 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Fri, 5 Apr 2024 15:30:07 +0900 Subject: [PATCH 55/61] consistently refer to `ctx->ctx->psk` --- lib/picotls.c | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/lib/picotls.c b/lib/picotls.c index 69c8d568d..e7b8a6574 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -4085,13 +4085,13 @@ static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_d uint32_t age_add; uint16_t ticket_key_exchange_id, ticket_csid; uint8_t binder_key[PTLS_MAX_DIGEST_SIZE]; - const char *binder_label = "res binder"; int ret; ptls_buffer_init(&decbuf, "", 0); for (*psk_index = 0; *psk_index < ch->psk.identities.count; ++*psk_index) { ptls_client_hello_psk_identity_t *identity = ch->psk.identities.list + *psk_index; + /* negotiate using fixed pre-shared key */ if (tls->ctx->pre_shared_key.secret.base != NULL) { assert(tls->ctx->pre_shared_key.secret.len != 0 && tls->ctx->pre_shared_key.identity.len != 0 && @@ -4101,11 +4101,11 @@ static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_d *accept_early_data = ch->psk.early_data_indication && *psk_index == 0; tls->key_share = NULL; secret = tls->ctx->pre_shared_key.secret; - binder_label = "ext binder"; goto Found; } continue; } + /* decrypt ticket and decode */ if (tls->ctx->encrypt_ticket == NULL || tls->ctx->key_exchanges == NULL) continue; @@ -4192,7 +4192,8 @@ static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_d Found: if (!is_second_flight && (ret = key_schedule_extract(tls->key_schedule, secret)) != 0) goto Exit; - if ((ret = derive_secret_with_empty_digest(tls->key_schedule, binder_key, binder_label)) != 0) + if ((ret = derive_secret_with_empty_digest(tls->key_schedule, binder_key, + tls->ctx->pre_shared_key.secret.base != NULL ? "ext binder" : "res binder")) != 0) goto Exit; 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, From 96826a4c8322173397adbadc888244d6f8220625 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Fri, 5 Apr 2024 15:52:41 +0900 Subject: [PATCH 56/61] refine comments --- lib/picotls.c | 23 ++++++++++------------- 1 file changed, 10 insertions(+), 13 deletions(-) diff --git a/lib/picotls.c b/lib/picotls.c index e7b8a6574..f44d25354 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -1657,7 +1657,6 @@ static int setup_traffic_protection(ptls_t *tls, int is_enc, const char *secret_ } #endif - return 0; } @@ -2817,8 +2816,8 @@ static int client_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl return handle_hello_retry_request(tls, emitter, &sh, message, properties); } - if ((ret = key_schedule_select_cipher(tls->key_schedule, tls->cipher_suite, - tls->client.offered_psk && !tls->is_psk_handshake, ptls_iovec_init(NULL, 0))) != 0) + if ((ret = key_schedule_select_cipher(tls->key_schedule, tls->cipher_suite, tls->client.offered_psk && !tls->is_psk_handshake, + ptls_iovec_init(NULL, 0))) != 0) goto Exit; /* check if ECH is accepted */ @@ -2841,7 +2840,7 @@ static int client_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl tls->key_schedule->hashes[0].ctx_outer = NULL; } - /* if we (the client) offered PSK but the server did not use that, we call it a handshake failure */ + /* if the client offered external PSK but the server did not use that, we call it a handshake failure */ if (tls->ctx->pre_shared_key.identity.base != NULL && !tls->is_psk_handshake) { ret = PTLS_ALERT_HANDSHAKE_FAILURE; goto Exit; @@ -4074,7 +4073,7 @@ static int vec_is_string(ptls_iovec_t x, const char *y) /** * Looks for a PSK identity that can be used, and if found, updates the handshake state and returns the necessary variables. If - * external_psk is set, only tries handshake using those keys provided. Otherwise, tries resumption. + * `ptls_context_t::pre_shared_key` is set, only tries handshake using those keys provided. Otherwise, tries resumption. */ static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_data, struct st_ptls_client_hello_t *ch, ptls_iovec_t ch_trunc, int is_second_flight) @@ -4489,13 +4488,9 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl } if (!is_second_flight) { - /* the only ikm we adopt alongside HRR is the external PSK; session tickets are disregarded when sending HRR */ - ptls_iovec_t ikm_of_1st_ch = tls->ctx->pre_shared_key.secret; - if (ch->cookie.all.len != 0 && key_share.algorithm != NULL) { - /* use cookie to check the integrity of the handshake, and update the context */ - { + { /* use cookie to check the integrity of the handshake, and update the context */ uint8_t sig[PTLS_MAX_DIGEST_SIZE]; size_t sigsize = tls->ctx->cipher_suites[0]->hash->digest_size; if ((ret = calc_cookie_signature(tls, properties, key_share.algorithm, ch->cookie.tbs, sig)) != 0) @@ -4508,7 +4503,9 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl /* 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, 0); - key_schedule_extract(tls->key_schedule, ikm_of_1st_ch); + key_schedule_extract(tls->key_schedule, + tls->ctx->pre_shared_key.secret /* this argument will be a zero-length vector unless external PSK + is used, and that's fine; we never resume when sending HRR */); /* ... 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, @@ -4541,7 +4538,7 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl properties != NULL && properties->server.retry_uses_cookie && !ptls_is_ech_handshake(tls, NULL, NULL, NULL); if (!retry_uses_cookie) { key_schedule_transform_post_ch1hash(tls->key_schedule); - key_schedule_extract(tls->key_schedule, ikm_of_1st_ch); + key_schedule_extract(tls->key_schedule, tls->ctx->pre_shared_key.secret /* see comment above */); } size_t ech_confirm_off = 0; EMIT_HELLO_RETRY_REQUEST( @@ -4628,7 +4625,7 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl } /* If the server was setup to use an external PSK but failed to agree, abort the handshake. Because external PSK is a form of - * mutual authentication, we should abort continue the handshake upon negotiation failure, at least by default. */ + * mutual authentication, it makes sense to abort (at least as the default). */ if (tls->ctx->pre_shared_key.identity.base != NULL && psk_index == SIZE_MAX) { ret = PTLS_ALERT_UNKNOWN_PSK_IDENTITY; goto Exit; From 3d9b4699eaeb86296fbdf1b5db2fa2db14d349e5 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Fri, 5 Apr 2024 16:30:35 +0900 Subject: [PATCH 57/61] [cli] update help following the changes --- t/cli.c | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/t/cli.c b/t/cli.c index 13c1ee882..c1e3fac4a 100644 --- a/t/cli.c +++ b/t/cli.c @@ -371,7 +371,9 @@ static void usage(const char *cmd) " -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" + " -N named-group named group to be used (default: secp256r1); if \"null\"\n" + " is specified alongside `-p`, external PSK handshake with\n" + " no ECDHE is performed\n" " -s session-file file to read/write the session ticket\n" " -S require public key exchange when resuming a session\n" " -E echconfiglist file that contains ECHConfigList or an empty file to\n" From 56438191f3a1c20be4bfa07b595dc7f1f7147d7a Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Sat, 6 Apr 2024 10:09:40 +0900 Subject: [PATCH 58/61] refine comment --- include/picotls.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/picotls.h b/include/picotls.h index 222f6caf4..81ecbf406 100644 --- a/include/picotls.h +++ b/include/picotls.h @@ -861,7 +861,7 @@ struct st_ptls_context_t { size_t count; } certificates; /** - * if set, the endpoints negotiate using the provided pre-shared key + * External pre-shared key used for mutual authentication. Unless when using PSK, all the fields must be set to NULL / 0. */ struct st_ptls_external_psk_t { ptls_iovec_t identity; From 9eb094c366a28eb7f32a6e27466d9d5899d357ca Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Sat, 6 Apr 2024 10:10:25 +0900 Subject: [PATCH 59/61] omit typename as it is unused --- include/picotls.h | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/include/picotls.h b/include/picotls.h index 81ecbf406..97e499220 100644 --- a/include/picotls.h +++ b/include/picotls.h @@ -863,7 +863,7 @@ struct st_ptls_context_t { /** * External pre-shared key used for mutual authentication. Unless when using PSK, all the fields must be set to NULL / 0. */ - struct st_ptls_external_psk_t { + struct { ptls_iovec_t identity; ptls_iovec_t secret; /** From 71d4dfbd80b98f6a39266fb8f2d8e904874e9fd8 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Mon, 8 Apr 2024 10:10:59 +0900 Subject: [PATCH 60/61] check consistency of `ptls_context_t` upon instantiation --- lib/picotls.c | 15 +++++++++++---- 1 file changed, 11 insertions(+), 4 deletions(-) diff --git a/lib/picotls.c b/lib/picotls.c index f44d25354..6c86295f4 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -2353,7 +2353,6 @@ static int send_client_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptls_ /* use external PSK if provided */ if (tls->ctx->pre_shared_key.identity.base != NULL) { if (!is_second_flight) { - assert(tls->ctx->pre_shared_key.hash != NULL); tls->client.offered_psk = 1; for (size_t i = 0; tls->ctx->cipher_suites[i] != NULL; ++i) { if (tls->ctx->cipher_suites[i]->hash == tls->ctx->pre_shared_key.hash) { @@ -4092,9 +4091,7 @@ static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_d ptls_client_hello_psk_identity_t *identity = ch->psk.identities.list + *psk_index; /* negotiate using fixed pre-shared key */ - if (tls->ctx->pre_shared_key.secret.base != NULL) { - assert(tls->ctx->pre_shared_key.secret.len != 0 && tls->ctx->pre_shared_key.identity.len != 0 && - tls->ctx->pre_shared_key.hash != NULL && "`ptls_context_t::pre_shared_key` in incosistent state"); + if (tls->ctx->pre_shared_key.identity.base != NULL) { if (identity->identity.len == tls->ctx->pre_shared_key.identity.len && memcmp(identity->identity.base, tls->ctx->pre_shared_key.identity.base, identity->identity.len) == 0) { *accept_early_data = ch->psk.early_data_indication && *psk_index == 0; @@ -5074,7 +5071,17 @@ static ptls_t *new_instance(ptls_context_t *ctx, int is_server) { ptls_t *tls; + /* check consistency of `ptls_context_t` before instantiating a connection object */ assert(ctx->get_time != NULL && "please set ctx->get_time to `&ptls_get_time`; see #92"); + if (ctx->pre_shared_key.identity.base != NULL) { + assert(ctx->pre_shared_key.identity.len != 0 && ctx->pre_shared_key.secret.base != NULL && + ctx->pre_shared_key.secret.len != 0 && ctx->pre_shared_key.hash != NULL && + "`ptls_context_t::pre_shared_key` in incosistent state"); + } else { + assert(ctx->pre_shared_key.identity.len == 0 && ctx->pre_shared_key.secret.base == NULL && + ctx->pre_shared_key.secret.len == 0 && ctx->pre_shared_key.hash == NULL && + "`ptls_context_t::pre_shared_key` in inconsitent state"); + } if ((tls = malloc(sizeof(*tls))) == NULL) return NULL; From 2af3d242e4a2814fa59d048807c7f5e0029f47b6 Mon Sep 17 00:00:00 2001 From: Kazuho Oku Date: Mon, 8 Apr 2024 11:19:49 +0900 Subject: [PATCH 61/61] more tests --- t/picotls.c | 74 ++++++++++++++++++++++++++++++++++++----------------- 1 file changed, 51 insertions(+), 23 deletions(-) diff --git a/t/picotls.c b/t/picotls.c index e5129bf8e..33c0a8bd3 100644 --- a/t/picotls.c +++ b/t/picotls.c @@ -1621,47 +1621,64 @@ static void test_ech_config_mismatch(void) static void do_test_pre_shared_key(int mode) { - ptls_context_t ctx_backup = *ctx; + ptls_context_t ctx_client = *ctx; ptls_key_exchange_algorithm_t *alternate_keyex[3]; size_t client_max_early_data_size = 0; ptls_handshake_properties_t client_prop = {.client.max_early_data_size = &client_max_early_data_size}; switch (mode) { case 0: /* no keyex */ - ctx->key_exchanges = NULL; + ctx_client.key_exchanges = NULL; break; case 1: /* keyex match */ break; - case 2: /* keyex mismatch */ - if (!(ctx->key_exchanges[0] != NULL && ctx->key_exchanges[1] != NULL)) { + case 2: /* server has no keyex */ + break; + case 3: /* keyex mismatch */ + if (!(ctx_client.key_exchanges[0] != NULL && ctx_client.key_exchanges[1] != NULL)) { note("keyex mismatch test requires two key exchange algorithms"); return; } - alternate_keyex[0] = ctx->key_exchanges[1]; - alternate_keyex[1] = ctx->key_exchanges[0]; + alternate_keyex[0] = ctx_client.key_exchanges[1]; + alternate_keyex[1] = ctx_client.key_exchanges[0]; alternate_keyex[2] = NULL; - ctx->key_exchanges = alternate_keyex; + ctx_client.key_exchanges = alternate_keyex; break; - case 3: /* negotiate */ + case 4: /* negotiate */ client_prop.client.negotiate_before_key_exchange = 1; break; + case -1: /* fail:requires-psk-dhe */ + ctx_client.key_exchanges = NULL; + break; default: assert(!"FIXME"); } - ctx->max_early_data_size = 16384; - assert(ctx->pre_shared_key.identity.len == 0 && ctx->pre_shared_key.secret.len == 0); - ctx->pre_shared_key.identity = ptls_iovec_init("", 1); - ctx->pre_shared_key.secret = ptls_iovec_init("hello world", 11); - for (size_t i = 0; ctx->cipher_suites[i] != NULL; ++i) { - if (strcmp(ctx->cipher_suites[i]->hash->name, "sha256") == 0) { - ctx->pre_shared_key.hash = ctx->cipher_suites[i]->hash; + ctx_client.max_early_data_size = 16384; + assert(ctx_client.pre_shared_key.identity.len == 0 && ctx_client.pre_shared_key.secret.len == 0); + ctx_client.pre_shared_key.identity = ptls_iovec_init("", 1); + ctx_client.pre_shared_key.secret = ptls_iovec_init("hello world", 11); + for (size_t i = 0; ctx_client.cipher_suites[i] != NULL; ++i) { + if (strcmp(ctx_client.cipher_suites[i]->hash->name, "sha256") == 0) { + ctx_client.pre_shared_key.hash = ctx_client.cipher_suites[i]->hash; break; } } - assert(ctx->pre_shared_key.hash != NULL); + assert(ctx_client.pre_shared_key.hash != NULL); + + ptls_context_t ctx_server = ctx_client; + switch (mode) { + case 2: /* server has no keyex */ + ctx_server.key_exchanges = NULL; + break; + case -1: /* fail:requires-psk-dhe */ + ctx_server.require_dhe_on_psk = 1; + break; + default: + break; + } - ptls_t *client = ptls_new(ctx, 0), *server = ptls_new(ctx, 1); + ptls_t *client = ptls_new(&ctx_client, 0), *server = ptls_new(&ctx_server, 1); ptls_buffer_t cbuf, sbuf, decbuf; ptls_buffer_init(&cbuf, "", 0); ptls_buffer_init(&sbuf, "", 0); @@ -1678,10 +1695,14 @@ static void do_test_pre_shared_key(int mode) /* [server] read CH and generate up to ServerFinished */ size_t consumed = cbuf.off; ret = ptls_handshake(server, &sbuf, cbuf.base, &consumed, NULL); + if (mode < 0) { + ok(ret == PTLS_ALERT_HANDSHAKE_FAILURE); + goto Exit; + } ok(consumed <= cbuf.off); memmove(cbuf.base, cbuf.base + consumed, cbuf.off - consumed); cbuf.off -= consumed; - if (mode >= 2) { + if (mode >= 3) { ok(ret == PTLS_ERROR_IN_PROGRESS); ok(cbuf.off == 0); consumed = sbuf.off; @@ -1716,7 +1737,7 @@ static void do_test_pre_shared_key(int mode) consumed = sbuf.off; ret = ptls_handshake(client, &cbuf, sbuf.base, &consumed, &client_prop); ok(ret == 0); - ok(client_prop.client.early_data_acceptance == (mode < 2 ? PTLS_EARLY_DATA_ACCEPTED : PTLS_EARLY_DATA_REJECTED)); + ok(client_prop.client.early_data_acceptance == (mode < 3 ? PTLS_EARLY_DATA_ACCEPTED : PTLS_EARLY_DATA_REJECTED)); ok(consumed < sbuf.off); memmove(sbuf.base, sbuf.base + consumed, sbuf.off - consumed); sbuf.off -= consumed; @@ -1746,21 +1767,28 @@ static void do_test_pre_shared_key(int mode) ok(decbuf.off == 3); ok(memcmp(decbuf.base, "bye", 3) == 0); +Exit: ptls_buffer_dispose(&cbuf); ptls_buffer_dispose(&sbuf); ptls_buffer_dispose(&decbuf); ptls_free(client); ptls_free(server); - - *ctx = ctx_backup; } static void test_pre_shared_key(void) { + if (ctx != ctx_peer) { + note("psk tests use `ctx` only"); + return; + } + subtest("key-share:no", do_test_pre_shared_key, 0); subtest("key-share:yes", do_test_pre_shared_key, 1); - subtest("key-share:mismatch", do_test_pre_shared_key, 2); - subtest("key-share:negotiate", do_test_pre_shared_key, 3); + subtest("key-share:server-wo-key-share", do_test_pre_shared_key, 2); + subtest("key-share:mismatch", do_test_pre_shared_key, 3); + subtest("key-share:negotiate", do_test_pre_shared_key, 4); + + subtest("fail:requires-psk-dhe", do_test_pre_shared_key, -1); } typedef uint8_t traffic_secrets_t[2 /* is_enc */][4 /* epoch */][PTLS_MAX_DIGEST_SIZE /* octets */];