Skip to documentation
SLOP

tiny.quic.tls

Reference tiny.quic tls

Defined in tiny.quic.

The TLS 1.3 handshake a QUIC connection needs runs as a state machine over bytes the caller carries.

API (60)

Actions

Public operations.

Types and contracts

Public types and contracts.

Namespaces

Public namespaces.

Values and defaults

Public values and defaults.

No direct callersNo direct callstiny.quictls
Static calls · unresolved targets: unknown · external targets: unknown.

Source

Source: lib/quic/src/tls/alert.zig:3

zig
pub const Alert = enum(u8) {    unexpected_message = 10,    handshake_failure = 40,    bad_certificate = 42,    unsupported_certificate = 43,    illegal_parameter = 47,    decode_error = 50,    decrypt_error = 51,    protocol_version = 70,    internal_error = 80,    missing_extension = 109,    unsupported_extension = 110,    unrecognized_name = 112,    certificate_required = 116,    no_application_protocol = 120,    /// Returns the QUIC transport error code for one TLS alert. A connection closing after a    /// handshake failure calls it to get the code it puts in the CONNECTION_CLOSE frame. RFC 9001    /// section 4.8 forms the code by adding the alert number to 0x0100. Distinct alerts give    /// distinct codes, so the peer learns which check failed.    pub fn quicError(self: Alert) u16 {        return 0x0100 + @as(u16, @backingInt(self));    }};

Source: lib/quic/src/tls/engine/machine.zig:39

zig
pub const Engine = struct {    config: model.Config,    limits: storage_mod.Storage.Limits,    storage: *storage_mod.Storage,    phase: Phase,    transcript: Sha256,    key_pair: X25519.KeyPair,    hello_random: [32]u8,    schedule: ?tls.schedule.Schedule,    application: ?tls.schedule.Application,    receive_length: usize,    receive_total: usize,    receive_level: ?model.Level,    initial_length: usize,    initial_offset: usize,    handshake_length: usize,    handshake_offset: usize,    peer_key: tls.PublicKey,    peer_key_present: bool,    peer_verified: bool,    peer_parameters_length: usize,    pending_alert: ?tls.Alert,    cipher_suite: ?std.crypto.tls.CipherSuite,    handshake_secrets_present: bool,    pub fn init(        config: model.Config,        limits: storage_mod.Storage.Limits,        storage: *storage_mod.Storage,    ) InitError!Engine {        try validateConfig(config, limits, storage);        var random = try config.random.values();        defer std.crypto.secureZero(u8, std.mem.asBytes(&random));        var key_pair = X25519.KeyPair.generateDeterministic(random.key_exchange) catch {            return error.InvalidKey;        };        defer std.crypto.secureZero(u8, &key_pair.secret_key);        var self = Engine{            .config = config,            .limits = limits,            .storage = storage,            .phase = if (config.role == .client)                .client_wait_server_hello            else                .server_wait_client_hello,            .transcript = Sha256.init(.{}),            .key_pair = key_pair,            .hello_random = random.hello,            .schedule = null,            .application = null,            .receive_length = 0,            .receive_total = 0,            .receive_level = null,            .initial_length = 0,            .initial_offset = 0,            .handshake_length = 0,            .handshake_offset = 0,            .peer_key = undefined,            .peer_key_present = false,            .peer_verified = false,            .peer_parameters_length = 0,            .pending_alert = null,            .cipher_suite = null,            .handshake_secrets_present = false,        };        errdefer std.crypto.secureZero(u8, std.mem.asBytes(&self));        if (config.role == .client) {            try self.queueClientHello(random.hello, storage.initialBytesForInitialization());        }        storage.activate();        return self;    }    /// Overwrites every retained handshake secret and every region of the engine's storage with    /// zeros so a caller finishing with a connection leaves no key material behind in the block it    /// hands back. The four storage regions and the engine value itself are all covered. The call    /// frees nothing, because the caller owns the block.    pub fn deinit(self: *Engine) void {        const storage = self.storage;        std.crypto.secureZero(u8, storage.receiveBytes());        std.crypto.secureZero(u8, storage.initialBytes());        std.crypto.secureZero(u8, storage.handshakeBytes());        std.crypto.secureZero(u8, storage.peerBytes());        std.crypto.secureZero(u8, std.mem.asBytes(self));    }    pub fn receive(self: *Engine, level: model.Level, bytes: []const u8) Error!void {        if (self.pending_alert != null) return error.TlsAlert;        if (self.receive_level) |partial_level| {            if (partial_level != level) return self.reject(.unexpected_message);        }        for (bytes) |byte| {            const input = self.storage.receiveBytes();            if (self.receive_length >= input.len) return self.reject(.decode_error);            input[self.receive_length] = byte;            self.receive_length += 1;            if (self.receive_length == 1) self.receive_level = level;            if (self.receive_length == 4) try self.readMessageLength(input[0..4]);            if (self.receive_total == 0) continue;            if (self.receive_length != self.receive_total) continue;            try self.process(level, input[0..self.receive_total]);            self.receive_length = 0;            self.receive_total = 0;        }        if (self.receive_length == 0) self.receive_level = null;    }    pub fn emit(self: *Engine, level: model.Level, output: []u8) usize {        if (self.pending_alert != null) return 0;        const written = switch (level) {            .initial => emitQueue(                self.storage.initialBytes(),                &self.initial_offset,                self.initial_length,                output,            ),            .handshake => emitQueue(                self.storage.handshakeBytes(),                &self.handshake_offset,                self.handshake_length,                output,            ),            .one_rtt => 0,        };        if (self.phase == .client_sending_finished) {            if (self.handshake_offset == self.handshake_length) self.phase = .client_complete;        }        return written;    }    /// Returns the handshake bytes the caller has already taken at one encryption level. The bytes    /// stay in the engine's storage, so an endpoint that lost a CRYPTO frame retransmits from them,    /// which RFC 9000 section 13.3 asks of a sender. The 1-RTT level gives an empty slice, because    /// it carries no handshake bytes. The slice stays valid while the engine does, because it    /// points into the engine's storage.    pub fn emitted(self: *const Engine, level: model.Level) []const u8 {        std.debug.assert(self.initial_offset <= self.initial_length);        std.debug.assert(self.handshake_offset <= self.handshake_length);        return switch (level) {            .initial => self.storage.initialBytes()[0..self.initial_offset],            .handshake => self.storage.handshakeBytes()[0..self.handshake_offset],            .one_rtt => &.{},        };    }    pub fn secret(        self: *const Engine,        level: model.Level,        direction: model.Direction,    ) ?model.Secret {        if (self.pending_alert != null) return null;        return switch (level) {            .initial => null,            .handshake => self.handshakeSecret(direction),            .one_rtt => self.applicationSecret(direction),        };    }    pub fn state(self: *const Engine) model.Status {        return switch (self.phase) {            .client_complete => .handshake_complete,            .client_confirmed, .server_confirmed => .handshake_confirmed,            else => .handshaking,        };    }    pub fn confirm(self: *Engine) void {        if (self.phase == .client_complete) {            self.eraseHandshakeTrafficSecrets();            self.phase = .client_confirmed;        }    }    pub fn peer(self: *const Engine) ?model.Peer {        if (self.pending_alert != null) return null;        if (!self.peer_verified) return null;        const peer_bytes = self.storage.peerBytes();        return .{            .public_key = self.peer_key,            .alpn = self.config.alpn,            .transport_parameters = peer_bytes[0..self.peer_parameters_length],        };    }    pub fn alert(self: *const Engine) ?tls.Alert {        return self.pending_alert;    }    pub fn cipherSuite(self: *const Engine) ?std.crypto.tls.CipherSuite {        return self.cipher_suite;    }    fn readMessageLength(self: *Engine, header: []const u8) Error!void {        std.debug.assert(header.len == 4);        const body_length = (@as(usize, header[1]) << 16) |            (@as(usize, header[2]) << 8) | header[3];        if (body_length > self.limits.max_message) {            self.pending_alert = .decode_error;            self.phase = .alerted;            return error.MessageTooLarge;        }        self.receive_total = body_length + 4;    }    fn process(self: *Engine, level: model.Level, bytes: []const u8) Error!void {        const value = tls.message.decode(bytes) catch return self.reject(.decode_error);        if (value.kind == .key_update) return self.reject(.unexpected_message);        if (value.kind == .new_session_ticket) {            if (level != .one_rtt) return self.reject(.unexpected_message);            if (self.config.role == .server) return self.reject(.unexpected_message);            _ = tls.message.decodeNewSessionTicket(bytes) catch return self.reject(.decode_error);            return;        }        if (self.config.role == .client) return self.processClient(level, bytes);        return self.processServer(level, bytes);    }    fn processClient(        self: *Engine,        level: model.Level,        bytes: []const u8,    ) Error!void {        return switch (self.phase) {            .client_wait_server_hello => self.clientServerHello(level, bytes),            .client_wait_encrypted_extensions => self.clientEncrypted(level, bytes),            .client_wait_certificate_request => self.clientRequest(level, bytes),            .client_wait_certificate => self.receiveCertificate(level, bytes, false),            .client_wait_certificate_verify => self.receiveCertificateVerify(level, bytes, false),            .client_wait_finished => self.clientFinished(level, bytes),            else => self.reject(.unexpected_message),        };    }    fn processServer(        self: *Engine,        level: model.Level,        bytes: []const u8,    ) Error!void {        return switch (self.phase) {            .server_wait_client_hello => self.serverClientHello(level, bytes),            .server_wait_certificate => self.receiveCertificate(level, bytes, true),            .server_wait_certificate_verify => self.receiveCertificateVerify(level, bytes, true),            .server_wait_finished => self.serverFinished(level, bytes),            else => self.reject(.unexpected_message),        };    }    fn clientServerHello(self: *Engine, level: model.Level, bytes: []const u8) Error!void {        if (level != .initial) return self.reject(.unexpected_message);        const hello = tls.message.decodeServerHello(bytes) catch return self.reject(.decode_error);        if (std.mem.eql(u8, &hello.random, &std.crypto.tls.hello_retry_request_sequence)) {            return self.reject(.handshake_failure);        }        const suite = validServerHello(hello) orelse return self.reject(.illegal_parameter);        const selected = extension.serverSelection(hello.extensions) catch |err| {            return self.reject(extensionAlert(err));        };        if (!selected.tls_1_3) {            if (hasDowngradeSentinel(hello.random)) return self.reject(.illegal_parameter);            return self.reject(.protocol_version);        }        const peer_share = selected.key_share orelse return self.reject(.missing_extension);        self.transcript.update(bytes);        try self.installSchedule(peer_share);        self.cipher_suite = suite;        self.phase = .client_wait_encrypted_extensions;    }    fn clientEncrypted(self: *Engine, level: model.Level, bytes: []const u8) Error!void {        if (level != .handshake) return self.reject(.unexpected_message);        const encrypted = tls.message.decodeEncryptedExtensions(bytes) catch {            return self.reject(.decode_error);        };        const selected = extension.encryptedSelection(            encrypted.extensions,            self.config.alpn,            self.config.server_name != null,        ) catch |err| return self.reject(extensionAlert(err));        if (selected.alpn == null) return self.reject(.no_application_protocol);        const parameters = selected.transport_parameters orelse {            return self.reject(.missing_extension);        };        if (!selected.server_raw_key) return self.reject(.unsupported_certificate);        if (!selected.client_raw_key) return self.reject(.unsupported_certificate);        try self.retainParameters(parameters);        self.transcript.update(bytes);        self.phase = .client_wait_certificate_request;    }    fn clientRequest(self: *Engine, level: model.Level, bytes: []const u8) Error!void {        if (level != .handshake) return self.reject(.unexpected_message);        const request = tls.message.decodeCertificateRequest(bytes) catch {            return self.reject(.decode_error);        };        if (request.context.len != 0) return self.reject(.illegal_parameter);        const offered = extension.requestOffersEd25519(request.extensions) catch |err| {            return self.reject(extensionAlert(err));        };        if (!offered) return self.reject(.handshake_failure);        self.transcript.update(bytes);        self.phase = .client_wait_certificate;    }    fn serverClientHello(self: *Engine, level: model.Level, bytes: []const u8) Error!void {        if (level != .initial) return self.reject(.unexpected_message);        const hello = tls.message.decodeClientHello(bytes) catch return self.reject(.decode_error);        const suite = validClientHello(hello, self.config.cipher_suite) orelse            return self.reject(.illegal_parameter);        const offer = extension.clientOffer(hello.extensions, self.config.alpn) catch |err| {            return self.reject(extensionAlert(err));        };        try self.validateOffer(offer);        const peer_share = offer.key_share orelse return self.reject(.missing_extension);        try self.retainParameters(offer.transport_parameters.?);        self.transcript.update(bytes);        try self.queueServerFlight(peer_share, offer.server_name != null, suite);        self.cipher_suite = suite;        self.phase = .server_wait_certificate;    }    fn validateOffer(self: *Engine, offer: extension.ClientOffer) Error!void {        if (!offer.tls_1_3) return self.reject(.protocol_version);        if (!offer.x25519_group) return self.reject(.handshake_failure);        if (!offer.ed25519_signature) return self.reject(.handshake_failure);        if (offer.alpn == null) return self.reject(.no_application_protocol);        if (offer.transport_parameters == null) return self.reject(.missing_extension);        if (!offer.server_raw_key) return self.reject(.unsupported_certificate);        if (!offer.client_raw_key) return self.reject(.unsupported_certificate);        if (self.config.server_name) |wanted| {            const received = offer.server_name orelse return self.reject(.unrecognized_name);            if (!std.mem.eql(u8, wanted, received)) return self.reject(.unrecognized_name);        }    }    fn receiveCertificate(        self: *Engine,        level: model.Level,        bytes: []const u8,        client_certificate: bool,    ) Error!void {        if (level != .handshake) return self.reject(.unexpected_message);        const certificate = tls.message.decodeCertificate(bytes) catch {            return self.reject(.decode_error);        };        if (certificate.request_context.len != 0) return self.reject(.illegal_parameter);        var entries = certificate.entries.iterator();        const entry = entries.next() catch return self.reject(.decode_error);        if (entry == null) {            if (client_certificate) return self.reject(.certificate_required);            return self.reject(.decode_error);        }        const second = entries.next() catch return self.reject(.decode_error);        if (second != null) return self.reject(.unsupported_certificate);        var entry_extensions = entry.?.extensions.iterator();        if ((entry_extensions.next() catch return self.reject(.decode_error)) != null) {            return self.reject(.unsupported_extension);        }        self.peer_key = tls.decodeSubjectPublicKeyInfo(entry.?.data) catch {            return self.reject(.bad_certificate);        };        self.peer_key_present = true;        if (self.config.expected_peer) |expected| {            if (!std.crypto.timing_safe.eql(tls.PublicKey, expected, self.peer_key)) {                return self.reject(.bad_certificate);            }        }        self.transcript.update(bytes);        self.phase = if (client_certificate)            .server_wait_certificate_verify        else            .client_wait_certificate_verify;    }    fn receiveCertificateVerify(        self: *Engine,        level: model.Level,        bytes: []const u8,        client_signature: bool,    ) Error!void {        if (level != .handshake) return self.reject(.unexpected_message);        if (!self.peer_key_present) return self.reject(.unexpected_message);        const value = tls.message.decodeCertificateVerify(bytes) catch {            return self.reject(.decode_error);        };        if (value.algorithm != .ed25519) return self.reject(.illegal_parameter);        if (value.signature.len != 64) return self.reject(.decrypt_error);        const hash = self.transcriptHash();        const content = certificateVerifyContent(client_signature, hash);        tls.verifySignature(            self.peer_key,            &content,            value.signature[0..64].*,        ) catch return self.reject(.decrypt_error);        self.transcript.update(bytes);        self.peer_verified = true;        self.phase = if (client_signature) .server_wait_finished else .client_wait_finished;    }    fn clientFinished(self: *Engine, level: model.Level, bytes: []const u8) Error!void {        if (level != .handshake) return self.reject(.unexpected_message);        try self.verifyFinished(bytes, self.schedule.?.server_handshake);        self.transcript.update(bytes);        self.application = self.schedule.?.application(self.transcriptHash());        self.schedule.?.eraseMasterSecret();        try self.queueClientFlight();        self.phase = .client_sending_finished;    }    fn serverFinished(self: *Engine, level: model.Level, bytes: []const u8) Error!void {        if (level != .handshake) return self.reject(.unexpected_message);        try self.verifyFinished(bytes, self.schedule.?.client_handshake);        self.transcript.update(bytes);        self.eraseHandshakeTrafficSecrets();        self.phase = .server_confirmed;    }    fn verifyFinished(        self: *Engine,        bytes: []const u8,        traffic_secret: model.Secret,    ) Error!void {        const value = tls.message.decodeFinished(bytes) catch return self.reject(.decode_error);        if (value.verify_data.len != 32) return self.reject(.decrypt_error);        const expected = tls.schedule.finishedVerifyData(traffic_secret, self.transcriptHash());        if (!std.crypto.timing_safe.eql(model.Secret, expected, value.verify_data[0..32].*)) {            return self.reject(.decrypt_error);        }    }    fn installSchedule(self: *Engine, peer_share: [32]u8) Error!void {        var shared = X25519.scalarmult(self.key_pair.secret_key, peer_share) catch {            std.crypto.secureZero(u8, &self.key_pair.secret_key);            return self.reject(.illegal_parameter);        };        defer std.crypto.secureZero(u8, &shared);        std.crypto.secureZero(u8, &self.key_pair.secret_key);        var installed = tls.schedule.Schedule.init(shared, self.transcriptHash());        installed.eraseDerivationSecrets();        self.schedule = installed;        self.handshake_secrets_present = true;    }    fn queueClientHello(        self: *Engine,        random: [32]u8,        output_bytes: []u8,    ) InitError!void {        var output = quic.cursor.Write.init(output_bytes);        tls.message.profile.encodeClientHello(.{            .random = random,            .key_share = self.key_pair.public_key,            .alpn = self.config.alpn,            .transport_parameters = self.config.transport_parameters,            .server_name = self.config.server_name,            .cipher_suite = self.config.cipher_suite,        }, &output) catch return error.EncodingFailed;        self.initial_length = output.index;        self.transcript.update(output.written());    }    fn queueServerFlight(        self: *Engine,        peer_share: [32]u8,        acknowledge_name: bool,        suite: std.crypto.tls.CipherSuite,    ) Error!void {        var initial = quic.cursor.Write.init(self.storage.initialBytes());        tls.message.profile.encodeServerHello(.{            .random = self.hello_random,            .key_share = self.key_pair.public_key,            .cipher_suite = suite,        }, &initial) catch return self.reject(.internal_error);        self.initial_length = initial.index;        self.transcript.update(initial.written());        try self.installSchedule(peer_share);        try self.queueServerAuthentication(acknowledge_name);    }    fn queueServerAuthentication(self: *Engine, acknowledge_name: bool) Error!void {        var output = quic.cursor.Write.init(self.storage.handshakeBytes());        tls.message.profile.encodeEncryptedExtensions(.{            .alpn = self.config.alpn,            .transport_parameters = self.config.transport_parameters,            .acknowledge_server_name = acknowledge_name,        }, &output) catch return self.reject(.internal_error);        self.hashNew(output.written(), &self.handshake_length);        tls.message.profile.encodeCertificateRequest(&output) catch {            return self.reject(.internal_error);        };        self.hashNew(output.written(), &self.handshake_length);        tls.message.profile.encodeCertificate(self.config.identity.public(), &output) catch {            return self.reject(.internal_error);        };        self.hashNew(output.written(), &self.handshake_length);        try self.queueSignedAndFinished(&output, false);        self.application = self.schedule.?.application(self.transcriptHash());        self.schedule.?.eraseMasterSecret();    }    fn queueClientFlight(self: *Engine) Error!void {        var output = quic.cursor.Write.init(self.storage.handshakeBytes());        tls.message.profile.encodeCertificate(            self.config.identity.public(),            &output,        ) catch return self.reject(.internal_error);        self.hashNew(output.written(), &self.handshake_length);        try self.queueSignedAndFinished(&output, true);    }    fn queueSignedAndFinished(        self: *Engine,        output: *quic.cursor.Write,        client_signature: bool,    ) Error!void {        const hash = self.transcriptHash();        const content = certificateVerifyContent(client_signature, hash);        const signature = self.config.identity.sign(&content) catch {            return self.reject(.internal_error);        };        tls.message.profile.encodeCertificateVerify(signature, output) catch {            return self.reject(.internal_error);        };        self.hashNew(output.written(), &self.handshake_length);        const traffic = if (client_signature)            self.schedule.?.client_handshake        else            self.schedule.?.server_handshake;        const verify_data = tls.schedule.finishedVerifyData(traffic, self.transcriptHash());        tls.message.profile.encodeFinished(verify_data, output) catch {            return self.reject(.internal_error);        };        self.hashNew(output.written(), &self.handshake_length);    }    fn hashNew(self: *Engine, bytes: []const u8, previous_length: *usize) void {        std.debug.assert(previous_length.* <= bytes.len);        self.transcript.update(bytes[previous_length.*..]);        previous_length.* = bytes.len;    }    fn retainParameters(self: *Engine, parameters: []const u8) Error!void {        const output = self.storage.peerBytes();        if (parameters.len > output.len) return self.reject(.internal_error);        @memcpy(output[0..parameters.len], parameters);        self.peer_parameters_length = parameters.len;    }    fn transcriptHash(self: *const Engine) model.Secret {        var transcript = self.transcript;        var digest: model.Secret = undefined;        transcript.final(&digest);        return digest;    }    fn handshakeSecret(self: *const Engine, direction: model.Direction) ?model.Secret {        if (!self.handshake_secrets_present) return null;        const value = self.schedule orelse return null;        return switch (self.config.role) {            .client => if (direction == .write) value.client_handshake else value.server_handshake,            .server => if (direction == .write) value.server_handshake else value.client_handshake,        };    }    fn applicationSecret(self: *const Engine, direction: model.Direction) ?model.Secret {        if (self.config.role == .server and direction == .read) {            if (self.phase != .server_confirmed) return null;        }        const value = self.application orelse return null;        return switch (self.config.role) {            .client => if (direction == .write) value.client else value.server,            .server => if (direction == .write) value.server else value.client,        };    }    fn eraseHandshakeTrafficSecrets(self: *Engine) void {        if (!self.handshake_secrets_present) return;        self.schedule.?.eraseHandshakeTrafficSecrets();        self.handshake_secrets_present = false;    }    fn reject(self: *Engine, alert_value: tls.Alert) Error {        self.pending_alert = alert_value;        self.phase = .alerted;        return error.TlsAlert;    }};

Source: lib/quic/src/tls/engine/machine.zig:11

zig
pub const Error = error{ TlsAlert, MessageTooLarge };

Source: lib/quic/src/tls/engine/model.zig:39

zig
pub const Config = struct {    role: Role,    identity: tls.Identity,    expected_peer: ?tls.PublicKey,    alpn: []const u8,    transport_parameters: []const u8,    server_name: ?[]const u8,    random: Random,    cipher_suite: std.crypto.tls.CipherSuite = .AES_128_GCM_SHA256,};

Source: lib/quic/src/tls/engine/model.zig:6

zig
pub const Direction = enum { read, write };

Source: lib/quic/src/tls/engine/model.zig:9

zig
pub const FixedRandom = struct {    hello: [32]u8,    key_exchange: [32]u8,};

Source: lib/quic/src/tls/engine/model.zig:5

zig
pub const Level = enum { initial, handshake, one_rtt };

Source: lib/quic/src/tls/engine/model.zig:61

zig
pub const Peer = struct {    public_key: tls.PublicKey,    alpn: []const u8,    transport_parameters: []const u8,};

Source: lib/quic/src/tls/engine/model.zig:14

zig
pub const Random = union(enum) {    secure: std.Io,    fixed_for_testing: FixedRandom,    /// Builds a random source whose bytes the caller fixed in advance. The engine takes the hello    /// random and the key exchange secret from it, so a handshake built this way repeats exactly. A    /// test uses this form, and the other form of the source takes its bytes from the caller's I/O    /// interface through a secure-random call.    pub fn testing(value: FixedRandom) Random {        return .{ .fixed_for_testing = value };    }    pub fn values(self: Random) error{RandomFailed}!FixedRandom {        return switch (self) {            .fixed_for_testing => |value| value,            .secure => |io| blk: {                var value: FixedRandom = undefined;                io.randomSecure(&value.hello) catch return error.RandomFailed;                io.randomSecure(&value.key_exchange) catch return error.RandomFailed;                break :blk value;            },        };    }};

Source: lib/quic/src/tls/engine/model.zig:4

zig
pub const Role = enum { client, server };

Source: lib/quic/src/tls/engine/model.zig:55

zig
/// How far the handshake has reached: still running, complete, or confirmed. A client passes/// through completion first and reaches confirmation when the caller confirms it, which also erases/// the handshake traffic secrets. A server reports confirmation directly, once the client's/// Finished message verifies. A caller therefore treats a server's confirmation as both completion/// and confirmation.pub const Status = enum {    handshaking,    handshake_complete,    handshake_confirmed,};

Source: lib/quic/src/tls/engine/storage.zig:11

zig
pub const Storage = struct {    pub const storage_alignment: usize = 16;    pub const Storage = []align(storage_alignment) u8;    pub const Limits = struct { max_message: u16 };    pub const Capacity = struct {        message_bytes: usize,        receive_bytes: usize,        initial_bytes: usize,        handshake_bytes: usize,        peer_bytes: usize,        storage_bytes: usize,        pub const DeriveError = error{ MessageLimitEmpty, CapacityOverflow };        pub fn derive(limits: Limits) DeriveError!Capacity {            if (limits.max_message == 0) return error.MessageLimitEmpty;            const message_bytes = std.math.add(                usize,                limits.max_message,                4,            ) catch return error.CapacityOverflow;            const handshake_bytes = std.math.mul(                usize,                message_bytes,                server_flight_messages_max,            ) catch return error.CapacityOverflow;            const message_regions = std.math.mul(                usize,                message_bytes,                7,            ) catch return error.CapacityOverflow;            const storage_bytes = std.math.add(                usize,                message_regions,                limits.max_message,            ) catch return error.CapacityOverflow;            return .{                .message_bytes = message_bytes,                .receive_bytes = message_bytes,                .initial_bytes = message_bytes,                .handshake_bytes = handshake_bytes,                .peer_bytes = limits.max_message,                .storage_bytes = storage_bytes,            };        }    };    pub const InitError = Capacity.DeriveError || error{StorageTooShort};    pub const work_limits: alloc_phase.capacity.WorkLimits = .{        .transition_steps_max = 1,        .cleanup_steps_per_call_max = 0,        .cleanup_calls_at_capacity_max = 0,    };    pub const claim: alloc_phase.capacity.Declaration = .{        .source = .{            .id = "quic.tls_storage",            .kind = .phase_static,            .limit_source = .caller,            .storage = .{                .covered = &.{                    .{                        .id = "handshake_input_output_and_peer_bytes",                        .lifetime = .steady,                        .detail = "one receive message, two flights, and peer parameters",                    },                },                .excluded = &.{                    "caller configuration and identity key storage",                    "QUIC CRYPTO reassembly and packet protection storage",                    "cryptographic primitive stack storage and external I/O state",                },            },            .capacity = .{                .inputs = &.{                    alloc_phase.capacity.bindInput(Limits, "max_message", "max_message"),                },                .type_selectors = &.{},                .nodes = &.{                    .{ .input = 0 },                    .{ .constant = 4 },                    .{ .add = .{ .left = 0, .right = 1 } },                    .{                        .scale = .{ .node = 2, .coefficient = .{ .literal = 7 } },                    },                    .{ .add = .{ .left = 3, .right = 0 } },                },                .assertions = &.{.{                    .scope = .closure_total,                    .measure = .retained,                    .relation = .exact,                    .expression = 4,                }},            },            .overload = .{                .kind = .reject_before_seal,                .detail = "empty limits and short storage reject before activation",            },            .risks = .{                .transitive = .{                    .status = .witnessed,                    .detail = "message parsing and emission use caller storage only",                },                .foreign = .{                    .status = .excluded,                    .detail = "standard cryptography retains no dynamic engine storage",                },            },            .work = .{ .equation = "initialization to handshake transition steps <= 1" },            .obligations = &.{                .{ .key = "quic_tls_capacity", .role = .capacity_model },                .{ .key = "quic_tls_boundary", .role = .overload },                .{ .key = "quic_tls_transitive", .role = .transitive_risk },                .{ .key = "quic_tls_foreign", .role = .foreign_risk },                .{ .key = "quic_tls_work", .role = .work_bound },                .{ .key = "quic_tls_root", .role = .custom },            },        },        .bindings = .{            .owner = @This(),            .seal = .{                .family = alloc_phase.capacity.selector(@This().activate),                .premise = .{                    .class = .checked_semantic_fact,                    .authority = .checker,                },            },            .teardown = .{                .family = alloc_phase.capacity.selector(@This().deinit),                .premise = .{                    .class = .checked_semantic_fact,                    .authority = .checker,                },            },        },    };    phase: alloc_phase.capacity.Phase,    capacity: Capacity,    storage: []align(storage_alignment) u8,    pub fn init(bytes: @This().Storage, limits: Limits) InitError!@This() {        const capacity = try Capacity.derive(limits);        if (bytes.len < capacity.storage_bytes) return error.StorageTooShort;        return .{            .phase = .initialization,            .capacity = capacity,            .storage = bytes[0..capacity.storage_bytes],        };    }    pub fn activate(self: *@This()) void {        std.debug.assert(self.phase == .initialization);        self.phase = .steady;    }    pub fn deinit(self: *@This()) @This().Storage {        std.debug.assert(self.phase == .steady);        std.crypto.secureZero(u8, self.storage);        self.phase = .teardown;        const bytes = self.storage;        self.* = undefined;        return bytes;    }    /// A client engine writes its ClientHello into the Initial flight region during startup, before    /// the storage is sealed into its steady phase. The ordinary accessor requires the steady    /// phase, so this function returns that region while the storage is still in its initialization    /// phase. The call returns the same bytes the steady accessor later returns.    pub fn initialBytesForInitialization(self: *@This()) []u8 {        std.debug.assert(self.phase == .initialization);        const start = self.capacity.receive_bytes;        return self.storage[start..][0..self.capacity.initial_bytes];    }    pub fn receiveBytes(self: *@This()) []u8 {        self.assertSteady();        return self.storage[0..self.capacity.receive_bytes];    }    pub fn initialBytes(self: *@This()) []u8 {        self.assertSteady();        const start = self.capacity.receive_bytes;        return self.storage[start..][0..self.capacity.initial_bytes];    }    pub fn handshakeBytes(self: *@This()) []u8 {        self.assertSteady();        const start = self.capacity.receive_bytes + self.capacity.initial_bytes;        return self.storage[start..][0..self.capacity.handshake_bytes];    }    pub fn peerBytes(self: *@This()) []u8 {        self.assertSteady();        const start = self.capacity.receive_bytes +            self.capacity.initial_bytes + self.capacity.handshake_bytes;        return self.storage[start..][0..self.capacity.peer_bytes];    }    fn assertSteady(self: *const @This()) void {        std.debug.assert(self.phase == .steady);        std.debug.assert(self.storage.len >= self.capacity.storage_bytes);    }};

Source: lib/quic/src/tls/identity.zig:12

zig
pub const Identity = struct {    key_pair: Ed25519.KeyPair,    pub fn fromSeed(seed: [Ed25519.KeyPair.seed_length]u8) error{InvalidSeed}!Identity {        const key_pair = Ed25519.KeyPair.generateDeterministic(seed) catch {            return error.InvalidSeed;        };        return .{ .key_pair = key_pair };    }    pub fn public(self: Identity) PublicKey {        return self.key_pair.public_key.toBytes();    }    pub fn sign(self: Identity, content: []const u8) error{SigningFailed}![64]u8 {        const signature = self.key_pair.sign(content, null) catch {            return error.SigningFailed;        };        return signature.toBytes();    }};
Called byCallsNo direct callersprivate sourcelib.quic.src.tls.engine.machine.EngineeraseHandshakeTrafficSecretstls.Engineconfirm
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate sourcelib.quic.src.tls.engine.machineemitQueuetls.Engineemit
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallsNo direct callersprivate sourcelib.quic.src.tls.engine.machinevalidateConfigtls.Engineinit
Static calls · unresolved targets: 1 · external targets: 5.
Called byCallsNo direct callersprivate sourcelib.quic.src.tls.engine.machine.Engineprocessprivate sourcelib.quic.src.tls.engine.machine.EnginereadMessageLengthprivate sourcelib.quic.src.tls.engine.machine.Enginerejecttls.Enginereceive
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallsNo direct callersprivate sourcelib.quic.src.tls.engine.machine.EngineapplicationSecretprivate sourcelib.quic.src.tls.engine.machine.EnginehandshakeSecrettls.Enginesecret
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/quic/src/tls/engine/machine.zig:19

zig
pub const InitError = storage_mod.Storage.InitError || InitSpecific;
Called byCallsNo direct callstls.Storageinittest sourcelib.quic.src.tls.engine.storagetest: TLS storage accepts exact capac...tls.Capacityderive
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.quic.src.tls.engine.storagetest: TLS storage accepts exact capac...tls.Storageactivate
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callstest sourcelib.quic.src.tls.engine.storagetest: TLS storage accepts exact capac...tls.Storagedeinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate sourcelib.quic.src.tls.engine.storage.StorageassertSteadytls.StoragehandshakeBytes
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallstest sourcelib.quic.src.tls.engine.storagetest: TLS storage accepts exact capac...tls.Capacityderivetls.Storageinit
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate sourcelib.quic.src.tls.engine.storage.StorageassertSteadytls.StorageinitialBytes
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate sourcelib.quic.src.tls.engine.storage.StorageassertSteadytls.StoragepeerBytes
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsNo direct callersprivate sourcelib.quic.src.tls.engine.storage.StorageassertSteadytls.StoragereceiveBytes
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/quic/src/tls/identity.zig:5

zig
pub const PublicKey = [Ed25519.PublicKey.encoded_length]u8;

Source: lib/quic/src/tls/identity.zig:47

zig
pub fn decodeSubjectPublicKeyInfo(    encoded: []const u8,) error{ InvalidLength, InvalidPrefix }!PublicKey {    if (encoded.len != spki_length) return error.InvalidLength;    if (!std.mem.eql(u8, encoded[0..spki_prefix.len], &spki_prefix)) {        return error.InvalidPrefix;    }    return encoded[spki_prefix.len..][0..@sizeOf(PublicKey)].*;}
Called byCallsNo direct callstest sourcelib.quic.src.tls.identitytest: RFC 8410 section 4 and RFC 7250...test sourcelib.quic.src.tls.identitytest: RFC 8410 section 4 rejects shor...tlsdecodeSubjectPublicKeyInfo
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/quic/src/tls/identity.zig:40

zig
pub fn encodeSubjectPublicKeyInfo(public_key: PublicKey) [spki_length]u8 {    var encoded: [spki_length]u8 = undefined;    encoded[0..spki_prefix.len].* = spki_prefix;    encoded[spki_prefix.len..].* = public_key;    return encoded;}
Called byCallsNo direct callstest sourcelib.quic.src.tls.identitytest: RFC 8410 section 4 and RFC 7250...test sourcelib.quic.src.tls.identitytest: RFC 8410 section 4 rejects shor...tlsencodeSubjectPublicKeyInfo
Static calls · unresolved targets: 0 · external targets: 0.

Source: lib/quic/src/tls/identity.zig:6

zig
pub const spki_length: usize = 44;

Source: lib/quic/src/tls/identity.zig:34

zig
pub fn verify(public_key: PublicKey, content: []const u8, signature: [64]u8) !void {    const parsed_key = try Ed25519.PublicKey.fromBytes(public_key);    const parsed_signature = Ed25519.Signature.fromBytes(signature);    try parsed_signature.verify(content, parsed_key);}

Source: lib/quic/src/tls/engine/model.zig:7

zig
pub const Secret = tls.schedule.Secret;

Source: lib/quic/src/root.zig:50

zig
pub const tls = @import("tls/root.zig");

Source: lib/quic/src/tls/root.zig

zig
//! The TLS 1.3 handshake a QUIC connection needs runs as a state machine over bytes the caller//! carries.//!//! Before any application data moves, the two endpoints have to agree on a shared secret, each//! prove who it is, and exchange the settings that govern the connection. Handshake messages travel//! inside the transport's own frames. The keys a handshake message travels under are selected by an//! *encryption level*: one of Initial, Handshake, and 1-RTT, so the handshake has to hand bytes out//! and take bytes in one level at a time.//!//! A message can arrive split across more than one delivery, so partial messages have to be held//! until they are whole. The frames carrying handshake bytes can be lost, so the bytes already//! handed out have to stay available to be sent again. The secrets derived along the way stop being//! needed at known points, and leaving them in memory past that point widens what a later//! compromise reaches.//!//! RFC 8446 defines TLS 1.3, and this code follows its message layouts, its key schedule from the//! shared secret to the application *traffic secret* (the 32 bytes from which one direction's//! packet keys are derived), and its Finished verification. RFC 9001 section 4 defines how TLS//! meets QUIC, and this code follows it by carrying handshake bytes per encryption level and//! turning a TLS alert into a transport error code. The values two endpoints exchange inside the//! TLS handshake are the *transport parameters*, and an extension carries them. RFC 7250 and RFC//! 8410 define raw public keys and the Ed25519 key encoding, and identities here are Ed25519 public//! keys in that 44-byte form.//!//! The accepted profile is narrow, taking X25519 for the key exchange, Ed25519 raw public keys for//! identity, and one of two cipher suites. Both endpoints authenticate, because the server asks the//! client for a certificate and rejects a client that omits it. A session ticket is parsed and//! dropped, so each handshake starts fresh. A TLS KeyUpdate message is rejected, because packet key//! updates are the transport's own business. The engine reaches for no allocator, so the caller//! derives a byte count from one maximum message size and hands over the block the engine//! partitions.const alert_mod = @import("alert.zig");const engine_mod = @import("engine/root.zig");const identity_mod = @import("identity.zig");pub const message = @import("message/root.zig");pub const schedule = @import("schedule.zig");pub const Alert = alert_mod.Alert;pub const PublicKey = identity_mod.PublicKey;pub const Identity = identity_mod.Identity;pub const spki_length = identity_mod.spki_length;pub const encodeSubjectPublicKeyInfo = identity_mod.encodeSubjectPublicKeyInfo;pub const decodeSubjectPublicKeyInfo = identity_mod.decodeSubjectPublicKeyInfo;pub const verifySignature = identity_mod.verify;pub const Role = engine_mod.Role;pub const Level = engine_mod.Level;pub const Direction = engine_mod.Direction;pub const Limits = engine_mod.Limits;pub const Capacity = engine_mod.Capacity;pub const Storage = engine_mod.Storage;pub const Config = engine_mod.Config;pub const FixedRandom = engine_mod.FixedRandom;pub const Random = engine_mod.Random;pub const Secret = engine_mod.Secret;pub const Status = engine_mod.Status;pub const Peer = engine_mod.Peer;pub const Engine = engine_mod.Engine;pub const Error = engine_mod.Error;pub const InitError = engine_mod.InitError;

Audit

Definitions55
Public names59
Members77
Version26.7.0
Revisiondaab053ee433