authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-02 18:24:13-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-02 18:27:38-07:00
log7178451d6258d9d04fdf03269478948643c39f02
treeaaad703aa158587ef6dee320bd1cf28e7a0f7b2b
parent9ca6d673457723548d8fe721b44499817ead1d2d

std.crypto.tls.Client: make close_notify optional

Although RFC 8446 states: > Each party MUST send a "close_notify" alert before closing its write > side of the connection In practice many servers do not do this. Also in practice, truncation attacks are thwarted at the application layer by comparing the amount of bytes received with the amount expected via the HTTP headers.

2 files changed, 26 insertions(+), 2 deletions(-)

lib/std/crypto/tls/Client.zig+16-2
......@@ -26,6 +26,14 @@ partial_ciphertext_end: u15,
2626/// When this is true, the stream may still not be at the end because there
2727/// may be data in `partially_read_buffer`.
2828received_close_notify: bool,
29/// By default, reaching the end-of-stream when reading from the server will
30/// cause `error.TlsConnectionTruncated` to be returned, unless a close_notify
31/// message has been received. By setting this flag to `true`, instead, the
32/// end-of-stream will be forwarded to the application layer above TLS.
33/// This makes the application vulnerable to truncation attacks unless the
34/// application layer itself verifies that the amount of data received equals
35/// the amount of data expected, such as HTTP with the Content-Length header.
36allow_truncation_attacks: bool = false,
2937application_cipher: tls.ApplicationCipher,
3038/// The size is enough to contain exactly one TLSCiphertext record.
3139/// This buffer is segmented into four parts:
......@@ -900,8 +908,14 @@ pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.os.iovec)
900908 const ask_iovecs = limitVecs(&ask_iovecs_buf, ask_len);
901909 const actual_read_len = try stream.readv(ask_iovecs);
902910 if (actual_read_len == 0) {
903 // This is either a truncation attack, or a bug in the server.
904 return error.TlsConnectionTruncated;
911 // This is either a truncation attack, a bug in the server, or an
912 // intentional omission of the close_notify message due to truncation
913 // detection handled above the TLS layer.
914 if (c.allow_truncation_attacks) {
915 c.received_close_notify = true;
916 } else {
917 return error.TlsConnectionTruncated;
918 }
905919 }
906920
907921 // There might be more bytes inside `in_stack_buffer` that need to be processed,
lib/std/http/Client.zig+10
......@@ -1,3 +1,7 @@
1//! This API is a barely-touched, barely-functional http client, just the
2//! absolute minimum thing I needed in order to test `std.crypto.tls`. Bear
3//! with me and I promise the API will become useful and streamlined.
4
15const std = @import("../std.zig");
26const assert = std.debug.assert;
37const http = std.http;
......@@ -10,6 +14,9 @@ headers: std.ArrayListUnmanaged(u8) = .{},
1014active_requests: usize = 0,
1115ca_bundle: std.crypto.Certificate.Bundle = .{},
1216
17/// TODO: emit error.UnexpectedEndOfStream or something like that when the read
18/// data does not match the content length. This is necessary since HTTPS disables
19/// close_notify protection on underlying TLS streams.
1320pub const Request = struct {
1421 client: *Client,
1522 stream: net.Stream,
......@@ -133,6 +140,9 @@ pub fn request(client: *Client, url: Url, options: Request.Options) !Request {
133140 .http => {},
134141 .https => {
135142 req.tls_client = try std.crypto.tls.Client.init(req.stream, client.ca_bundle, url.host);
143 // This is appropriate for HTTPS because the HTTP headers contain
144 // the content length which is used to detect truncation attacks.
145 req.tls_client.allow_truncation_attacks = true;
136146 },
137147 }
138148