diff --git a/lib/picotls.c b/lib/picotls.c index eefd4f671..117031bfa 100644 --- a/lib/picotls.c +++ b/lib/picotls.c @@ -160,6 +160,10 @@ struct st_ptls_t { * result of ALPN */ char *negotiated_protocol; + /** + * selected key-exchange + */ + ptls_key_exchange_algorithm_t *key_share; /** * selected cipher-suite */ @@ -186,10 +190,7 @@ struct st_ptls_t { union { struct { uint8_t legacy_session_id[16]; - struct { - ptls_key_exchange_algorithm_t *algo; - ptls_key_exchange_context_t *ctx; - } key_exchange; + ptls_key_exchange_context_t *key_share_ctx; struct { int (*cb)(void *verify_ctx, ptls_iovec_t data, ptls_iovec_t signature); void *verify_ctx; @@ -892,11 +893,11 @@ static int decode_new_session_ticket(uint32_t *lifetime, uint32_t *age_add, ptls return ret; } -static int decode_stored_session_ticket(ptls_context_t *ctx, ptls_cipher_suite_t **cs, ptls_iovec_t *secret, - uint32_t *obfuscated_ticket_age, ptls_iovec_t *ticket, uint32_t *max_early_data_size, - const uint8_t *src, const uint8_t *const end) +static int decode_stored_session_ticket(ptls_context_t *ctx, ptls_key_exchange_algorithm_t **key_share, ptls_cipher_suite_t **cs, + ptls_iovec_t *secret, uint32_t *obfuscated_ticket_age, ptls_iovec_t *ticket, + uint32_t *max_early_data_size, const uint8_t *src, const uint8_t *const end) { - uint16_t csid; + uint16_t kxid, csid; uint32_t lifetime, age_add; uint64_t obtained_at, now; ptls_iovec_t nonce; @@ -905,6 +906,8 @@ static int decode_stored_session_ticket(ptls_context_t *ctx, ptls_cipher_suite_t /* decode */ if ((ret = ptls_decode64(&obtained_at, &src, end)) != 0) goto Exit; + if ((ret = ptls_decode16(&kxid, &src, end)) != 0) + goto Exit; if ((ret = ptls_decode16(&csid, &src, end)) != 0) goto Exit; ptls_decode_open_block(src, end, 3, { @@ -917,6 +920,18 @@ static int decode_stored_session_ticket(ptls_context_t *ctx, ptls_cipher_suite_t src = end; }); + { /* determine the key-exchange */ + ptls_key_exchange_algorithm_t **cand; + for (cand = ctx->key_exchanges; *cand != NULL; ++cand) + if ((*cand)->id == kxid) + break; + if (*cand == NULL) { + ret = PTLS_ERROR_LIBRARY; + goto Exit; + } + *key_share = *cand; + } + { /* determine the cipher-suite */ ptls_cipher_suite_t **cand; for (cand = ctx->cipher_suites; *cand != NULL; ++cand) @@ -986,12 +1001,12 @@ static int retire_early_data_secret(ptls_t *tls, int is_enc) return setup_traffic_protection(tls, is_enc, NULL, "CLIENT_HANDSHAKE_TRAFFIC_SECRET"); } -#define SESSION_IDENTIFIER_MAGIC "ptls0000" /* the number should be changed upon incompatible format change */ +#define SESSION_IDENTIFIER_MAGIC "ptls0001" /* the number should be changed upon incompatible format change */ #define SESSION_IDENTIFIER_MAGIC_SIZE (sizeof(SESSION_IDENTIFIER_MAGIC) - 1) static int encode_session_identifier(ptls_context_t *ctx, ptls_buffer_t *buf, uint32_t ticket_age_add, ptls_iovec_t ticket_nonce, - struct st_ptls_key_schedule_t *sched, const char *server_name, uint16_t csid, - const char *negotiated_protocol) + struct st_ptls_key_schedule_t *sched, const char *server_name, uint16_t key_exchange_id, + uint16_t csid, const char *negotiated_protocol) { int ret = 0; @@ -1008,6 +1023,8 @@ static int encode_session_identifier(ptls_context_t *ctx, ptls_buffer_t *buf, ui goto Exit; buf->off += sched->hashes[0].algo->digest_size; }); + /* key-exchange */ + ptls_buffer_push16(buf, key_exchange_id); /* cipher-suite */ ptls_buffer_push16(buf, csid); /* ticket_age_add */ @@ -1029,7 +1046,8 @@ static int encode_session_identifier(ptls_context_t *ctx, ptls_buffer_t *buf, ui } int decode_session_identifier(uint64_t *issued_at, ptls_iovec_t *psk, uint32_t *ticket_age_add, ptls_iovec_t *server_name, - uint16_t *csid, ptls_iovec_t *negotiated_protocol, const uint8_t *src, const uint8_t *const end) + uint16_t *key_exchange_id, uint16_t *csid, ptls_iovec_t *negotiated_protocol, const uint8_t *src, + const uint8_t *const end) { int ret = 0; @@ -1046,6 +1064,8 @@ int decode_session_identifier(uint64_t *issued_at, ptls_iovec_t *psk, uint32_t * *psk = ptls_iovec_init(src, end - src); src = end; }); + if ((ret = ptls_decode16(key_exchange_id, &src, end)) != 0) + goto Exit; if ((ret = ptls_decode16(csid, &src, end)) != 0) goto Exit; if ((ret = ptls_decode32(ticket_age_add, &src, end)) != 0) @@ -1176,7 +1196,7 @@ static int send_session_ticket(ptls_t *tls, ptls_buffer_t *sendbuf) /* build the raw nsk */ ptls_buffer_init(&session_id, session_id_smallbuf, sizeof(session_id_smallbuf)); ret = encode_session_identifier(tls->ctx, &session_id, ticket_age_add, ptls_iovec_init(NULL, 0), tls->key_schedule, - tls->server_name, tls->cipher_suite->id, tls->negotiated_protocol); + tls->server_name, tls->key_share->id, tls->cipher_suite->id, tls->negotiated_protocol); if (ret != 0) goto Exit; @@ -1234,8 +1254,7 @@ static int push_additional_extensions(ptls_handshake_properties_t *properties, p return ret; } -static int send_client_hello(ptls_t *tls, ptls_buffer_t *sendbuf, ptls_handshake_properties_t *properties, - ptls_key_exchange_algorithm_t *key_share, ptls_iovec_t *cookie) +static int send_client_hello(ptls_t *tls, ptls_buffer_t *sendbuf, ptls_handshake_properties_t *properties, ptls_iovec_t *cookie) { ptls_iovec_t resumption_secret = {NULL}, resumption_ticket; uint32_t obfuscated_ticket_age = 0; @@ -1246,12 +1265,14 @@ static int send_client_hello(ptls_t *tls, ptls_buffer_t *sendbuf, ptls_handshake if (properties != NULL) { /* setup resumption-related data. If successful, resumption_secret becomes a non-zero value. */ if (properties->client.session_ticket.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->ctx, &cipher_suite, &resumption_secret, &obfuscated_ticket_age, + if (decode_stored_session_ticket(tls->ctx, &key_share, &cipher_suite, &resumption_secret, &obfuscated_ticket_age, &resumption_ticket, &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; + 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) { *properties->client.max_early_data_size = max_early_data_size; @@ -1268,6 +1289,10 @@ static int send_client_hello(ptls_t *tls, ptls_buffer_t *sendbuf, ptls_handshake *properties->client.max_early_data_size = 0; } + /* use the default key share if still not undetermined */ + if (tls->key_share == NULL && !(properties != NULL && properties->client.negotiate_before_key_exchange)) + tls->key_share = tls->ctx->key_exchanges[0]; + if (!is_second_flight) { tls->key_schedule = key_schedule_new(tls->cipher_suite, tls->ctx->cipher_suites); if ((ret = key_schedule_extract(tls->key_schedule, resumption_secret)) != 0) @@ -1335,12 +1360,11 @@ static int send_client_hello(ptls_t *tls, ptls_buffer_t *sendbuf, ptls_handshake }); buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_KEY_SHARE, { ptls_buffer_push_block(sendbuf, 2, { - if (key_share != NULL) { + if (tls->key_share != NULL) { ptls_iovec_t pubkey; - if ((ret = key_share->create(&tls->client.key_exchange.ctx, &pubkey)) != 0) + if ((ret = tls->key_share->create(&tls->client.key_share_ctx, &pubkey)) != 0) goto Exit; - tls->client.key_exchange.algo = key_share; - ptls_buffer_push16(sendbuf, tls->client.key_exchange.algo->id); + ptls_buffer_push16(sendbuf, tls->key_share->id); ptls_buffer_push_block(sendbuf, 2, { ptls_buffer_pushv(sendbuf, pubkey.base, pubkey.len); }); } }); @@ -1507,7 +1531,7 @@ static int decode_server_hello(ptls_t *tls, struct st_ptls_server_hello_t *sh, c ret = PTLS_ALERT_DECODE_ERROR; goto Exit; } - if (tls->client.key_exchange.algo == NULL || tls->client.key_exchange.algo->id != group) { + if (tls->key_share == NULL || tls->key_share->id != group) { ret = PTLS_ALERT_ILLEGAL_PARAMETER; goto Exit; } @@ -1573,31 +1597,34 @@ static int decode_server_hello(ptls_t *tls, struct st_ptls_server_hello_t *sh, c static int handle_hello_retry_request(ptls_t *tls, ptls_buffer_t *sendbuf, struct st_ptls_server_hello_t *sh, ptls_iovec_t message, ptls_handshake_properties_t *properties) { - ptls_key_exchange_algorithm_t **selected_group = NULL; int ret; - if (tls->client.key_exchange.ctx != NULL) { - tls->client.key_exchange.ctx->on_exchange(&tls->client.key_exchange.ctx, NULL, ptls_iovec_init(NULL, 0)); - tls->client.key_exchange.ctx = NULL; + if (tls->client.key_share_ctx != NULL) { + tls->client.key_share_ctx->on_exchange(&tls->client.key_share_ctx, NULL, ptls_iovec_init(NULL, 0)); + tls->client.key_share_ctx = NULL; } if (sh->retry_request.selected_group != UINT16_MAX) { /* we offer the first key_exchanges[0] as KEY_SHARE unless client.negotiate_before_key_exchange is set */ - for (selected_group = tls->ctx->key_exchanges; *selected_group != NULL; ++selected_group) - if ((*selected_group)->id == sh->retry_request.selected_group) + ptls_key_exchange_algorithm_t **cand; + for (cand = tls->ctx->key_exchanges; *cand != NULL; ++cand) + if ((*cand)->id == sh->retry_request.selected_group) break; - if (*selected_group == NULL) { + if (*cand == NULL) { ret = PTLS_ALERT_ILLEGAL_PARAMETER; goto Exit; } + tls->key_share = *cand; + } else if (tls->key_share != NULL) { + /* retain the key-share using in first CH, if server does not specify one */ } else { - /* This will happen when there was no Key Share extension in the HRR */ - selected_group = &tls->ctx->key_exchanges[0]; + ret = PTLS_ALERT_ILLEGAL_PARAMETER; + goto Exit; } key_schedule_transform_post_ch1hash(tls->key_schedule); key_schedule_update_hash(tls->key_schedule, message.base, message.len); - ret = send_client_hello(tls, sendbuf, properties, *selected_group, &sh->retry_request.cookie); + ret = send_client_hello(tls, sendbuf, properties, &sh->retry_request.cookie); Exit: return ret; @@ -1628,7 +1655,7 @@ static int client_handle_hello(ptls_t *tls, ptls_buffer_t *sendbuf, ptls_iovec_t goto Exit; if (sh.peerkey.base != NULL) { - if ((ret = tls->client.key_exchange.ctx->on_exchange(&tls->client.key_exchange.ctx, &ecdh_secret, sh.peerkey)) != 0) + if ((ret = tls->client.key_share_ctx->on_exchange(&tls->client.key_share_ctx, &ecdh_secret, sh.peerkey)) != 0) goto Exit; } @@ -1905,6 +1932,7 @@ static int client_handle_new_session_ticket(ptls_t *tls, ptls_iovec_t message) uint8_t ticket_buf_small[512]; ptls_buffer_init(&ticket_buf, ticket_buf_small, sizeof(ticket_buf_small)); ptls_buffer_push64(&ticket_buf, tls->ctx->get_time->cb(tls->ctx->get_time)); + ptls_buffer_push16(&ticket_buf, tls->key_share->id); ptls_buffer_push16(&ticket_buf, tls->cipher_suite->id); ptls_buffer_push_block(&ticket_buf, 3, { ptls_buffer_pushv(&ticket_buf, src, end - src); }); ptls_buffer_push_block(&ticket_buf, 2, { @@ -2272,7 +2300,7 @@ static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_d ptls_iovec_t ticket_psk, 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_csid; + uint16_t ticket_key_exchange_id, ticket_csid; uint8_t decbuf_small[256], binder_key[PTLS_MAX_DIGEST_SIZE], verify_data[PTLS_MAX_DIGEST_SIZE]; int ret; @@ -2284,7 +2312,7 @@ static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_d decbuf.off = 0; if ((tls->ctx->encrypt_ticket->cb(tls->ctx->encrypt_ticket, tls, 0, &decbuf, identity->identity)) != 0) continue; - if (decode_session_identifier(&issue_at, &ticket_psk, &age_add, &ticket_server_name, &ticket_csid, + if (decode_session_identifier(&issue_at, &ticket_psk, &age_add, &ticket_server_name, &ticket_key_exchange_id, &ticket_csid, &ticket_negotiated_protocol, decbuf.base, decbuf.base + decbuf.off) != 0) continue; /* check age */ @@ -2308,6 +2336,14 @@ static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_d if (tls->server_name != NULL) continue; } + { /* check key-exchange */ + ptls_key_exchange_algorithm_t **a; + for (a = tls->ctx->key_exchanges; *a != NULL && (*a)->id != ticket_key_exchange_id; ++a) + ; + if (*a == NULL) + continue; + tls->key_share = *a; + } /* check cipher-suite */ if (ticket_csid != tls->cipher_suite->id) continue; @@ -2331,6 +2367,7 @@ static int try_psk_handshake(ptls_t *tls, size_t *psk_index, int *accept_early_d /* not found */ *psk_index = SIZE_MAX; *accept_early_data = 0; + tls->key_share = NULL; ret = 0; goto Exit; @@ -2651,6 +2688,7 @@ static int server_handle_hello(ptls_t *tls, ptls_buffer_t *sendbuf, ptls_iovec_t } if ((ret = key_share.algorithm->exchange(&pubkey, &ecdh_secret, key_share.peer_key)) != 0) goto Exit; + tls->key_share = key_share.algorithm; } /* send ServerHello */ @@ -2945,8 +2983,8 @@ void ptls_free(ptls_t *tls) if (tls->is_server) { /* nothing to do */ } else { - if (tls->client.key_exchange.ctx != NULL) - tls->client.key_exchange.ctx->on_exchange(&tls->client.key_exchange.ctx, NULL, ptls_iovec_init(NULL, 0)); + if (tls->client.key_share_ctx != NULL) + tls->client.key_share_ctx->on_exchange(&tls->client.key_share_ctx, NULL, ptls_iovec_init(NULL, 0)); if (tls->client.certificate_verify.cb != NULL) tls->client.certificate_verify.cb(tls->client.certificate_verify.verify_ctx, ptls_iovec_init(NULL, 0), ptls_iovec_init(NULL, 0)); @@ -3305,9 +3343,7 @@ int ptls_handshake(ptls_t *tls, ptls_buffer_t *sendbuf, const void *input, size_ case PTLS_STATE_CLIENT_HANDSHAKE_START: assert(input == NULL || *inlen == 0); assert(tls->ctx->key_exchanges[0] != NULL); - return send_client_hello( - tls, sendbuf, properties, - properties != NULL && properties->client.negotiate_before_key_exchange ? NULL : tls->ctx->key_exchanges[0], NULL); + return send_client_hello(tls, sendbuf, properties, NULL); default: break; } diff --git a/t/picotls.c b/t/picotls.c index 331146e38..bb90c62cb 100644 --- a/t/picotls.c +++ b/t/picotls.c @@ -26,6 +26,7 @@ #include #include #include "picotls.h" +#include "picotls/minicrypto.h" #include "../deps/picotest/picotest.h" #include "../lib/picotls.c" #include "test.h" @@ -332,9 +333,9 @@ static int save_client_hello(ptls_on_client_hello_t *self, ptls_t *tls, ptls_iov return 0; } -enum { TEST_HANDSHAKE_FULL, TEST_HANDSHAKE_HRR, TEST_HANDSHAKE_HRR_STATELESS, TEST_HANDSHAKE_RESUME, TEST_HANDSHAKE_EARLY_DATA }; +enum { TEST_HANDSHAKE_1RTT, TEST_HANDSHAKE_2RTT, TEST_HANDSHAKE_HRR, TEST_HANDSHAKE_HRR_STATELESS, TEST_HANDSHAKE_EARLY_DATA }; -static void test_handshake(ptls_iovec_t ticket, int mode, int check_ch) +static void test_handshake(ptls_iovec_t ticket, int mode, int expect_ticket, int check_ch) { ptls_t *client, *server; ptls_handshake_properties_t client_hs_prop = {{{{NULL}, ticket}}}, server_hs_prop = {{{{NULL}}}}; @@ -380,6 +381,7 @@ static void test_handshake(ptls_iovec_t ticket, int mode, int check_ch) ok(cbuf.off != 0); switch (mode) { + case TEST_HANDSHAKE_2RTT: case TEST_HANDSHAKE_HRR: case TEST_HANDSHAKE_HRR_STATELESS: consumed = cbuf.off; @@ -458,7 +460,7 @@ static void test_handshake(ptls_iovec_t ticket, int mode, int check_ch) ok(ptls_get_negotiated_protocol(server) == NULL); } - if (mode >= TEST_HANDSHAKE_RESUME) { + if (expect_ticket) { ok(consumed < sbuf.off); memmove(sbuf.base, sbuf.base + consumed, sbuf.off - consumed); sbuf.off -= consumed; @@ -525,25 +527,25 @@ static int sign_certificate(ptls_sign_certificate_t *self, ptls_t *tls, uint16_t static void test_full_handshake(void) { sc_callcnt = 0; - test_handshake(ptls_iovec_init(NULL, 0), TEST_HANDSHAKE_FULL, 0); + test_handshake(ptls_iovec_init(NULL, 0), TEST_HANDSHAKE_1RTT, 0, 0); ok(sc_callcnt == 1); - test_handshake(ptls_iovec_init(NULL, 0), TEST_HANDSHAKE_FULL, 0); + test_handshake(ptls_iovec_init(NULL, 0), TEST_HANDSHAKE_1RTT, 0, 0); ok(sc_callcnt == 2); - test_handshake(ptls_iovec_init(NULL, 0), TEST_HANDSHAKE_FULL, 1); + test_handshake(ptls_iovec_init(NULL, 0), TEST_HANDSHAKE_1RTT, 0, 1); ok(sc_callcnt == 3); } static void test_hrr_handshake(void) { sc_callcnt = 0; - test_handshake(ptls_iovec_init(NULL, 0), TEST_HANDSHAKE_HRR, 0); + test_handshake(ptls_iovec_init(NULL, 0), TEST_HANDSHAKE_HRR, 0, 0); ok(sc_callcnt == 1); } static void test_hrr_stateless_handshake(void) { sc_callcnt = 0; - test_handshake(ptls_iovec_init(NULL, 0), TEST_HANDSHAKE_HRR_STATELESS, 0); + test_handshake(ptls_iovec_init(NULL, 0), TEST_HANDSHAKE_HRR_STATELESS, 0, 0); ok(sc_callcnt == 1); } @@ -569,8 +571,18 @@ static int save_ticket(ptls_save_ticket_t *self, ptls_t *tls, ptls_iovec_t src) return 0; } -static void test_resumption(void) +static void do_test_resumption(int different_preferred_key_share) { + assert(ctx->key_exchanges[0]->id == ctx_peer->key_exchanges[0]->id); + assert(ctx->key_exchanges[1] == NULL); + assert(ctx_peer->key_exchanges[1] == NULL); + assert(ctx->key_exchanges[0]->id != ptls_minicrypto_x25519.id); + ptls_key_exchange_algorithm_t *different_key_exchanges[] = {&ptls_minicrypto_x25519, ctx->key_exchanges[0], NULL}, + **key_exchanges_orig = ctx->key_exchanges; + + if (different_preferred_key_share) + ctx->key_exchanges = different_key_exchanges; + ptls_encrypt_ticket_t et = {copy_ticket}; ptls_save_ticket_t st = {save_ticket}; @@ -586,27 +598,46 @@ static void test_resumption(void) ctx->save_ticket = &st; sc_callcnt = 0; - test_handshake(saved_ticket, TEST_HANDSHAKE_RESUME, 0); + test_handshake(saved_ticket, different_preferred_key_share ? TEST_HANDSHAKE_2RTT : TEST_HANDSHAKE_1RTT, 1, 0); ok(sc_callcnt == 1); ok(saved_ticket.base != NULL); /* psk using saved ticket */ - test_handshake(saved_ticket, TEST_HANDSHAKE_RESUME, 0); + test_handshake(saved_ticket, TEST_HANDSHAKE_1RTT, 1, 0); + ok(sc_callcnt == 1); + + /* 0-rtt psk using saved ticket */ + test_handshake(saved_ticket, TEST_HANDSHAKE_EARLY_DATA, 1, 0); ok(sc_callcnt == 1); - /* psk-dhe using saved ticket */ ctx->require_dhe_on_psk = 1; - test_handshake(saved_ticket, TEST_HANDSHAKE_RESUME, 0); + + /* psk-dhe using saved ticket */ + test_handshake(saved_ticket, TEST_HANDSHAKE_1RTT, 1, 0); ok(sc_callcnt == 1); - ctx->require_dhe_on_psk = 0; - /* 0-rtt psk using saved ticket */ - test_handshake(saved_ticket, TEST_HANDSHAKE_EARLY_DATA, 0); + /* 0-rtt psk-dhe using saved ticket */ + test_handshake(saved_ticket, TEST_HANDSHAKE_EARLY_DATA, 1, 0); + ok(sc_callcnt == 1); + ctx->require_dhe_on_psk = 0; ctx_peer->ticket_lifetime = 0; ctx_peer->max_early_data_size = 0; ctx_peer->encrypt_ticket = NULL; ctx->save_ticket = NULL; + ctx->key_exchanges = key_exchanges_orig; +} + +static void test_resumption(void) +{ + do_test_resumption(0); +} + +static void test_resumption_different_preferred_key_share(void) +{ + if (ctx == ctx_peer) + return; + do_test_resumption(1); } static void test_enforce_retry(int use_cookie) @@ -776,6 +807,7 @@ void test_picotls(void) subtest("hrr-handshake", test_hrr_handshake); subtest("hrr-stateless-handshake", test_hrr_stateless_handshake); subtest("resumption", test_resumption); + subtest("resumption-different-preferred-key-share", test_resumption_different_preferred_key_share); subtest("enforce-retry-stateful", test_enforce_retry_stateful); subtest("enforce-retry-stateless", test_enforce_retry_stateless);