quic_tls (quic v1.4.2)

View Source

TLS 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(Context, Certs)

-spec build_certificate(binary(), [binary()]) -> binary().

Build Certificate message. Certs is a list of DER-encoded certificates (server cert first).

build_certificate_request(Context)

-spec build_certificate_request(binary()) -> binary().

Build a CertificateRequest message (RFC 8446 §4.3.2) with the default advertised signature schemes.

build_certificate_request(Context, SigAlgAtoms)

-spec build_certificate_request(binary(), [atom()]) -> binary().

Build a CertificateRequest advertising the given signature schemes (RFC 8446 §4.3.2; signature_algorithms is required).

build_certificate_verify(SignatureAlgorithm, PrivateKey, TranscriptHash)

-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.

build_certificate_verify_client(SignatureAlgorithm, PrivateKey, TranscriptHash)

-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_client_hello(Opts)

-spec build_client_hello(map()) -> {binary(), binary(), binary()}.

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_encrypted_extensions(Opts)

-spec build_encrypted_extensions(map()) -> binary().

Build EncryptedExtensions message. Options: - alpn: Selected ALPN protocol - transport_params: QUIC transport parameters

build_finished(VerifyData)

-spec build_finished(binary()) -> binary().

Build a Finished message. VerifyData should be computed using quic_crypto:compute_finished_verify/2.

build_hello_retry_request(SessionId, CipherSuite, SelectedGroup)

-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_server_hello(Opts)

-spec build_server_hello(map()) -> {binary(), binary()}.

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_handshake_message(_)

-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}.

decode_preferred_address(_)

-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_transport_params(Data)

-spec decode_transport_params(binary()) -> {ok, map()} | {error, term()}.

Decode QUIC transport parameters. RFC 9000 Section 7.4: Validates against duplicate parameters and semantic constraints

encode_handshake_message(Type, Body)

-spec encode_handshake_message(non_neg_integer(), binary()) -> binary().

Encode a TLS handshake message with type and length.

encode_preferred_address(Preferred_address)

-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_transport_params(Params)

-spec encode_transport_params(map()) -> binary().

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(_)

-spec parse_certificate(binary()) ->
                           {ok, #{context := binary(), certificates := [binary()]}} | {error, term()}.

Parse Certificate message.

parse_certificate_request(_)

-spec parse_certificate_request(binary()) -> {ok, map()} | {error, term()}.

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_certificate_verify(_)

-spec parse_certificate_verify(binary()) ->
                                  {ok, #{algorithm := non_neg_integer(), signature := binary()}} |
                                  {error, term()}.

Parse CertificateVerify message.

parse_client_hello(Data)

-spec parse_client_hello(binary()) -> {ok, map()} | {error, term()}.

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_encrypted_extensions(_)

-spec parse_encrypted_extensions(binary()) ->
                                    {ok, #{alpn => binary(), transport_params => map()}} |
                                    {error, term()}.

Parse EncryptedExtensions message.

parse_extensions_ordered(Data)

-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(VerifyData)

-spec parse_finished(binary()) -> {ok, binary()} | {error, term()}.

Parse Finished message.

parse_server_hello(_)

-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.

select_psk(_, FullMsg, PskConfig, ServerModes)

-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_certificate_verify(Body, PeerCertDER, TranscriptHash, Role)

-spec verify_certificate_verify(binary(), binary(), binary(), client | server) -> boolean().

Verify CertificateVerify signature. Role is 'client' or 'server' - determines context string.

verify_finished(ReceivedVerifyData, TrafficSecret, TranscriptHash)

-spec verify_finished(binary(), binary(), binary()) -> boolean().

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_finished(ReceivedVerifyData, TrafficSecret, TranscriptHash, Cipher)

-spec verify_finished(binary(), binary(), binary(), atom()) -> boolean().

Verify a Finished message with cipher-specific hash.