lib/quic/src/crypto/retry.zig

daab053ee43316e1809a84551d573ddd1e5bf3d2

 1 const std = @import("std");
 2 const quic = @import("../root.zig");
 3 
 4 const Aes128 = std.crypto.core.aes.Aes128;
 5 const Ghash = std.crypto.onetimeauth.Ghash;
 6 
 7 pub const tag_bytes: usize = Ghash.mac_length;
 8 pub const connection_id_bytes_max: usize = 20;
 9 pub const pseudo_bytes_max: usize = quic.crypto.packet.packet_bytes_max - tag_bytes;
10 
11 /// The 16-byte AES-128 key RFC 9001 section 5.8 fixes for QUIC version 1 Retry integrity tags. A
12 /// caller checking a Retry integrity tag against another implementation needs the key that
13 /// implementation used. Every endpoint uses it, so the tag shows the sender saw the original
14 /// destination connection ID. It carries no proof of the sender's identity.
15 pub const v1_key = [_]u8{
16     0xbe, 0x0c, 0x69, 0x0b, 0x9f, 0x66, 0x57, 0x5a,
17     0x1d, 0x76, 0x6b, 0x54, 0xe3, 0x68, 0xc8, 0x4e,
18 };
19 /// The 12-byte nonce RFC 9001 section 5.8 fixes for QUIC version 1 Retry integrity tags. It pairs
20 /// with the key for the same check. The tag computation uses it with the fixed key over the
21 /// pseudo-packet.
22 pub const v1_nonce = [_]u8{
23     0x46, 0x15, 0x99, 0xd3, 0x5d, 0x63,
24     0x2b, 0xf2, 0x23, 0x98, 0x25, 0xbb,
25 };
26 
27 pub const TagError = error{
28     ConnectionIdTooLong,
29     PacketTooLarge,
30 };
31 
32 fn authenticate(prefix: []const u8, retry_pseudo: []const u8) [tag_bytes]u8 {
33     const aad_len = prefix.len + retry_pseudo.len;
34     const cipher = Aes128.initEnc(v1_key);
35     var hash_key: [Ghash.key_length]u8 = undefined;
36     cipher.encrypt(&hash_key, &@splat(0));
37     const block_count = @divCeil(aad_len, Ghash.block_length) + 1;
38     var mac = Ghash.initForBlockCount(&hash_key, block_count);
39     mac.update(prefix);
40     mac.update(retry_pseudo);
41     mac.pad();
42     var lengths: [Ghash.block_length]u8 = @splat(0);
43     std.mem.writeInt(u64, lengths[0..8], @as(u64, aad_len) * 8, .big);
44     mac.update(&lengths);
45     var result: [tag_bytes]u8 = undefined;
46     mac.final(&result);
47     var counter: [Aes128.block.block_length]u8 = @splat(0);
48     counter[0..v1_nonce.len].* = v1_nonce;
49     std.mem.writeInt(u32, counter[v1_nonce.len..][0..4], 1, .big);
50     var encrypted_counter: [Aes128.block.block_length]u8 = undefined;
51     cipher.encrypt(&encrypted_counter, &counter);
52     for (0..tag_bytes) |index| result[index] ^= encrypted_counter[index];
53     return result;
54 }
55 
56 pub fn tag(odcid: []const u8, retry_pseudo: []const u8) TagError![tag_bytes]u8 {
57     if (odcid.len > connection_id_bytes_max) return error.ConnectionIdTooLong;
58     if (retry_pseudo.len > pseudo_bytes_max) return error.PacketTooLarge;
59     var prefix: [1 + connection_id_bytes_max]u8 = undefined;
60     prefix[0] = @intCast(odcid.len);
61     @memcpy(prefix[1..][0..odcid.len], odcid);
62     return authenticate(prefix[0 .. 1 + odcid.len], retry_pseudo);
63 }
64 
65 pub fn verify(odcid: []const u8, retry_packet: []const u8) bool {
66     if (odcid.len > connection_id_bytes_max) return false;
67     if (retry_packet.len <= tag_bytes) return false;
68     if (retry_packet.len > quic.crypto.packet.packet_bytes_max) return false;
69     const tag_offset = retry_packet.len - tag_bytes;
70     const expected = tag(odcid, retry_packet[0..tag_offset]) catch return false;
71     const actual: [tag_bytes]u8 = retry_packet[tag_offset..][0..tag_bytes].*;
72     return std.crypto.timing_safe.eql([tag_bytes]u8, expected, actual);
73 }