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 }