Skip to documentation
SLOP

tiny.quic.crypto.packet

Reference tiny.quic crypto packet

Defined in crypto.

API (11)

Actions

Public operations.

Types and contracts

Public types and contracts.

Values and defaults

Public values and defaults.

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

Source

Called byCallsNo direct callsprivate sourcelib.quic.src.crypto.packetopenPayloadInnercrypto.packetsealprivate sourcelib.quic.src.properties.protection.NonceMirrorpropertytest sourcelib.quic.src.properties.protection.test_RFC_9...5 Lean nonce and header mask mirror v...crypto.packetnonce
Static calls · unresolved targets: 0 · external targets: 0.
Called byCallsprivate sourcelib.quic.src.crypto.testopenPackettest sourcelib.quic.src.crypto.test.test_RFC_9000_sectio...2 packet maximum plus one rejects bef...private sourcelib.quic.src.properties.crypto.Totalitypropertyprivate sourcelib.quic.src.properties.cryptorejectEveryByteprivate sourcelib.quic.src.properties.cryptoroundTripcrypto.packetopenPayloadprivate sourcelib.quic.src.crypto.packetrestoreHeadercrypto.packetunprotectcrypto.packetopen
Static calls · unresolved targets: 0 · external targets: 1.
Called byCallscrypto.packetopentest sourcelib.quic.src.crypto.test.test_RFC_9001_section_62 exposes key phase before payload de...private sourcelib.quic.src.crypto.packetopenPayloadInnercrypto.packetopenPayload
Static calls · unresolved targets: 0 · external targets: 2.
Called byCallstest sourcelib.quic.src.crypto.test.test_RFC_9000_Append...3 packet number expansion wrap branch...test sourcelib.quic.src.crypto.test.test_RFC_9000_sectio...2 packet maximum plus one rejects bef...test sourcelib.quic.src.crypto.test.test_RFC_9000_sectio...3 reject authenticated reserved bitstest sourcelib.quic.src.crypto.test.test_RFC_9001_Append...2 client Initial packet protectiontest sourcelib.quic.src.crypto.test.test_RFC_9001_Append...3 server Initial packet protection+10 morecrypto.headermaskcrypto.headerprotectprivate sourcelib.quic.src.crypto.packetencryptprivate sourcelib.quic.src.crypto.packetlongEndcrypto.packetnonceprivate sourcelib.quic.src.crypto.packetpacketNumberMatchescrypto.packetseal
Static calls · unresolved targets: 0 · external targets: 3.
Called byCallscrypto.packetopentest sourcelib.quic.src.crypto.test.test_RFC_9001_section_62 exposes key phase before payload de...private sourcelib.quic.src.properties.protectioncheckOpenSamplecrypto.headerinspectcrypto.headermaskcrypto.headerunprotectprivate sourcelib.quic.src.crypto.packetkeyPhaseprivate sourcelib.quic.src.crypto.packetprotectedEnd+2 morecrypto.packetunprotect
Static calls · unresolved targets: 0 · external targets: 1.

Source: lib/quic/src/crypto/packet.zig

