authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-12-16 18:06:00-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-02 16:57:15-07:00
log942b5b468fe0a517618b62f0260d3a32c7cc642e
tree02358931e04082da37565ff53d4545da812d141b
parent93ab8be8d8464452af6d2e686e91be2c1da98979

std.crypto.tls: implement the rest of the cipher suites

Also: * Use KeyPair.create() function * Don't bother with CCM

3 files changed, 79 insertions(+), 125 deletions(-)

lib/std/crypto/tls.zig+33-47
...@@ -211,17 +211,20 @@ pub const NamedGroup = enum(u16) {...@@ -211,17 +211,20 @@ pub const NamedGroup = enum(u16) {
211};211};
212212
213pub const CipherSuite = enum(u16) {213pub const CipherSuite = enum(u16) {
214 TLS_AES_128_GCM_SHA256 = 0x1301,214 AES_128_GCM_SHA256 = 0x1301,
215 TLS_AES_256_GCM_SHA384 = 0x1302,215 AES_256_GCM_SHA384 = 0x1302,
216 TLS_CHACHA20_POLY1305_SHA256 = 0x1303,216 CHACHA20_POLY1305_SHA256 = 0x1303,
217 TLS_AES_128_CCM_SHA256 = 0x1304,217 AES_128_CCM_SHA256 = 0x1304,
218 TLS_AES_128_CCM_8_SHA256 = 0x1305,218 AES_128_CCM_8_SHA256 = 0x1305,
219 AEGIS_256_SHA384 = 0x1306,
220 AEGIS_128L_SHA256 = 0x1307,
221 _,
219};222};
220223
221pub const CipherParams = union(CipherSuite) {224pub fn CipherParamsT(comptime AeadType: type, comptime HashType: type) type {
222 TLS_AES_128_GCM_SHA256: struct {225 return struct {
223 pub const AEAD = crypto.aead.aes_gcm.Aes128Gcm;226 pub const AEAD = AeadType;
224 pub const Hash = crypto.hash.sha2.Sha256;227 pub const Hash = HashType;
225 pub const Hmac = crypto.auth.hmac.Hmac(Hash);228 pub const Hmac = crypto.auth.hmac.Hmac(Hash);
226 pub const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac);229 pub const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac);
227230
...@@ -234,33 +237,21 @@ pub const CipherParams = union(CipherSuite) {...@@ -234,33 +237,21 @@ pub const CipherParams = union(CipherSuite) {
234 client_handshake_iv: [AEAD.nonce_length]u8,237 client_handshake_iv: [AEAD.nonce_length]u8,
235 server_handshake_iv: [AEAD.nonce_length]u8,238 server_handshake_iv: [AEAD.nonce_length]u8,
236 transcript_hash: Hash,239 transcript_hash: Hash,
237 },240 };
238 TLS_AES_256_GCM_SHA384: struct {241}
239 pub const AEAD = crypto.aead.aes_gcm.Aes256Gcm;
240 pub const Hash = crypto.hash.sha2.Sha384;
241 pub const Hmac = crypto.auth.hmac.Hmac(Hash);
242 pub const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac);
243242
244 handshake_secret: [Hkdf.prk_length]u8,243pub const CipherParams = union(enum) {
245 master_secret: [Hkdf.prk_length]u8,244 AES_128_GCM_SHA256: CipherParamsT(crypto.aead.aes_gcm.Aes128Gcm, crypto.hash.sha2.Sha256),
246 client_handshake_key: [AEAD.key_length]u8,245 AES_256_GCM_SHA384: CipherParamsT(crypto.aead.aes_gcm.Aes256Gcm, crypto.hash.sha2.Sha384),
247 server_handshake_key: [AEAD.key_length]u8,246 CHACHA20_POLY1305_SHA256: CipherParamsT(crypto.aead.chacha_poly.ChaCha20Poly1305, crypto.hash.sha2.Sha256),
248 client_finished_key: [Hmac.key_length]u8,247 AEGIS_256_SHA384: CipherParamsT(crypto.aead.aegis.Aegis256, crypto.hash.sha2.Sha384),
249 server_finished_key: [Hmac.key_length]u8,248 AEGIS_128L_SHA256: CipherParamsT(crypto.aead.aegis.Aegis128L, crypto.hash.sha2.Sha256),
250 client_handshake_iv: [AEAD.nonce_length]u8,
251 server_handshake_iv: [AEAD.nonce_length]u8,
252 transcript_hash: Hash,
253 },
254 TLS_CHACHA20_POLY1305_SHA256: void,
255 TLS_AES_128_CCM_SHA256: void,
256 TLS_AES_128_CCM_8_SHA256: void,
257};249};
258250
259/// Encryption parameters for application traffic.251pub fn ApplicationCipherT(comptime AeadType: type, comptime HashType: type) type {
260pub const ApplicationCipher = union(CipherSuite) {252 return struct {
261 TLS_AES_128_GCM_SHA256: struct {253 pub const AEAD = AeadType;
262 pub const AEAD = crypto.aead.aes_gcm.Aes128Gcm;254 pub const Hash = HashType;
263 pub const Hash = crypto.hash.sha2.Sha256;
264 pub const Hmac = crypto.auth.hmac.Hmac(Hash);255 pub const Hmac = crypto.auth.hmac.Hmac(Hash);
265 pub const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac);256 pub const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac);
266257
...@@ -268,21 +259,16 @@ pub const ApplicationCipher = union(CipherSuite) {...@@ -268,21 +259,16 @@ pub const ApplicationCipher = union(CipherSuite) {
268 server_key: [AEAD.key_length]u8,259 server_key: [AEAD.key_length]u8,
269 client_iv: [AEAD.nonce_length]u8,260 client_iv: [AEAD.nonce_length]u8,
270 server_iv: [AEAD.nonce_length]u8,261 server_iv: [AEAD.nonce_length]u8,
271 },262 };
272 TLS_AES_256_GCM_SHA384: struct {263}
273 pub const AEAD = crypto.aead.aes_gcm.Aes256Gcm;
274 pub const Hash = crypto.hash.sha2.Sha384;
275 pub const Hmac = crypto.auth.hmac.Hmac(Hash);
276 pub const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac);
277264
278 client_key: [AEAD.key_length]u8,265/// Encryption parameters for application traffic.
279 server_key: [AEAD.key_length]u8,266pub const ApplicationCipher = union(enum) {
280 client_iv: [AEAD.nonce_length]u8,267 AES_128_GCM_SHA256: ApplicationCipherT(crypto.aead.aes_gcm.Aes128Gcm, crypto.hash.sha2.Sha256),
281 server_iv: [AEAD.nonce_length]u8,268 AES_256_GCM_SHA384: ApplicationCipherT(crypto.aead.aes_gcm.Aes256Gcm, crypto.hash.sha2.Sha384),
282 },269 CHACHA20_POLY1305_SHA256: ApplicationCipherT(crypto.aead.chacha_poly.ChaCha20Poly1305, crypto.hash.sha2.Sha256),
283 TLS_CHACHA20_POLY1305_SHA256: void,270 AEGIS_256_SHA384: ApplicationCipherT(crypto.aead.aegis.Aegis256, crypto.hash.sha2.Sha384),
284 TLS_AES_128_CCM_SHA256: void,271 AEGIS_128L_SHA256: ApplicationCipherT(crypto.aead.aegis.Aegis128L, crypto.hash.sha2.Sha256),
285 TLS_AES_128_CCM_8_SHA256: void,
286};272};
287273
288pub fn hkdfExpandLabel(274pub fn hkdfExpandLabel(
lib/std/crypto/tls/Client.zig+38-74
...@@ -23,27 +23,27 @@ partially_read_buffer: [tls.max_ciphertext_record_len]u8,...@@ -23,27 +23,27 @@ partially_read_buffer: [tls.max_ciphertext_record_len]u8,
23partially_read_len: u15,23partially_read_len: u15,
24eof: bool,24eof: bool,
2525
26const cipher_suites = blk: {26// Measurement taken with 0.11.0-dev.810+c2f5848fe
27 const fields = @typeInfo(CipherSuite).Enum.fields;27// on x86_64-linux Intel(R) Core(TM) i9-9980HK CPU @ 2.40GHz:
28 var result: [(fields.len + 1) * 2]u8 = undefined;28// zig run .lib/std/crypto/benchmark.zig -OReleaseFast
29 mem.writeIntBig(u16, result[0..2], result.len - 2);29// aegis-128l: 15382 MiB/s
30 for (fields) |field, i| {30// aegis-256: 9553 MiB/s
31 const int = @enumToInt(@field(CipherSuite, field.name));31// aes128-gcm: 3721 MiB/s
32 result[(i + 1) * 2] = @truncate(u8, int >> 8);32// aes256-gcm: 3010 MiB/s
33 result[(i + 1) * 2 + 1] = @truncate(u8, int);33// chacha20Poly1305: 597 MiB/s
34 }34
35 break :blk result;35const cipher_suites =
36};36 int2(@enumToInt(tls.CipherSuite.AEGIS_128L_SHA256)) ++
37 int2(@enumToInt(tls.CipherSuite.AEGIS_256_SHA384)) ++
38 int2(@enumToInt(tls.CipherSuite.AES_128_GCM_SHA256)) ++
39 int2(@enumToInt(tls.CipherSuite.AES_256_GCM_SHA384)) ++
40 int2(@enumToInt(tls.CipherSuite.CHACHA20_POLY1305_SHA256));
3741
38/// `host` is only borrowed during this function call.42/// `host` is only borrowed during this function call.
39pub fn init(stream: net.Stream, host: []const u8) !Client {43pub fn init(stream: net.Stream, host: []const u8) !Client {
40 var x25519_priv_key: [32]u8 = undefined;44 const kp = crypto.dh.X25519.KeyPair.create(null) catch |err| switch (err) {
41 crypto.random.bytes(&x25519_priv_key);45 // Only possible to happen if the private key is all zeroes.
42 const x25519_pub_key = crypto.dh.X25519.recoverPublicKey(x25519_priv_key) catch |err| {46 error.IdentityElement => return error.InsufficientEntropy,
43 switch (err) {
44 // Only possible to happen if the private key is all zeroes.
45 error.IdentityElement => return error.InsufficientEntropy,
46 }
47 };47 };
4848
49 // random (u32)49 // random (u32)
...@@ -98,7 +98,7 @@ pub fn init(stream: net.Stream, host: []const u8) !Client {...@@ -98,7 +98,7 @@ pub fn init(stream: net.Stream, host: []const u8) !Client {
98 0, 36, // byte length of client_shares98 0, 36, // byte length of client_shares
99 0x00, 0x1D, // NamedGroup.x2551999 0x00, 0x1D, // NamedGroup.x25519
100 0, 32, // byte length of key_exchange100 0, 32, // byte length of key_exchange
101 } ++ x25519_pub_key ++ [_]u8{101 } ++ kp.public_key ++ [_]u8{
102102
103 // Extension: server_name103 // Extension: server_name
104 0, 0, // ExtensionType.server_name104 0, 0, // ExtensionType.server_name
...@@ -120,7 +120,9 @@ pub fn init(stream: net.Stream, host: []const u8) !Client {...@@ -120,7 +120,9 @@ pub fn init(stream: net.Stream, host: []const u8) !Client {
120120
121 // ClientHello121 // ClientHello
122 0x03, 0x03, // legacy_version122 0x03, 0x03, // legacy_version
123 } ++ rand_buf ++ [1]u8{0} ++ cipher_suites ++ [_]u8{123 } ++ rand_buf ++ [1]u8{0} ++
124 int2(cipher_suites.len) ++ cipher_suites ++
125 [_]u8{
124 0x01, 0x00, // legacy_compression_methods126 0x01, 0x00, // legacy_compression_methods
125 } ++ extensions_header;127 } ++ extensions_header;
126128
...@@ -191,9 +193,8 @@ pub fn init(stream: net.Stream, host: []const u8) !Client {...@@ -191,9 +193,8 @@ pub fn init(stream: net.Stream, host: []const u8) !Client {
191 const legacy_session_id_echo_len = hello[34];193 const legacy_session_id_echo_len = hello[34];
192 if (legacy_session_id_echo_len != 0) return error.TlsIllegalParameter;194 if (legacy_session_id_echo_len != 0) return error.TlsIllegalParameter;
193 const cipher_suite_int = mem.readIntBig(u16, hello[35..37]);195 const cipher_suite_int = mem.readIntBig(u16, hello[35..37]);
194 const cipher_suite_tag = std.meta.intToEnum(CipherSuite, cipher_suite_int) catch196 const cipher_suite_tag = @intToEnum(CipherSuite, cipher_suite_int);
195 return error.TlsIllegalParameter;197 std.debug.print("server wants cipher suite {any}\n", .{cipher_suite_tag});
196 std.debug.print("server wants cipher suite {s}\n", .{@tagName(cipher_suite_tag)});
197 const legacy_compression_method = hello[37];198 const legacy_compression_method = hello[37];
198 _ = legacy_compression_method;199 _ = legacy_compression_method;
199 const extensions_size = mem.readIntBig(u16, hello[38..40]);200 const extensions_size = mem.readIntBig(u16, hello[38..40]);
...@@ -250,13 +251,18 @@ pub fn init(stream: net.Stream, host: []const u8) !Client {...@@ -250,13 +251,18 @@ pub fn init(stream: net.Stream, host: []const u8) !Client {
250 }251 }
251252
252 const shared_key = crypto.dh.X25519.scalarmult(253 const shared_key = crypto.dh.X25519.scalarmult(
253 x25519_priv_key,254 kp.secret_key,
254 x25519_server_pub_key.*,255 x25519_server_pub_key.*,
255 ) catch return error.TlsDecryptFailure;256 ) catch return error.TlsDecryptFailure;
256257
257 switch (cipher_suite_tag) {258 switch (cipher_suite_tag) {
258 inline .TLS_AES_128_GCM_SHA256, .TLS_AES_256_GCM_SHA384 => |tag| {259 inline .AES_128_GCM_SHA256,
259 const P = std.meta.TagPayload(CipherParams, tag);260 .AES_256_GCM_SHA384,
261 .CHACHA20_POLY1305_SHA256,
262 .AEGIS_256_SHA384,
263 .AEGIS_128L_SHA256,
264 => |tag| {
265 const P = std.meta.TagPayloadByName(CipherParams, @tagName(tag));
260 cipher_params = @unionInit(CipherParams, @tagName(tag), .{266 cipher_params = @unionInit(CipherParams, @tagName(tag), .{
261 .handshake_secret = undefined,267 .handshake_secret = undefined,
262 .master_secret = undefined,268 .master_secret = undefined,
...@@ -301,14 +307,8 @@ pub fn init(stream: net.Stream, host: []const u8) !Client {...@@ -301,14 +307,8 @@ pub fn init(stream: net.Stream, host: []const u8) !Client {
301 // std.fmt.fmtSliceHexLower(&p.server_handshake_iv),307 // std.fmt.fmtSliceHexLower(&p.server_handshake_iv),
302 //});308 //});
303 },309 },
304 .TLS_CHACHA20_POLY1305_SHA256 => {310 else => {
305 @panic("TODO");311 return error.TlsIllegalParameter;
306 },
307 .TLS_AES_128_CCM_SHA256 => {
308 @panic("TODO");
309 },
310 .TLS_AES_128_CCM_8_SHA256 => {
311 @panic("TODO");
312 },312 },
313 }313 }
314 },314 },
...@@ -347,7 +347,7 @@ pub fn init(stream: net.Stream, host: []const u8) !Client {...@@ -347,7 +347,7 @@ pub fn init(stream: net.Stream, host: []const u8) !Client {
347 .application_data => {347 .application_data => {
348 var cleartext_buf: [8000]u8 = undefined;348 var cleartext_buf: [8000]u8 = undefined;
349 const cleartext = switch (cipher_params) {349 const cleartext = switch (cipher_params) {
350 inline .TLS_AES_128_GCM_SHA256, .TLS_AES_256_GCM_SHA384 => |*p| c: {350 inline else => |*p| c: {
351 const P = @TypeOf(p.*);351 const P = @TypeOf(p.*);
352 const ciphertext_len = record_size - P.AEAD.tag_length;352 const ciphertext_len = record_size - P.AEAD.tag_length;
353 const ciphertext = handshake_buf[i..][0..ciphertext_len];353 const ciphertext = handshake_buf[i..][0..ciphertext_len];
...@@ -366,15 +366,6 @@ pub fn init(stream: net.Stream, host: []const u8) !Client {...@@ -366,15 +366,6 @@ pub fn init(stream: net.Stream, host: []const u8) !Client {
366 p.transcript_hash.update(cleartext[0 .. cleartext.len - 1]);366 p.transcript_hash.update(cleartext[0 .. cleartext.len - 1]);
367 break :c cleartext;367 break :c cleartext;
368 },368 },
369 .TLS_CHACHA20_POLY1305_SHA256 => {
370 @panic("TODO");
371 },
372 .TLS_AES_128_CCM_SHA256 => {
373 @panic("TODO");
374 },
375 .TLS_AES_128_CCM_8_SHA256 => {
376 @panic("TODO");
377 },
378 };369 };
379370
380 const inner_ct = @intToEnum(ContentType, cleartext[cleartext.len - 1]);371 const inner_ct = @intToEnum(ContentType, cleartext[cleartext.len - 1]);
...@@ -426,7 +417,7 @@ pub fn init(stream: net.Stream, host: []const u8) !Client {...@@ -426,7 +417,7 @@ pub fn init(stream: net.Stream, host: []const u8) !Client {
426 0x01,417 0x01,
427 };418 };
428 const app_cipher = switch (cipher_params) {419 const app_cipher = switch (cipher_params) {
429 inline .TLS_AES_128_GCM_SHA256, .TLS_AES_256_GCM_SHA384 => |*p, tag| c: {420 inline else => |*p, tag| c: {
430 const P = @TypeOf(p.*);421 const P = @TypeOf(p.*);
431 // TODO verify the server's data422 // TODO verify the server's data
432 const handshake_hash = p.transcript_hash.finalResult();423 const handshake_hash = p.transcript_hash.finalResult();
...@@ -467,15 +458,6 @@ pub fn init(stream: net.Stream, host: []const u8) !Client {...@@ -467,15 +458,6 @@ pub fn init(stream: net.Stream, host: []const u8) !Client {
467 .server_iv = hkdfExpandLabel(P.Hkdf, server_secret, "iv", "", P.AEAD.nonce_length),458 .server_iv = hkdfExpandLabel(P.Hkdf, server_secret, "iv", "", P.AEAD.nonce_length),
468 });459 });
469 },460 },
470 .TLS_CHACHA20_POLY1305_SHA256 => {
471 @panic("TODO");
472 },
473 .TLS_AES_128_CCM_SHA256 => {
474 @panic("TODO");
475 },
476 .TLS_AES_128_CCM_8_SHA256 => {
477 @panic("TODO");
478 },
479 };461 };
480 std.debug.print("remaining bytes: {d}\n", .{len - end});462 std.debug.print("remaining bytes: {d}\n", .{len - end});
481 return .{463 return .{
...@@ -524,7 +506,7 @@ pub fn write(c: *Client, stream: net.Stream, bytes: []const u8) !usize {...@@ -524,7 +506,7 @@ pub fn write(c: *Client, stream: net.Stream, bytes: []const u8) !usize {
524 var bytes_i: usize = 0;506 var bytes_i: usize = 0;
525 // How many bytes are taken up by overhead per record.507 // How many bytes are taken up by overhead per record.
526 const overhead_len: usize = switch (c.application_cipher) {508 const overhead_len: usize = switch (c.application_cipher) {
527 inline .TLS_AES_128_GCM_SHA256, .TLS_AES_256_GCM_SHA384 => |*p| l: {509 inline else => |*p| l: {
528 const P = @TypeOf(p.*);510 const P = @TypeOf(p.*);
529 const V = @Vector(P.AEAD.nonce_length, u8);511 const V = @Vector(P.AEAD.nonce_length, u8);
530 const overhead_len = tls.ciphertext_record_header_len + P.AEAD.tag_length + 1;512 const overhead_len = tls.ciphertext_record_header_len + P.AEAD.tag_length + 1;
...@@ -577,15 +559,6 @@ pub fn write(c: *Client, stream: net.Stream, bytes: []const u8) !usize {...@@ -577,15 +559,6 @@ pub fn write(c: *Client, stream: net.Stream, bytes: []const u8) !usize {
577 iovec_end += 1;559 iovec_end += 1;
578 }560 }
579 },561 },
580 .TLS_CHACHA20_POLY1305_SHA256 => {
581 @panic("TODO");
582 },
583 .TLS_AES_128_CCM_SHA256 => {
584 @panic("TODO");
585 },
586 .TLS_AES_128_CCM_8_SHA256 => {
587 @panic("TODO");
588 },
589 };562 };
590563
591 // Ideally we would call writev exactly once here, however, we must ensure564 // Ideally we would call writev exactly once here, however, we must ensure
...@@ -659,7 +632,7 @@ pub fn read(c: *Client, stream: net.Stream, buffer: []u8) !usize {...@@ -659,7 +632,7 @@ pub fn read(c: *Client, stream: net.Stream, buffer: []u8) !usize {
659 },632 },
660 .application_data => {633 .application_data => {
661 const cleartext_len = switch (c.application_cipher) {634 const cleartext_len = switch (c.application_cipher) {
662 inline .TLS_AES_128_GCM_SHA256, .TLS_AES_256_GCM_SHA384 => |*p| c: {635 inline else => |*p| c: {
663 const P = @TypeOf(p.*);636 const P = @TypeOf(p.*);
664 const V = @Vector(P.AEAD.nonce_length, u8);637 const V = @Vector(P.AEAD.nonce_length, u8);
665 const ad = frag[in - 5 ..][0..5];638 const ad = frag[in - 5 ..][0..5];
...@@ -682,15 +655,6 @@ pub fn read(c: *Client, stream: net.Stream, buffer: []u8) !usize {...@@ -682,15 +655,6 @@ pub fn read(c: *Client, stream: net.Stream, buffer: []u8) !usize {
682 return error.TlsBadRecordMac;655 return error.TlsBadRecordMac;
683 break :c cleartext.len;656 break :c cleartext.len;
684 },657 },
685 .TLS_CHACHA20_POLY1305_SHA256 => {
686 @panic("TODO");
687 },
688 .TLS_AES_128_CCM_SHA256 => {
689 @panic("TODO");
690 },
691 .TLS_AES_128_CCM_8_SHA256 => {
692 @panic("TODO");
693 },
694 };658 };
695659
696 const inner_ct = @intToEnum(ContentType, buffer[out + cleartext_len - 1]);660 const inner_ct = @intToEnum(ContentType, buffer[out + cleartext_len - 1]);
lib/std/meta.zig+8-4
...@@ -810,21 +810,25 @@ test "std.meta.activeTag" {...@@ -810,21 +810,25 @@ test "std.meta.activeTag" {
810810
811const TagPayloadType = TagPayload;811const TagPayloadType = TagPayload;
812812
813///Given a tagged union type, and an enum, return the type of the union813pub fn TagPayloadByName(comptime U: type, comptime tag_name: []const u8) type {
814/// field corresponding to the enum tag.
815pub fn TagPayload(comptime U: type, comptime tag: Tag(U)) type {
816 comptime debug.assert(trait.is(.Union)(U));814 comptime debug.assert(trait.is(.Union)(U));
817815
818 const info = @typeInfo(U).Union;816 const info = @typeInfo(U).Union;
819817
820 inline for (info.fields) |field_info| {818 inline for (info.fields) |field_info| {
821 if (comptime mem.eql(u8, field_info.name, @tagName(tag)))819 if (comptime mem.eql(u8, field_info.name, tag_name))
822 return field_info.type;820 return field_info.type;
823 }821 }
824822
825 unreachable;823 unreachable;
826}824}
827825
826/// Given a tagged union type, and an enum, return the type of the union field
827/// corresponding to the enum tag.
828pub fn TagPayload(comptime U: type, comptime tag: Tag(U)) type {
829 return TagPayloadByName(U, @tagName(tag));
830}
831
828test "std.meta.TagPayload" {832test "std.meta.TagPayload" {
829 const Event = union(enum) {833 const Event = union(enum) {
830 Moved: struct {834 Moved: struct {