authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-12-29 15:45:51-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-02 16:57:16-07:00
loge4a9b19a1490d5c41a4d8c10f47ba5639de48404
tree0ed259b5e272adab65c2f81dfa4c384ebe259096
parent7391df2be5143db8308ab7c5281842aea99cb1d7

std.crypto.tls.Client: rework the read function

Here's what I landed on for the TLS client. It's 16896 bytes (max_ciphertext_record_len is 16640). I believe this is the theoretical minimum size, give or take a few bytes. These constraints are satisfied: * a call to the readvAdvanced() function makes at most one call to the underlying readv function * iovecs are provided by the API, and used by the implementation for underlying readv() calls to the socket * the theoretical minimum number of memcpy() calls are issued in all circumstances * decryption is only performed once for any given TLS record * large read buffers are fully exploited This is accomplished by using the partial read buffer to storing both cleartext and ciphertext.

2 files changed, 341 insertions(+), 113 deletions(-)

lib/std/crypto/tls/Client.zig+330-113
...@@ -16,17 +16,25 @@ const enum_array = tls.enum_array;...@@ -16,17 +16,25 @@ const enum_array = tls.enum_array;
1616
17read_seq: u64,17read_seq: u64,
18write_seq: u64,18write_seq: u64,
19/// The number of partially read bytes inside `partially_read_buffer`.19/// The starting index of cleartext bytes inside `partially_read_buffer`.
20partially_read_len: u15,20partial_cleartext_idx: u15,
21/// The number of cleartext bytes from decoding `partially_read_buffer` which21/// The ending index of cleartext bytes inside `partially_read_buffer` as well
22/// have already been transferred via read() calls. This implementation will22/// as the starting index of ciphertext bytes.
23/// re-decrypt bytes from `partially_read_buffer` when the buffer supplied by23partial_ciphertext_idx: u15,
24/// the read() API user is not large enough.24/// The ending index of ciphertext bytes inside `partially_read_buffer`.
25partial_cleartext_index: u15,25partial_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`.
28received_close_notify: bool,
26application_cipher: tls.ApplicationCipher,29application_cipher: tls.ApplicationCipher,
27eof: 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.
30partially_read_buffer: [tls.max_ciphertext_record_len]u8,38partially_read_buffer: [tls.max_ciphertext_record_len]u8,
3139
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}
729738
739pub 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 the743/// 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.
734pub fn readAtLeast(c: *Client, stream: anytype, buffer: []u8, len: usize) !usize {747pub 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}
744751
745pub fn read(c: *Client, stream: anytype, buffer: []u8) !usize {752pub 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}
755762
756/// Returns number of bytes that have been read, populated inside `buffer`. A763/// 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 to765/// 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.
768pub 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.
778pub 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 this799/// `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 API801/// 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 least803pub fn readvAdvanced(c: *Client, stream: net.Stream, iovecs: []const std.os.iovec) !usize {
764/// `tls.max_ciphertext_len` bytes to avoid redundantly decrypting the same804 var vp: VecPut = .{ .iovecs = iovecs };
765/// encoded data.805
766pub 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 small809 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 large833 // 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]);
787855
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;
811887
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 else958 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;
859967
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 stack1034 // 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 and1036 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 {
9711073
972fn finishRead(c: *Client, frag: []const u8, in: usize, out: usize) usize {1074fn 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}
9781088
1089fn 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
1105fn 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
979fn hostMatchesCommonName(host: []const u8, common_name: []const u8) bool {1113fn 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 match1115 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}
10171151
1152/// Abstraction for sending multiple byte buffers to a slice of iovecs.
1153const 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.
1220fn 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 has1235/// 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 are1236/// 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.
lib/std/net.zig+11
...@@ -1672,6 +1672,17 @@ pub const Stream = struct {...@@ -1672,6 +1672,17 @@ pub const Stream = struct {
1672 }1672 }
1673 }1673 }
16741674
1675 pub fn readv(s: Stream, iovecs: []const os.iovec) ReadError!usize {
1676 if (builtin.os.tag == .windows) {
1677 // TODO improve this to use ReadFileScatter
1678 if (iovecs.len == 0) return @as(usize, 0);
1679 const first = iovecs[0];
1680 return os.windows.ReadFile(s.handle, first.iov_base[0..first.iov_len], null, io.default_mode);
1681 }
1682
1683 return os.readv(s.handle, iovecs);
1684 }
1685
1675 /// Returns the number of bytes read. If the number read is smaller than1686 /// Returns the number of bytes read. If the number read is smaller than
1676 /// `buffer.len`, it means the stream reached the end. Reaching the end of1687 /// `buffer.len`, it means the stream reached the end. Reaching the end of
1677 /// a stream is not an error condition.1688 /// a stream is not an error condition.