lib/quic/src/tls/schedule.zig
daab053ee43316e1809a84551d573ddd1e5bf3d2
1 const std = @import("std");
2
3 const Hkdf = std.crypto.kdf.hkdf.HkdfSha256;
4 const Hmac = std.crypto.auth.hmac.sha2.HmacSha256;
5 const Sha256 = std.crypto.hash.sha2.Sha256;
6 const tls = std.crypto.tls;
7
8 pub const Secret = [Sha256.digest_length]u8;
9
10 pub const Application = struct {
11 client: Secret,
12 server: Secret,
13 exporter: Secret,
14 };
15
16 pub const Schedule = struct {
17 early: Secret,
18 handshake: Secret,
19 client_handshake: Secret,
20 server_handshake: Secret,
21 master: Secret,
22
23 pub fn init(shared_secret: Secret, hello_hash: Secret) Schedule {
24 const zero: Secret = @splat(0);
25 var early = Hkdf.extract(&zero, &zero);
26 defer std.crypto.secureZero(u8, &early);
27 const empty_hash = tls.emptyHash(Sha256);
28 var early_derived = tls.hkdfExpandLabel(
29 Hkdf,
30 early,
31 "derived",
32 &empty_hash,
33 @sizeOf(Secret),
34 );
35 defer std.crypto.secureZero(u8, &early_derived);
36 var handshake = Hkdf.extract(&early_derived, &shared_secret);
37 defer std.crypto.secureZero(u8, &handshake);
38 var client = derive(handshake, "c hs traffic", hello_hash);
39 defer std.crypto.secureZero(u8, &client);
40 var server = derive(handshake, "s hs traffic", hello_hash);
41 defer std.crypto.secureZero(u8, &server);
42 var handshake_derived = tls.hkdfExpandLabel(
43 Hkdf,
44 handshake,
45 "derived",
46 &empty_hash,
47 @sizeOf(Secret),
48 );
49 defer std.crypto.secureZero(u8, &handshake_derived);
50 var master = Hkdf.extract(&handshake_derived, &zero);
51 defer std.crypto.secureZero(u8, &master);
52 return .{
53 .early = early,
54 .handshake = handshake,
55 .client_handshake = client,
56 .server_handshake = server,
57 .master = master,
58 };
59 }
60
61 pub fn application(self: *const Schedule, transcript_hash: Secret) Application {
62 return .{
63 .client = derive(self.master, "c ap traffic", transcript_hash),
64 .server = derive(self.master, "s ap traffic", transcript_hash),
65 .exporter = derive(self.master, "exp master", transcript_hash),
66 };
67 }
68
69 pub fn eraseDerivationSecrets(self: *Schedule) void {
70 std.crypto.secureZero(u8, &self.early);
71 std.crypto.secureZero(u8, &self.handshake);
72 }
73
74 pub fn eraseHandshakeTrafficSecrets(self: *Schedule) void {
75 std.crypto.secureZero(u8, &self.client_handshake);
76 std.crypto.secureZero(u8, &self.server_handshake);
77 }
78
79 pub fn eraseMasterSecret(self: *Schedule) void {
80 std.crypto.secureZero(u8, &self.master);
81 }
82 };
83
84 pub fn finishedKey(traffic_secret: Secret) Secret {
85 return tls.hkdfExpandLabel(Hkdf, traffic_secret, "finished", &.{}, @sizeOf(Secret));
86 }
87
88 pub fn finishedVerifyData(traffic_secret: Secret, transcript_hash: Secret) Secret {
89 return tls.hmac(Hmac, &transcript_hash, finishedKey(traffic_secret));
90 }
91
92 fn derive(secret: Secret, label: []const u8, transcript_hash: Secret) Secret {
93 return tls.hkdfExpandLabel(Hkdf, secret, label, &transcript_hash, @sizeOf(Secret));
94 }
95
96 fn hexBytes(comptime length: usize, comptime value: []const u8) [length]u8 {
97 var result: [length]u8 = undefined;
98 _ = std.fmt.hexToBytes(&result, value) catch unreachable;
99 return result;
100 }
101
102 fn rfc8448ClientHello() [196]u8 {
103 return hexBytes(196, "010000c00303cb34ecb1e78163ba1c38c6dacb196a6dffa21a8d9912ec18a2" ++
104 "ef6283024dece7000006130113031302010000910000000b0009000006736572" ++
105 "766572ff01000100000a00140012001d00170018001901000101010201030104" ++
106 "00230000003300260024001d002099381de560e4bd43d23d8e435a7dbafeb3c0" ++
107 "6e51c13cae4d5413691e529aaf2c002b0003020304000d0020001e0403050306" ++
108 "03020308040805080604010501060102010402050206020202002d0002010100" ++
109 "1c00024001");
110 }
111
112 fn rfc8448ServerHello() [90]u8 {
113 return hexBytes(90, "020000560303a6af06a4121860dc5e6e60249cd34c95930c8ac5cb1434dac155" ++
114 "772ed3e2692800130100002e00330024001d0020c9828876112095fe66762bdb" ++
115 "f7c672e156d6cc253b833df1dd69b1b04e751f0f002b00020304");
116 }
117
118 fn rfc8448HelloHash() Secret {
119 var transcript = Sha256.init(.{});
120 transcript.update(&rfc8448ClientHello());
121 transcript.update(&rfc8448ServerHello());
122 var digest: Secret = undefined;
123 transcript.final(&digest);
124 return digest;
125 }
126
127 test "RFC 8448 section 3 X25519 and TLS 1.3 handshake schedule" {
128 const client_private = hexBytes(
129 32,
130 "49af42ba7f7994852d713ef2784bcbcaa7911de26adc5642cb634540e7ea5005",
131 );
132 const server_private = hexBytes(
133 32,
134 "b1580eeadf6dd589b8ef4f2d5652578cc810e9980191ec8d058308cea216a21e",
135 );
136 const client_public = try std.crypto.dh.X25519.recoverPublicKey(client_private);
137 const server_public = try std.crypto.dh.X25519.recoverPublicKey(server_private);
138 const shared = try std.crypto.dh.X25519.scalarmult(server_private, client_public);
139 const client_shared = try std.crypto.dh.X25519.scalarmult(client_private, server_public);
140 try std.testing.expectEqual(shared, client_shared);
141 try std.testing.expectEqual(
142 hexBytes(32, "8bd4054fb55b9d63fdfbacf9f04b9f0d35e6d63f537563efd46272900f89492d"),
143 shared,
144 );
145 const hello_hash = rfc8448HelloHash();
146 try expectSecret(
147 "860c06edc07858ee8e78f0e7428c58edd6b43f2ca3e6e95f02ed063cf0e1cad8",
148 hello_hash,
149 );
150 const schedule = Schedule.init(shared, hello_hash);
151 try expectSecret(
152 "33ad0a1c607ec03b09e6cd9893680ce210adf300aa1f2660e1b22e10f170f92a",
153 schedule.early,
154 );
155 try expectSecret(
156 "1dc826e93606aa6fdc0aadc12f741b01046aa6b99f691ed221a9f0ca043fbeac",
157 schedule.handshake,
158 );
159 try expectSecret(
160 "b3eddb126e067f35a780b3abf45e2d8f3b1a950738f52e9600746a0e27a55a21",
161 schedule.client_handshake,
162 );
163 try expectSecret(
164 "b67b7d690cc16c4e75e54213cb2d37b4e9c912bcded9105d42befd59d391ad38",
165 schedule.server_handshake,
166 );
167 try expectSecret(
168 "18df06843d13a08bf2a449844c5f8a478001bc4d4c627984d5a41da8d0402919",
169 schedule.master,
170 );
171 }
172
173 test "RFC 8448 section 3 application schedule and server Finished" {
174 const master = hexBytes(
175 32,
176 "18df06843d13a08bf2a449844c5f8a478001bc4d4c627984d5a41da8d0402919",
177 );
178 const base = Schedule{
179 .early = undefined,
180 .handshake = undefined,
181 .client_handshake = undefined,
182 .server_handshake = hexBytes(
183 32,
184 "b67b7d690cc16c4e75e54213cb2d37b4e9c912bcded9105d42befd59d391ad38",
185 ),
186 .master = master,
187 };
188 const application = base.application(hexBytes(
189 32,
190 "9608102a0f1ccc6db6250b7b7e417b1a000eaada3daae4777a7686c9ff83df13",
191 ));
192 try expectSecret(
193 "9e40646ce79a7f9dc05af8889bce6552875afa0b06df0087f792ebb7c17504a5",
194 application.client,
195 );
196 try expectSecret(
197 "a11af9f05531f856ad47116b45a950328204b4f44bfb6b3a4b4f1f3fcb631643",
198 application.server,
199 );
200 try expectSecret(
201 "fe22f881176eda18eb8f44529e6792c50c9a3f89452f68d8ae311b4309d3cf50",
202 application.exporter,
203 );
204 const verify_data = finishedVerifyData(base.server_handshake, hexBytes(
205 32,
206 "edb7725fa7a3473b031ec8ef65a2485493900138a2b91291407d7951a06110ed",
207 ));
208 try expectSecret(
209 "9b9b141d906337fbd2cbdce71df4deda4ab42c309572cb7fffee5454b78f0718",
210 verify_data,
211 );
212 }
213
214 fn expectSecret(comptime expected: []const u8, actual: Secret) !void {
215 try std.testing.expectEqual(hexBytes(32, expected), actual);
216 }