| author | |
| committer | |
| log | aac26f3b31ddb43e863ac7186fd19a7e251c1b8a |
| tree | 8bdf34e6009de36d786477e960a7636759bcb78c |
| parent | ac18b98aa398b9d310bee0420c6130e51b973332 |
* TLS: add missing assert for output buffer length requirement
* TLS: add missing flushes
* TLS: add flush implementation
* TLS: finish drain implementation
* HTTP: correct buffer sizes for TLS
* HTTP: expose a getReadError method on Connection
* HTTP: add missing flush on sendBodyComplete
* Fetch: remove unwanted deinit
* Fetch: improve error reporting3 files changed, 84 insertions(+), 28 deletions(-)
lib/std/crypto/tls/Client.zig+46-16| ... | ... | @@ -8,8 +8,8 @@ const mem = std.mem; |
| 8 | 8 | const crypto = std.crypto; |
| 9 | 9 | const assert = std.debug.assert; |
| 10 | 10 | const Certificate = std.crypto.Certificate; |
| 11 | const Reader = std.io.Reader; | |
| 12 | const Writer = std.io.Writer; | |
| 11 | const Reader = std.Io.Reader; | |
| 12 | const Writer = std.Io.Writer; | |
| 13 | 13 | |
| 14 | 14 | const max_ciphertext_len = tls.max_ciphertext_len; |
| 15 | 15 | const hmacExpandLabel = tls.hmacExpandLabel; |
| ... | ... | @@ -27,6 +27,8 @@ reader: Reader, |
| 27 | 27 | |
| 28 | 28 | /// The encrypted stream from the client to the server. Bytes are pushed here |
| 29 | 29 | /// via `writer`. |
| 30 | /// | |
| 31 | /// The buffer is asserted to have capacity at least `min_buffer_len`. | |
| 30 | 32 | output: *Writer, |
| 31 | 33 | /// The plaintext stream from the client to the server. |
| 32 | 34 | writer: Writer, |
| ... | ... | @@ -122,7 +124,6 @@ pub const Options = struct { |
| 122 | 124 | /// the amount of data expected, such as HTTP with the Content-Length header. |
| 123 | 125 | allow_truncation_attacks: bool = false, |
| 124 | 126 | write_buffer: []u8, |
| 125 | /// Asserted to have capacity at least `min_buffer_len`. | |
| 126 | 127 | read_buffer: []u8, |
| 127 | 128 | /// Populated when `error.TlsAlert` is returned from `init`. |
| 128 | 129 | alert: ?*tls.Alert = null, |
| ... | ... | @@ -185,6 +186,7 @@ const InitError = error{ |
| 185 | 186 | /// `input` is asserted to have buffer capacity at least `min_buffer_len`. |
| 186 | 187 | pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client { |
| 187 | 188 | assert(input.buffer.len >= min_buffer_len); |
| 189 | assert(output.buffer.len >= min_buffer_len); | |
| 188 | 190 | const host = switch (options.host) { |
| 189 | 191 | .no_verification => "", |
| 190 | 192 | .explicit => |host| host, |
| ... | ... | @@ -278,6 +280,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client |
| 278 | 280 | { |
| 279 | 281 | var iovecs: [2][]const u8 = .{ cleartext_header, host }; |
| 280 | 282 | try output.writeVecAll(iovecs[0..if (host.len == 0) 1 else 2]); |
| 283 | try output.flush(); | |
| 281 | 284 | } |
| 282 | 285 | |
| 283 | 286 | var tls_version: tls.ProtocolVersion = undefined; |
| ... | ... | @@ -763,6 +766,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client |
| 763 | 766 | &client_verify_msg, |
| 764 | 767 | }; |
| 765 | 768 | try output.writeVecAll(&all_msgs_vec); |
| 769 | try output.flush(); | |
| 766 | 770 | }, |
| 767 | 771 | } |
| 768 | 772 | write_seq += 1; |
| ... | ... | @@ -828,6 +832,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client |
| 828 | 832 | &finished_msg, |
| 829 | 833 | }; |
| 830 | 834 | try output.writeVecAll(&all_msgs_vec); |
| 835 | try output.flush(); | |
| 831 | 836 | |
| 832 | 837 | const client_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "c ap traffic", &handshake_hash, P.Hash.digest_length); |
| 833 | 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 | 882 | .buffer = options.write_buffer, |
| 878 | 883 | .vtable = &.{ |
| 879 | 884 | .drain = drain, |
| 880 | .sendFile = Writer.unimplementedSendFile, | |
| 885 | .flush = flush, | |
| 881 | 886 | }, |
| 882 | 887 | }, |
| 883 | 888 | .tls_version = tls_version, |
| ... | ... | @@ -911,31 +916,56 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client |
| 911 | 916 | |
| 912 | 917 | fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize { |
| 913 | 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 | 919 | const output = c.output; |
| 917 | 920 | const ciphertext_buf = try output.writableSliceGreedy(min_buffer_len); |
| 918 | var total_clear: usize = 0; | |
| 919 | 921 | var ciphertext_end: usize = 0; |
| 920 | for (sliced_data) |buf| { | |
| 921 | const prepared = prepareCiphertextRecord(c, ciphertext_buf[ciphertext_end..], buf, .application_data); | |
| 922 | total_clear += prepared.cleartext_len; | |
| 923 | ciphertext_end += prepared.ciphertext_end; | |
| 924 | if (total_clear < buf.len) break; | |
| 922 | var total_clear: usize = 0; | |
| 923 | done: { | |
| 924 | { | |
| 925 | const buf = w.buffered(); | |
| 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 | 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 | 960 | /// Sends a `close_notify` alert, which is necessary for the server to |
| 931 | 961 | /// distinguish between a properly finished TLS session, or a truncation |
| 932 | 962 | /// attack. |
| 933 | 963 | pub fn end(c: *Client) Writer.Error!void { |
| 964 | try flush(&c.writer); | |
| 934 | 965 | const output = c.output; |
| 935 | 966 | const ciphertext_buf = try output.writableSliceGreedy(min_buffer_len); |
| 936 | 967 | const prepared = prepareCiphertextRecord(c, ciphertext_buf, &tls.close_notify_alert, .alert); |
| 937 | output.advance(prepared.cleartext_len); | |
| 938 | return prepared.ciphertext_end; | |
| 968 | output.advance(prepared.ciphertext_end); | |
| 939 | 969 | } |
| 940 | 970 | |
| 941 | 971 | fn prepareCiphertextRecord( |
| ... | ... | @@ -1045,7 +1075,7 @@ pub fn eof(c: Client) bool { |
| 1045 | 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 | 1079 | const c: *Client = @alignCast(@fieldParentPtr("reader", r)); |
| 1050 | 1080 | if (c.eof()) return error.EndOfStream; |
| 1051 | 1081 | const input = c.input; |
lib/std/http/Client.zig+29-9| ... | ... | @@ -42,7 +42,7 @@ connection_pool: ConnectionPool = .{}, |
| 42 | 42 | /// |
| 43 | 43 | /// If the entire HTTP header cannot fit in this amount of bytes, |
| 44 | 44 | /// `error.HttpHeadersOversize` will be returned from `Request.wait`. |
| 45 | read_buffer_size: usize = 4096, | |
| 45 | read_buffer_size: usize = 4096 + if (disable_tls) 0 else std.crypto.tls.Client.min_buffer_len, | |
| 46 | 46 | /// Each `Connection` allocates this amount for the writer buffer. |
| 47 | 47 | write_buffer_size: usize = 1024, |
| 48 | 48 | |
| ... | ... | @@ -304,15 +304,16 @@ pub const Connection = struct { |
| 304 | 304 | const host_buffer = base[@sizeOf(Tls)..][0..remote_host.len]; |
| 305 | 305 | const tls_read_buffer = host_buffer.ptr[host_buffer.len..][0..client.tls_buffer_size]; |
| 306 | 306 | const tls_write_buffer = tls_read_buffer.ptr[tls_read_buffer.len..][0..client.tls_buffer_size]; |
| 307 | const socket_write_buffer = tls_write_buffer.ptr[tls_write_buffer.len..][0..client.write_buffer_size]; | |
| 308 | assert(base.ptr + alloc_len == socket_write_buffer.ptr + socket_write_buffer.len); | |
| 307 | const write_buffer = tls_write_buffer.ptr[tls_write_buffer.len..][0..client.write_buffer_size]; | |
| 308 | const read_buffer = write_buffer.ptr[write_buffer.len..][0..client.read_buffer_size]; | |
| 309 | assert(base.ptr + alloc_len == read_buffer.ptr + read_buffer.len); | |
| 309 | 310 | @memcpy(host_buffer, remote_host); |
| 310 | 311 | const tls: *Tls = @ptrCast(base); |
| 311 | 312 | tls.* = .{ |
| 312 | 313 | .connection = .{ |
| 313 | 314 | .client = client, |
| 314 | .stream_writer = stream.writer(socket_write_buffer), | |
| 315 | .stream_reader = stream.reader(&.{}), | |
| 315 | .stream_writer = stream.writer(tls_write_buffer), | |
| 316 | .stream_reader = stream.reader(tls_read_buffer), | |
| 316 | 317 | .pool_node = .{}, |
| 317 | 318 | .port = port, |
| 318 | 319 | .host_len = @intCast(remote_host.len), |
| ... | ... | @@ -328,8 +329,8 @@ pub const Connection = struct { |
| 328 | 329 | .host = .{ .explicit = remote_host }, |
| 329 | 330 | .ca = .{ .bundle = client.ca_bundle }, |
| 330 | 331 | .ssl_key_log = client.ssl_key_log, |
| 331 | .read_buffer = tls_read_buffer, | |
| 332 | .write_buffer = tls_write_buffer, | |
| 332 | .read_buffer = read_buffer, | |
| 333 | .write_buffer = write_buffer, | |
| 333 | 334 | // This is appropriate for HTTPS because the HTTP headers contain |
| 334 | 335 | // the content length which is used to detect truncation attacks. |
| 335 | 336 | .allow_truncation_attacks = true, |
| ... | ... | @@ -347,7 +348,8 @@ pub const Connection = struct { |
| 347 | 348 | } |
| 348 | 349 | |
| 349 | 350 | fn allocLen(client: *Client, host_len: usize) usize { |
| 350 | return @sizeOf(Tls) + host_len + client.tls_buffer_size + client.tls_buffer_size + client.write_buffer_size; | |
| 351 | return @sizeOf(Tls) + host_len + client.tls_buffer_size + client.tls_buffer_size + | |
| 352 | client.write_buffer_size + client.read_buffer_size; | |
| 351 | 353 | } |
| 352 | 354 | |
| 353 | 355 | fn host(tls: *Tls) []u8 { |
| ... | ... | @@ -356,6 +358,21 @@ pub const Connection = struct { |
| 356 | 358 | } |
| 357 | 359 | }; |
| 358 | 360 | |
| 361 | pub const ReadError = std.crypto.tls.Client.ReadError || std.net.Stream.ReadError; | |
| 362 | ||
| 363 | pub fn getReadError(c: *const Connection) ?ReadError { | |
| 364 | return switch (c.protocol) { | |
| 365 | .tls => { | |
| 366 | if (disable_tls) unreachable; | |
| 367 | const tls: *const Tls = @alignCast(@fieldParentPtr("connection", c)); | |
| 368 | return tls.client.read_err orelse c.stream_reader.getError(); | |
| 369 | }, | |
| 370 | .plain => { | |
| 371 | return c.stream_reader.getError(); | |
| 372 | }, | |
| 373 | }; | |
| 374 | } | |
| 375 | ||
| 359 | 376 | fn getStream(c: *Connection) net.Stream { |
| 360 | 377 | return c.stream_reader.getStream(); |
| 361 | 378 | } |
| ... | ... | @@ -434,7 +451,6 @@ pub const Connection = struct { |
| 434 | 451 | if (disable_tls) unreachable; |
| 435 | 452 | const tls: *Tls = @alignCast(@fieldParentPtr("connection", c)); |
| 436 | 453 | try tls.client.end(); |
| 437 | try tls.client.writer.flush(); | |
| 438 | 454 | } |
| 439 | 455 | try c.stream_writer.interface.flush(); |
| 440 | 456 | } |
| ... | ... | @@ -874,6 +890,7 @@ pub const Request = struct { |
| 874 | 890 | var bw = try sendBodyUnflushed(r, body); |
| 875 | 891 | bw.writer.end = body.len; |
| 876 | 892 | try bw.end(); |
| 893 | try r.connection.?.flush(); | |
| 877 | 894 | } |
| 878 | 895 | |
| 879 | 896 | /// Transfers the HTTP head over the connection, which is not flushed until |
| ... | ... | @@ -1063,6 +1080,9 @@ pub const Request = struct { |
| 1063 | 1080 | /// buffer capacity would be exceeded, `error.HttpRedirectLocationOversize` |
| 1064 | 1081 | /// is returned instead. This buffer may be empty if no redirects are to be |
| 1065 | 1082 | /// handled. |
| 1083 | /// | |
| 1084 | /// If this fails with `error.ReadFailed` then the `Connection.getReadError` | |
| 1085 | /// method of `r.connection` can be used to get more detailed information. | |
| 1066 | 1086 | pub fn receiveHead(r: *Request, redirect_buffer: []u8) ReceiveHeadError!Response { |
| 1067 | 1087 | var aux_buf = redirect_buffer; |
| 1068 | 1088 | while (true) { |
src/Package/Fetch.zig+9-3| ... | ... | @@ -998,15 +998,21 @@ fn initResource(f: *Fetch, uri: std.Uri, resource: *Resource, reader_buffer: []u |
| 998 | 998 | .buffer = reader_buffer, |
| 999 | 999 | } }; |
| 1000 | 1000 | const request = &resource.http_request.request; |
| 1001 | defer request.deinit(); | |
| 1001 | errdefer request.deinit(); | |
| 1002 | 1002 | |
| 1003 | 1003 | request.sendBodiless() catch |err| |
| 1004 | 1004 | return f.fail(f.location_tok, try eb.printString("HTTP request failed: {t}", .{err})); |
| 1005 | 1005 | |
| 1006 | 1006 | var redirect_buffer: [1024]u8 = undefined; |
| 1007 | 1007 | const response = &resource.http_request.response; |
| 1008 | response.* = request.receiveHead(&redirect_buffer) catch |err| | |
| 1009 | return f.fail(f.location_tok, try eb.printString("invalid HTTP response: {t}", .{err})); | |
| 1008 | response.* = request.receiveHead(&redirect_buffer) catch |err| switch (err) { | |
| 1009 | error.ReadFailed => { | |
| 1010 | return f.fail(f.location_tok, try eb.printString("HTTP response read failure: {t}", .{ | |
| 1011 | request.connection.?.getReadError().?, | |
| 1012 | })); | |
| 1013 | }, | |
| 1014 | else => |e| return f.fail(f.location_tok, try eb.printString("invalid HTTP response: {t}", .{e})), | |
| 1015 | }; | |
| 1010 | 1016 | |
| 1011 | 1017 | if (response.head.status != .ok) return f.fail(f.location_tok, try eb.printString( |
| 1012 | 1018 | "bad HTTP response code: '{d} {s}'", |