authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-12-15 00:55:33-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-02 16:57:15-07:00
log595fff7cb664b5dc517a682b3daec5ee2767fe0d
tree9130bcf283f637744672769a0c44a42cafab8488
parent920e5bc4ff4bdfee173768809e712f8004f7132d

std.crypto.Tls: decrypting handshake messages


1 files changed, 216 insertions(+), 9 deletions(-)

lib/std/crypto/Tls.zig+216-9
...@@ -234,7 +234,12 @@ const cipher_suites = blk: {...@@ -234,7 +234,12 @@ const cipher_suites = blk: {
234pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void {234pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void {
235 assert(tls.state == .start);235 assert(tls.state == .start);
236 crypto.random.bytes(&tls.x25519_priv_key);236 crypto.random.bytes(&tls.x25519_priv_key);
237 tls.x25519_pub_key = try crypto.dh.X25519.recoverPublicKey(tls.x25519_priv_key);237 tls.x25519_pub_key = crypto.dh.X25519.recoverPublicKey(tls.x25519_priv_key) catch |err| {
238 switch (err) {
239 // Only possible to happen if the private key is all zeroes.
240 error.IdentityElement => return error.InsufficientEntropy,
241 }
242 };
238243
239 // random (u32)244 // random (u32)
240 var rand_buf: [32]u8 = undefined;245 var rand_buf: [32]u8 = undefined;
...@@ -337,6 +342,14 @@ pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void {...@@ -337,6 +342,14 @@ pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void {
337 };342 };
338 try stream.writevAll(&iovecs);343 try stream.writevAll(&iovecs);
339344
345 const client_hello_bytes1 = hello_header[5..];
346
347 var client_handshake_key: [32]u8 = undefined;
348 var server_handshake_key: [32]u8 = undefined;
349 var client_handshake_iv: [12]u8 = undefined;
350 var server_handshake_iv: [12]u8 = undefined;
351 var cipher_suite: CipherSuite = undefined;
352
340 var handshake_buf: [4000]u8 = undefined;353 var handshake_buf: [4000]u8 = undefined;
341 var len: usize = 0;354 var len: usize = 0;
342 var i: usize = i: {355 var i: usize = i: {
...@@ -373,7 +386,7 @@ pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void {...@@ -373,7 +386,7 @@ pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void {
373 const legacy_session_id_echo_len = hello[34];386 const legacy_session_id_echo_len = hello[34];
374 if (legacy_session_id_echo_len != 0) return error.TlsIllegalParameter;387 if (legacy_session_id_echo_len != 0) return error.TlsIllegalParameter;
375 const cipher_suite_int = mem.readIntBig(u16, hello[35..37]);388 const cipher_suite_int = mem.readIntBig(u16, hello[35..37]);
376 const cipher_suite = std.meta.intToEnum(CipherSuite, cipher_suite_int) catch389 cipher_suite = std.meta.intToEnum(CipherSuite, cipher_suite_int) catch
377 return error.TlsIllegalParameter;390 return error.TlsIllegalParameter;
378 std.debug.print("server wants cipher suite {s}\n", .{@tagName(cipher_suite)});391 std.debug.print("server wants cipher suite {s}\n", .{@tagName(cipher_suite)});
379 const legacy_compression_method = hello[37];392 const legacy_compression_method = hello[37];
...@@ -404,12 +417,7 @@ pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void {...@@ -404,12 +417,7 @@ pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void {
404 const key_size = mem.readIntBig(u16, hello[i..][0..2]);417 const key_size = mem.readIntBig(u16, hello[i..][0..2]);
405 i += 2;418 i += 2;
406 if (key_size != 32) return error.TlsBadLength;419 if (key_size != 32) return error.TlsBadLength;
407 const encrypted_key = hello[i..][0..32].*;420 tls.x25519_server_pub_key = hello[i..][0..32].*;
408 const server_pub_key = try crypto.dh.X25519.scalarmult(
409 tls.x25519_priv_key,
410 encrypted_key,
411 );
412 tls.x25519_server_pub_key = server_pub_key;
413 have_server_pub_key = true;421 have_server_pub_key = true;
414 },422 },
415 else => {423 else => {
...@@ -435,12 +443,77 @@ pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void {...@@ -435,12 +443,77 @@ pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void {
435 },443 },
436 else => return error.TlsIllegalParameter,444 else => return error.TlsIllegalParameter,
437 }445 }
446
447 const shared_key = crypto.dh.X25519.scalarmult(
448 tls.x25519_priv_key,
449 tls.x25519_server_pub_key,
450 ) catch return error.TlsDecryptFailure;
451
452 switch (cipher_suite) {
453 .TLS_AES_128_GCM_SHA256 => {
454 const AEAD = crypto.aead.aes_gcm.Aes128Gcm;
455 const Hash = crypto.hash.sha2.Sha256;
456 const Hmac = crypto.auth.hmac.Hmac(Hash);
457 const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac);
458
459 const hello_hash = helloHash(client_hello_bytes1, host, frag, Hash);
460 const early_secret = Hkdf.extract(&[1]u8{0}, &([1]u8{0} ** Hash.digest_length));
461 const empty_hash = emptyHash(Hash);
462 const derived_secret = hkdfExpandLabel(Hkdf, early_secret, "derived", &empty_hash, Hash.digest_length);
463 const handshake_secret = Hkdf.extract(&derived_secret, &shared_key);
464 const client_secret = hkdfExpandLabel(Hkdf, handshake_secret, "c hs traffic", &hello_hash, Hash.digest_length);
465 const server_secret = hkdfExpandLabel(Hkdf, handshake_secret, "s hs traffic", &hello_hash, Hash.digest_length);
466 client_handshake_key[0..AEAD.key_length].* = hkdfExpandLabel(Hkdf, client_secret, "key", "", AEAD.key_length);
467 server_handshake_key[0..AEAD.key_length].* = hkdfExpandLabel(Hkdf, server_secret, "key", "", AEAD.key_length);
468 client_handshake_iv = hkdfExpandLabel(Hkdf, client_secret, "iv", "", AEAD.nonce_length);
469 server_handshake_iv = hkdfExpandLabel(Hkdf, server_secret, "iv", "", AEAD.nonce_length);
470 //std.debug.print("shared_key: {}\nhello_hash: {}\nearly_secret: {}\nempty_hash: {}\nderived_secret: {}\nhandshake_secret: {}\n client_secret: {}\n server_secret: {}\n", .{
471 // std.fmt.fmtSliceHexLower(&shared_key),
472 // std.fmt.fmtSliceHexLower(&hello_hash),
473 // std.fmt.fmtSliceHexLower(&early_secret),
474 // std.fmt.fmtSliceHexLower(&empty_hash),
475 // std.fmt.fmtSliceHexLower(&derived_secret),
476 // std.fmt.fmtSliceHexLower(&handshake_secret),
477 // std.fmt.fmtSliceHexLower(&client_secret),
478 // std.fmt.fmtSliceHexLower(&server_secret),
479 //});
480 },
481 .TLS_AES_256_GCM_SHA384 => {
482 const AEAD = crypto.aead.aes_gcm.Aes256Gcm;
483 const Hash = crypto.hash.sha2.Sha384;
484 const Hmac = crypto.auth.hmac.Hmac(Hash);
485 const Hkdf = crypto.kdf.hkdf.Hkdf(Hmac);
486
487 const hello_hash = helloHash(client_hello_bytes1, host, frag, Hash);
488 const early_secret = Hkdf.extract(&[1]u8{0}, &([1]u8{0} ** Hash.digest_length));
489 const empty_hash = emptyHash(Hash);
490 const derived_secret = hkdfExpandLabel(Hkdf, early_secret, "derived", &empty_hash, Hash.digest_length);
491 const handshake_secret = Hkdf.extract(&derived_secret, &shared_key);
492 const client_secret = hkdfExpandLabel(Hkdf, handshake_secret, "c hs traffic", &hello_hash, Hash.digest_length);
493 const server_secret = hkdfExpandLabel(Hkdf, handshake_secret, "s hs traffic", &hello_hash, Hash.digest_length);
494 client_handshake_key = hkdfExpandLabel(Hkdf, client_secret, "key", "", AEAD.key_length);
495 server_handshake_key = hkdfExpandLabel(Hkdf, server_secret, "key", "", AEAD.key_length);
496 client_handshake_iv = hkdfExpandLabel(Hkdf, client_secret, "iv", "", AEAD.nonce_length);
497 server_handshake_iv = hkdfExpandLabel(Hkdf, server_secret, "iv", "", AEAD.nonce_length);
498 },
499 .TLS_CHACHA20_POLY1305_SHA256 => {
500 @panic("TODO");
501 },
502 .TLS_AES_128_CCM_SHA256 => {
503 @panic("TODO");
504 },
505 .TLS_AES_128_CCM_8_SHA256 => {
506 @panic("TODO");
507 },
508 }
438 },509 },
439 else => return error.TlsUnexpectedMessage,510 else => return error.TlsUnexpectedMessage,
440 }511 }
441 break :i end;512 break :i end;
442 };513 };
443514
515 var read_seq: u64 = 0;
516
444 while (true) {517 while (true) {
445 const end_hdr = i + 5;518 const end_hdr = i + 5;
446 if (end_hdr > handshake_buf.len) return error.TlsRecordOverflow;519 if (end_hdr > handshake_buf.len) return error.TlsRecordOverflow;
...@@ -467,7 +540,88 @@ pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void {...@@ -467,7 +540,88 @@ pub fn init(tls: *Tls, stream: net.Stream, host: []const u8) !void {
467 if (handshake_buf[i] != 0x01) return error.TlsUnexpectedMessage;540 if (handshake_buf[i] != 0x01) return error.TlsUnexpectedMessage;
468 },541 },
469 .application_data => {542 .application_data => {
470 std.debug.print("TODO: decrypt these {d} bytes\n", .{record_size});543 var cleartext_buf: [1000]u8 = undefined;
544 const cleartext = switch (cipher_suite) {
545 .TLS_AES_128_GCM_SHA256 => c: {
546 const AEAD = crypto.aead.aes_gcm.Aes128Gcm;
547 const ciphertext_len = record_size - AEAD.tag_length;
548 const ciphertext = handshake_buf[i..][0..ciphertext_len];
549 i += ciphertext.len;
550 if (ciphertext.len > cleartext_buf.len) return error.TlsRecordOverflow;
551 const cleartext = cleartext_buf[0..ciphertext.len];
552 const auth_tag = handshake_buf[i..][0..AEAD.tag_length].*;
553 const V = @Vector(AEAD.nonce_length, u8);
554 const pad = [1]u8{0} ** (AEAD.nonce_length - 8);
555 const operand: V = pad ++ @bitCast([8]u8, big(read_seq));
556 read_seq += 1;
557 const nonce: [AEAD.nonce_length]u8 = @as(V, server_handshake_iv) ^ operand;
558 //std.debug.print("seq: {d} nonce: {} operand: {}\n", .{
559 // read_seq - 1,
560 // std.fmt.fmtSliceHexLower(&nonce),
561 // std.fmt.fmtSliceHexLower(&@as([12]u8, operand)),
562 //});
563 const ad = handshake_buf[end_hdr - 5 ..][0..5];
564 const key = server_handshake_key[0..AEAD.key_length].*;
565 AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, key) catch
566 return error.TlsBadRecordMac;
567
568 break :c cleartext;
569 },
570 .TLS_AES_256_GCM_SHA384 => c: {
571 const AEAD = crypto.aead.aes_gcm.Aes256Gcm;
572 const ciphertext_len = record_size - AEAD.tag_length;
573 const ciphertext = handshake_buf[i..][0..ciphertext_len];
574 i += ciphertext.len;
575 if (ciphertext.len > cleartext_buf.len) return error.TlsRecordOverflow;
576 const cleartext = cleartext_buf[0..ciphertext.len];
577 const auth_tag = handshake_buf[i..][0..AEAD.tag_length].*;
578 const V = @Vector(AEAD.nonce_length, u8);
579 const pad = [1]u8{0} ** (AEAD.nonce_length - 8);
580 const operand: V = pad ++ @bitCast([8]u8, big(read_seq));
581 read_seq += 1;
582 const nonce: [AEAD.nonce_length]u8 = @as(V, server_handshake_iv) ^ operand;
583 const ad = handshake_buf[end_hdr - 5 ..][0..5];
584 const key = server_handshake_key[0..AEAD.key_length].*;
585 AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, key) catch
586 return error.TlsBadRecordMac;
587
588 break :c cleartext;
589 },
590 .TLS_CHACHA20_POLY1305_SHA256 => {
591 @panic("TODO");
592 },
593 .TLS_AES_128_CCM_SHA256 => {
594 @panic("TODO");
595 },
596 .TLS_AES_128_CCM_8_SHA256 => {
597 @panic("TODO");
598 },
599 };
600
601 const inner_ct = cleartext[cleartext.len - 1];
602 switch (inner_ct) {
603 @enumToInt(ContentType.handshake) => {
604 const handshake_len = mem.readIntBig(u24, cleartext[1..4]);
605 if (4 + handshake_len != cleartext.len - 1) return error.TlsBadLength;
606 switch (cleartext[0]) {
607 @enumToInt(HandshakeType.encrypted_extensions) => {
608 const ext_size = mem.readIntBig(u16, cleartext[4..6]);
609 if (ext_size != 0) {
610 @panic("TODO handle encrypted extensions");
611 }
612 std.debug.print("empty encrypted extensions\n", .{});
613 },
614 else => {
615 std.debug.print("handshake type: {d}\n", .{cleartext[0]});
616 return error.TlsUnexpectedMessage;
617 },
618 }
619 },
620 else => {
621 std.debug.print("inner content type: {d}\n", .{inner_ct});
622 return error.TlsUnexpectedMessage;
623 },
624 }
471 },625 },
472 else => {626 else => {
473 std.debug.print("content type: {s}\n", .{@tagName(ct)});627 std.debug.print("content type: {s}\n", .{@tagName(ct)});
...@@ -486,3 +640,56 @@ pub fn writeAll(tls: *Tls, stream: net.Stream, buffer: []const u8) !void {...@@ -486,3 +640,56 @@ pub fn writeAll(tls: *Tls, stream: net.Stream, buffer: []const u8) !void {
486 _ = buffer;640 _ = buffer;
487 @panic("hold on a minute, we didn't finish implementing the handshake yet");641 @panic("hold on a minute, we didn't finish implementing the handshake yet");
488}642}
643
644fn hkdfExpandLabel(
645 comptime Hkdf: type,
646 key: [Hkdf.prk_length]u8,
647 label: []const u8,
648 context: []const u8,
649 comptime len: usize,
650) [len]u8 {
651 const max_label_len = 255;
652 const max_context_len = 255;
653 const tls13 = "tls13 ";
654 var buf: [2 + 1 + tls13.len + max_label_len + 1 + max_context_len]u8 = undefined;
655 mem.writeIntBig(u16, buf[0..2], len);
656 buf[2] = @intCast(u8, tls13.len + label.len);
657 buf[3..][0..tls13.len].* = tls13.*;
658 var i: usize = 3 + tls13.len;
659 mem.copy(u8, buf[i..], label);
660 i += label.len;
661 buf[i] = @intCast(u8, context.len);
662 i += 1;
663 mem.copy(u8, buf[i..], context);
664 i += context.len;
665
666 var result: [len]u8 = undefined;
667 Hkdf.expand(&result, buf[0..i], key);
668 return result;
669}
670
671fn emptyHash(comptime Hash: type) [Hash.digest_length]u8 {
672 var result: [Hash.digest_length]u8 = undefined;
673 Hash.hash(&.{}, &result, .{});
674 return result;
675}
676
677fn helloHash(s0: []const u8, s1: []const u8, s2: []const u8, comptime Hash: type) [Hash.digest_length]u8 {
678 var h = Hash.init(.{});
679 h.update(s0);
680 h.update(s1);
681 h.update(s2);
682 var result: [Hash.digest_length]u8 = undefined;
683 h.final(&result);
684 return result;
685}
686
687const builtin = @import("builtin");
688const native_endian = builtin.cpu.arch.endian();
689
690inline fn big(x: anytype) @TypeOf(x) {
691 return switch (native_endian) {
692 .Big => x,
693 .Little => @byteSwap(x),
694 };
695}