Skip to content

Commit

Permalink
Merge pull request #548 from h2o/kazuho/ticket-requests
Browse files Browse the repository at this point in the history
add support for ticket_request extension (RFC 9149)
  • Loading branch information
kazuho authored Nov 6, 2024
2 parents f07db15 + 2b05a22 commit e7d65c7
Show file tree
Hide file tree
Showing 4 changed files with 125 additions and 43 deletions.
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

0 comments on commit e7d65c7

Please sign in to comment.