Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add support for ticket_request extension (RFC 9149) #548

Merged
merged 4 commits into from
Nov 6, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
25 changes: 25 additions & 0 deletions include/picotls.h
Original file line number Diff line number Diff line change
Expand Up @@ -308,6 +308,11 @@ extern "C" {
#define PTLS_TO__STR(n) #n
#define PTLS_TO_STR(n) PTLS_TO__STR(n)

/**
* default maximum of tickets to send (see ptls_context_t::ticket_requests.server.max_count)
*/
#define PTLS_DEFAULT_MAX_TICKETS_TO_SERVE 4

typedef struct st_ptls_t ptls_t;
typedef struct st_ptls_context_t ptls_context_t;
typedef struct st_ptls_key_schedule_t ptls_key_schedule_t;
Expand Down Expand Up @@ -1021,6 +1026,26 @@ struct st_ptls_context_t {
const ptls_iovec_t *list;
size_t count;
} client_ca_names;
/**
* (optional)
*/
struct {
/**
* if set to non-zero and if the save_ticket callback is provided, a ticket_request extension containing the specified
* values is sent
*/
struct {
uint8_t new_session_count;
uint8_t resumption_count;
} client;
/**
* if set to non-zero, the maximum number of tickets being sent is capped to the specifed value; if set to zero, the maximum
* adopted is PTLS_DEFAULT_MAX_TICKETS_TO_SERVE.
*/
struct {
uint8_t max_count;
} server;
} ticket_requests;
};

typedef struct st_ptls_raw_extension_t {
Expand Down
51 changes: 46 additions & 5 deletions lib/picotls.c
Original file line number Diff line number Diff line change
Expand Up @@ -69,6 +69,7 @@
#define PTLS_EXTENSION_TYPE_PSK_KEY_EXCHANGE_MODES 45
#define PTLS_EXTENSION_TYPE_CERTIFICATE_AUTHORITIES 47
#define PTLS_EXTENSION_TYPE_KEY_SHARE 51
#define PTLS_EXTENSION_TYPE_TICKET_REQUEST 58
#define PTLS_EXTENSION_TYPE_ECH_OUTER_EXTENSIONS 0xfd00
#define PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO 0xfe0d

Expand Down Expand Up @@ -294,7 +295,7 @@ struct st_ptls_t {
struct {
uint8_t pending_traffic_secret[PTLS_MAX_DIGEST_SIZE];
uint32_t early_data_skipped_bytes; /* if not UINT32_MAX, the server is skipping early data */
unsigned can_send_session_ticket : 1;
uint8_t num_tickets_to_send;
ptls_async_job_t *async_job;
} server;
};
Expand Down Expand Up @@ -359,6 +360,10 @@ struct st_ptls_client_hello_t {
size_t count;
} server_certificate_types;
unsigned status_request : 1;
struct {
uint8_t new_session_count;
uint8_t resumption_count;
} ticket_request;
/**
* ECH: payload.base != NULL indicates that the extension was received
*/
Expand Down Expand Up @@ -2258,6 +2263,13 @@ static int encode_client_hello(ptls_context_t *ctx, ptls_buffer_t *sendbuf, enum
ptls_buffer_push_block(sendbuf, 1, { ptls_buffer_push(sendbuf, PTLS_CERTIFICATE_TYPE_RAW_PUBLIC_KEY); });
});
}
if (ctx->save_ticket != NULL &&
(ctx->ticket_requests.client.new_session_count != 0 || ctx->ticket_requests.client.resumption_count != 0)) {
buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_TICKET_REQUEST, {
ptls_buffer_push(sendbuf, ctx->ticket_requests.client.new_session_count,
ctx->ticket_requests.client.resumption_count);
});
}
if ((ret = push_additional_extensions(properties, sendbuf)) != 0)
goto Exit;
if (ctx->save_ticket != NULL || psk_secret.base != NULL) {
Expand Down Expand Up @@ -3822,6 +3834,14 @@ static int decode_client_hello(ptls_context_t *ctx, struct st_ptls_client_hello_
case PTLS_EXTENSION_TYPE_STATUS_REQUEST:
ch->status_request = 1;
break;
case PTLS_EXTENSION_TYPE_TICKET_REQUEST:
if (end - src != 2) {
ret = PTLS_ALERT_DECODE_ERROR;
goto Exit;
}
ch->ticket_request.new_session_count = *src++;
ch->ticket_request.resumption_count = *src++;
break;
case PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO:
if ((ret = ptls_decode8(&ch->ech.type, &src, end)) != 0)
goto Exit;
Expand Down Expand Up @@ -4660,7 +4680,23 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl
properties->server.selected_psk_binder.len = selected->len;
}
}
tls->server.can_send_session_ticket = ch->psk.ke_modes != 0;

