quic_tls (quic v1.4.2)
View SourceTLS 1.3 message generation and parsing for QUIC.
This module handles TLS 1.3 handshake messages as they appear in QUIC CRYPTO frames. Messages are encoded without the TLS record layer.
TLS Messages in QUIC
QUIC uses TLS 1.3 for the cryptographic handshake, but without the TLS record layer. TLS handshake messages are sent directly in CRYPTO frames.
Summary
Functions
Build Certificate message. Certs is a list of DER-encoded certificates (server cert first).
Build a CertificateRequest message (RFC 8446 §4.3.2) with the default advertised signature schemes.
Build a CertificateRequest advertising the given signature schemes (RFC 8446 §4.3.2; signature_algorithms is required).
Build CertificateVerify message. PrivateKey is the server's private key. TranscriptHash is the hash of all handshake messages up to (not including) CertificateVerify.
Build a CertificateVerify message for client (RFC 8446 Section 4.4.3). Uses "TLS 1.3, client CertificateVerify" context string.
Build a TLS 1.3 ClientHello message for QUIC. Options: - server_name: SNI hostname (binary) - alpn: List of ALPN protocols (list of binaries) - transport_params: QUIC transport parameters (map) - session_ticket: #session_ticket{} for resumption with PSK (optional)
Build EncryptedExtensions message. Options: - alpn: Selected ALPN protocol - transport_params: QUIC transport parameters
Build a Finished message. VerifyData should be computed using quic_crypto:compute_finished_verify/2.
Build a HelloRetryRequest (RFC 8446 §4.1.4). Wire-encoded as a ServerHello with the HRR sentinel random; its key_share carries only the selected group (no public key). Echoes the client's legacy_session_id and the negotiated cipher suite.
Build a ServerHello message. Options: - random: Server random (32 bytes, generated if not provided) - session_id: Legacy session ID to echo - cipher_suite: Selected cipher suite - key_share: Server's public key
Decode a TLS handshake message. Returns {Type, Body, Rest} or {error, Reason}.
Decode preferred_address transport parameter (RFC 9000 Section 18.2). Format: IPv4 address: 4 bytes IPv4 port: 2 bytes IPv6 address: 16 bytes IPv6 port: 2 bytes CID length: 1 byte Connection ID: variable (0-20 bytes) Stateless reset: 16 bytes
Decode QUIC transport parameters. RFC 9000 Section 7.4: Validates against duplicate parameters and semantic constraints
Encode a TLS handshake message with type and length.
Encode preferred_address transport parameter (RFC 9000 Section 18.2).
Encode QUIC transport parameters. Params is a map with keys like: original_dcid, max_idle_timeout, max_udp_payload_size, initial_max_data, initial_max_stream_data_bidi_local, etc.
Parse Certificate message.
Parse a CertificateRequest message. Returns the context and the advertised signature_algorithms (wire codes) so an mTLS client can pick a compatible CertificateVerify scheme.
Parse CertificateVerify message.
Parse a ClientHello message. Returns a map with: - random: 32-byte client random - session_id: Legacy session ID - cipher_suites: List of cipher suites offered - extensions: Map of extensions - key_share: Client's public key for key exchange - server_name: SNI hostname (if present) - alpn_protocols: List of ALPN protocols (if present) - transport_params: QUIC transport parameters (if present)
Parse EncryptedExtensions message.
Parse an extensions blob preserving order and byte offsets. Returns [{Type, Data, ByteOffset, ByteSize}] where ByteOffset is the position of Type within Data. Rejects duplicate extension types per RFC 8446 §4.2 with {error, {duplicate_extension, Type}}; the caller maps that to an illegal_parameter alert.
Parse Finished message.
Parse a ServerHello message. Returns server's public key and selected cipher suite.
Select a PSK from a ClientHello and verify the binder.
Verify CertificateVerify signature. Role is 'client' or 'server' - determines context string.
Verify a Finished message (default SHA-256). TrafficSecret is the sender's traffic secret. TranscriptHash is the hash of all messages up to (but not including) Finished.
Verify a Finished message with cipher-specific hash.
Functions
Build Certificate message. Certs is a list of DER-encoded certificates (server cert first).
Build a CertificateRequest message (RFC 8446 §4.3.2) with the default advertised signature schemes.
Build a CertificateRequest advertising the given signature schemes (RFC 8446 §4.3.2; signature_algorithms is required).
-spec build_certificate_verify(non_neg_integer(), crypto:key_id(), binary()) -> binary().
Build CertificateVerify message. PrivateKey is the server's private key. TranscriptHash is the hash of all handshake messages up to (not including) CertificateVerify.
-spec build_certificate_verify_client(non_neg_integer(), term(), binary()) -> binary().
Build a CertificateVerify message for client (RFC 8446 Section 4.4.3). Uses "TLS 1.3, client CertificateVerify" context string.
Build a TLS 1.3 ClientHello message for QUIC. Options: - server_name: SNI hostname (binary) - alpn: List of ALPN protocols (list of binaries) - transport_params: QUIC transport parameters (map) - session_ticket: #session_ticket{} for resumption with PSK (optional)
Returns: {ClientHelloMsg, PrivateKey, Random}
Build EncryptedExtensions message. Options: - alpn: Selected ALPN protocol - transport_params: QUIC transport parameters
Build a Finished message. VerifyData should be computed using quic_crypto:compute_finished_verify/2.
-spec build_hello_retry_request(binary(), non_neg_integer(), atom()) -> binary().
Build a HelloRetryRequest (RFC 8446 §4.1.4). Wire-encoded as a ServerHello with the HRR sentinel random; its key_share carries only the selected group (no public key). Echoes the client's legacy_session_id and the negotiated cipher suite.
Build a ServerHello message. Options: - random: Server random (32 bytes, generated if not provided) - session_id: Legacy session ID to echo - cipher_suite: Selected cipher suite - key_share: Server's public key
-spec decode_handshake_message(binary()) -> {ok, {non_neg_integer(), binary()}, binary()} | {error, term()}.
Decode a TLS handshake message. Returns {Type, Body, Rest} or {error, Reason}.
-spec decode_preferred_address(binary()) -> #preferred_address{ipv4_addr :: inet:ip4_address() | undefined, ipv4_port :: inet:port_number() | undefined, ipv6_addr :: inet:ip6_address() | undefined, ipv6_port :: inet:port_number() | undefined, cid :: binary(), stateless_reset_token :: binary()}.
Decode preferred_address transport parameter (RFC 9000 Section 18.2). Format: IPv4 address: 4 bytes IPv4 port: 2 bytes IPv6 address: 16 bytes IPv6 port: 2 bytes CID length: 1 byte Connection ID: variable (0-20 bytes) Stateless reset: 16 bytes
Decode QUIC transport parameters. RFC 9000 Section 7.4: Validates against duplicate parameters and semantic constraints
-spec encode_handshake_message(non_neg_integer(), binary()) -> binary().
Encode a TLS handshake message with type and length.
-spec encode_preferred_address(#preferred_address{ipv4_addr :: inet:ip4_address() | undefined, ipv4_port :: inet:port_number() | undefined, ipv6_addr :: inet:ip6_address() | undefined, ipv6_port :: inet:port_number() | undefined, cid :: binary(), stateless_reset_token :: binary()}) -> binary().
Encode preferred_address transport parameter (RFC 9000 Section 18.2).
Encode QUIC transport parameters. Params is a map with keys like: original_dcid, max_idle_timeout, max_udp_payload_size, initial_max_data, initial_max_stream_data_bidi_local, etc.
-spec parse_certificate(binary()) -> {ok, #{context := binary(), certificates := [binary()]}} | {error, term()}.
Parse Certificate message.
Parse a CertificateRequest message. Returns the context and the advertised signature_algorithms (wire codes) so an mTLS client can pick a compatible CertificateVerify scheme.
-spec parse_certificate_verify(binary()) -> {ok, #{algorithm := non_neg_integer(), signature := binary()}} | {error, term()}.
Parse CertificateVerify message.
Parse a ClientHello message. Returns a map with: - random: 32-byte client random - session_id: Legacy session ID - cipher_suites: List of cipher suites offered - extensions: Map of extensions - key_share: Client's public key for key exchange - server_name: SNI hostname (if present) - alpn_protocols: List of ALPN protocols (if present) - transport_params: QUIC transport parameters (if present)
-spec parse_encrypted_extensions(binary()) -> {ok, #{alpn => binary(), transport_params => map()}} | {error, term()}.
Parse EncryptedExtensions message.
-spec parse_extensions_ordered(binary()) -> {ok, [{non_neg_integer(), binary(), non_neg_integer(), non_neg_integer()}]} | {error, term()}.
Parse an extensions blob preserving order and byte offsets. Returns [{Type, Data, ByteOffset, ByteSize}] where ByteOffset is the position of Type within Data. Rejects duplicate extension types per RFC 8446 §4.2 with {error, {duplicate_extension, Type}}; the caller maps that to an illegal_parameter alert.
Parse Finished message.
-spec parse_server_hello(binary()) -> {ok, #{public_key := binary() | undefined, cipher := atom(), random := binary(), selected_psk_identity => non_neg_integer()}} | {hrr, #{cipher := atom(), selected_group := atom() | unknown, extensions := map()}} | {error, term()}.
Parse a ServerHello message. Returns server's public key and selected cipher suite.
-spec select_psk(map(), binary(), map(), [psk_dhe_ke | psk_ke]) -> {ok, map()} | none | {error, bad_binder}.
Select a PSK from a ClientHello and verify the binder.
ClientHelloMap is the result of parse_client_hello/1. FullHandshakeMsg is the raw 4-byte-headered handshake bytes for ClientHello (msg_type + length + body) — needed to compute the truncated ClientHello hash for binder verification. PskConfig is #{psk_callback => Fn | undefined, psks => Map | undefined}`. `ServerModes is the list of psk_key_exchange modes the server is willing to negotiate (defaults to [psk_dhe_ke]`). Returns: `{ok, #{identity_idx => N, secret => Secret, mode => Mode}}` on successful selection (identity found, binder verified, modes compatible); `none when the client didn't offer pre_shared_key OR offered identities don't match local config OR no compatible mode (caller falls through to cert path or sends unknown_psk_identity); {error, bad_binder}` when an identity matched but the binder didnt verify — caller MUST send decrypt_error (no cert fallback).
Verify CertificateVerify signature. Role is 'client' or 'server' - determines context string.
Verify a Finished message (default SHA-256). TrafficSecret is the sender's traffic secret. TranscriptHash is the hash of all messages up to (but not including) Finished.
Verify a Finished message with cipher-specific hash.