zig
const std = @import("std");const quic = @import("../root.zig");const crypto = quic.crypto;const Aes128Gcm = std.crypto.aead.aes_gcm.Aes128Gcm;const ChaCha20Poly1305 = std.crypto.aead.chacha_poly.ChaCha20Poly1305;pub const tag_bytes: usize = 16;/// The largest QUIC packet in bytes, which RFC 9000 section 18.2 sets as the largest UDP payload at/// 65,527, so a caller sizing a datagram buffer takes the largest packet this code will seal or/// open. Sealing or opening past it gives `PacketTooLarge`.pub const packet_bytes_max: usize = 65_527;const SealFailure = error{    ConfidentialityLimitReached,    InvalidHeader,    InvalidLength,    PacketCounterOverflow,    PacketTooLarge,    PacketTooShort,};const OpenFailure = error{    AuthenticationFailed,    IntegrityLimitReached,    InvalidHeader,    InvalidLength,    InvalidPacketNumber,    PacketTooLarge,    PacketTooShort,    ReservedBits,    ScratchTooShort,};pub const SealError = crypto.header.ApplyError || SealFailure;pub const OpenError = quic.packet.DecodeError || crypto.header.ApplyError || OpenFailure;pub const Header = struct {    first_byte: u8,    pn_len: u3,    pn_offset: usize,    packet_end: usize,    packet_number: u62,    key_phase: ?bool,};pub const Opened = struct {    packet_number: u62,    payload: []u8,    key_phase: ?bool,};/// Returns the 12-byte AEAD nonce for one packet number under one initialization vector, as RFC/// 9001 section 5.3 constructs it, so a caller sealing or opening a packet by hand computes the/// same nonce the peer will compute. The packet number is written big-endian into the low eight/// bytes of a zero field the width of the vector, and the two are exclusive-ored. Distinct packet/// numbers give distinct nonces under one vector, so a key stays usable across packets.pub fn nonce(iv: [crypto.iv_bytes]u8, packet_number: u62) [crypto.iv_bytes]u8 {    var result = iv;    var encoded: [8]u8 = undefined;    std.mem.writeInt(u64, &encoded, packet_number, .big);    for (0..8) |index| result[4 + index] ^= encoded[index];    return result;}fn longEnd(packet: []const u8, pn_offset: usize) OpenError!usize {    const decoded = try quic.packet.decodeLong(packet);    const Metadata = struct { offset: usize, length: u62 };    const metadata: Metadata = switch (decoded) {        .initial => |value| .{ .offset = value.packet_number_offset, .length = value.length },        .zero_rtt, .handshake => |value| .{            .offset = value.packet_number_offset,            .length = value.length,        },        .retry, .version_negotiation => return error.InvalidHeader,    };    if (metadata.offset != pn_offset) return error.InvalidHeader;    const length = std.math.cast(usize, metadata.length) orelse        return error.InvalidLength;    if (pn_offset > packet.len) return error.InvalidLength;    if (length > packet.len - pn_offset) return error.InvalidLength;    return pn_offset + length;}fn protectedEnd(packet: []const u8, pn_offset: usize) OpenError!usize {    if (packet.len == 0) return error.PacketTooShort;    if (pn_offset == 0) return error.InvalidHeader;    if (packet[0] & 0x80 != 0) {        const packet_end = try longEnd(packet, pn_offset);        if (packet_end > packet_bytes_max) return error.PacketTooLarge;        return packet_end;    }    if (packet[0] & 0x40 == 0) return error.InvalidHeader;    if (pn_offset > packet.len) return error.InvalidHeader;    if (packet.len > packet_bytes_max) return error.PacketTooLarge;    return packet.len;}fn sampleAt(packet: []const u8, pn_offset: usize, end: usize) OpenError![16]u8 {    const sample_offset = std.math.add(usize, pn_offset, 4) catch        return error.PacketTooShort;    const sample_end = std.math.add(usize, sample_offset, 16) catch        return error.PacketTooShort;    if (sample_end > end) return error.PacketTooShort;    return packet[sample_offset..][0..16].*;}fn encrypt(    keys: *const crypto.Keys,    payload: []u8,    aad: []const u8,    packet_nonce: [12]u8,) [tag_bytes]u8 {    var tag: [tag_bytes]u8 = undefined;    const packet_key = keys.packetKey();    switch (keys.selectedSuite()) {        .aes_128_gcm_sha256 => Aes128Gcm.encrypt(            payload,            &tag,            payload,            aad,            packet_nonce,            packet_key[0..16].*,        ),        .chacha20_poly1305_sha256 => ChaCha20Poly1305.encrypt(            payload,            &tag,            payload,            aad,            packet_nonce,            packet_key,        ),    }    return tag;}pub fn seal(    keys: *crypto.Keys,    packet_number: u62,    packet: []u8,    pn_offset: usize,    pn_len: u3,    payload_len: u16,) SealError!void {    if (keys.confidentialityExhausted()) return error.ConfidentialityLimitReached;    if (keys.sealed_packets == std.math.maxInt(u64)) return error.PacketCounterOverflow;    if (packet.len == 0) return error.PacketTooShort;    if (pn_len == 0 or pn_len > 4) return error.InvalidLength;    if (quic.packet.packetNumberLength(packet[0]) != pn_len) return error.InvalidHeader;    const header_len = std.math.add(usize, pn_offset, pn_len) catch        return error.InvalidLength;    const payload_bytes: usize = payload_len;    const payload_end = std.math.add(usize, header_len, payload_bytes) catch        return error.InvalidLength;    const packet_end = std.math.add(usize, payload_end, tag_bytes) catch        return error.InvalidLength;    if (packet_end > packet.len) return error.PacketTooShort;    if (packet_end > packet_bytes_max) return error.PacketTooLarge;    if (packet[0] & 0x80 != 0) {        const encoded_end = longEnd(packet, pn_offset) catch return error.InvalidHeader;        if (encoded_end != packet_end) return error.InvalidLength;    } else if (packet[0] & 0x40 == 0) return error.InvalidHeader;    std.debug.assert(packetNumberMatches(packet[pn_offset..header_len], packet_number));    const sample_offset = std.math.add(usize, pn_offset, 4) catch        return error.PacketTooShort;    const sample_end = std.math.add(usize, sample_offset, 16) catch        return error.PacketTooShort;    if (sample_end > packet_end) return error.PacketTooShort;    const payload = packet[header_len..payload_end];    const packet_nonce = nonce(keys.initializationVector(), packet_number);    const tag = encrypt(keys, payload, packet[0..header_len], packet_nonce);    packet[payload_end..][0..tag_bytes].* = tag;    std.debug.assert(sample_end <= packet_end);    const protected_sample = packet[sample_offset..][0..16].*;    const protection_mask = crypto.header.mask(keys, &protected_sample);    try crypto.header.protect(protection_mask, packet[0..packet_end], pn_offset, pn_len);    keys.sealed_packets += 1;}fn packetNumberMatches(bytes: []const u8, packet_number: u62) bool {    std.debug.assert(bytes.len >= 1);    std.debug.assert(bytes.len <= 4);    var encoded: [8]u8 = undefined;    std.mem.writeInt(u64, &encoded, packet_number, .big);    return std.mem.eql(u8, bytes, encoded[encoded.len - bytes.len ..]);}test "RFC 9001 section 5.3 seal packet number assertion contract" {    try std.testing.expect(packetNumberMatches(&.{0x34}, 0x1234));    try std.testing.expect(packetNumberMatches(&.{ 0x12, 0x34 }, 0x1234));    try std.testing.expect(!packetNumberMatches(&.{0x35}, 0x1234));}fn readProtectedPacketNumber(    bytes: []const u8,    protection_mask: [crypto.header.mask_bytes]u8,) u32 {    std.debug.assert(bytes.len >= 1);    std.debug.assert(bytes.len <= 4);    var result: u32 = 0;    for (0..4) |index| {        if (index >= bytes.len) break;        result = (result << 8) | (bytes[index] ^ protection_mask[index + 1]);    }    return result;}fn decrypt(    keys: *const crypto.Keys,    plaintext: []u8,    ciphertext: []const u8,    tag: [tag_bytes]u8,    aad: []const u8,    packet_nonce: [12]u8,) error{AuthenticationFailed}!void {    std.debug.assert(plaintext.len == ciphertext.len);    const packet_key = keys.packetKey();    switch (keys.selectedSuite()) {        .aes_128_gcm_sha256 => try Aes128Gcm.decrypt(            plaintext,            ciphertext,            tag,            aad,            packet_nonce,            packet_key[0..16].*,        ),        .chacha20_poly1305_sha256 => try ChaCha20Poly1305.decrypt(            plaintext,            ciphertext,            tag,            aad,            packet_nonce,            packet_key,        ),    }}fn keyPhase(first_byte: u8) ?bool {    if (first_byte & 0x80 != 0) return null;    return first_byte & 0x04 != 0;}pub fn unprotect(    keys: *const crypto.Keys,    packet: []u8,    pn_offset: usize,    largest_acked: ?u62,) OpenError!Header {    const packet_end = try protectedEnd(packet, pn_offset);    const sample = try sampleAt(packet, pn_offset, packet_end);    const protection_mask = crypto.header.mask(keys, &sample);    const inspected = try crypto.header.inspect(protection_mask, packet[0..packet_end], pn_offset);    const header_len = pn_offset + inspected.pn_len;    if (packet_end - header_len < tag_bytes) return error.InvalidLength;    if (largest_acked == std.math.maxInt(u62)) return error.InvalidPacketNumber;    const truncated = readProtectedPacketNumber(        packet[pn_offset..header_len],        protection_mask,    );    const bits: u6 = @as(u6, inspected.pn_len) * 8;    const packet_number = quic.packet.Number.expand(truncated, bits, largest_acked);    const pn_len = try crypto.header.unprotect(        protection_mask,        packet[0..packet_end],        pn_offset,    );    std.debug.assert(pn_len == inspected.pn_len);    return .{        .first_byte = inspected.first_byte,        .pn_len = pn_len,        .pn_offset = pn_offset,        .packet_end = packet_end,        .packet_number = packet_number,        .key_phase = keyPhase(inspected.first_byte),    };}const PayloadLayout = struct {    header_len: usize,    tag_offset: usize,};fn payloadLayout(packet: []const u8, parsed: Header) OpenError!PayloadLayout {    if (packet.len == 0) return error.PacketTooShort;    if (parsed.pn_len == 0 or parsed.pn_len > 4) return error.InvalidLength;    if (parsed.pn_offset == 0) return error.InvalidHeader;    if (parsed.pn_offset > parsed.packet_end) return error.InvalidHeader;    if (parsed.pn_len > parsed.packet_end - parsed.pn_offset) return error.InvalidLength;    const packet_end = try protectedEnd(packet, parsed.pn_offset);    if (packet_end != parsed.packet_end) return error.InvalidHeader;    const header_len = parsed.pn_offset + parsed.pn_len;    if (parsed.packet_end - header_len < tag_bytes) return error.InvalidLength;    if (packet[0] != parsed.first_byte) return error.InvalidHeader;    if (!packetNumberMatches(packet[parsed.pn_offset..header_len], parsed.packet_number)) {        return error.InvalidPacketNumber;    }    if (keyPhase(parsed.first_byte) != parsed.key_phase) return error.InvalidHeader;    return .{ .header_len = header_len, .tag_offset = parsed.packet_end - tag_bytes };}fn slicesOverlap(first: []const u8, second: []const u8) bool {    if (first.len == 0 or second.len == 0) return false;    const first_address = @intFromPtr(first.ptr);    const second_address = @intFromPtr(second.ptr);    if (first_address <= second_address) {        return second_address - first_address < first.len;    }    return first_address - second_address < second.len;}fn reservedBitsSet(first_byte: u8) bool {    const reserved_mask: u8 = if (first_byte & 0x80 != 0) 0x0c else 0x18;    return first_byte & reserved_mask != 0;}fn openPayloadInner(    keys: *crypto.Keys,    packet: []u8,    parsed: Header,    scratch: []u8,) OpenError!Opened {    const layout = try payloadLayout(packet, parsed);    const ciphertext = packet[layout.header_len..layout.tag_offset];    if (scratch.len < ciphertext.len) return error.ScratchTooShort;    const plaintext = scratch[0..ciphertext.len];    std.debug.assert(!slicesOverlap(packet[0..parsed.packet_end], plaintext));    const tag: [tag_bytes]u8 = packet[layout.tag_offset..][0..tag_bytes].*;    try decrypt(        keys,        plaintext,        ciphertext,        tag,        packet[0..layout.header_len],        nonce(keys.initializationVector(), parsed.packet_number),    );    if (reservedBitsSet(parsed.first_byte)) return error.ReservedBits;    @memcpy(packet[layout.header_len..layout.tag_offset], plaintext);    return .{        .packet_number = parsed.packet_number,        .payload = packet[layout.header_len..layout.tag_offset],        .key_phase = parsed.key_phase,    };}/// Decrypts the payload of a packet whose header `unprotect` has already parsed, and leaves the/// plaintext in the packet where the ciphertext was, so a receiver decrypts the payload in place./// The caller supplies scratch bytes for the plaintext, and a scratch slice shorter than the/// ciphertext gives `ScratchTooShort`. The ciphertext runs from the end of the packet number to the/// authentication tag, so scratch needs the packet end less the packet number offset, the packet/// number length, and the tag bytes. Scratch must lie outside the packet bytes, and only debug/// builds check that. A failed authentication counts against the key's integrity limit, and a key/// that has reached its limit gives `IntegrityLimitReached` before any decryption.pub fn openPayload(    keys: *crypto.Keys,    packet: []u8,    parsed: Header,    scratch: []u8,) OpenError!Opened {    if (keys.exhausted()) return error.IntegrityLimitReached;    return openPayloadInner(keys, packet, parsed, scratch) catch |failure| {        if (failure != error.AuthenticationFailed) return failure;        std.debug.assert(keys.failed_opens < keys.integrityLimit());        keys.failed_opens += 1;        return failure;    };}fn restoreHeader(keys: *const crypto.Keys, packet: []u8, parsed: Header) void {    const sample = sampleAt(packet, parsed.pn_offset, parsed.packet_end) catch unreachable;    const protection_mask = crypto.header.mask(keys, &sample);    crypto.header.protect(        protection_mask,        packet[0..parsed.packet_end],        parsed.pn_offset,        parsed.pn_len,    ) catch unreachable;}pub fn open(    keys: *crypto.Keys,    packet: []u8,    scratch: []u8,    pn_offset: usize,    largest_acked: ?u62,) OpenError!Opened {    if (keys.exhausted()) return error.IntegrityLimitReached;    const parsed = try unprotect(keys, packet, pn_offset, largest_acked);    return openPayload(keys, packet, parsed, scratch) catch |failure| {        restoreHeader(keys, packet, parsed);        return failure;    };}

Source: lib/quic/src/crypto/root.zig:19

zig
pub const packet = @import("packet.zig");

Complete caller list for crypto.packet.seal

15 direct callers.

Complete call list for crypto.packet.unprotect

7 direct calls.

Audit

Definitions12
Public names12
Members9
Version26.7.0
Revisiondaab053ee433