| ... | @@ -8,8 +8,8 @@ const mem = std.mem; | ... | @@ -8,8 +8,8 @@ const mem = std.mem; |
| 8 | const crypto = std.crypto; | 8 | const crypto = std.crypto; |
| 9 | const assert = std.debug.assert; | 9 | const assert = std.debug.assert; |
| 10 | const Certificate = std.crypto.Certificate; | 10 | const Certificate = std.crypto.Certificate; |
| 11 | const Reader = std.io.Reader; | 11 | const Reader = std.Io.Reader; |
| 12 | const Writer = std.io.Writer; | 12 | const Writer = std.Io.Writer; |
| 13 | | 13 | |
| 14 | const max_ciphertext_len = tls.max_ciphertext_len; | 14 | const max_ciphertext_len = tls.max_ciphertext_len; |
| 15 | const hmacExpandLabel = tls.hmacExpandLabel; | 15 | const hmacExpandLabel = tls.hmacExpandLabel; |
| ... | @@ -27,6 +27,8 @@ reader: Reader, | ... | @@ -27,6 +27,8 @@ reader: Reader, |
| 27 | | 27 | |
| 28 | /// The encrypted stream from the client to the server. Bytes are pushed here | 28 | /// The encrypted stream from the client to the server. Bytes are pushed here |
| 29 | /// via `writer`. | 29 | /// via `writer`. |
| | 30 | /// |
| | 31 | /// The buffer is asserted to have capacity at least `min_buffer_len`. |
| 30 | output: *Writer, | 32 | output: *Writer, |
| 31 | /// The plaintext stream from the client to the server. | 33 | /// The plaintext stream from the client to the server. |
| 32 | writer: Writer, | 34 | writer: Writer, |
| ... | @@ -122,7 +124,6 @@ pub const Options = struct { | ... | @@ -122,7 +124,6 @@ pub const Options = struct { |
| 122 | /// the amount of data expected, such as HTTP with the Content-Length header. | 124 | /// the amount of data expected, such as HTTP with the Content-Length header. |
| 123 | allow_truncation_attacks: bool = false, | 125 | allow_truncation_attacks: bool = false, |
| 124 | write_buffer: []u8, | 126 | write_buffer: []u8, |
| 125 | /// Asserted to have capacity at least `min_buffer_len`. | | |
| 126 | read_buffer: []u8, | 127 | read_buffer: []u8, |
| 127 | /// Populated when `error.TlsAlert` is returned from `init`. | 128 | /// Populated when `error.TlsAlert` is returned from `init`. |
| 128 | alert: ?*tls.Alert = null, | 129 | alert: ?*tls.Alert = null, |
| ... | @@ -185,6 +186,7 @@ const InitError = error{ | ... | @@ -185,6 +186,7 @@ const InitError = error{ |
| 185 | /// `input` is asserted to have buffer capacity at least `min_buffer_len`. | 186 | /// `input` is asserted to have buffer capacity at least `min_buffer_len`. |
| 186 | pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client { | 187 | pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client { |
| 187 | assert(input.buffer.len >= min_buffer_len); | 188 | assert(input.buffer.len >= min_buffer_len); |
| | 189 | assert(output.buffer.len >= min_buffer_len); |
| 188 | const host = switch (options.host) { | 190 | const host = switch (options.host) { |
| 189 | .no_verification => "", | 191 | .no_verification => "", |
| 190 | .explicit => |host| host, | 192 | .explicit => |host| host, |
| ... | @@ -278,6 +280,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client | ... | @@ -278,6 +280,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client |
| 278 | { | 280 | { |
| 279 | var iovecs: [2][]const u8 = .{ cleartext_header, host }; | 281 | var iovecs: [2][]const u8 = .{ cleartext_header, host }; |
| 280 | try output.writeVecAll(iovecs[0..if (host.len == 0) 1 else 2]); | 282 | try output.writeVecAll(iovecs[0..if (host.len == 0) 1 else 2]); |
| | 283 | try output.flush(); |
| 281 | } | 284 | } |
| 282 | | 285 | |
| 283 | var tls_version: tls.ProtocolVersion = undefined; | 286 | var tls_version: tls.ProtocolVersion = undefined; |
| ... | @@ -763,6 +766,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client | ... | @@ -763,6 +766,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client |
| 763 | &client_verify_msg, | 766 | &client_verify_msg, |
| 764 | }; | 767 | }; |
| 765 | try output.writeVecAll(&all_msgs_vec); | 768 | try output.writeVecAll(&all_msgs_vec); |
| | 769 | try output.flush(); |
| 766 | }, | 770 | }, |
| 767 | } | 771 | } |
| 768 | write_seq += 1; | 772 | write_seq += 1; |
| ... | @@ -828,6 +832,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client | ... | @@ -828,6 +832,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client |
| 828 | &finished_msg, | 832 | &finished_msg, |
| 829 | }; | 833 | }; |
| 830 | try output.writeVecAll(&all_msgs_vec); | 834 | try output.writeVecAll(&all_msgs_vec); |
| | 835 | try output.flush(); |
| 831 | | 836 | |
| 832 | const client_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "c ap traffic", &handshake_hash, P.Hash.digest_length); | 837 | const client_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "c ap traffic", &handshake_hash, P.Hash.digest_length); |
| 833 | const server_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "s ap traffic", &handshake_hash, P.Hash.digest_length); | 838 | const server_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "s ap traffic", &handshake_hash, P.Hash.digest_length); |
| ... | @@ -877,7 +882,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client | ... | @@ -877,7 +882,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client |
| 877 | .buffer = options.write_buffer, | 882 | .buffer = options.write_buffer, |
| 878 | .vtable = &.{ | 883 | .vtable = &.{ |
| 879 | .drain = drain, | 884 | .drain = drain, |
| 880 | .sendFile = Writer.unimplementedSendFile, | 885 | .flush = flush, |
| 881 | }, | 886 | }, |
| 882 | }, | 887 | }, |
| 883 | .tls_version = tls_version, | 888 | .tls_version = tls_version, |
| ... | @@ -911,31 +916,56 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client | ... | @@ -911,31 +916,56 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client |
| 911 | | 916 | |
| 912 | fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize { | 917 | fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize { |
| 913 | const c: *Client = @alignCast(@fieldParentPtr("writer", w)); | 918 | const c: *Client = @alignCast(@fieldParentPtr("writer", w)); |
| 914 | if (true) @panic("update to use the buffer and flush"); | | |
| 915 | const sliced_data = if (splat == 0) data[0..data.len -| 1] else data; | | |
| 916 | const output = c.output; | 919 | const output = c.output; |
| 917 | const ciphertext_buf = try output.writableSliceGreedy(min_buffer_len); | 920 | const ciphertext_buf = try output.writableSliceGreedy(min_buffer_len); |
| 918 | var total_clear: usize = 0; | | |
| 919 | var ciphertext_end: usize = 0; | 921 | var ciphertext_end: usize = 0; |
| 920 | for (sliced_data) |buf| { | 922 | var total_clear: usize = 0; |
| 921 | const prepared = prepareCiphertextRecord(c, ciphertext_buf[ciphertext_end..], buf, .application_data); | 923 | done: { |
| 922 | total_clear += prepared.cleartext_len; | 924 | { |
| 923 | ciphertext_end += prepared.ciphertext_end; | 925 | const buf = w.buffered(); |
| 924 | if (total_clear < buf.len) break; | 926 | const prepared = prepareCiphertextRecord(c, ciphertext_buf[ciphertext_end..], buf, .application_data); |
| | 927 | total_clear += prepared.cleartext_len; |
| | 928 | ciphertext_end += prepared.ciphertext_end; |
| | 929 | if (prepared.cleartext_len < buf.len) break :done; |
| | 930 | } |
| | 931 | for (data[0 .. data.len - 1]) |buf| { |
| | 932 | if (buf.len < min_buffer_len) break :done; |
| | 933 | const prepared = prepareCiphertextRecord(c, ciphertext_buf[ciphertext_end..], buf, .application_data); |
| | 934 | total_clear += prepared.cleartext_len; |
| | 935 | ciphertext_end += prepared.ciphertext_end; |
| | 936 | if (prepared.cleartext_len < buf.len) break :done; |
| | 937 | } |
| | 938 | const buf = data[data.len - 1]; |
| | 939 | for (0..splat) |_| { |
| | 940 | if (buf.len < min_buffer_len) break :done; |
| | 941 | const prepared = prepareCiphertextRecord(c, ciphertext_buf[ciphertext_end..], buf, .application_data); |
| | 942 | total_clear += prepared.cleartext_len; |
| | 943 | ciphertext_end += prepared.ciphertext_end; |
| | 944 | if (prepared.cleartext_len < buf.len) break :done; |
| | 945 | } |
| 925 | } | 946 | } |
| 926 | output.advance(ciphertext_end); | 947 | output.advance(ciphertext_end); |
| 927 | return total_clear; | 948 | return w.consume(total_clear); |
| | 949 | } |
| | 950 | |
| | 951 | fn flush(w: *Writer) Writer.Error!void { |
| | 952 | const c: *Client = @alignCast(@fieldParentPtr("writer", w)); |
| | 953 | const output = c.output; |
| | 954 | const ciphertext_buf = try output.writableSliceGreedy(min_buffer_len); |
| | 955 | const prepared = prepareCiphertextRecord(c, ciphertext_buf, w.buffered(), .application_data); |
| | 956 | output.advance(prepared.ciphertext_end); |
| | 957 | w.end = 0; |
| 928 | } | 958 | } |
| 929 | | 959 | |
| 930 | /// Sends a `close_notify` alert, which is necessary for the server to | 960 | /// Sends a `close_notify` alert, which is necessary for the server to |
| 931 | /// distinguish between a properly finished TLS session, or a truncation | 961 | /// distinguish between a properly finished TLS session, or a truncation |
| 932 | /// attack. | 962 | /// attack. |
| 933 | pub fn end(c: *Client) Writer.Error!void { | 963 | pub fn end(c: *Client) Writer.Error!void { |
| | 964 | try flush(&c.writer); |
| 934 | const output = c.output; | 965 | const output = c.output; |
| 935 | const ciphertext_buf = try output.writableSliceGreedy(min_buffer_len); | 966 | const ciphertext_buf = try output.writableSliceGreedy(min_buffer_len); |
| 936 | const prepared = prepareCiphertextRecord(c, ciphertext_buf, &tls.close_notify_alert, .alert); | 967 | const prepared = prepareCiphertextRecord(c, ciphertext_buf, &tls.close_notify_alert, .alert); |
| 937 | output.advance(prepared.cleartext_len); | 968 | output.advance(prepared.ciphertext_end); |
| 938 | return prepared.ciphertext_end; | | |
| 939 | } | 969 | } |
| 940 | | 970 | |
| 941 | fn prepareCiphertextRecord( | 971 | fn prepareCiphertextRecord( |
| ... | @@ -1045,7 +1075,7 @@ pub fn eof(c: Client) bool { | ... | @@ -1045,7 +1075,7 @@ pub fn eof(c: Client) bool { |
| 1045 | return c.received_close_notify; | 1075 | return c.received_close_notify; |
| 1046 | } | 1076 | } |
| 1047 | | 1077 | |
| 1048 | fn stream(r: *Reader, w: *Writer, limit: std.io.Limit) Reader.StreamError!usize { | 1078 | fn stream(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize { |
| 1049 | const c: *Client = @alignCast(@fieldParentPtr("reader", r)); | 1079 | const c: *Client = @alignCast(@fieldParentPtr("reader", r)); |
| 1050 | if (c.eof()) return error.EndOfStream; | 1080 | if (c.eof()) return error.EndOfStream; |
| 1051 | const input = c.input; | 1081 | const input = c.input; |