| ... | ... | @@ -9,12 +9,14 @@ application_cipher: ApplicationCipher, |
| 9 | 9 | read_seq: u64, |
| 10 | 10 | write_seq: u64, |
| 11 | 11 | /// The size is enough to contain exactly one TLSCiphertext record. |
| 12 | | partially_read_buffer: [max_ciphertext_len + ciphertext_record_header_len]u8, |
| 12 | partially_read_buffer: [max_ciphertext_record_len]u8, |
| 13 | 13 | /// The number of partially read bytes inside `partiall_read_buffer`. |
| 14 | 14 | partially_read_len: u15, |
| 15 | eof: bool, |
| 15 | 16 | |
| 16 | 17 | pub const ciphertext_record_header_len = 5; |
| 17 | 18 | pub const max_ciphertext_len = (1 << 14) + 256; |
| 19 | pub const max_ciphertext_record_len = max_ciphertext_len + ciphertext_record_header_len; |
| 18 | 20 | |
| 19 | 21 | pub const ProtocolVersion = enum(u16) { |
| 20 | 22 | tls_1_2 = 0x0303, |
| ... | ... | @@ -416,7 +418,7 @@ pub fn init(stream: net.Stream, host: []const u8) !Tls { |
| 416 | 418 | |
| 417 | 419 | var cipher_params: CipherParams = undefined; |
| 418 | 420 | |
| 419 | | var handshake_buf: [4000]u8 = undefined; |
| 421 | var handshake_buf: [8000]u8 = undefined; |
| 420 | 422 | var len: usize = 0; |
| 421 | 423 | var i: usize = i: { |
| 422 | 424 | const plaintext = handshake_buf[0..5]; |
| ... | ... | @@ -554,8 +556,8 @@ pub fn init(stream: net.Stream, host: []const u8) !Tls { |
| 554 | 556 | // std.fmt.fmtSliceHexLower(&hello_hash), |
| 555 | 557 | // std.fmt.fmtSliceHexLower(&early_secret), |
| 556 | 558 | // std.fmt.fmtSliceHexLower(&empty_hash), |
| 557 | | // std.fmt.fmtSliceHexLower(&derived_secret), |
| 558 | | // std.fmt.fmtSliceHexLower(&handshake_secret), |
| 559 | // std.fmt.fmtSliceHexLower(&hs_derived_secret), |
| 560 | // std.fmt.fmtSliceHexLower(&p.handshake_secret), |
| 559 | 561 | // std.fmt.fmtSliceHexLower(&client_secret), |
| 560 | 562 | // std.fmt.fmtSliceHexLower(&server_secret), |
| 561 | 563 | //}); |
| ... | ... | @@ -582,7 +584,9 @@ pub fn init(stream: net.Stream, host: []const u8) !Tls { |
| 582 | 584 | const end_hdr = i + 5; |
| 583 | 585 | if (end_hdr > handshake_buf.len) return error.TlsRecordOverflow; |
| 584 | 586 | if (end_hdr > len) { |
| 587 | std.debug.print("read len={d} atleast={d}\n", .{ len, end_hdr - len }); |
| 585 | 588 | len += try stream.readAtLeast(handshake_buf[len..], end_hdr - len); |
| 589 | std.debug.print("new len: {d} bytes\n", .{len}); |
| 586 | 590 | if (end_hdr > len) return error.EndOfStream; |
| 587 | 591 | } |
| 588 | 592 | const ct = @intToEnum(ContentType, handshake_buf[i]); |
| ... | ... | @@ -593,9 +597,12 @@ pub fn init(stream: net.Stream, host: []const u8) !Tls { |
| 593 | 597 | const record_size = mem.readIntBig(u16, handshake_buf[i..][0..2]); |
| 594 | 598 | i += 2; |
| 595 | 599 | const end = i + record_size; |
| 600 | std.debug.print("ct={any} record_size={d} end={d}\n", .{ ct, record_size, end }); |
| 596 | 601 | if (end > handshake_buf.len) return error.TlsRecordOverflow; |
| 597 | 602 | if (end > len) { |
| 603 | std.debug.print("read len={d} atleast={d}\n", .{ len, end - len }); |
| 598 | 604 | len += try stream.readAtLeast(handshake_buf[len..], end - len); |
| 605 | std.debug.print("new len: {d} bytes\n", .{len}); |
| 599 | 606 | if (end > len) return error.EndOfStream; |
| 600 | 607 | } |
| 601 | 608 | switch (ct) { |
| ... | ... | @@ -604,7 +611,7 @@ pub fn init(stream: net.Stream, host: []const u8) !Tls { |
| 604 | 611 | if (handshake_buf[i] != 0x01) return error.TlsUnexpectedMessage; |
| 605 | 612 | }, |
| 606 | 613 | .application_data => { |
| 607 | | var cleartext_buf: [1000]u8 = undefined; |
| 614 | var cleartext_buf: [8000]u8 = undefined; |
| 608 | 615 | const cleartext = switch (cipher_params) { |
| 609 | 616 | inline .TLS_AES_128_GCM_SHA256, .TLS_AES_256_GCM_SHA384 => |*p| c: { |
| 610 | 617 | const P = @TypeOf(p.*); |
| ... | ... | @@ -637,17 +644,18 @@ pub fn init(stream: net.Stream, host: []const u8) !Tls { |
| 637 | 644 | }; |
| 638 | 645 | |
| 639 | 646 | const inner_ct = cleartext[cleartext.len - 1]; |
| 647 | std.debug.print("inner_ct={any}\n", .{@intToEnum(ContentType, inner_ct)}); |
| 640 | 648 | switch (inner_ct) { |
| 641 | 649 | @enumToInt(ContentType.handshake) => { |
| 642 | 650 | const handshake_len = mem.readIntBig(u24, cleartext[1..4]); |
| 643 | | if (4 + handshake_len != cleartext.len - 1) return error.TlsBadLength; |
| 651 | if (4 + handshake_len > cleartext.len - 1) return error.TlsBadLength; |
| 652 | std.debug.print("handshake type: {any} size: {d}\n", .{ @intToEnum(HandshakeType, cleartext[0]), handshake_len }); |
| 644 | 653 | switch (cleartext[0]) { |
| 645 | 654 | @enumToInt(HandshakeType.encrypted_extensions) => { |
| 646 | 655 | const ext_size = mem.readIntBig(u16, cleartext[4..6]); |
| 647 | | if (ext_size != 0) { |
| 648 | | @panic("TODO handle encrypted extensions"); |
| 649 | | } |
| 650 | | std.debug.print("empty encrypted extensions\n", .{}); |
| 656 | std.debug.print("{d} bytes of encrypted extensions\n", .{ |
| 657 | ext_size, |
| 658 | }); |
| 651 | 659 | }, |
| 652 | 660 | @enumToInt(HandshakeType.certificate) => { |
| 653 | 661 | std.debug.print("cool certificate bro\n", .{}); |
| ... | ... | @@ -688,22 +696,18 @@ pub fn init(stream: net.Stream, host: []const u8) !Tls { |
| 688 | 696 | const nonce = p.client_handshake_iv; |
| 689 | 697 | P.AEAD.encrypt(ciphertext, auth_tag, &out_cleartext, ad, nonce, p.client_handshake_key); |
| 690 | 698 | |
| 691 | | { |
| 692 | | var iovecs = [_]std.os.iovec_const{ |
| 693 | | .{ |
| 694 | | .iov_base = &client_change_cipher_spec_msg, |
| 695 | | .iov_len = client_change_cipher_spec_msg.len, |
| 696 | | }, |
| 697 | | .{ |
| 698 | | .iov_base = &finished_msg, |
| 699 | | .iov_len = finished_msg.len, |
| 700 | | }, |
| 701 | | }; |
| 702 | | try stream.writevAll(&iovecs); |
| 703 | | } |
| 699 | //const both_msgs = client_change_cipher_spec_msg ++ finished_msg; |
| 700 | _ = client_change_cipher_spec_msg; |
| 701 | const both_msgs = finished_msg; |
| 702 | try stream.writeAll(&both_msgs); |
| 704 | 703 | |
| 705 | 704 | const client_secret = hkdfExpandLabel(P.Hkdf, p.master_secret, "c ap traffic", &handshake_hash, P.Hash.digest_length); |
| 706 | 705 | const server_secret = hkdfExpandLabel(P.Hkdf, p.master_secret, "s ap traffic", &handshake_hash, P.Hash.digest_length); |
| 706 | //std.debug.print("master_secret={}\nclient_secret={}\nserver_secret={}\n", .{ |
| 707 | // std.fmt.fmtSliceHexLower(&p.master_secret), |
| 708 | // std.fmt.fmtSliceHexLower(&client_secret), |
| 709 | // std.fmt.fmtSliceHexLower(&server_secret), |
| 710 | //}); |
| 707 | 711 | break :c @unionInit(ApplicationCipher, @tagName(tag), .{ |
| 708 | 712 | .client_key = hkdfExpandLabel(P.Hkdf, client_secret, "key", "", P.AEAD.key_length), |
| 709 | 713 | .server_key = hkdfExpandLabel(P.Hkdf, server_secret, "key", "", P.AEAD.key_length), |
| ... | ... | @@ -721,12 +725,14 @@ pub fn init(stream: net.Stream, host: []const u8) !Tls { |
| 721 | 725 | @panic("TODO"); |
| 722 | 726 | }, |
| 723 | 727 | }; |
| 728 | std.debug.print("remaining bytes: {d}\n", .{len - end}); |
| 724 | 729 | return .{ |
| 725 | 730 | .application_cipher = app_cipher, |
| 726 | | .read_seq = read_seq, |
| 727 | | .write_seq = 1, |
| 731 | .read_seq = 0, |
| 732 | .write_seq = 0, |
| 728 | 733 | .partially_read_buffer = undefined, |
| 729 | 734 | .partially_read_len = 0, |
| 735 | .eof = false, |
| 730 | 736 | }; |
| 731 | 737 | }, |
| 732 | 738 | else => { |
| ... | ... | @@ -753,49 +759,67 @@ pub fn init(stream: net.Stream, host: []const u8) !Tls { |
| 753 | 759 | } |
| 754 | 760 | |
| 755 | 761 | pub fn write(tls: *Tls, stream: net.Stream, bytes: []const u8) !usize { |
| 756 | | var ciphertext_buf: [max_ciphertext_len * 4]u8 = undefined; |
| 762 | var ciphertext_buf: [max_ciphertext_record_len * 4]u8 = undefined; |
| 763 | // Due to the trailing inner content type byte in the ciphertext, we need |
| 764 | // an additional buffer for storing the cleartext into before encrypting. |
| 765 | var cleartext_buf: [max_ciphertext_len]u8 = undefined; |
| 757 | 766 | var iovecs_buf: [5]std.os.iovec_const = undefined; |
| 758 | 767 | var ciphertext_end: usize = 0; |
| 759 | 768 | var iovec_end: usize = 0; |
| 760 | 769 | var bytes_i: usize = 0; |
| 761 | | switch (tls.application_cipher) { |
| 762 | | inline .TLS_AES_128_GCM_SHA256, .TLS_AES_256_GCM_SHA384 => |*p| { |
| 770 | // How many bytes are taken up by overhead per record. |
| 771 | const overhead_len: usize = switch (tls.application_cipher) { |
| 772 | inline .TLS_AES_128_GCM_SHA256, .TLS_AES_256_GCM_SHA384 => |*p| l: { |
| 763 | 773 | const P = @TypeOf(p.*); |
| 764 | 774 | const V = @Vector(P.AEAD.nonce_length, u8); |
| 775 | const overhead_len = ciphertext_record_header_len + P.AEAD.tag_length + 1; |
| 765 | 776 | while (true) { |
| 766 | | const ciphertext_len = @intCast(u16, @min( |
| 767 | | @min(bytes.len - bytes_i, max_ciphertext_len), |
| 768 | | ciphertext_buf.len - 5 - P.AEAD.tag_length - ciphertext_end, |
| 777 | const encrypted_content_len = @intCast(u16, @min( |
| 778 | @min(bytes.len - bytes_i, max_ciphertext_len - 1), |
| 779 | ciphertext_buf.len - |
| 780 | ciphertext_record_header_len - P.AEAD.tag_length - ciphertext_end - 1, |
| 769 | 781 | )); |
| 770 | | if (ciphertext_len == 0) return bytes_i; |
| 782 | if (encrypted_content_len == 0) break :l overhead_len; |
| 771 | 783 | |
| 772 | | const wrapped_len = ciphertext_len + P.AEAD.tag_length; |
| 773 | | const record = ciphertext_buf[ciphertext_end..][0 .. 5 + wrapped_len]; |
| 784 | mem.copy(u8, &cleartext_buf, bytes[bytes_i..][0..encrypted_content_len]); |
| 785 | cleartext_buf[encrypted_content_len] = @enumToInt(ContentType.application_data); |
| 786 | bytes_i += encrypted_content_len; |
| 787 | const ciphertext_len = encrypted_content_len + 1; |
| 788 | const cleartext = cleartext_buf[0..ciphertext_len]; |
| 774 | 789 | |
| 775 | | const ad = record[0..5]; |
| 776 | | ciphertext_end += 5; |
| 790 | const record_start = ciphertext_end; |
| 791 | const ad = ciphertext_buf[ciphertext_end..][0..5]; |
| 792 | ad.* = |
| 793 | [_]u8{@enumToInt(ContentType.application_data)} ++ |
| 794 | int2(@enumToInt(ProtocolVersion.tls_1_2)) ++ |
| 795 | int2(ciphertext_len + P.AEAD.tag_length); |
| 796 | ciphertext_end += ad.len; |
| 777 | 797 | const ciphertext = ciphertext_buf[ciphertext_end..][0..ciphertext_len]; |
| 778 | 798 | ciphertext_end += ciphertext_len; |
| 779 | 799 | const auth_tag = ciphertext_buf[ciphertext_end..][0..P.AEAD.tag_length]; |
| 780 | | ciphertext_end += P.AEAD.tag_length; |
| 800 | ciphertext_end += auth_tag.len; |
| 781 | 801 | const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8); |
| 782 | 802 | const operand: V = pad ++ @bitCast([8]u8, big(tls.write_seq)); |
| 783 | 803 | tls.write_seq += 1; |
| 784 | 804 | const nonce: [P.AEAD.nonce_length]u8 = @as(V, p.client_iv) ^ operand; |
| 785 | | ad.* = |
| 786 | | [_]u8{@enumToInt(ContentType.application_data)} ++ |
| 787 | | int2(@enumToInt(ProtocolVersion.tls_1_2)) ++ |
| 788 | | int2(wrapped_len); |
| 789 | | const cleartext = bytes[bytes_i..ciphertext.len]; |
| 790 | 805 | P.AEAD.encrypt(ciphertext, auth_tag, cleartext, ad, nonce, p.client_key); |
| 791 | | |
| 806 | //std.debug.print("seq: {d} nonce: {} client_key: {} client_iv: {} ad: {} auth_tag: {}\nserver_key: {} server_iv: {}", .{ |
| 807 | // tls.write_seq - 1, |
| 808 | // std.fmt.fmtSliceHexLower(&nonce), |
| 809 | // std.fmt.fmtSliceHexLower(&p.client_key), |
| 810 | // std.fmt.fmtSliceHexLower(&p.client_iv), |
| 811 | // std.fmt.fmtSliceHexLower(ad), |
| 812 | // std.fmt.fmtSliceHexLower(auth_tag), |
| 813 | // std.fmt.fmtSliceHexLower(&p.server_key), |
| 814 | // std.fmt.fmtSliceHexLower(&p.server_iv), |
| 815 | //}); |
| 816 | |
| 817 | const record = ciphertext_buf[record_start..ciphertext_end]; |
| 792 | 818 | iovecs_buf[iovec_end] = .{ |
| 793 | 819 | .iov_base = record.ptr, |
| 794 | 820 | .iov_len = record.len, |
| 795 | 821 | }; |
| 796 | 822 | iovec_end += 1; |
| 797 | | |
| 798 | | bytes_i += ciphertext_len; |
| 799 | 823 | } |
| 800 | 824 | }, |
| 801 | 825 | .TLS_CHACHA20_POLY1305_SHA256 => { |
| ... | ... | @@ -807,7 +831,7 @@ pub fn write(tls: *Tls, stream: net.Stream, bytes: []const u8) !usize { |
| 807 | 831 | .TLS_AES_128_CCM_8_SHA256 => { |
| 808 | 832 | @panic("TODO"); |
| 809 | 833 | }, |
| 810 | | } |
| 834 | }; |
| 811 | 835 | |
| 812 | 836 | // Ideally we would call writev exactly once here, however, we must ensure |
| 813 | 837 | // that we don't return with a record partially written. |
| ... | ... | @@ -815,9 +839,10 @@ pub fn write(tls: *Tls, stream: net.Stream, bytes: []const u8) !usize { |
| 815 | 839 | var total_amt: usize = 0; |
| 816 | 840 | while (true) { |
| 817 | 841 | var amt = try stream.writev(iovecs_buf[i..iovec_end]); |
| 818 | | total_amt += amt; |
| 819 | 842 | while (amt >= iovecs_buf[i].iov_len) { |
| 820 | | amt -= iovecs_buf[i].iov_len; |
| 843 | const encrypted_amt = iovecs_buf[i].iov_len; |
| 844 | total_amt += encrypted_amt - overhead_len; |
| 845 | amt -= encrypted_amt; |
| 821 | 846 | i += 1; |
| 822 | 847 | // Rely on the property that iovecs delineate records, meaning that |
| 823 | 848 | // if amt equals zero here, we have fortunately found ourselves |
| ... | ... | @@ -849,11 +874,17 @@ pub fn read(tls: *Tls, stream: net.Stream, buffer: []u8) !usize { |
| 849 | 874 | const wanted_read_len = buf_cap * (max_ciphertext_len + ciphertext_record_header_len); |
| 850 | 875 | const actual_read_len = try stream.read(in_buf[prev_len..@min(wanted_read_len, in_buf.len)]); |
| 851 | 876 | const frag = in_buf[0 .. prev_len + actual_read_len]; |
| 877 | if (frag.len == 0) { |
| 878 | tls.eof = true; |
| 879 | return 0; |
| 880 | } |
| 881 | std.debug.print("actual_read_len={d} frag.len={d}\n", .{ actual_read_len, frag.len }); |
| 852 | 882 | var in: usize = 0; |
| 853 | 883 | var out: usize = 0; |
| 854 | 884 | |
| 855 | 885 | while (true) { |
| 856 | 886 | if (in + ciphertext_record_header_len > frag.len) { |
| 887 | std.debug.print("in={d} frag.len={d}\n", .{ in, frag.len }); |
| 857 | 888 | return finishRead(tls, frag, in, out); |
| 858 | 889 | } |
| 859 | 890 | const ct = @intToEnum(ContentType, frag[in]); |
| ... | ... | @@ -866,6 +897,7 @@ pub fn read(tls: *Tls, stream: net.Stream, buffer: []u8) !usize { |
| 866 | 897 | const end = in + record_size; |
| 867 | 898 | if (end > frag.len) { |
| 868 | 899 | if (record_size > max_ciphertext_len) return error.TlsRecordOverflow; |
| 900 | std.debug.print("end={d} frag.len={d}\n", .{ end, frag.len }); |
| 869 | 901 | return finishRead(tls, frag, in, out); |
| 870 | 902 | } |
| 871 | 903 | switch (ct) { |
| ... | ... | @@ -877,6 +909,7 @@ pub fn read(tls: *Tls, stream: net.Stream, buffer: []u8) !usize { |
| 877 | 909 | inline .TLS_AES_128_GCM_SHA256, .TLS_AES_256_GCM_SHA384 => |*p| c: { |
| 878 | 910 | const P = @TypeOf(p.*); |
| 879 | 911 | const V = @Vector(P.AEAD.nonce_length, u8); |
| 912 | const ad = frag[in - 5 ..][0..5]; |
| 880 | 913 | const ciphertext_len = record_size - P.AEAD.tag_length; |
| 881 | 914 | const ciphertext = frag[in..][0..ciphertext_len]; |
| 882 | 915 | in += ciphertext_len; |
| ... | ... | @@ -886,7 +919,12 @@ pub fn read(tls: *Tls, stream: net.Stream, buffer: []u8) !usize { |
| 886 | 919 | const operand: V = pad ++ @bitCast([8]u8, big(tls.read_seq)); |
| 887 | 920 | tls.read_seq += 1; |
| 888 | 921 | const nonce: [P.AEAD.nonce_length]u8 = @as(V, p.server_iv) ^ operand; |
| 889 | | const ad = frag[0..ciphertext_record_header_len]; |
| 922 | //std.debug.print("seq: {d} nonce: {} server_key: {} server_iv: {}\n", .{ |
| 923 | // tls.read_seq - 1, |
| 924 | // std.fmt.fmtSliceHexLower(&nonce), |
| 925 | // std.fmt.fmtSliceHexLower(&p.server_key), |
| 926 | // std.fmt.fmtSliceHexLower(&p.server_iv), |
| 927 | //}); |
| 890 | 928 | P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, p.server_key) catch |
| 891 | 929 | return error.TlsBadRecordMac; |
| 892 | 930 | break :c cleartext.len; |
| ... | ... | @@ -902,15 +940,26 @@ pub fn read(tls: *Tls, stream: net.Stream, buffer: []u8) !usize { |
| 902 | 940 | }, |
| 903 | 941 | }; |
| 904 | 942 | |
| 905 | | const inner_ct = buffer[out + cleartext_len - 1]; |
| 943 | const inner_ct = @intToEnum(ContentType, buffer[out + cleartext_len - 1]); |
| 906 | 944 | switch (inner_ct) { |
| 907 | | @enumToInt(ContentType.handshake) => { |
| 945 | .alert => { |
| 946 | const level = @intToEnum(AlertLevel, buffer[out]); |
| 947 | const desc = @intToEnum(AlertDescription, buffer[out + 1]); |
| 948 | if (desc == .close_notify) { |
| 949 | tls.eof = true; |
| 950 | return out; |
| 951 | } |
| 952 | std.debug.print("alert: {s} {s}\n", .{ @tagName(level), @tagName(desc) }); |
| 953 | return error.TlsAlert; |
| 954 | }, |
| 955 | .handshake => { |
| 908 | 956 | std.debug.print("the server wants to keep shaking hands\n", .{}); |
| 909 | 957 | }, |
| 910 | | @enumToInt(ContentType.application_data) => { |
| 958 | .application_data => { |
| 911 | 959 | out += cleartext_len - 1; |
| 912 | 960 | }, |
| 913 | 961 | else => { |
| 962 | std.debug.print("inner content type: {d}\n", .{inner_ct}); |
| 914 | 963 | return error.TlsUnexpectedMessage; |
| 915 | 964 | }, |
| 916 | 965 | } |