/* determine number of tickets to send */
if (ch->psk.ke_modes != 0 && tls->ctx->ticket_lifetime != 0) {
if (ch->ticket_request.new_session_count != 0) {
tls->server.num_tickets_to_send =
tls->is_psk_handshake ? ch->ticket_request.resumption_count : ch->ticket_request.new_session_count;
} else {
tls->server.num_tickets_to_send = 1;
}
uint8_t max_tickets = tls->ctx->ticket_requests.server.max_count;
if (max_tickets == 0)
max_tickets = PTLS_DEFAULT_MAX_TICKETS_TO_SERVE;
if (tls->server.num_tickets_to_send > max_tickets)
tls->server.num_tickets_to_send = max_tickets;
} else {
tls->server.num_tickets_to_send = 0;
}

if (accept_early_data && tls->ctx->max_early_data_size != 0 && psk_index == 0) {
if ((tls->pending_handshake_secret = malloc(PTLS_MAX_DIGEST_SIZE)) == NULL) {
Expand Down Expand Up @@ -4776,6 +4812,9 @@ static int server_handle_hello(ptls_t *tls, ptls_message_emitter_t *emitter, ptl
buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_ENCRYPTED_CLIENT_HELLO, {
ptls_buffer_pushv(sendbuf, tls->ctx->ech.server.retry_configs.base, tls->ctx->ech.server.retry_configs.len);
});
if (ch->ticket_request.new_session_count != 0 && tls->server.num_tickets_to_send != 0)
buffer_push_extension(sendbuf, PTLS_EXTENSION_TYPE_TICKET_REQUEST,
{ ptls_buffer_push(sendbuf, tls->server.num_tickets_to_send); });
if ((ret = push_additional_extensions(properties, sendbuf)) != 0)
goto Exit;
});
Expand Down Expand Up @@ -4888,9 +4927,11 @@ static int server_finish_handshake(ptls_t *tls, ptls_message_emitter_t *emitter,
}

