lib/quic/src/crypto/header.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const quic = @import("../root.zig");
3
4 const crypto = quic.crypto;
5 const Aes128 = std.crypto.core.aes.Aes128;
6 const ChaCha20 = std.crypto.stream.chacha.ChaCha20IETF;
7
8 pub const sample_bytes: usize = 16;
9 pub const mask_bytes: usize = 5;
10
11 pub const ApplyError = error{
12 InvalidPacketNumberLength,
13 InvalidPacketNumberOffset,
14 TruncatedPacket,
15 };
16
17 pub const Inspected = struct {
18 first_byte: u8,
19 pn_len: u3,
20 };
21
22 pub fn mask(keys: *const crypto.Keys, sample: *const [sample_bytes]u8) [mask_bytes]u8 {
23 return switch (keys.selectedSuite()) {
24 .aes_128_gcm_sha256 => aesMask(keys, sample),
25 .chacha20_poly1305_sha256 => chachaMask(keys, sample),
26 };
27 }
28
29 fn aesMask(keys: *const crypto.Keys, sample: *const [sample_bytes]u8) [mask_bytes]u8 {
30 const header_key = keys.headerKey();
31 const hp_key: [16]u8 = header_key[0..16].*;
32 const cipher = Aes128.initEnc(hp_key);
33 var block: [sample_bytes]u8 = undefined;
34 cipher.encrypt(&block, sample);
35 return block[0..mask_bytes].*;
36 }
37
38 fn chachaMask(keys: *const crypto.Keys, sample: *const [sample_bytes]u8) [mask_bytes]u8 {
39 const counter = std.mem.readInt(u32, sample[0..4], .little);
40 const nonce: [12]u8 = sample[4..16].*;
41 var zeros: [mask_bytes]u8 = @splat(0);
42 var result: [mask_bytes]u8 = undefined;
43 ChaCha20.xor(&result, &zeros, counter, keys.headerKey(), nonce);
44 return result;
45 }
46
47 fn protectedBits(first: u8) u8 {
48 return if (first & 0x80 != 0) 0x0f else 0x1f;
49 }
50
51 fn validate(packet: []const u8, pn_offset: usize, pn_len: u3) ApplyError!void {
52 if (packet.len == 0) return error.TruncatedPacket;
53 if (pn_offset == 0) return error.InvalidPacketNumberOffset;
54 if (pn_len == 0 or pn_len > 4) return error.InvalidPacketNumberLength;
55 if (pn_offset > packet.len) return error.TruncatedPacket;
56 if (pn_len > packet.len - pn_offset) return error.TruncatedPacket;
57 }
58
59 pub fn inspect(
60 protection_mask: [mask_bytes]u8,
61 packet: []const u8,
62 pn_offset: usize,
63 ) ApplyError!Inspected {
64 if (packet.len == 0) return error.TruncatedPacket;
65 if (pn_offset == 0) return error.InvalidPacketNumberOffset;
66 const first = packet[0] ^ (protection_mask[0] & protectedBits(packet[0]));
67 const pn_len = quic.packet.packetNumberLength(first);
68 try validate(packet, pn_offset, pn_len);
69 return .{ .first_byte = first, .pn_len = pn_len };
70 }
71
72 pub fn protect(
73 protection_mask: [mask_bytes]u8,
74 packet: []u8,
75 pn_offset: usize,
76 pn_len: u3,
77 ) ApplyError!void {
78 try validate(packet, pn_offset, pn_len);
79 packet[0] ^= protection_mask[0] & protectedBits(packet[0]);
80 for (0..4) |index| {
81 if (index >= pn_len) break;
82 packet[pn_offset + index] ^= protection_mask[index + 1];
83 }
84 }
85
86 pub fn unprotect(
87 protection_mask: [mask_bytes]u8,
88 packet: []u8,
89 pn_offset: usize,
90 ) ApplyError!u3 {
91 const inspected = try inspect(protection_mask, packet, pn_offset);
92 packet[0] = inspected.first_byte;
93 for (0..4) |index| {
94 if (index >= inspected.pn_len) break;
95 packet[pn_offset + index] ^= protection_mask[index + 1];
96 }
97 return inspected.pn_len;
98 }