Skip to main content

src/nquic_handshake.erl

-module(nquic_handshake).
-moduledoc """
QUIC handshake management.

This module handles TLS 1.3 handshake integration, key derivation,
and handshake packet construction for QUIC connections.
""".

-include("nquic_frame.hrl").
-export([derive_initial_keys/1, derive_initial_keys/2]).
-export([install_app_keys/2, install_handshake_keys/2]).
-export([format_keys/1, format_keys/2]).
-export([build_handshake_frames/1, build_initial_frames/1]).

-doc "Build frames for a Handshake packet with CRYPTO data.".
-spec build_handshake_frames(binary()) -> [nquic_frame:t()].
build_handshake_frames(CryptoData) ->
    [#crypto{offset = 0, data = CryptoData}].

-doc "Build frames for an Initial packet with CRYPTO data.".
-spec build_initial_frames(binary()) -> [nquic_frame:t()].
build_initial_frames(CryptoData) ->
    [#crypto{offset = 0, data = CryptoData}].

-doc "Derive initial encryption keys from DCID per RFC 9001.".
-spec derive_initial_keys(nquic:connection_id()) ->
    #{
        client := #{key := binary(), iv := binary(), hp := binary()},
        server := #{key := binary(), iv := binary(), hp := binary()}
    }.
derive_initial_keys(DCID) ->
    derive_initial_keys(DCID, 1).

-doc "Derive initial encryption keys from DCID for a specific QUIC version.".
-spec derive_initial_keys(nquic:connection_id(), non_neg_integer()) ->
    #{
        client := #{key := binary(), iv := binary(), hp := binary()},
        server := #{key := binary(), iv := binary(), hp := binary()}
    }.
derive_initial_keys(DCID, Version) ->
    {ClientSecret, ServerSecret} = nquic_keys:initial_secrets(DCID, Version),
    {CKey, CIV, CHP} = nquic_keys:derive_packet_protection(ClientSecret, aes_128_gcm, Version),
    {SKey, SIV, SHP} = nquic_keys:derive_packet_protection(ServerSecret, aes_128_gcm, Version),

    #{
        client => nquic_keys:make_role_keys(aes_128_gcm, CKey, CIV, CHP),
        server => nquic_keys:make_role_keys(aes_128_gcm, SKey, SIV, SHP)
    }.

-doc "Convert TLS key material to packet protection format.".
-spec format_keys(map()) ->
    #{
        client := #{key := binary(), iv := binary(), hp := binary()},
        server := #{key := binary(), iv := binary(), hp := binary()}
    }.
format_keys(Keys) ->
    #{
        client => #{
            key => maps:get(client_key, Keys),
            iv => maps:get(client_iv, Keys),
            hp => maps:get(client_hp, Keys)
        },
        server => #{
            key => maps:get(server_key, Keys),
            iv => maps:get(server_iv, Keys),
            hp => maps:get(server_hp, Keys)
        }
    }.

-doc "Convert TLS key material to packet protection format with cached HP context.".
-spec format_keys(map(), aes_128_gcm | aes_256_gcm | chacha20_poly1305) ->
    #{
        client := #{key := binary(), iv := binary(), hp := binary()},
        server := #{key := binary(), iv := binary(), hp := binary()}
    }.
format_keys(Keys, Cipher) ->
    CK = maps:get(client_key, Keys),
    CIV = maps:get(client_iv, Keys),
    CHP = maps:get(client_hp, Keys),
    SK = maps:get(server_key, Keys),
    SIV = maps:get(server_iv, Keys),
    SHP = maps:get(server_hp, Keys),
    #{
        client => nquic_keys:make_role_keys(Cipher, CK, CIV, CHP),
        server => nquic_keys:make_role_keys(Cipher, SK, SIV, SHP)
    }.

-doc "Format and install application keys from TLS-derived secrets.".
-spec install_app_keys(map(), map()) ->
    #{
        application := #{
            client := #{key := binary(), iv := binary(), hp := binary()},
            server := #{key := binary(), iv := binary(), hp := binary()}
        }
    }.
install_app_keys(Keys, ExistingKeys) ->
    FormattedKeys = format_keys(Keys),
    ExistingKeys#{application => FormattedKeys}.

-doc "Format and install handshake keys from TLS-derived secrets.".
-spec install_handshake_keys(map(), map()) ->
    #{
        handshake := #{
            client := #{key := binary(), iv := binary(), hp := binary()},
            server := #{key := binary(), iv := binary(), hp := binary()}
        }
    }.
install_handshake_keys(Keys, ExistingKeys) ->
    FormattedKeys = format_keys(Keys),
    ExistingKeys#{handshake => FormattedKeys}.