| ... | @@ -16,17 +16,25 @@ const enum_array = tls.enum_array; | ... | @@ -16,17 +16,25 @@ const enum_array = tls.enum_array; |
| 16 | | 16 | |
| 17 | read_seq: u64, | 17 | read_seq: u64, |
| 18 | write_seq: u64, | 18 | write_seq: u64, |
| 19 | /// The number of partially read bytes inside `partially_read_buffer`. | 19 | /// The starting index of cleartext bytes inside `partially_read_buffer`. |
| 20 | partially_read_len: u15, | 20 | partial_cleartext_idx: u15, |
| 21 | /// The number of cleartext bytes from decoding `partially_read_buffer` which | 21 | /// The ending index of cleartext bytes inside `partially_read_buffer` as well |
| 22 | /// have already been transferred via read() calls. This implementation will | 22 | /// as the starting index of ciphertext bytes. |
| 23 | /// re-decrypt bytes from `partially_read_buffer` when the buffer supplied by | 23 | partial_ciphertext_idx: u15, |
| 24 | /// the read() API user is not large enough. | 24 | /// The ending index of ciphertext bytes inside `partially_read_buffer`. |
| 25 | partial_cleartext_index: u15, | 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 | application_cipher: tls.ApplicationCipher, | 29 | application_cipher: tls.ApplicationCipher, |
| 27 | eof: bool, | | |
| 28 | /// The size is enough to contain exactly one TLSCiphertext record. | 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 | partially_read_buffer: [tls.max_ciphertext_record_len]u8, | 38 | partially_read_buffer: [tls.max_ciphertext_record_len]u8, |
| 31 | | 39 | |
| 32 | /// `host` is only borrowed during this function call. | 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,13 +605,14 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8) |
| 597 | }, | 605 | }, |
| 598 | }; | 606 | }; |
| 599 | var client: Client = .{ | 607 | var client: Client = .{ |
| 600 | .application_cipher = app_cipher, | | |
| 601 | .read_seq = 0, | 608 | .read_seq = 0, |
| 602 | .write_seq = 0, | 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 | .partially_read_buffer = undefined, | 615 | .partially_read_buffer = undefined, |
| 605 | .partially_read_len = @intCast(u15, len - end), | | |
| 606 | .eof = false, | | |
| 607 | }; | 616 | }; |
| 608 | mem.copy(u8, &client.partially_read_buffer, handshake_buf[len..end]); | 617 | mem.copy(u8, &client.partially_read_buffer, handshake_buf[len..end]); |
| 609 | return client; | 618 | return client; |
| ... | @@ -727,19 +736,17 @@ pub fn writeAll(c: *Client, stream: net.Stream, bytes: []const u8) !void { | ... | @@ -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 | /// Returns the number of bytes read, calling the underlying read function the | 743 | /// Returns the number of bytes read, calling the underlying read function the |
| 731 | /// minimal number of times until the buffer has at least `len` bytes filled. | 744 | /// minimal number of times until the buffer has at least `len` bytes filled. |
| 732 | /// If the number read is less than `len` it means the stream reached the end. | 745 | /// If the number read is less than `len` it means the stream reached the end. |
| 733 | /// Reaching the end of the stream is not an error condition. | 746 | /// Reaching the end of the stream is not an error condition. |
| 734 | pub fn readAtLeast(c: *Client, stream: anytype, buffer: []u8, len: usize) !usize { | 747 | pub fn readAtLeast(c: *Client, stream: anytype, buffer: []u8, len: usize) !usize { |
| 735 | assert(len <= buffer.len); | 748 | var iovecs = [1]std.os.iovec{.{ .iov_base = buffer.ptr, .iov_len = buffer.len }}; |
| 736 | if (c.eof) return 0; | 749 | return readvAtLeast(c, stream, &iovecs, len); |
| 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; | | |
| 743 | } | 750 | } |
| 744 | | 751 | |
| 745 | pub fn read(c: *Client, stream: anytype, buffer: []u8) !usize { | 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,78 +760,180 @@ pub fn readAll(c: *Client, stream: anytype, buffer: []u8) !usize { |
| 753 | return readAtLeast(c, stream, buffer, buffer.len); | 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 | 763 | /// Returns the number of bytes read. If the number read is less than the space |
| 757 | /// return value of zero bytes does not mean end of stream. Instead, the `eof` | 764 | /// provided it means the stream reached the end. Reaching the end of the |
| 758 | /// flag is set upon end of stream. The `eof` flag may be set after any call to | 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 | /// `read`, including when greater than zero bytes are returned, and this | 799 | /// `read`, including when greater than zero bytes are returned, and this |
| 760 | /// function asserts that `eof` is `false`. | 800 | /// function asserts that `eof()` is `false`. |
| 761 | /// See `read` for a higher level function that has the same, familiar API | 801 | /// See `readv` for a higher level function that has the same, familiar API as |
| 762 | /// as other read functions, such as `std.fs.File.read`. | 802 | /// other read functions, such as `std.fs.File.read`. |
| 763 | /// It is recommended to use a buffer size with length at least | 803 | pub fn readvAdvanced(c: *Client, stream: net.Stream, iovecs: []const std.os.iovec) !usize { |
| 764 | /// `tls.max_ciphertext_len` bytes to avoid redundantly decrypting the same | 804 | var vp: VecPut = .{ .iovecs = iovecs }; |
| 765 | /// encoded data. | 805 | |
| 766 | pub fn readAdvanced(c: *Client, stream: net.Stream, buffer: []u8) !usize { | 806 | // Give away the buffered cleartext we have, if any. |
| 767 | assert(!c.eof); | 807 | const partial_cleartext = c.partially_read_buffer[c.partial_cleartext_idx..c.partial_ciphertext_idx]; |
| 768 | const prev_len = c.partially_read_len; | 808 | if (partial_cleartext.len > 0) { |
| 769 | // Ideally, this buffer would never be used. It is needed when `buffer` is too small | 809 | const amt = @intCast(u15, vp.put(partial_cleartext)); |
| 770 | // to fit the cleartext, which may be as large as `max_ciphertext_len`. | 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 | var cleartext_stack_buffer: [max_ciphertext_len]u8 = undefined; | 832 | var cleartext_stack_buffer: [max_ciphertext_len]u8 = undefined; |
| 772 | // This buffer is typically used, except, as an optimization when a very large | 833 | // Temporarily stores ciphertext before decrypting it and giving it to `iovecs`. |
| 773 | // `buffer` is provided, we use half of it for buffering ciphertext and the | | |
| 774 | // other half for outputting cleartext. | | |
| 775 | var in_stack_buffer: [max_ciphertext_len * 4]u8 = undefined; | 834 | var in_stack_buffer: [max_ciphertext_len * 4]u8 = undefined; |
| 776 | const half_buffer_len = buffer.len / 2; | 835 | // How many bytes left in the user's buffer. |
| 777 | const out_in: struct { []u8, []u8 } = if (half_buffer_len >= in_stack_buffer.len) .{ | 836 | const free_size = vp.freeSize(); |
| 778 | buffer[0..half_buffer_len], | 837 | // The amount of the user's buffer that we need to repurpose for storing |
| 779 | buffer[half_buffer_len..], | 838 | // ciphertext. The end of the buffer will be used for such purposes. |
| 780 | } else .{ | 839 | const ciphertext_buf_len = (free_size / 2) -| in_stack_buffer.len; |
| 781 | buffer, | 840 | // The amount of the user's buffer that will be used to give cleartext. The |
| 782 | &in_stack_buffer, | 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. | 856 | // Cleartext capacity of output buffer, in records, rounded up. |
| 789 | const buf_cap = (out_buf.len +| (max_ciphertext_len - 1)) / max_ciphertext_len; | 857 | const buf_cap = (cleartext_buf_len +| (max_ciphertext_len - 1)) / max_ciphertext_len; |
| 790 | const wanted_read_len = buf_cap * (max_ciphertext_len + tls.ciphertext_record_header_len); | 858 | const wanted_read_len = buf_cap * (max_ciphertext_len + tls.ciphertext_record_header_len); |
| 791 | const ask_len = @max(wanted_read_len, cleartext_stack_buffer.len); | 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)]; | 860 | const ask_iovecs = limitVecs(&ask_iovecs_buf, ask_len); |
| 793 | assert(ask_slice.len > 0); | 861 | const actual_read_len = try stream.readv(ask_iovecs); |
| 794 | const frag = frag: { | 862 | if (actual_read_len == 0) { |
| 795 | if (prev_len >= 5) { | 863 | // This is either a truncation attack, or a bug in the server. |
| 796 | const record_size = mem.readIntBig(u16, in_buf[3..][0..2]); | 864 | return error.TlsConnectionTruncated; |
| 797 | if (prev_len >= 5 + record_size) { | 865 | } |
| 798 | // We can use our buffered data without calling read(). | 866 | |
| 799 | break :frag in_buf[0..prev_len]; | 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 | if (in + tls.ciphertext_record_header_len > frag.len) { | 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 | const ct = @intToEnum(tls.ContentType, frag[in]); | 913 | const ct = @intToEnum(tls.ContentType, frag[in]); |
| 818 | in += 1; | 914 | in += 1; |
| 819 | const legacy_version = mem.readIntBig(u16, frag[in..][0..2]); | 915 | const legacy_version = mem.readIntBig(u16, frag[in..][0..2]); |
| 820 | in += 2; | 916 | in += 2; |
| 821 | _ = legacy_version; | 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 | in += 2; | 920 | in += 2; |
| 824 | const end = in + record_size; | 921 | const end = in + record_len; |
| 825 | if (end > frag.len) { | 922 | if (end > frag.len) { |
| 826 | if (record_size > max_ciphertext_len) return error.TlsRecordOverflow; | 923 | if (frag.ptr == frag1.ptr) |
| 827 | return finishRead(c, frag, in, out); | 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 | switch (ct) { | 938 | switch (ct) { |
| 830 | .alert => { | 939 | .alert => { |
| ... | @@ -836,18 +945,16 @@ pub fn readAdvanced(c: *Client, stream: net.Stream, buffer: []u8) !usize { | ... | @@ -836,18 +945,16 @@ pub fn readAdvanced(c: *Client, stream: net.Stream, buffer: []u8) !usize { |
| 836 | const P = @TypeOf(p.*); | 945 | const P = @TypeOf(p.*); |
| 837 | const V = @Vector(P.AEAD.nonce_length, u8); | 946 | const V = @Vector(P.AEAD.nonce_length, u8); |
| 838 | const ad = frag[in - 5 ..][0..5]; | 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 | const ciphertext = frag[in..][0..ciphertext_len]; | 949 | const ciphertext = frag[in..][0..ciphertext_len]; |
| 841 | in += ciphertext_len; | 950 | in += ciphertext_len; |
| 842 | const auth_tag = frag[in..][0..P.AEAD.tag_length].*; | 951 | const auth_tag = frag[in..][0..P.AEAD.tag_length].*; |
| 843 | const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8); | 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 | const operand: V = pad ++ @bitCast([8]u8, big(c.read_seq)); | 953 | const operand: V = pad ++ @bitCast([8]u8, big(c.read_seq)); |
| 848 | const nonce: [P.AEAD.nonce_length]u8 = @as(V, p.server_iv) ^ operand; | 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) | 955 | const out_buf = vp.peek(); |
| 850 | out_buf[out..] | 956 | const cleartext_buf = if (ciphertext.len <= out_buf.len) |
| | 957 | out_buf |
| 851 | else | 958 | else |
| 852 | &cleartext_stack_buffer; | 959 | &cleartext_stack_buffer; |
| 853 | const cleartext = cleartext_buf[0..ciphertext.len]; | 960 | const cleartext = cleartext_buf[0..ciphertext.len]; |
| ... | @@ -856,22 +963,22 @@ pub fn readAdvanced(c: *Client, stream: net.Stream, buffer: []u8) !usize { | ... | @@ -856,22 +963,22 @@ pub fn readAdvanced(c: *Client, stream: net.Stream, buffer: []u8) !usize { |
| 856 | break :c cleartext; | 963 | break :c cleartext; |
| 857 | }, | 964 | }, |
| 858 | }; | 965 | }; |
| | 966 | c.read_seq += 1; |
| 859 | | 967 | |
| 860 | const inner_ct = @intToEnum(tls.ContentType, cleartext[cleartext.len - 1]); | 968 | const inner_ct = @intToEnum(tls.ContentType, cleartext[cleartext.len - 1]); |
| 861 | switch (inner_ct) { | 969 | switch (inner_ct) { |
| 862 | .alert => { | 970 | .alert => { |
| 863 | c.read_seq += 1; | 971 | const level = @intToEnum(tls.AlertLevel, cleartext[0]); |
| 864 | const level = @intToEnum(tls.AlertLevel, out_buf[out]); | 972 | const desc = @intToEnum(tls.AlertDescription, cleartext[1]); |
| 865 | const desc = @intToEnum(tls.AlertDescription, out_buf[out + 1]); | | |
| 866 | if (desc == .close_notify) { | 973 | if (desc == .close_notify) { |
| 867 | c.eof = true; | 974 | c.received_close_notify = true; |
| 868 | return out; | 975 | c.partial_ciphertext_end = c.partial_ciphertext_idx; |
| | 976 | return vp.total; |
| 869 | } | 977 | } |
| 870 | std.debug.print("alert: {s} {s}\n", .{ @tagName(level), @tagName(desc) }); | 978 | std.debug.print("alert: {s} {s}\n", .{ @tagName(level), @tagName(desc) }); |
| 871 | return error.TlsAlert; | 979 | return error.TlsAlert; |
| 872 | }, | 980 | }, |
| 873 | .handshake => { | 981 | .handshake => { |
| 874 | c.read_seq += 1; | | |
| 875 | var ct_i: usize = 0; | 982 | var ct_i: usize = 0; |
| 876 | while (true) { | 983 | while (true) { |
| 877 | const handshake_type = @intToEnum(tls.HandshakeType, cleartext[ct_i]); | 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,42 +1033,37 @@ pub fn readAdvanced(c: *Client, stream: net.Stream, buffer: []u8) !usize { |
| 926 | .application_data => { | 1033 | .application_data => { |
| 927 | // Determine whether the output buffer or a stack | 1034 | // Determine whether the output buffer or a stack |
| 928 | // buffer was used for storing the cleartext. | 1035 | // buffer was used for storing the cleartext. |
| 929 | if (c.partial_cleartext_index == 0 and | 1036 | if (cleartext.ptr == &cleartext_stack_buffer) { |
| 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 { | | |
| 938 | // Stack buffer was used, so we must copy to the output buffer. | 1037 | // Stack buffer was used, so we must copy to the output buffer. |
| 939 | const dest = out_buf[out..]; | 1038 | const msg = cleartext[0 .. cleartext.len - 1]; |
| 940 | const rest = cleartext[c.partial_cleartext_index..]; | 1039 | if (c.partial_ciphertext_idx > c.partial_cleartext_idx) { |
| 941 | const src = rest[0..@min(rest.len, dest.len)]; | 1040 | // We have already run out of room in iovecs. Continue |
| 942 | mem.copy(u8, dest, src); | 1041 | // appending to `partially_read_buffer`. |
| 943 | out += src.len; | 1042 | const dest = c.partially_read_buffer[c.partial_ciphertext_idx..]; |
| 944 | c.partial_cleartext_index = @intCast( | 1043 | mem.copy(u8, dest, msg); |
| 945 | @TypeOf(c.partial_cleartext_index), | 1044 | c.partial_ciphertext_idx = @intCast(@TypeOf(c.partial_ciphertext_idx), c.partial_ciphertext_idx + msg.len); |
| 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; | | |
| 951 | } else { | 1045 | } else { |
| 952 | in = record_start; | 1046 | const amt = vp.put(msg); |
| 953 | return finishRead(c, frag, in, out); | 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 | else => { | 1061 | else => { |
| 958 | std.debug.print("inner content type: {d}\n", .{inner_ct}); | | |
| 959 | return error.TlsUnexpectedMessage; | 1062 | return error.TlsUnexpectedMessage; |
| 960 | }, | 1063 | }, |
| 961 | } | 1064 | } |
| 962 | }, | 1065 | }, |
| 963 | else => { | 1066 | else => { |
| 964 | std.debug.print("unexpected ct: {any}\n", .{ct}); | | |
| 965 | return error.TlsUnexpectedMessage; | 1067 | return error.TlsUnexpectedMessage; |
| 966 | }, | 1068 | }, |
| 967 | } | 1069 | } |
| ... | @@ -971,11 +1073,43 @@ pub fn readAdvanced(c: *Client, stream: net.Stream, buffer: []u8) !usize { | ... | @@ -971,11 +1073,43 @@ pub fn readAdvanced(c: *Client, stream: net.Stream, buffer: []u8) !usize { |
| 971 | | 1073 | |
| 972 | fn finishRead(c: *Client, frag: []const u8, in: usize, out: usize) usize { | 1074 | fn finishRead(c: *Client, frag: []const u8, in: usize, out: usize) usize { |
| 973 | const saved_buf = frag[in..]; | 1075 | const saved_buf = frag[in..]; |
| 974 | mem.copy(u8, &c.partially_read_buffer, saved_buf); | 1076 | if (c.partial_ciphertext_idx > c.partial_cleartext_idx) { |
| 975 | c.partially_read_len = @intCast(u15, saved_buf.len); | 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 | return out; | 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 | fn hostMatchesCommonName(host: []const u8, common_name: []const u8) bool { | 1113 | fn hostMatchesCommonName(host: []const u8, common_name: []const u8) bool { |
| 980 | if (mem.eql(u8, common_name, host)) { | 1114 | if (mem.eql(u8, common_name, host)) { |
| 981 | return true; // exact match | 1115 | return true; // exact match |
| ... | @@ -1015,6 +1149,89 @@ fn SchemeEcdsa(comptime scheme: tls.SignatureScheme) type { | ... | @@ -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 | /// The priority order here is chosen based on what crypto algorithms Zig has | 1235 | /// The priority order here is chosen based on what crypto algorithms Zig has |
| 1019 | /// available in the standard library as well as what is faster. Following are | 1236 | /// available in the standard library as well as what is faster. Following are |
| 1020 | /// a few data points on the relative performance of these algorithms. | 1237 | /// a few data points on the relative performance of these algorithms. |