quic_ticket (quic v2.2.0)
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_issued/2 and friends are the ETS table of one listener (or listener pool), shared by its connections, which is what lets a server resume a session it issued on a different connection. A ticket is only ever looked up in the table of the listener that issued it. Those functions touch ETS, the clock and the random source, so they are not pure.
Summary
Functions
Build a NewSessionTicket message.
Remove all expired tickets from the store.
Remove a ticket for a server.
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)
Look an issued ticket up, 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 this server issued in its listener's table.
Store a session ticket for a server. The ticket is indexed by server name.
Remove an issued ticket and return it. Tickets are single-use (RFC 9001 Section 9.2); of two handshakes presenting the same ticket only one gets it.
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 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 lookup_issued(ets:table(), binary()) -> {ok, session_ticket()} | error.
Look an issued ticket up, 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_issued(ets:table(), session_ticket()) -> true.
Store a ticket this server issued in its listener's 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.
-spec take_issued(ets:table(), binary()) -> {ok, session_ticket()} | error.
Remove an issued ticket and return it. Tickets are single-use (RFC 9001 Section 9.2); of two handshakes presenting the same ticket only one gets it.