Skip to content

Commit 7178451

Browse files
committed
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.
1 parent 9ca6d67 commit 7178451

2 files changed

Lines changed: 26 additions & 2 deletions

File tree

lib/std/crypto/tls/Client.zig

Lines changed: 16 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -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`.
2828
received_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.
36+
allow_truncation_attacks: bool = false,
2937
application_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

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
15
const std = @import("../std.zig");
26
const assert = std.debug.assert;
37
const http = std.http;
@@ -10,6 +14,9 @@ headers: std.ArrayListUnmanaged(u8) = .{},
1014
active_requests: usize = 0,
1115
ca_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.
1320
pub 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

0 commit comments

Comments
 (0)