quic_ticket (quic v2.0.3)
View SourceSession ticket storage and PSK derivation for 0-RTT.
This module handles: - Storing and retrieving NewSessionTicket messages - Deriving PSK from resumption_master_secret - Managing ticket lifetimes and early data limits
Two stores
new_store/0 and friends are a plain map held in one connection's state: pure, per-connection, gone when the connection ends.
store_global/2 and friends are a node-wide ETS table shared by every connection, which is what lets a server resume a session it issued on a different connection. Those functions touch ETS, the clock and the random source, so they are not pure and cannot be called from a test without the table existing.
Summary
Functions
Build a NewSessionTicket message.
Remove all expired tickets from the store.
Remove a ticket for a server.
Remove a ticket after it is used for resumption (single-use 0-RTT anti-replay). Issued tickets live in the node-wide table.
Create a session ticket from connection state. This is used by the server to issue tickets to clients.
Derive PSK from resumption_master_secret and ticket nonce. RFC 8446 Section 4.6.1: PSK = HKDF-Expand-Label(resumption_master_secret, "resumption", ticket_nonce, Hash.length)
Derive the resumption_master_secret from the master secret. RFC 8446 Section 7.1: resumption_master_secret = Derive-Secret(Master Secret, "res master", ClientHello..client Finished)
Find a ticket by its identity in a per-connection store.
Look a ticket up in the node-wide table, dropping it if expired.
Look up a ticket for a server. Returns {ok, Ticket} if found and not expired, error otherwise.
Create a new empty ticket store.
Parse a NewSessionTicket message from the server. RFC 8446 Section 4.6.1: struct { uint32 ticket_lifetime; uint32 ticket_age_add; opaque ticket_nonce<0..255>; opaque ticket<1..2^16-1>; Extension extensions<0..2^16-2>; } NewSessionTicket;
Store a ticket in the node-wide table.
Store a session ticket for a server. The ticket is indexed by server name.
Types
-type session_ticket() :: #session_ticket{server_name :: binary(), ticket :: binary(), lifetime :: non_neg_integer(), age_add :: non_neg_integer(), nonce :: binary(), resumption_secret :: binary(), max_early_data :: non_neg_integer(), received_at :: non_neg_integer(), cipher :: atom(), alpn :: binary() | undefined}.
-type ticket_store() :: #{binary() => #session_ticket{server_name :: binary(), ticket :: binary(), lifetime :: non_neg_integer(), age_add :: non_neg_integer(), nonce :: binary(), resumption_secret :: binary(), max_early_data :: non_neg_integer(), received_at :: non_neg_integer(), cipher :: atom(), alpn :: binary() | undefined}}.
Functions
-spec build_new_session_ticket(#session_ticket{server_name :: binary(), ticket :: binary(), lifetime :: non_neg_integer(), age_add :: non_neg_integer(), nonce :: binary(), resumption_secret :: binary(), max_early_data :: non_neg_integer(), received_at :: non_neg_integer(), cipher :: atom(), alpn :: binary() | undefined}) -> binary().
Build a NewSessionTicket message.
-spec clear_expired(ticket_store()) -> ticket_store().
Remove all expired tickets from the store.
-spec clear_ticket(binary(), ticket_store()) -> ticket_store().
Remove a ticket for a server.
-spec consume_global(binary()) -> true.
Remove a ticket after it is used for resumption (single-use 0-RTT anti-replay). Issued tickets live in the node-wide table.
-spec create_ticket(binary(), binary(), non_neg_integer(), atom(), binary() | undefined) -> #session_ticket{server_name :: binary(), ticket :: binary(), lifetime :: non_neg_integer(), age_add :: non_neg_integer(), nonce :: binary(), resumption_secret :: binary(), max_early_data :: non_neg_integer(), received_at :: non_neg_integer(), cipher :: atom(), alpn :: binary() | undefined}.
Create a session ticket from connection state. This is used by the server to issue tickets to clients.
-spec derive_psk(binary(), #session_ticket{server_name :: binary(), ticket :: binary(), lifetime :: non_neg_integer(), age_add :: non_neg_integer(), nonce :: binary(), resumption_secret :: binary(), max_early_data :: non_neg_integer(), received_at :: non_neg_integer(), cipher :: atom(), alpn :: binary() | undefined}) -> binary().
Derive PSK from resumption_master_secret and ticket nonce. RFC 8446 Section 4.6.1: PSK = HKDF-Expand-Label(resumption_master_secret, "resumption", ticket_nonce, Hash.length)
Derive the resumption_master_secret from the master secret. RFC 8446 Section 7.1: resumption_master_secret = Derive-Secret(Master Secret, "res master", ClientHello..client Finished)
-spec find_by_identity(binary(), ticket_store()) -> {ok, session_ticket()} | error.
Find a ticket by its identity in a per-connection store.
-spec lookup_global(binary()) -> {ok, session_ticket()} | error.
Look a ticket up in the node-wide table, dropping it if expired.
-spec lookup_ticket(binary(), ticket_store()) -> {ok, session_ticket()} | error.
Look up a ticket for a server. Returns {ok, Ticket} if found and not expired, error otherwise.
-spec new_store() -> ticket_store().
Create a new empty ticket store.
-spec parse_new_session_ticket(binary()) -> {ok, #{lifetime := non_neg_integer(), age_add := non_neg_integer(), nonce := binary(), ticket := binary(), max_early_data := non_neg_integer()}} | {error, term()}.
Parse a NewSessionTicket message from the server. RFC 8446 Section 4.6.1: struct { uint32 ticket_lifetime; uint32 ticket_age_add; opaque ticket_nonce<0..255>; opaque ticket<1..2^16-1>; Extension extensions<0..2^16-2>; } NewSessionTicket;
-spec store_global(binary(), session_ticket()) -> true.
Store a ticket in the node-wide table.
-spec store_ticket(binary(), session_ticket(), ticket_store()) -> ticket_store().
Store a session ticket for a server. The ticket is indexed by server name.