| ... | ... | @@ -16,17 +16,25 @@ const enum_array = tls.enum_array; |
| 16 | 16 | |
| 17 | 17 | read_seq: u64, |
| 18 | 18 | write_seq: u64, |
| 19 | | /// The number of partially read bytes inside `partially_read_buffer`. |
| 20 | | partially_read_len: u15, |
| 21 | | /// The number of cleartext bytes from decoding `partially_read_buffer` which |
| 22 | | /// have already been transferred via read() calls. This implementation will |
| 23 | | /// re-decrypt bytes from `partially_read_buffer` when the buffer supplied by |
| 24 | | /// the read() API user is not large enough. |
| 25 | | partial_cleartext_index: u15, |
| 19 | /// The starting index of cleartext bytes inside `partially_read_buffer`. |
| 20 | partial_cleartext_idx: u15, |
| 21 | /// The ending index of cleartext bytes inside `partially_read_buffer` as well |
| 22 | /// as the starting index of ciphertext bytes. |
| 23 | partial_ciphertext_idx: u15, |
| 24 | /// The ending index of ciphertext bytes inside `partially_read_buffer`. |
| 25 | partial_ciphertext_end: u15, |
| 26 | /// When this is true, the stream may still not be at the end because there |
| 27 | /// may be data in `partially_read_buffer`. |
| 28 | received_close_notify: bool, |
| 26 | 29 | application_cipher: tls.ApplicationCipher, |
| 27 | | eof: bool, |
| 28 | 30 | /// The size is enough to contain exactly one TLSCiphertext record. |
| 29 | | /// Contains encrypted bytes. |
| 31 | /// This buffer is segmented into four parts: |
| 32 | /// 0. unused |
| 33 | /// 1. cleartext |
| 34 | /// 2. ciphertext |
| 35 | /// 3. unused |
| 36 | /// The fields `partial_cleartext_idx`, `partial_ciphertext_idx`, and |
| 37 | /// `partial_ciphertext_end` describe the span of the segments. |
| 30 | 38 | partially_read_buffer: [tls.max_ciphertext_record_len]u8, |
| 31 | 39 | |
| 32 | 40 | /// `host` is only borrowed during this function call. |
| ... | ... | @@ -597,13 +605,14 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8) |
| 597 | 605 | }, |
| 598 | 606 | }; |
| 599 | 607 | var client: Client = .{ |
| 600 | | .application_cipher = app_cipher, |
| 601 | 608 | .read_seq = 0, |
| 602 | 609 | .write_seq = 0, |
| 603 | | .partial_cleartext_index = 0, |
| 610 | .partial_cleartext_idx = 0, |
| 611 | .partial_ciphertext_idx = 0, |
| 612 | .partial_ciphertext_end = @intCast(u15, len - end), |
| 613 | .received_close_notify = false, |
| 614 | .application_cipher = app_cipher, |
| 604 | 615 | .partially_read_buffer = undefined, |
| 605 | | .partially_read_len = @intCast(u15, len - end), |
| 606 | | .eof = false, |
| 607 | 616 | }; |
| 608 | 617 | mem.copy(u8, &client.partially_read_buffer, handshake_buf[len..end]); |
| 609 | 618 | return client; |
| ... | ... | @@ -727,19 +736,17 @@ pub fn writeAll(c: *Client, stream: net.Stream, bytes: []const u8) !void { |
| 727 | 736 | } |
| 728 | 737 | } |
| 729 | 738 | |
| 739 | pub fn eof(c: Client) bool { |
| 740 | return c.received_close_notify and c.partial_ciphertext_end == 0; |
| 741 | } |
| 742 | |
| 730 | 743 | /// Returns the number of bytes read, calling the underlying read function the |
| 731 | 744 | /// minimal number of times until the buffer has at least `len` bytes filled. |
| 732 | 745 | /// If the number read is less than `len` it means the stream reached the end. |
| 733 | 746 | /// Reaching the end of the stream is not an error condition. |
| 734 | 747 | pub fn readAtLeast(c: *Client, stream: anytype, buffer: []u8, len: usize) !usize { |
| 735 | | assert(len <= buffer.len); |
| 736 | | if (c.eof) return 0; |
| 737 | | var index: usize = 0; |
| 738 | | while (index < len) { |
| 739 | | index += try c.readAdvanced(stream, buffer[index..]); |
| 740 | | if (c.eof) break; |
| 741 | | } |
| 742 | | return index; |
| 748 | var iovecs = [1]std.os.iovec{.{ .iov_base = buffer.ptr, .iov_len = buffer.len }}; |
| 749 | return readvAtLeast(c, stream, &iovecs, len); |
| 743 | 750 | } |
| 744 | 751 | |
| 745 | 752 | pub fn read(c: *Client, stream: anytype, buffer: []u8) !usize { |
| ... | ... | @@ -753,78 +760,180 @@ pub fn readAll(c: *Client, stream: anytype, buffer: []u8) !usize { |
| 753 | 760 | return readAtLeast(c, stream, buffer, buffer.len); |
| 754 | 761 | } |
| 755 | 762 | |
| 756 | | /// Returns number of bytes that have been read, populated inside `buffer`. A |
| 757 | | /// return value of zero bytes does not mean end of stream. Instead, the `eof` |
| 758 | | /// flag is set upon end of stream. The `eof` flag may be set after any call to |
| 763 | /// Returns the number of bytes read. If the number read is less than the space |
| 764 | /// provided it means the stream reached the end. Reaching the end of the |
| 765 | /// stream is not an error condition. |
| 766 | /// The `iovecs` parameter is mutable because this function needs to mutate the fields in |
| 767 | /// order to handle partial reads from the underlying stream layer. |
| 768 | pub fn readv(c: *Client, stream: anytype, iovecs: []std.os.iovec) !usize { |
| 769 | return readvAtLeast(c, stream, iovecs); |
| 770 | } |
| 771 | |
| 772 | /// Returns the number of bytes read, calling the underlying read function the |
| 773 | /// minimal number of times until the iovecs have at least `len` bytes filled. |
| 774 | /// If the number read is less than `len` it means the stream reached the end. |
| 775 | /// Reaching the end of the stream is not an error condition. |
| 776 | /// The `iovecs` parameter is mutable because this function needs to mutate the fields in |
| 777 | /// order to handle partial reads from the underlying stream layer. |
| 778 | pub fn readvAtLeast(c: *Client, stream: anytype, iovecs: []std.os.iovec, len: usize) !usize { |
| 779 | if (c.eof()) return 0; |
| 780 | |
| 781 | var off_i: usize = 0; |
| 782 | var vec_i: usize = 0; |
| 783 | while (true) { |
| 784 | var amt = try c.readvAdvanced(stream, iovecs[vec_i..]); |
| 785 | off_i += amt; |
| 786 | if (c.eof() or off_i >= len) return off_i; |
| 787 | while (amt >= iovecs[vec_i].iov_len) { |
| 788 | amt -= iovecs[vec_i].iov_len; |
| 789 | vec_i += 1; |
| 790 | } |
| 791 | iovecs[vec_i].iov_base += amt; |
| 792 | iovecs[vec_i].iov_len -= amt; |
| 793 | } |
| 794 | } |
| 795 | |
| 796 | /// Returns number of bytes that have been read, populated inside `iovecs`. A |
| 797 | /// return value of zero bytes does not mean end of stream. Instead, check the `eof()` |
| 798 | /// for the end of stream. The `eof()` may be true after any call to |
| 759 | 799 | /// `read`, including when greater than zero bytes are returned, and this |
| 760 | | /// function asserts that `eof` is `false`. |
| 761 | | /// See `read` for a higher level function that has the same, familiar API |
| 762 | | /// as other read functions, such as `std.fs.File.read`. |
| 763 | | /// It is recommended to use a buffer size with length at least |
| 764 | | /// `tls.max_ciphertext_len` bytes to avoid redundantly decrypting the same |
| 765 | | /// encoded data. |
| 766 | | pub fn readAdvanced(c: *Client, stream: net.Stream, buffer: []u8) !usize { |
| 767 | | assert(!c.eof); |
| 768 | | const prev_len = c.partially_read_len; |
| 769 | | // Ideally, this buffer would never be used. It is needed when `buffer` is too small |
| 770 | | // to fit the cleartext, which may be as large as `max_ciphertext_len`. |
| 800 | /// function asserts that `eof()` is `false`. |
| 801 | /// See `readv` for a higher level function that has the same, familiar API as |
| 802 | /// other read functions, such as `std.fs.File.read`. |
| 803 | pub fn readvAdvanced(c: *Client, stream: net.Stream, iovecs: []const std.os.iovec) !usize { |
| 804 | var vp: VecPut = .{ .iovecs = iovecs }; |
| 805 | |
| 806 | // Give away the buffered cleartext we have, if any. |
| 807 | const partial_cleartext = c.partially_read_buffer[c.partial_cleartext_idx..c.partial_ciphertext_idx]; |
| 808 | if (partial_cleartext.len > 0) { |
| 809 | const amt = @intCast(u15, vp.put(partial_cleartext)); |
| 810 | c.partial_cleartext_idx += amt; |
| 811 | if (amt < partial_cleartext.len) { |
| 812 | // We still have cleartext left so we cannot issue another read() call yet. |
| 813 | assert(vp.total == amt); |
| 814 | return amt; |
| 815 | } |
| 816 | if (c.received_close_notify) { |
| 817 | c.partial_ciphertext_end = 0; |
| 818 | assert(vp.total == amt); |
| 819 | return amt; |
| 820 | } |
| 821 | if (c.partial_ciphertext_end == c.partial_ciphertext_idx) { |
| 822 | c.partial_cleartext_idx = 0; |
| 823 | c.partial_ciphertext_idx = 0; |
| 824 | c.partial_ciphertext_end = 0; |
| 825 | } |
| 826 | } |
| 827 | |
| 828 | assert(!c.received_close_notify); |
| 829 | |
| 830 | // Ideally, this buffer would never be used. It is needed when `iovecs` are |
| 831 | // too small to fit the cleartext, which may be as large as `max_ciphertext_len`. |
| 771 | 832 | var cleartext_stack_buffer: [max_ciphertext_len]u8 = undefined; |
| 772 | | // This buffer is typically used, except, as an optimization when a very large |
| 773 | | // `buffer` is provided, we use half of it for buffering ciphertext and the |
| 774 | | // other half for outputting cleartext. |
| 833 | // Temporarily stores ciphertext before decrypting it and giving it to `iovecs`. |
| 775 | 834 | var in_stack_buffer: [max_ciphertext_len * 4]u8 = undefined; |
| 776 | | const half_buffer_len = buffer.len / 2; |
| 777 | | const out_in: struct { []u8, []u8 } = if (half_buffer_len >= in_stack_buffer.len) .{ |
| 778 | | buffer[0..half_buffer_len], |
| 779 | | buffer[half_buffer_len..], |
| 780 | | } else .{ |
| 781 | | buffer, |
| 782 | | &in_stack_buffer, |
| 835 | // How many bytes left in the user's buffer. |
| 836 | const free_size = vp.freeSize(); |
| 837 | // The amount of the user's buffer that we need to repurpose for storing |
| 838 | // ciphertext. The end of the buffer will be used for such purposes. |
| 839 | const ciphertext_buf_len = (free_size / 2) -| in_stack_buffer.len; |
| 840 | // The amount of the user's buffer that will be used to give cleartext. The |
| 841 | // beginning of the buffer will be used for such purposes. |
| 842 | const cleartext_buf_len = free_size - ciphertext_buf_len; |
| 843 | const first_iov = c.partially_read_buffer[c.partial_ciphertext_end..]; |
| 844 | |
| 845 | var ask_iovecs_buf: [2]std.os.iovec = .{ |
| 846 | .{ |
| 847 | .iov_base = first_iov.ptr, |
| 848 | .iov_len = first_iov.len, |
| 849 | }, |
| 850 | .{ |
| 851 | .iov_base = &in_stack_buffer, |
| 852 | .iov_len = in_stack_buffer.len, |
| 853 | }, |
| 783 | 854 | }; |
| 784 | | const out_buf = out_in[0]; |
| 785 | | const in_buf = out_in[1]; |
| 786 | | mem.copy(u8, in_buf, c.partially_read_buffer[0..prev_len]); |
| 787 | 855 | |
| 788 | | // Capacity of output buffer, in records, rounded up. |
| 789 | | const buf_cap = (out_buf.len +| (max_ciphertext_len - 1)) / max_ciphertext_len; |
| 856 | // Cleartext capacity of output buffer, in records, rounded up. |
| 857 | const buf_cap = (cleartext_buf_len +| (max_ciphertext_len - 1)) / max_ciphertext_len; |
| 790 | 858 | const wanted_read_len = buf_cap * (max_ciphertext_len + tls.ciphertext_record_header_len); |
| 791 | 859 | const ask_len = @max(wanted_read_len, cleartext_stack_buffer.len); |
| 792 | | const ask_slice = in_buf[prev_len..][0..@min(ask_len, in_buf.len - prev_len)]; |
| 793 | | assert(ask_slice.len > 0); |
| 794 | | const frag = frag: { |
| 795 | | if (prev_len >= 5) { |
| 796 | | const record_size = mem.readIntBig(u16, in_buf[3..][0..2]); |
| 797 | | if (prev_len >= 5 + record_size) { |
| 798 | | // We can use our buffered data without calling read(). |
| 799 | | break :frag in_buf[0..prev_len]; |
| 860 | const ask_iovecs = limitVecs(&ask_iovecs_buf, ask_len); |
| 861 | const actual_read_len = try stream.readv(ask_iovecs); |
| 862 | if (actual_read_len == 0) { |
| 863 | // This is either a truncation attack, or a bug in the server. |
| 864 | return error.TlsConnectionTruncated; |
| 865 | } |
| 866 | |
| 867 | // There might be more bytes inside `in_stack_buffer` that need to be processed, |
| 868 | // but at least frag0 will have one complete ciphertext record. |
| 869 | const frag0 = c.partially_read_buffer[0..@min(c.partially_read_buffer.len, actual_read_len)]; |
| 870 | var frag1 = in_stack_buffer[0 .. actual_read_len - frag0.len]; |
| 871 | // We need to decipher frag0 and frag1 but there may be a ciphertext record |
| 872 | // straddling the boundary. We can handle this with two memcpy() calls to |
| 873 | // assemble the straddling record in between handling the two sides. |
| 874 | var frag = frag0; |
| 875 | var in: usize = 0; |
| 876 | while (true) { |
| 877 | if (in == frag.len) { |
| 878 | // Perfect split. |
| 879 | if (frag.ptr == frag1.ptr) { |
| 880 | c.partial_ciphertext_end = c.partial_ciphertext_idx; |
| 881 | return vp.total; |
| 800 | 882 | } |
| 883 | frag = frag1; |
| 884 | in = 0; |
| 885 | continue; |
| 801 | 886 | } |
| 802 | | const actual_read_len = try stream.read(ask_slice); |
| 803 | | if (actual_read_len == 0) { |
| 804 | | // This is either a truncation attack, or a bug in the server. |
| 805 | | return error.TlsConnectionTruncated; |
| 806 | | } |
| 807 | | break :frag in_buf[0 .. prev_len + actual_read_len]; |
| 808 | | }; |
| 809 | | var in: usize = 0; |
| 810 | | var out: usize = 0; |
| 811 | 887 | |
| 812 | | while (true) { |
| 813 | 888 | if (in + tls.ciphertext_record_header_len > frag.len) { |
| 814 | | return finishRead(c, frag, in, out); |
| 889 | if (frag.ptr == frag1.ptr) |
| 890 | return finishRead(c, frag, in, vp.total); |
| 891 | |
| 892 | const first = frag[in..]; |
| 893 | |
| 894 | if (frag1.len < tls.ciphertext_record_header_len) |
| 895 | return finishRead2(c, first, frag1, vp.total); |
| 896 | |
| 897 | // A record straddles the two fragments. Copy into the now-empty first fragment. |
| 898 | const record_len_byte_0: u16 = straddleByte(frag, frag1, in + 3); |
| 899 | const record_len_byte_1: u16 = straddleByte(frag, frag1, in + 4); |
| 900 | const record_len = (record_len_byte_0 << 8) | record_len_byte_1; |
| 901 | if (record_len > max_ciphertext_len) return error.TlsRecordOverflow; |
| 902 | |
| 903 | const second_len = record_len + tls.ciphertext_record_header_len - first.len; |
| 904 | if (frag1.len < second_len) |
| 905 | return finishRead2(c, first, frag1, vp.total); |
| 906 | |
| 907 | mem.copy(u8, frag[0..in], first); |
| 908 | mem.copy(u8, frag[first.len..], frag1[0..second_len]); |
| 909 | frag1 = frag1[second_len..]; |
| 910 | in = 0; |
| 911 | continue; |
| 815 | 912 | } |
| 816 | | const record_start = in; |
| 817 | 913 | const ct = @intToEnum(tls.ContentType, frag[in]); |
| 818 | 914 | in += 1; |
| 819 | 915 | const legacy_version = mem.readIntBig(u16, frag[in..][0..2]); |
| 820 | 916 | in += 2; |
| 821 | 917 | _ = legacy_version; |
| 822 | | const record_size = mem.readIntBig(u16, frag[in..][0..2]); |
| 918 | const record_len = mem.readIntBig(u16, frag[in..][0..2]); |
| 919 | if (record_len > max_ciphertext_len) return error.TlsRecordOverflow; |
| 823 | 920 | in += 2; |
| 824 | | const end = in + record_size; |
| 921 | const end = in + record_len; |
| 825 | 922 | if (end > frag.len) { |
| 826 | | if (record_size > max_ciphertext_len) return error.TlsRecordOverflow; |
| 827 | | return finishRead(c, frag, in, out); |
| 923 | if (frag.ptr == frag1.ptr) |
| 924 | return finishRead(c, frag, in, vp.total); |
| 925 | |
| 926 | // A record straddles the two fragments. Copy into the now-empty first fragment. |
| 927 | const first = frag[in..]; |
| 928 | const second_len = record_len + tls.ciphertext_record_header_len - first.len; |
| 929 | if (frag1.len < second_len) |
| 930 | return finishRead2(c, first, frag1, vp.total); |
| 931 | |
| 932 | mem.copy(u8, frag[0..in], first); |
| 933 | mem.copy(u8, frag[first.len..], frag1[0..second_len]); |
| 934 | frag1 = frag1[second_len..]; |
| 935 | in = 0; |
| 936 | continue; |
| 828 | 937 | } |
| 829 | 938 | switch (ct) { |
| 830 | 939 | .alert => { |
| ... | ... | @@ -836,18 +945,16 @@ pub fn readAdvanced(c: *Client, stream: net.Stream, buffer: []u8) !usize { |
| 836 | 945 | const P = @TypeOf(p.*); |
| 837 | 946 | const V = @Vector(P.AEAD.nonce_length, u8); |
| 838 | 947 | const ad = frag[in - 5 ..][0..5]; |
| 839 | | const ciphertext_len = record_size - P.AEAD.tag_length; |
| 948 | const ciphertext_len = record_len - P.AEAD.tag_length; |
| 840 | 949 | const ciphertext = frag[in..][0..ciphertext_len]; |
| 841 | 950 | in += ciphertext_len; |
| 842 | 951 | const auth_tag = frag[in..][0..P.AEAD.tag_length].*; |
| 843 | 952 | const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8); |
| 844 | | // Here we use read_seq and then intentionally don't |
| 845 | | // increment it until later when it is certain the same |
| 846 | | // ciphertext does not need to be decrypted again. |
| 847 | 953 | const operand: V = pad ++ @bitCast([8]u8, big(c.read_seq)); |
| 848 | 954 | const nonce: [P.AEAD.nonce_length]u8 = @as(V, p.server_iv) ^ operand; |
| 849 | | const cleartext_buf = if (c.partial_cleartext_index == 0 and out + ciphertext.len <= out_buf.len) |
| 850 | | out_buf[out..] |
| 955 | const out_buf = vp.peek(); |
| 956 | const cleartext_buf = if (ciphertext.len <= out_buf.len) |
| 957 | out_buf |
| 851 | 958 | else |
| 852 | 959 | &cleartext_stack_buffer; |
| 853 | 960 | const cleartext = cleartext_buf[0..ciphertext.len]; |
| ... | ... | @@ -856,22 +963,22 @@ pub fn readAdvanced(c: *Client, stream: net.Stream, buffer: []u8) !usize { |
| 856 | 963 | break :c cleartext; |
| 857 | 964 | }, |
| 858 | 965 | }; |
| 966 | c.read_seq += 1; |
| 859 | 967 | |
| 860 | 968 | const inner_ct = @intToEnum(tls.ContentType, cleartext[cleartext.len - 1]); |
| 861 | 969 | switch (inner_ct) { |
| 862 | 970 | .alert => { |
| 863 | | c.read_seq += 1; |
| 864 | | const level = @intToEnum(tls.AlertLevel, out_buf[out]); |
| 865 | | const desc = @intToEnum(tls.AlertDescription, out_buf[out + 1]); |
| 971 | const level = @intToEnum(tls.AlertLevel, cleartext[0]); |
| 972 | const desc = @intToEnum(tls.AlertDescription, cleartext[1]); |
| 866 | 973 | if (desc == .close_notify) { |
| 867 | | c.eof = true; |
| 868 | | return out; |
| 974 | c.received_close_notify = true; |
| 975 | c.partial_ciphertext_end = c.partial_ciphertext_idx; |
| 976 | return vp.total; |
| 869 | 977 | } |
| 870 | 978 | std.debug.print("alert: {s} {s}\n", .{ @tagName(level), @tagName(desc) }); |
| 871 | 979 | return error.TlsAlert; |
| 872 | 980 | }, |
| 873 | 981 | .handshake => { |
| 874 | | c.read_seq += 1; |
| 875 | 982 | var ct_i: usize = 0; |
| 876 | 983 | while (true) { |
| 877 | 984 | const handshake_type = @intToEnum(tls.HandshakeType, cleartext[ct_i]); |
| ... | ... | @@ -926,42 +1033,37 @@ pub fn readAdvanced(c: *Client, stream: net.Stream, buffer: []u8) !usize { |
| 926 | 1033 | .application_data => { |
| 927 | 1034 | // Determine whether the output buffer or a stack |
| 928 | 1035 | // buffer was used for storing the cleartext. |
| 929 | | if (c.partial_cleartext_index == 0 and |
| 930 | | out + cleartext.len <= out_buf.len) |
| 931 | | { |
| 932 | | // Output buffer was used directly which means no |
| 933 | | // memory copying needs to occur, and we can move |
| 934 | | // on to the next ciphertext record. |
| 935 | | out += cleartext.len - 1; |
| 936 | | c.read_seq += 1; |
| 937 | | } else { |
| 1036 | if (cleartext.ptr == &cleartext_stack_buffer) { |
| 938 | 1037 | // Stack buffer was used, so we must copy to the output buffer. |
| 939 | | const dest = out_buf[out..]; |
| 940 | | const rest = cleartext[c.partial_cleartext_index..]; |
| 941 | | const src = rest[0..@min(rest.len, dest.len)]; |
| 942 | | mem.copy(u8, dest, src); |
| 943 | | out += src.len; |
| 944 | | c.partial_cleartext_index = @intCast( |
| 945 | | @TypeOf(c.partial_cleartext_index), |
| 946 | | c.partial_cleartext_index + src.len, |
| 947 | | ); |
| 948 | | if (c.partial_cleartext_index >= cleartext.len) { |
| 949 | | c.partial_cleartext_index = 0; |
| 950 | | c.read_seq += 1; |
| 1038 | const msg = cleartext[0 .. cleartext.len - 1]; |
| 1039 | if (c.partial_ciphertext_idx > c.partial_cleartext_idx) { |
| 1040 | // We have already run out of room in iovecs. Continue |
| 1041 | // appending to `partially_read_buffer`. |
| 1042 | const dest = c.partially_read_buffer[c.partial_ciphertext_idx..]; |
| 1043 | mem.copy(u8, dest, msg); |
| 1044 | c.partial_ciphertext_idx = @intCast(@TypeOf(c.partial_ciphertext_idx), c.partial_ciphertext_idx + msg.len); |
| 951 | 1045 | } else { |
| 952 | | in = record_start; |
| 953 | | return finishRead(c, frag, in, out); |
| 1046 | const amt = vp.put(msg); |
| 1047 | if (amt < msg.len) { |
| 1048 | const rest = msg[amt..]; |
| 1049 | c.partial_cleartext_idx = 0; |
| 1050 | c.partial_ciphertext_idx = @intCast(@TypeOf(c.partial_ciphertext_idx), rest.len); |
| 1051 | mem.copy(u8, &c.partially_read_buffer, rest); |
| 1052 | } |
| 954 | 1053 | } |
| 1054 | } else { |
| 1055 | // Output buffer was used directly which means no |
| 1056 | // memory copying needs to occur, and we can move |
| 1057 | // on to the next ciphertext record. |
| 1058 | vp.next(cleartext.len - 1); |
| 955 | 1059 | } |
| 956 | 1060 | }, |
| 957 | 1061 | else => { |
| 958 | | std.debug.print("inner content type: {d}\n", .{inner_ct}); |
| 959 | 1062 | return error.TlsUnexpectedMessage; |
| 960 | 1063 | }, |
| 961 | 1064 | } |
| 962 | 1065 | }, |
| 963 | 1066 | else => { |
| 964 | | std.debug.print("unexpected ct: {any}\n", .{ct}); |
| 965 | 1067 | return error.TlsUnexpectedMessage; |
| 966 | 1068 | }, |
| 967 | 1069 | } |
| ... | ... | @@ -971,11 +1073,43 @@ pub fn readAdvanced(c: *Client, stream: net.Stream, buffer: []u8) !usize { |
| 971 | 1073 | |
| 972 | 1074 | fn finishRead(c: *Client, frag: []const u8, in: usize, out: usize) usize { |
| 973 | 1075 | const saved_buf = frag[in..]; |
| 974 | | mem.copy(u8, &c.partially_read_buffer, saved_buf); |
| 975 | | c.partially_read_len = @intCast(u15, saved_buf.len); |
| 1076 | if (c.partial_ciphertext_idx > c.partial_cleartext_idx) { |
| 1077 | // There is cleartext at the beginning already which we need to preserve. |
| 1078 | c.partial_ciphertext_end = @intCast(@TypeOf(c.partial_ciphertext_end), c.partial_ciphertext_idx + saved_buf.len); |
| 1079 | mem.copy(u8, c.partially_read_buffer[c.partial_ciphertext_idx..], saved_buf); |
| 1080 | } else { |
| 1081 | c.partial_cleartext_idx = 0; |
| 1082 | c.partial_ciphertext_idx = 0; |
| 1083 | c.partial_ciphertext_end = @intCast(@TypeOf(c.partial_ciphertext_end), saved_buf.len); |
| 1084 | mem.copy(u8, &c.partially_read_buffer, saved_buf); |
| 1085 | } |
| 976 | 1086 | return out; |
| 977 | 1087 | } |
| 978 | 1088 | |
| 1089 | fn finishRead2(c: *Client, first: []const u8, frag1: []const u8, out: usize) usize { |
| 1090 | if (c.partial_ciphertext_idx > c.partial_cleartext_idx) { |
| 1091 | // There is cleartext at the beginning already which we need to preserve. |
| 1092 | c.partial_ciphertext_end = @intCast(@TypeOf(c.partial_ciphertext_end), c.partial_ciphertext_idx + first.len + frag1.len); |
| 1093 | mem.copy(u8, c.partially_read_buffer[c.partial_ciphertext_idx..], first); |
| 1094 | mem.copy(u8, c.partially_read_buffer[c.partial_ciphertext_idx + first.len ..], frag1); |
| 1095 | } else { |
| 1096 | c.partial_cleartext_idx = 0; |
| 1097 | c.partial_ciphertext_idx = 0; |
| 1098 | c.partial_ciphertext_end = @intCast(@TypeOf(c.partial_ciphertext_end), first.len + frag1.len); |
| 1099 | mem.copy(u8, &c.partially_read_buffer, first); |
| 1100 | mem.copy(u8, c.partially_read_buffer[first.len..], frag1); |
| 1101 | } |
| 1102 | return out; |
| 1103 | } |
| 1104 | |
| 1105 | fn straddleByte(s1: []const u8, s2: []const u8, index: usize) u8 { |
| 1106 | if (index < s1.len) { |
| 1107 | return s1[index]; |
| 1108 | } else { |
| 1109 | return s2[index - s1.len]; |
| 1110 | } |
| 1111 | } |
| 1112 | |
| 979 | 1113 | fn hostMatchesCommonName(host: []const u8, common_name: []const u8) bool { |
| 980 | 1114 | if (mem.eql(u8, common_name, host)) { |
| 981 | 1115 | return true; // exact match |
| ... | ... | @@ -1015,6 +1149,89 @@ fn SchemeEcdsa(comptime scheme: tls.SignatureScheme) type { |
| 1015 | 1149 | }; |
| 1016 | 1150 | } |
| 1017 | 1151 | |
| 1152 | /// Abstraction for sending multiple byte buffers to a slice of iovecs. |
| 1153 | const VecPut = struct { |
| 1154 | iovecs: []const std.os.iovec, |
| 1155 | idx: usize = 0, |
| 1156 | off: usize = 0, |
| 1157 | total: usize = 0, |
| 1158 | |
| 1159 | /// Returns the amount actually put which is always equal to bytes.len |
| 1160 | /// unless the vectors ran out of space. |
| 1161 | fn put(vp: *VecPut, bytes: []const u8) usize { |
| 1162 | var bytes_i: usize = 0; |
| 1163 | while (true) { |
| 1164 | const v = vp.iovecs[vp.idx]; |
| 1165 | const dest = v.iov_base[vp.off..v.iov_len]; |
| 1166 | const src = bytes[bytes_i..][0..@min(dest.len, bytes.len - bytes_i)]; |
| 1167 | mem.copy(u8, dest, src); |
| 1168 | bytes_i += src.len; |
| 1169 | if (bytes_i >= bytes.len) { |
| 1170 | vp.total += bytes_i; |
| 1171 | return bytes_i; |
| 1172 | } |
| 1173 | vp.off += src.len; |
| 1174 | if (vp.off >= v.iov_len) { |
| 1175 | vp.off = 0; |
| 1176 | vp.idx += 1; |
| 1177 | if (vp.idx >= vp.iovecs.len) { |
| 1178 | vp.total += bytes_i; |
| 1179 | return bytes_i; |
| 1180 | } |
| 1181 | } |
| 1182 | } |
| 1183 | } |
| 1184 | |
| 1185 | /// Returns the next buffer that consecutive bytes can go into. |
| 1186 | fn peek(vp: VecPut) []u8 { |
| 1187 | if (vp.idx >= vp.iovecs.len) return &.{}; |
| 1188 | const v = vp.iovecs[vp.idx]; |
| 1189 | return v.iov_base[vp.off..v.iov_len]; |
| 1190 | } |
| 1191 | |
| 1192 | // After writing to the result of peek(), one can call next() to |
| 1193 | // advance the cursor. |
| 1194 | fn next(vp: *VecPut, len: usize) void { |
| 1195 | vp.total += len; |
| 1196 | vp.off += len; |
| 1197 | if (vp.off >= vp.iovecs[vp.idx].iov_len) { |
| 1198 | vp.off = 0; |
| 1199 | vp.idx += 1; |
| 1200 | } |
| 1201 | } |
| 1202 | |
| 1203 | fn freeSize(vp: VecPut) usize { |
| 1204 | var total: usize = 0; |
| 1205 | |
| 1206 | total += vp.iovecs[vp.idx].iov_len - vp.off; |
| 1207 | |
| 1208 | if (vp.idx + 1 >= vp.iovecs.len) |
| 1209 | return total; |
| 1210 | |
| 1211 | for (vp.iovecs[vp.idx + 1 ..]) |v| { |
| 1212 | total += v.iov_len; |
| 1213 | } |
| 1214 | |
| 1215 | return total; |
| 1216 | } |
| 1217 | }; |
| 1218 | |
| 1219 | /// Limit iovecs to a specific byte size. |
| 1220 | fn limitVecs(iovecs: []std.os.iovec, len: usize) []std.os.iovec { |
| 1221 | var vec_i: usize = 0; |
| 1222 | var bytes_left: usize = len; |
| 1223 | while (true) { |
| 1224 | if (bytes_left >= iovecs[vec_i].iov_len) { |
| 1225 | bytes_left -= iovecs[vec_i].iov_len; |
| 1226 | vec_i += 1; |
| 1227 | if (vec_i == iovecs.len or bytes_left == 0) return iovecs[0..vec_i]; |
| 1228 | continue; |
| 1229 | } |
| 1230 | iovecs[vec_i].iov_len = bytes_left; |
| 1231 | return iovecs[0..vec_i]; |
| 1232 | } |
| 1233 | } |
| 1234 | |
| 1018 | 1235 | /// The priority order here is chosen based on what crypto algorithms Zig has |
| 1019 | 1236 | /// available in the standard library as well as what is faster. Following are |
| 1020 | 1237 | /// a few data points on the relative performance of these algorithms. |