tiny.quic.crypto
Defined in tiny.quic.
API (43)
Actions
Public operations.
Capacity.deriveKeys.activateKeys.confidentialityExhaustedKeys.deinitKeys.deriveKeys.exhaustedKeys.headerKeyKeys.initKeys.initializationVectorKeys.integrityLimitKeys.keyLengthKeys.needsUpdateKeys.nextKeys.packetKeyKeys.selectedSuiteKeys.updateSuite.keyLength
Types and contracts
Public types and contracts.
CapacityCapacity.DeriveErrorKeysKeys.CapacityKeys.InitErrorKeys.LimitsKeys.StorageLimitsSecretSuite
Namespaces
Public namespaces.
Values and defaults
Public values and defaults.
Keys.claimKeys.storage_alignmentKeys.storage_bytes_maxKeys.work_limitsaes_confidentiality_limit: The count of packets one AES-128-GCM key may seal, which RFC 9001 section 6.6 puts at 2^23.aes_integrity_limit: The count of packets that may fail authentication under one AES-128-GCM key, which RFC 9001 section 6.6 puts at 2^52.aes_update_margin: The number of packets before the AES-GCM confidentiality limit at which an update is already called for.chacha_confidentiality_limit: The absence of a confidentiality limit for ChaCha20-Poly1305, because RFC 9001 section 6.6 puts the bound above the 2^62 packets a connection can number.chacha_integrity_limit: The count of packets that may fail authentication under one ChaCha20-Poly1305 key, which RFC 9001 section 6.6 puts at 2^36.iv_byteskey_bytes_maxsecret_bytes
Source
Source: lib/quic/src/crypto/keys.zig:62
zig
pub const Capacity = struct { suite: Suite, key_bytes: u6, storage_bytes: usize, pub const DeriveError = error{CapacityOverflow}; pub fn derive(limits: Limits) DeriveError!Capacity { const key_lanes = std.math.mul(usize, key_bytes_max, 2) catch return error.CapacityOverflow; const derived_bytes = std.math.add(usize, key_lanes, iv_bytes) catch return error.CapacityOverflow; if (derived_bytes != storage_bytes) return error.CapacityOverflow; return .{ .suite = limits.suite, .key_bytes = limits.suite.keyLength(), .storage_bytes = derived_bytes, }; }};Source: lib/quic/src/crypto/keys.zig:86
zig
pub const Keys = struct { phase: alloc_phase.capacity.Phase, capacity: KeyCapacity, storage: []align(storage_alignment) u8, sealed_packets: u64, failed_opens: u64, pub const storage_alignment: usize = @alignOf(Material); pub const storage_bytes_max: usize = storage_bytes; pub const Storage = []align(storage_alignment) u8; pub const Limits: type = KeyLimits; pub const Capacity: type = KeyCapacity; pub const InitError = KeyCapacity.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.crypto_keys", .kind = .startup_static, .limit_source = .caller, .storage = .{ .covered = &.{ .{ .id = "packet_key_bytes", .lifetime = .steady, .detail = "fixed caller-provisioned packet protection key bytes", }, .{ .id = "packet_iv_bytes", .lifetime = .steady, .detail = "fixed caller-provisioned packet protection IV bytes", }, .{ .id = "header_key_bytes", .lifetime = .steady, .detail = "fixed caller-provisioned header protection key bytes", }, }, .excluded = &.{ "caller-owned packet and traffic secret bytes", "fixed suite tag and usage counters", }, }, .capacity = .{ .inputs = &.{}, .type_selectors = &.{ alloc_phase.capacity.bindType(Material, "key_material"), }, .nodes = &.{ .{ .constant = 1 }, .{ .scale = .{ .node = 0, .coefficient = .{ .size_of_concrete_type = 0 }, } }, }, .assertions = &.{.{ .scope = .closure_total, .measure = .retained, .relation = .exact, .expression = 1, }}, }, .overload = .{ .kind = .reject_before_seal, .detail = "short caller storage rejects before key derivation", }, .risks = .{ .transitive = .{ .status = .witnessed, .detail = "standard cryptographic primitives use fixed value storage", }, .foreign = .{ .status = .excluded, .detail = "key derivation and packet protection cross no foreign boundary", }, }, .work = .{ .equation = "initialization to steady transition steps <= 1" }, .obligations = &.{ .{ .key = "quic_crypto_keys_capacity", .role = .capacity_model }, .{ .key = "quic_crypto_keys_overload", .role = .overload }, .{ .key = "quic_crypto_keys_transitive", .role = .transitive_risk }, .{ .key = "quic_crypto_keys_work", .role = .work_bound }, }, }, .bindings = .{ .owner = @This() }, }; pub fn init(storage: Storage, limits: KeyLimits) InitError!Keys { const capacity = try KeyCapacity.derive(limits); if (storage.len < capacity.storage_bytes) return error.StorageTooShort; const owned = storage[0..capacity.storage_bytes]; @memset(owned, 0); return .{ .phase = .initialization, .capacity = capacity, .storage = owned, .sealed_packets = 0, .failed_opens = 0, }; } pub fn activate(self: *Keys) void { std.debug.assert(self.phase == .initialization); std.debug.assert(self.storage.len == self.capacity.storage_bytes); self.phase = .steady; } pub fn derive(storage: Storage, suite: Suite, secret: Secret) InitError!Keys { var result = try init(storage, .{ .suite = suite }); result.writePacketMaterial(secret); result.writeHeaderMaterial(secret); result.activate(); return result; } pub fn deinit(self: *Keys) Storage { std.debug.assert(self.phase == .steady); std.crypto.secureZero(u8, self.storage); self.phase = .teardown; const owned = self.storage; self.* = undefined; return owned; } fn material(self: *Keys) *Material { std.debug.assert(self.phase != .teardown); std.debug.assert(self.storage.len == storage_bytes); return @ptrCast(self.storage.ptr); } fn materialConst(self: *const Keys) *const Material { std.debug.assert(self.phase == .steady); std.debug.assert(self.storage.len == storage_bytes); return @ptrCast(self.storage.ptr); } fn writePacketMaterial(self: *Keys, secret: Secret) void { const value = self.material(); value.key = @splat(0); value.iv = tls.hkdfExpandLabel(Hkdf, secret, "quic iv", "", iv_bytes); switch (self.capacity.suite) { .aes_128_gcm_sha256 => value.key[0..16].* = tls.hkdfExpandLabel(Hkdf, secret, "quic key", "", 16), .chacha20_poly1305_sha256 => value.key = tls.hkdfExpandLabel(Hkdf, secret, "quic key", "", 32), } } fn writeHeaderMaterial(self: *Keys, secret: Secret) void { const value = self.material(); value.hp = @splat(0); switch (self.capacity.suite) { .aes_128_gcm_sha256 => value.hp[0..16].* = tls.hkdfExpandLabel(Hkdf, secret, "quic hp", "", 16), .chacha20_poly1305_sha256 => value.hp = tls.hkdfExpandLabel(Hkdf, secret, "quic hp", "", 32), } } pub fn next(suite: Suite, secret: Secret) Secret { return switch (suite) { .aes_128_gcm_sha256, .chacha20_poly1305_sha256, => tls.hkdfExpandLabel(Hkdf, secret, "quic ku", "", secret_bytes), }; } pub fn update(self: *Keys, secret: Secret) void { std.debug.assert(self.phase == .steady); self.writePacketMaterial(secret); self.sealed_packets = 0; } pub fn packetKey(self: *const Keys) [key_bytes_max]u8 { return self.materialConst().key; } pub fn initializationVector(self: *const Keys) [iv_bytes]u8 { return self.materialConst().iv; } pub fn headerKey(self: *const Keys) [key_bytes_max]u8 { return self.materialConst().hp; } pub fn selectedSuite(self: *const Keys) Suite { std.debug.assert(self.phase == .steady); return self.capacity.suite; } pub fn keyLength(self: *const Keys) u6 { std.debug.assert(self.phase == .steady); return self.capacity.key_bytes; } pub fn needsUpdate(self: *const Keys) bool { std.debug.assert(self.phase == .steady); return switch (self.capacity.suite) { .aes_128_gcm_sha256 => self.sealed_packets >= aes_confidentiality_limit - aes_update_margin, .chacha20_poly1305_sha256 => chacha_confidentiality_limit != null, }; } pub fn confidentialityExhausted(self: *const Keys) bool { std.debug.assert(self.phase == .steady); return switch (self.capacity.suite) { .aes_128_gcm_sha256 => self.sealed_packets >= aes_confidentiality_limit, .chacha20_poly1305_sha256 => chacha_confidentiality_limit != null, }; } pub fn integrityLimit(self: *const Keys) u64 { std.debug.assert(self.phase == .steady); return switch (self.capacity.suite) { .aes_128_gcm_sha256 => aes_integrity_limit, .chacha20_poly1305_sha256 => chacha_integrity_limit, }; } pub fn exhausted(self: *const Keys) bool { std.debug.assert(self.phase == .steady); return self.failed_opens >= self.integrityLimit(); }};Source: lib/quic/src/crypto/keys.zig:50
zig
pub const Limits = struct { suite: Suite,};Source: lib/quic/src/crypto/keys.zig:38
zig
pub const Suite = enum { aes_128_gcm_sha256, chacha20_poly1305_sha256, pub fn keyLength(self: Suite) u6 { return switch (self) { .aes_128_gcm_sha256 => 16, .chacha20_poly1305_sha256 => 32, }; }};Source: lib/quic/src/crypto/keys.zig:36
zig
pub const Secret = [secret_bytes]u8;Source: lib/quic/src/crypto/keys.zig:14
zig
/// The count of packets one AES-128-GCM key may seal, which RFC 9001 section 6.6 puts at 2^23. A/// caller compares it against the packets one key has sealed to decide when a key update is due./// Reaching it stops further sealing under that key.pub const aes_confidentiality_limit: u64 = @as(u64, 1) << 23;Source: lib/quic/src/crypto/keys.zig:24
zig
/// The count of packets that may fail authentication under one AES-128-GCM key, which RFC 9001/// section 6.6 puts at 2^52. A caller compares it against failed decryptions to decide when an/// attacker has had too many guesses. Reaching it stops further opening under that key.pub const aes_integrity_limit: u64 = @as(u64, 1) << 52;Source: lib/quic/src/crypto/keys.zig:20
zig
/// The number of packets before the AES-GCM confidentiality limit at which an update is already/// called for. RFC 9001 section 6.6 requires this margin, and the update comes one packet ahead of/// the hard limit so a sender never has to stop. A key that has sealed the confidentiality limit/// less this margin reports that it needs an update. This margin sits below the confidentiality/// limit, which the compiler checks.pub const aes_update_margin: u64 = 1;Source: lib/quic/src/crypto/keys.zig:34
zig
/// The count of packets that may fail authentication under one ChaCha20-Poly1305 key, which RFC/// 9001 section 6.6 puts at 2^36. A caller compares it against failed decryptions under this suite./// Reaching it stops further opening under that key.pub const chacha_integrity_limit: u64 = @as(u64, 1) << 36;Source: lib/quic/src/crypto/keys.zig:9
zig
pub const iv_bytes: usize = 12;Source: lib/quic/src/crypto/keys.zig:8
zig
pub const key_bytes_max: usize = 32;Source: lib/quic/src/crypto/keys.zig:7
zig
pub const secret_bytes: usize = 32;Source: lib/quic/src/crypto/keys.zig:30
zig
/// The absence of a confidentiality limit for ChaCha20-Poly1305, because RFC 9001 section 6.6 puts/// the bound above the 2^62 packets a connection can number. A caller reading the limits for a/// suite finds out that this one carries no packet count to watch. A ChaCha20-Poly1305 key/// therefore reports that it needs no update and that its confidentiality is intact, whatever it/// has sealed.pub const chacha_confidentiality_limit: ?u64 = null;Source: lib/quic/src/crypto/root.zig
zig
const keys = @import("keys.zig");pub const Suite = keys.Suite;pub const Secret = keys.Secret;pub const Keys = keys.Keys;pub const Limits = keys.Limits;pub const Capacity = keys.Capacity;pub const aes_confidentiality_limit = keys.aes_confidentiality_limit;pub const aes_integrity_limit = keys.aes_integrity_limit;pub const aes_update_margin = keys.aes_update_margin;pub const chacha_confidentiality_limit = keys.chacha_confidentiality_limit;pub const chacha_integrity_limit = keys.chacha_integrity_limit;pub const iv_bytes = keys.iv_bytes;pub const key_bytes_max = keys.key_bytes_max;pub const secret_bytes = keys.secret_bytes;pub const header = @import("header.zig");pub const initial = @import("initial.zig");pub const packet = @import("packet.zig");pub const retry = @import("retry.zig");Source: lib/quic/src/root.zig:45
zig
pub const crypto = @import("crypto/root.zig");Audit
| Definitions | 40 |
|---|---|
| Public names | 40 |
| Members | 12 |
| Version | 26.7.0 |
| Revision | daab053ee433 |