lib/quic/src/connection/keys.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2 const quic = @import("../root.zig");
3
4 pub const State = struct {
5 storage: *quic.connection.Storage,
6 initial_write: ?quic.crypto.Keys = null,
7 initial_read: ?quic.crypto.Keys = null,
8 handshake_write: ?quic.crypto.Keys = null,
9 handshake_read: ?quic.crypto.Keys = null,
10 application_write: ?quic.crypto.Keys = null,
11 application_read_previous: ?quic.crypto.Keys = null,
12 application_read_current: ?quic.crypto.Keys = null,
13 application_read_next: ?quic.crypto.Keys = null,
14 application_write_secret: quic.crypto.Secret = @splat(0),
15 application_read_secret: quic.crypto.Secret = @splat(0),
16 application_read_next_secret: quic.crypto.Secret = @splat(0),
17 send_phase: bool = false,
18 receive_phase: bool = false,
19 previous_read_valid: bool = false,
20 first_current_read_packet: u62 = 0,
21 receive_update_acknowledged: bool = true,
22 previous_read_largest: ?u62 = null,
23 update_requested: bool = false,
24 update_awaiting_ack: bool = false,
25 update_first_packet: ?u62 = null,
26
27 pub const InstallError = quic.crypto.Keys.InitError || error{UnsupportedCipherSuite};
28
29 pub fn init(storage: *quic.connection.Storage) State {
30 return .{ .storage = storage };
31 }
32
33 pub fn installInitial(
34 self: *State,
35 role: quic.tls.Role,
36 destination: []const u8,
37 ) InstallError!void {
38 std.debug.assert(self.initial_write == null);
39 std.debug.assert(self.initial_read == null);
40 const secrets = quic.crypto.initial.secrets(destination);
41 const write_secret = if (role == .client) secrets.client else secrets.server;
42 const read_secret = if (role == .client) secrets.server else secrets.client;
43 self.initial_write = try quic.crypto.Keys.derive(
44 self.storage.keys(.initial_write),
45 .aes_128_gcm_sha256,
46 write_secret,
47 );
48 self.initial_read = try quic.crypto.Keys.derive(
49 self.storage.keys(.initial_read),
50 .aes_128_gcm_sha256,
51 read_secret,
52 );
53 }
54
55 pub fn installTls(self: *State, engine: *const quic.tls.Engine) InstallError!void {
56 const suite = try selectedSuite(engine);
57 if (self.handshake_write == null) {
58 if (engine.secret(.handshake, .write)) |secret| {
59 self.handshake_write = try quic.crypto.Keys.derive(
60 self.storage.keys(.handshake_write),
61 suite,
62 secret,
63 );
64 }
65 }
66 if (self.handshake_read == null) {
67 if (engine.secret(.handshake, .read)) |secret| {
68 self.handshake_read = try quic.crypto.Keys.derive(
69 self.storage.keys(.handshake_read),
70 suite,
71 secret,
72 );
73 }
74 }
75 try self.installApplicationWrite(engine, suite);
76 try self.installApplicationRead(engine, suite);
77 }
78
79 fn installApplicationWrite(
80 self: *State,
81 engine: *const quic.tls.Engine,
82 suite: quic.crypto.Suite,
83 ) InstallError!void {
84 if (self.application_write != null) return;
85 const secret = engine.secret(.one_rtt, .write) orelse return;
86 self.application_write_secret = secret;
87 self.application_write = try quic.crypto.Keys.derive(
88 self.storage.keys(.application_write),
89 suite,
90 secret,
91 );
92 }
93
94 fn installApplicationRead(
95 self: *State,
96 engine: *const quic.tls.Engine,
97 suite: quic.crypto.Suite,
98 ) InstallError!void {
99 if (self.application_read_current != null) return;
100 const secret = engine.secret(.one_rtt, .read) orelse return;
101 self.application_read_secret = secret;
102 self.application_read_next_secret = quic.crypto.Keys.next(suite, secret);
103 self.application_read_previous = try quic.crypto.Keys.derive(
104 self.storage.keys(.application_read_previous),
105 suite,
106 secret,
107 );
108 self.application_read_current = try quic.crypto.Keys.derive(
109 self.storage.keys(.application_read_current),
110 suite,
111 secret,
112 );
113 self.application_read_next = try quic.crypto.Keys.derive(
114 self.storage.keys(.application_read_next),
115 suite,
116 secret,
117 );
118 self.application_read_next.?.update(self.application_read_next_secret);
119 }
120
121 pub fn write(self: *State, kind: quic.connection.SpaceKind) ?*quic.crypto.Keys {
122 return switch (kind) {
123 .initial => if (self.initial_write) |*keys| keys else null,
124 .handshake => if (self.handshake_write) |*keys| keys else null,
125 .application => if (self.application_write) |*keys| keys else null,
126 };
127 }
128
129 pub fn read(self: *State, kind: quic.connection.SpaceKind) ?*quic.crypto.Keys {
130 return switch (kind) {
131 .initial => if (self.initial_read) |*keys| keys else null,
132 .handshake => if (self.handshake_read) |*keys| keys else null,
133 .application => if (self.application_read_current) |*keys| keys else null,
134 };
135 }
136
137 pub fn selectApplicationRead(
138 self: *State,
139 header: quic.crypto.packet.Header,
140 ) ?*quic.crypto.Keys {
141 const phase = header.key_phase orelse return null;
142 if (phase == self.receive_phase) {
143 return if (self.application_read_current) |*keys| keys else null;
144 }
145 if (self.previous_read_valid and header.packet_number < self.first_current_read_packet) {
146 return if (self.application_read_previous) |*keys| keys else null;
147 }
148 return if (self.application_read_next) |*keys| keys else null;
149 }
150
151 pub fn promoteApplicationRead(
152 self: *State,
153 packet_number: u62,
154 next_send: u62,
155 ) error{ConsecutiveUpdate}!void {
156 std.debug.assert(self.application_read_previous != null);
157 std.debug.assert(self.application_read_current != null);
158 std.debug.assert(self.application_read_next != null);
159 const peer_initiated = self.send_phase == self.receive_phase;
160 if (peer_initiated and !self.receive_update_acknowledged) return error.ConsecutiveUpdate;
161 std.mem.swap(
162 quic.crypto.Keys,
163 &self.application_read_previous.?,
164 &self.application_read_current.?,
165 );
166 std.mem.swap(
167 quic.crypto.Keys,
168 &self.application_read_current.?,
169 &self.application_read_next.?,
170 );
171 self.application_read_secret = self.application_read_next_secret;
172 const suite = self.application_read_current.?.selectedSuite();
173 self.application_read_next_secret = quic.crypto.Keys.next(
174 suite,
175 self.application_read_secret,
176 );
177 self.application_read_next.?.update(self.application_read_next_secret);
178 self.previous_read_valid = true;
179 self.receive_phase = !self.receive_phase;
180 self.first_current_read_packet = packet_number;
181 self.previous_read_largest = null;
182 self.receive_update_acknowledged = false;
183 if (peer_initiated) self.updateWrite(next_send);
184 std.debug.assert(self.send_phase == self.receive_phase);
185 }
186
187 pub fn requestUpdate(self: *State) void {
188 self.update_requested = true;
189 }
190
191 pub fn prepareSend(self: *State, confirmed: bool, next_packet: u62) void {
192 const keys = if (self.application_write) |*value| value else return;
193 if (!confirmed) return;
194 if (!self.update_requested and !keys.needsUpdate()) return;
195 if (self.update_awaiting_ack) return;
196 self.updateWrite(next_packet);
197 self.update_requested = false;
198 }
199
200 fn updateWrite(self: *State, next_packet: u62) void {
201 const keys = &self.application_write.?;
202 self.application_write_secret = quic.crypto.Keys.next(
203 keys.selectedSuite(),
204 self.application_write_secret,
205 );
206 keys.update(self.application_write_secret);
207 self.send_phase = !self.send_phase;
208 self.update_awaiting_ack = true;
209 self.update_first_packet = next_packet;
210 }
211
212 /// Frees the state that holds a further rotation back, `update_awaiting_ack`, once the peer has
213 /// acknowledged something protected by the write keys now in use. The connection hands every
214 /// ACK frame that arrives in the application space to this function, so a second key update can
215 /// follow the first, because `prepareSend` tests that field before it rotates the write keys
216 /// again. An ACK whose largest number falls under the first packet protected by those keys
217 /// leaves the state as it was. The call returns at once when write key rotation is already
218 /// complete.
219 pub fn observeAck(self: *State, value: quic.frame.Ack) void {
220 const first = self.update_first_packet orelse return;
221 std.debug.assert(self.update_awaiting_ack);
222 if (value.largest < first) return;
223 self.update_awaiting_ack = false;
224 self.update_first_packet = null;
225 }
226
227 /// Marks the peer's most recent phase change as covered by an acknowledgment this endpoint has
228 /// now sent. The connection calls this function after it puts an ACK into a 1-RTT packet, so a
229 /// peer-driven key update can be followed by another, because `promoteApplicationRead` reads
230 /// that mark and turns a second peer-driven rotation away when the mark is missing, answering
231 /// `ConsecutiveUpdate`. The number handed in sits at or above the first packet of the phase
232 /// being read.
233 pub fn observeAckSent(self: *State, largest_received: u62) void {
234 std.debug.assert(largest_received >= self.first_current_read_packet);
235 self.receive_update_acknowledged = true;
236 }
237
238 /// Pulls the phase boundary down to the number of a packet that arrived behind its neighbors.
239 /// That boundary is `first_current_read_packet`, the lowest number the current keys have been
240 /// seen to open. The connection reports every 1-RTT packet the current read keys opened to this
241 /// function, so the boundary between the two key generations follows the peer's numbering,
242 /// which the order of arrival can disturb. The call answers `PreviousKeysAboveCurrent` when the
243 /// older keys have opened something higher since the last rotation, because a newer generation
244 /// covering a lower number would break the order the peer promised. A number the older keys
245 /// handled already leaves the boundary in place, which holds `previous_read_largest` strictly
246 /// under the boundary.
247 pub fn observeCurrentRead(
248 self: *State,
249 packet_number: u62,
250 ) error{PreviousKeysAboveCurrent}!void {
251 std.debug.assert(self.application_read_current != null);
252 if (self.previous_read_largest) |largest| {
253 std.debug.assert(largest < self.first_current_read_packet);
254 if (packet_number < largest) return error.PreviousKeysAboveCurrent;
255 if (packet_number == largest) return;
256 }
257 if (packet_number >= self.first_current_read_packet) return;
258 self.first_current_read_packet = packet_number;
259 }
260
261 /// Keeps the highest number the older keys have opened since the last rotation. The connection
262 /// reports every 1-RTT packet the older read keys opened to this function, so
263 /// `observeCurrentRead` compares a later packet against that number to separate a tolerable
264 /// reordering from one it refuses. The number handed in falls under the phase boundary.
265 pub fn observePreviousRead(self: *State, packet_number: u62) void {
266 std.debug.assert(self.previous_read_valid);
267 std.debug.assert(packet_number < self.first_current_read_packet);
268 const largest = self.previous_read_largest orelse packet_number;
269 self.previous_read_largest = @max(largest, packet_number);
270 }
271
272 pub fn discardInitial(self: *State) void {
273 discard(&self.initial_write);
274 discard(&self.initial_read);
275 }
276
277 pub fn discardHandshake(self: *State) void {
278 discard(&self.handshake_write);
279 discard(&self.handshake_read);
280 }
281
282 pub fn deinit(self: *State) void {
283 discard(&self.initial_write);
284 discard(&self.initial_read);
285 discard(&self.handshake_write);
286 discard(&self.handshake_read);
287 discard(&self.application_write);
288 discard(&self.application_read_previous);
289 discard(&self.application_read_current);
290 discard(&self.application_read_next);
291 std.crypto.secureZero(u8, &self.application_write_secret);
292 std.crypto.secureZero(u8, &self.application_read_secret);
293 std.crypto.secureZero(u8, &self.application_read_next_secret);
294 }
295 };
296
297 fn selectedSuite(engine: *const quic.tls.Engine) State.InstallError!quic.crypto.Suite {
298 const suite = engine.cipherSuite() orelse return error.UnsupportedCipherSuite;
299 return switch (suite) {
300 .AES_128_GCM_SHA256 => .aes_128_gcm_sha256,
301 .CHACHA20_POLY1305_SHA256 => .chacha20_poly1305_sha256,
302 else => error.UnsupportedCipherSuite,
303 };
304 }
305
306 fn discard(keys: *?quic.crypto.Keys) void {
307 if (keys.*) |*value| _ = value.deinit();
308 keys.* = null;
309 }