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 }