/* send session ticket if necessary */
if (tls->server.can_send_session_ticket && tls->ctx->ticket_lifetime != 0) {
if ((ret = send_session_ticket(tls, emitter)) != 0)
goto Exit;
if (tls->server.num_tickets_to_send != 0) {
assert(tls->ctx->ticket_lifetime != 0);
for (uint8_t i = 0; i < tls->server.num_tickets_to_send; ++i)
if ((ret = send_session_ticket(tls, emitter)) != 0)
goto Exit;
}

if (tls->ctx->require_client_authentication) {
Expand Down
11 changes: 10 additions & 1 deletion t/cli.c
Original file line number Diff line number Diff line change
Expand Up @@ -391,6 +391,8 @@ static void usage(const char *cmd)
" -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"
" -T new_session_count,resumption_count\n"
" set number of session tickets to request\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"
Expand Down Expand Up @@ -459,7 +461,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:p:P:E:K:l:uy:vV:h")) != -1) {
while ((ch = getopt(argc, argv, "46abBC:c:i:Ij:k:nN:es:Sr:p:P:E:K:l:T:uy:vV:h")) != -1) {
switch (ch) {
case '4':
family = AF_INET;
Expand Down Expand Up @@ -594,6 +596,13 @@ int main(int argc, char **argv)
}
cipher_suites[slot] = added;
} break;
case 'T':
if (sscanf(optarg, "%" SCNu8 ",%" SCNu8, &ctx.ticket_requests.client.new_session_count,
&ctx.ticket_requests.client.resumption_count) != 2) {
fprintf(stderr, "invalid argument passed to -T, should be in the form of <new_session_count>,<resumption_count>\n");
exit(1);
}
break;
case 'h':
usage(argv[0]);
exit(0);
Expand Down
81 changes: 44 additions & 37 deletions t/picotls.c
Original file line number Diff line number Diff line change
Expand Up @@ -1314,17 +1314,19 @@ static int on_copy_ticket(ptls_encrypt_ticket_t *self, ptls_t *tls, int is_encry
return 0;
}

static ptls_iovec_t saved_ticket = {NULL};
static ptls_iovec_t saved_tickets[8] = {{NULL}};

static int on_save_ticket(ptls_save_ticket_t *self, ptls_t *tls, ptls_iovec_t src)
{
saved_ticket.base = malloc(src.len);
memcpy(saved_ticket.base, src.base, src.len);
saved_ticket.len = src.len;
memmove(saved_tickets + 1, saved_tickets, sizeof(saved_tickets[0]) * (PTLS_ELEMENTSOF(saved_tickets) - 1));
saved_tickets[0].base = malloc(src.len);
memcpy(saved_tickets[0].base, src.base, src.len);
saved_tickets[0].len = src.len;
return 0;
}

static void test_resumption_impl(int different_preferred_key_share, int require_client_authentication, int transfer_session)
static void test_resumption_impl(int different_preferred_key_share, int require_client_authentication, int use_ticket_request,
int transfer_session)
{
assert(ctx->key_exchanges[0]->id == ctx_peer->key_exchanges[0]->id);
assert(ctx->key_exchanges[1] == NULL);
Expand All @@ -1335,6 +1337,10 @@ static void test_resumption_impl(int different_preferred_key_share, int require_

if (different_preferred_key_share)
ctx->key_exchanges = different_key_exchanges;
if (use_ticket_request) {
ctx->ticket_requests.client.new_session_count = 3;
ctx->ticket_requests.client.resumption_count = 2;
}

ptls_encrypt_ticket_t et = {on_copy_ticket};
ptls_save_ticket_t st = {on_save_ticket};
Expand All @@ -1343,66 +1349,66 @@ static void test_resumption_impl(int different_preferred_key_share, int require_
assert(ctx_peer->max_early_data_size == 0);
assert(ctx_peer->encrypt_ticket == NULL);
assert(ctx_peer->save_ticket == NULL);
saved_ticket = ptls_iovec_init(NULL, 0);
memset(saved_tickets, 0, sizeof(saved_tickets));

ctx_peer->ticket_lifetime = 86400;
ctx_peer->max_early_data_size = 8192;
ctx_peer->encrypt_ticket = &et;
ctx->save_ticket = &st;

test_handshake(saved_ticket, different_preferred_key_share ? TEST_HANDSHAKE_2RTT : TEST_HANDSHAKE_1RTT, 1, 0, 0,
test_handshake(ptls_iovec_init(NULL, 0), different_preferred_key_share ? TEST_HANDSHAKE_2RTT : TEST_HANDSHAKE_1RTT, 1, 0, 0,
transfer_session);
ok(server_sc_callcnt == 1);
ok(saved_ticket.base != NULL);
if (use_ticket_request) {
/* should have received 3 tickets */
ok(saved_tickets[2].base != NULL);
ok(saved_tickets[3].base == NULL);
} else {
ok(saved_tickets[0].base != NULL);
}

/* psk using saved ticket */
test_handshake(saved_ticket, TEST_HANDSHAKE_1RTT, 1, 0, require_client_authentication, transfer_session);
test_handshake(saved_tickets[0], TEST_HANDSHAKE_1RTT, 1, 0, require_client_authentication, transfer_session);
ok(server_sc_callcnt == require_client_authentication); /* client authentication turns off resumption */
ok(client_sc_callcnt == require_client_authentication);
if (use_ticket_request && !require_client_authentication) {
/* should have received 2 tickets */
ok(saved_tickets[4].base != NULL);
ok(saved_tickets[5].base == NULL);
}

/* 0-rtt psk using saved ticket */
test_handshake(saved_ticket, TEST_HANDSHAKE_EARLY_DATA, 1, 0, require_client_authentication, transfer_session);
test_handshake(saved_tickets[0], TEST_HANDSHAKE_EARLY_DATA, 1, 0, require_client_authentication, transfer_session);
ok(server_sc_callcnt == require_client_authentication); /* client authentication turns off resumption */
ok(client_sc_callcnt == require_client_authentication);

ctx->require_dhe_on_psk = 1;

/* psk-dhe using saved ticket */
test_handshake(saved_ticket, TEST_HANDSHAKE_1RTT, 1, 0, require_client_authentication, transfer_session);
test_handshake(saved_tickets[0], TEST_HANDSHAKE_1RTT, 1, 0, require_client_authentication, transfer_session);
ok(server_sc_callcnt == require_client_authentication); /* client authentication turns off resumption */
ok(client_sc_callcnt == require_client_authentication);

/* 0-rtt psk-dhe using saved ticket */
test_handshake(saved_ticket, TEST_HANDSHAKE_EARLY_DATA, 1, 0, require_client_authentication, transfer_session);
test_handshake(saved_tickets[0], TEST_HANDSHAKE_EARLY_DATA, 1, 0, require_client_authentication, transfer_session);
ok(server_sc_callcnt == require_client_authentication); /* client authentication turns off resumption */
ok(client_sc_callcnt == require_client_authentication);

ctx->require_dhe_on_psk = 0;
ctx->ticket_requests.client.new_session_count = 0;
ctx->ticket_requests.client.resumption_count = 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)
{
test_resumption_impl(0, 0, 0);
test_resumption_impl(0, 0, 1);
}

static void test_resumption_different_preferred_key_share(void)
{
if (ctx == ctx_peer)
return;
test_resumption_impl(1, 0, 0);
test_resumption_impl(0, 0, 1);
}

static void test_resumption_with_client_authentication(void)
static void test_resumption(int different_preferred_key_share, int require_client_authentication)
{
test_resumption_impl(0, 0, 0);
test_resumption_impl(0, 1, 1);
subtest("basic", test_resumption_impl, different_preferred_key_share, require_client_authentication, 0, 0);
subtest("transfer-session", test_resumption_impl, different_preferred_key_share, require_client_authentication, 0, 1);
subtest("ticket-request", test_resumption_impl, different_preferred_key_share, require_client_authentication, 1, 0);
}

static void test_async_sign_certificate(void)
Expand Down Expand Up @@ -1844,7 +1850,7 @@ static void test_handshake_api(void)
ctx_peer->ticket_lifetime = 86400;
ctx_peer->max_early_data_size = 8192;

saved_ticket = ptls_iovec_init(NULL, 0);
memset(saved_tickets, 0, sizeof(saved_tickets));

ptls_buffer_init(&cbuf, "", 0);
ptls_buffer_init(&sbuf, "", 0);
Expand Down Expand Up @@ -1893,7 +1899,7 @@ static void test_handshake_api(void)

/* 0-RTT resumption */
size_t max_early_data_size = 0;
ptls_handshake_properties_t client_hs_prop = {{{{NULL}, saved_ticket, &max_early_data_size}}};
ptls_handshake_properties_t client_hs_prop = {{{{NULL}, saved_tickets[0], &max_early_data_size}}};
client = ptls_new(ctx, 0);
*ptls_get_data_ptr(client) = &client_secrets;
server = ptls_new(ctx_peer, 1);
Expand Down Expand Up @@ -1935,7 +1941,7 @@ static void test_handshake_api(void)

/* 0-RTT rejection */
ctx_peer->max_early_data_size = 0;
client_hs_prop = (ptls_handshake_properties_t){{{{NULL}, saved_ticket, &max_early_data_size}}};
client_hs_prop = (ptls_handshake_properties_t){{{{NULL}, saved_tickets[0], &max_early_data_size}}};
client = ptls_new(ctx, 0);
*ptls_get_data_ptr(client) = &client_secrets;
server = ptls_new(ctx_peer, 1);
Expand Down Expand Up @@ -1973,7 +1979,7 @@ static void test_handshake_api(void)
ctx_peer->max_early_data_size = 8192;
ptls_handshake_properties_t server_hs_prop = {{{{NULL}}}};
server_hs_prop.server.enforce_retry = 1;
client_hs_prop = (ptls_handshake_properties_t){{{{NULL}, saved_ticket, &max_early_data_size}}};
client_hs_prop = (ptls_handshake_properties_t){{{{NULL}, saved_tickets[0], &max_early_data_size}}};
client = ptls_new(ctx, 0);
*ptls_get_data_ptr(client) = &client_secrets;
server = ptls_new(ctx_peer, 1);
Expand Down Expand Up @@ -2015,7 +2021,7 @@ static void test_handshake_api(void)
ctx->omit_end_of_early_data = 0;
ctx_peer->update_traffic_key = NULL;
ctx_peer->omit_end_of_early_data = 0;
client_hs_prop = (ptls_handshake_properties_t){{{{NULL}, saved_ticket, &max_early_data_size}}};
client_hs_prop = (ptls_handshake_properties_t){{{{NULL}, saved_tickets[0], &max_early_data_size}}};
server_hs_prop = (ptls_handshake_properties_t){{{{NULL}}}};
server_hs_prop.server.enforce_retry = 1;
client = ptls_new(ctx, 0);
Expand Down Expand Up @@ -2083,9 +2089,10 @@ static void test_all_handshakes_core(void)
subtest("hrr-handshake", test_hrr_handshake);
/* resumption does not work when the client offers ECH but the server does not recognize that */
if (!(can_ech(ctx, 0) && !can_ech(ctx_peer, 1))) {
subtest("resumption", test_resumption);
subtest("resumption-different-preferred-key-share", test_resumption_different_preferred_key_share);
subtest("resumption-with-client-authentication", test_resumption_with_client_authentication);
subtest("resumption", test_resumption, 0, 0);
if (ctx != ctx_peer)
subtest("resumption-different-preferred-key-share", test_resumption, 1, 0);
subtest("resumption-with-client-authentication", test_resumption, 0, 1);
}
subtest("async-sign-certificate", test_async_sign_certificate);
subtest("enforce-retry-stateful", test_enforce_retry_stateful);
Expand Down
Loading