authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-08-01 00:13:28-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-08-07 10:04:29-07:00
log28190cc4046e6faf87c09dd95cdceb09c5d82c7a
treee1e1e94494dccac30c4b6200c7980d9306f782b2
parent02908a2d8c0376fa2f9b793ac22d648632fde735

std.crypto.tls: rework for new std.Io API


4 files changed, 498 insertions(+), 945 deletions(-)

lib/std/Io/Reader.zig-25
......@@ -1306,31 +1306,6 @@ pub fn defaultRebase(r: *Reader, capacity: usize) RebaseError!void {
13061306 r.end = data.len;
13071307}
13081308
1309/// Advances the stream and decreases the size of the storage buffer by `n`,
1310/// returning the range of bytes no longer accessible by `r`.
1311///
1312/// This action can be undone by `restitute`.
1313///
1314/// Asserts there are at least `n` buffered bytes already.
1315///
1316/// Asserts that `r.seek` is zero, i.e. the buffer is in a rebased state.
1317pub fn steal(r: *Reader, n: usize) []u8 {
1318 assert(r.seek == 0);
1319 assert(n <= r.end);
1320 const stolen = r.buffer[0..n];
1321 r.buffer = r.buffer[n..];
1322 r.end -= n;
1323 return stolen;
1324}
1325
1326/// Expands the storage buffer, undoing the effects of `steal`
1327/// Assumes that `n` does not exceed the total number of stolen bytes.
1328pub fn restitute(r: *Reader, n: usize) void {
1329 r.buffer = (r.buffer.ptr - n)[0 .. r.buffer.len + n];
1330 r.end += n;
1331 r.seek += n;
1332}
1333
13341309test fixed {
13351310 var r: Reader = .fixed("a\x02");
13361311 try testing.expect((try r.takeByte()) == 'a');
lib/std/crypto/tls.zig+106-99
......@@ -49,8 +49,8 @@ pub const hello_retry_request_sequence = [32]u8{
4949};
5050
5151pub const close_notify_alert = [_]u8{
52 @intFromEnum(AlertLevel.warning),
53 @intFromEnum(AlertDescription.close_notify),
52 @intFromEnum(Alert.Level.warning),
53 @intFromEnum(Alert.Description.close_notify),
5454};
5555
5656pub const ProtocolVersion = enum(u16) {
......@@ -138,103 +138,108 @@ pub const ExtensionType = enum(u16) {
138138 _,
139139};
140140
141pub const AlertLevel = enum(u8) {
142 warning = 1,
143 fatal = 2,
144 _,
145};
141pub const Alert = struct {
142 level: Level,
143 description: Description,
146144
147pub const AlertDescription = enum(u8) {
148 pub const Error = error{
149 TlsAlertUnexpectedMessage,
150 TlsAlertBadRecordMac,
151 TlsAlertRecordOverflow,
152 TlsAlertHandshakeFailure,
153 TlsAlertBadCertificate,
154 TlsAlertUnsupportedCertificate,
155 TlsAlertCertificateRevoked,
156 TlsAlertCertificateExpired,
157 TlsAlertCertificateUnknown,
158 TlsAlertIllegalParameter,
159 TlsAlertUnknownCa,
160 TlsAlertAccessDenied,
161 TlsAlertDecodeError,
162 TlsAlertDecryptError,
163 TlsAlertProtocolVersion,
164 TlsAlertInsufficientSecurity,
165 TlsAlertInternalError,
166 TlsAlertInappropriateFallback,
167 TlsAlertMissingExtension,
168 TlsAlertUnsupportedExtension,
169 TlsAlertUnrecognizedName,
170 TlsAlertBadCertificateStatusResponse,
171 TlsAlertUnknownPskIdentity,
172 TlsAlertCertificateRequired,
173 TlsAlertNoApplicationProtocol,
174 TlsAlertUnknown,
145 pub const Level = enum(u8) {
146 warning = 1,
147 fatal = 2,
148 _,
175149 };
176150
177 close_notify = 0,
178 unexpected_message = 10,
179 bad_record_mac = 20,
180 record_overflow = 22,
181 handshake_failure = 40,
182 bad_certificate = 42,
183 unsupported_certificate = 43,
184 certificate_revoked = 44,
185 certificate_expired = 45,
186 certificate_unknown = 46,
187 illegal_parameter = 47,
188 unknown_ca = 48,
189 access_denied = 49,
190 decode_error = 50,
191 decrypt_error = 51,
192 protocol_version = 70,
193 insufficient_security = 71,
194 internal_error = 80,
195 inappropriate_fallback = 86,
196 user_canceled = 90,
197 missing_extension = 109,
198 unsupported_extension = 110,
199 unrecognized_name = 112,
200 bad_certificate_status_response = 113,
201 unknown_psk_identity = 115,
202 certificate_required = 116,
203 no_application_protocol = 120,
204 _,
151 pub const Description = enum(u8) {
152 pub const Error = error{
153 TlsAlertUnexpectedMessage,
154 TlsAlertBadRecordMac,
155 TlsAlertRecordOverflow,
156 TlsAlertHandshakeFailure,
157 TlsAlertBadCertificate,
158 TlsAlertUnsupportedCertificate,
159 TlsAlertCertificateRevoked,
160 TlsAlertCertificateExpired,
161 TlsAlertCertificateUnknown,
162 TlsAlertIllegalParameter,
163 TlsAlertUnknownCa,
164 TlsAlertAccessDenied,
165 TlsAlertDecodeError,
166 TlsAlertDecryptError,
167 TlsAlertProtocolVersion,
168 TlsAlertInsufficientSecurity,
169 TlsAlertInternalError,
170 TlsAlertInappropriateFallback,
171 TlsAlertMissingExtension,
172 TlsAlertUnsupportedExtension,
173 TlsAlertUnrecognizedName,
174 TlsAlertBadCertificateStatusResponse,
175 TlsAlertUnknownPskIdentity,
176 TlsAlertCertificateRequired,
177 TlsAlertNoApplicationProtocol,
178 TlsAlertUnknown,
179 };
205180
206 pub fn toError(alert: AlertDescription) Error!void {
207 switch (alert) {
208 .close_notify => {}, // not an error
209 .unexpected_message => return error.TlsAlertUnexpectedMessage,
210 .bad_record_mac => return error.TlsAlertBadRecordMac,
211 .record_overflow => return error.TlsAlertRecordOverflow,
212 .handshake_failure => return error.TlsAlertHandshakeFailure,
213 .bad_certificate => return error.TlsAlertBadCertificate,
214 .unsupported_certificate => return error.TlsAlertUnsupportedCertificate,
215 .certificate_revoked => return error.TlsAlertCertificateRevoked,
216 .certificate_expired => return error.TlsAlertCertificateExpired,
217 .certificate_unknown => return error.TlsAlertCertificateUnknown,
218 .illegal_parameter => return error.TlsAlertIllegalParameter,
219 .unknown_ca => return error.TlsAlertUnknownCa,
220 .access_denied => return error.TlsAlertAccessDenied,
221 .decode_error => return error.TlsAlertDecodeError,
222 .decrypt_error => return error.TlsAlertDecryptError,
223 .protocol_version => return error.TlsAlertProtocolVersion,
224 .insufficient_security => return error.TlsAlertInsufficientSecurity,
225 .internal_error => return error.TlsAlertInternalError,
226 .inappropriate_fallback => return error.TlsAlertInappropriateFallback,
227 .user_canceled => {}, // not an error
228 .missing_extension => return error.TlsAlertMissingExtension,
229 .unsupported_extension => return error.TlsAlertUnsupportedExtension,
230 .unrecognized_name => return error.TlsAlertUnrecognizedName,
231 .bad_certificate_status_response => return error.TlsAlertBadCertificateStatusResponse,
232 .unknown_psk_identity => return error.TlsAlertUnknownPskIdentity,
233 .certificate_required => return error.TlsAlertCertificateRequired,
234 .no_application_protocol => return error.TlsAlertNoApplicationProtocol,
235 _ => return error.TlsAlertUnknown,
181 close_notify = 0,
182 unexpected_message = 10,
183 bad_record_mac = 20,
184 record_overflow = 22,
185 handshake_failure = 40,
186 bad_certificate = 42,
187 unsupported_certificate = 43,
188 certificate_revoked = 44,
189 certificate_expired = 45,
190 certificate_unknown = 46,
191 illegal_parameter = 47,
192 unknown_ca = 48,
193 access_denied = 49,
194 decode_error = 50,
195 decrypt_error = 51,
196 protocol_version = 70,
197 insufficient_security = 71,
198 internal_error = 80,
199 inappropriate_fallback = 86,
200 user_canceled = 90,
201 missing_extension = 109,
202 unsupported_extension = 110,
203 unrecognized_name = 112,
204 bad_certificate_status_response = 113,
205 unknown_psk_identity = 115,
206 certificate_required = 116,
207 no_application_protocol = 120,
208 _,
209
210 pub fn toError(description: Description) Error!void {
211 switch (description) {
212 .close_notify => {}, // not an error
213 .unexpected_message => return error.TlsAlertUnexpectedMessage,
214 .bad_record_mac => return error.TlsAlertBadRecordMac,
215 .record_overflow => return error.TlsAlertRecordOverflow,
216 .handshake_failure => return error.TlsAlertHandshakeFailure,
217 .bad_certificate => return error.TlsAlertBadCertificate,
218 .unsupported_certificate => return error.TlsAlertUnsupportedCertificate,
219 .certificate_revoked => return error.TlsAlertCertificateRevoked,
220 .certificate_expired => return error.TlsAlertCertificateExpired,
221 .certificate_unknown => return error.TlsAlertCertificateUnknown,
222 .illegal_parameter => return error.TlsAlertIllegalParameter,
223 .unknown_ca => return error.TlsAlertUnknownCa,
224 .access_denied => return error.TlsAlertAccessDenied,
225 .decode_error => return error.TlsAlertDecodeError,
226 .decrypt_error => return error.TlsAlertDecryptError,
227 .protocol_version => return error.TlsAlertProtocolVersion,
228 .insufficient_security => return error.TlsAlertInsufficientSecurity,
229 .internal_error => return error.TlsAlertInternalError,
230 .inappropriate_fallback => return error.TlsAlertInappropriateFallback,
231 .user_canceled => {}, // not an error
232 .missing_extension => return error.TlsAlertMissingExtension,
233 .unsupported_extension => return error.TlsAlertUnsupportedExtension,
234 .unrecognized_name => return error.TlsAlertUnrecognizedName,
235 .bad_certificate_status_response => return error.TlsAlertBadCertificateStatusResponse,
236 .unknown_psk_identity => return error.TlsAlertUnknownPskIdentity,
237 .certificate_required => return error.TlsAlertCertificateRequired,
238 .no_application_protocol => return error.TlsAlertNoApplicationProtocol,
239 _ => return error.TlsAlertUnknown,
240 }
236241 }
237 }
242 };
238243};
239244
240245pub const SignatureScheme = enum(u16) {
......@@ -650,7 +655,7 @@ pub const Decoder = struct {
650655 }
651656
652657 /// Use this function to increase `their_end`.
653 pub fn readAtLeast(d: *Decoder, stream: anytype, their_amt: usize) !void {
658 pub fn readAtLeast(d: *Decoder, stream: *std.io.Reader, their_amt: usize) !void {
654659 assert(!d.disable_reads);
655660 const existing_amt = d.cap - d.idx;
656661 d.their_end = d.idx + their_amt;
......@@ -658,14 +663,16 @@ pub const Decoder = struct {
658663 const request_amt = their_amt - existing_amt;
659664 const dest = d.buf[d.cap..];
660665 if (request_amt > dest.len) return error.TlsRecordOverflow;
661 const actual_amt = try stream.readAtLeast(dest, request_amt);
662 if (actual_amt < request_amt) return error.TlsConnectionTruncated;
663 d.cap += actual_amt;
666 stream.readSlice(dest[0..request_amt]) catch |err| switch (err) {
667 error.EndOfStream => return error.TlsConnectionTruncated,
668 error.ReadFailed => return error.ReadFailed,
669 };
670 d.cap += request_amt;
664671 }
665672
666673 /// Same as `readAtLeast` but also increases `our_end` by exactly `our_amt`.
667674 /// Use when `our_amt` is calculated by us, not by them.
668 pub fn readAtLeastOurAmt(d: *Decoder, stream: anytype, our_amt: usize) !void {
675 pub fn readAtLeastOurAmt(d: *Decoder, stream: *std.io.Reader, our_amt: usize) !void {
669676 assert(!d.disable_reads);
670677 try readAtLeast(d, stream, our_amt);
671678 d.our_end = d.idx + our_amt;
lib/std/crypto/tls/Client.zig+387-807
......@@ -1,11 +1,15 @@
1const builtin = @import("builtin");
2const native_endian = builtin.cpu.arch.endian();
3
14const std = @import("../../std.zig");
25const tls = std.crypto.tls;
36const Client = @This();
4const net = std.net;
57const mem = std.mem;
68const crypto = std.crypto;
79const assert = std.debug.assert;
810const Certificate = std.crypto.Certificate;
11const Reader = std.io.Reader;
12const Writer = std.io.Writer;
913
1014const max_ciphertext_len = tls.max_ciphertext_len;
1115const hmacExpandLabel = tls.hmacExpandLabel;
......@@ -13,44 +17,58 @@ const hkdfExpandLabel = tls.hkdfExpandLabel;
1317const int = tls.int;
1418const array = tls.array;
1519
20/// The encrypted stream from the server to the client. Bytes are pulled from
21/// here via `reader`.
22///
23/// The buffer is asserted to have capacity at least `min_buffer_len`.
24input: *Reader,
25/// Decrypted stream from the server to the client.
26reader: Reader,
27
28/// The encrypted stream from the client to the server. Bytes are pushed here
29/// via `writer`.
30output: *Writer,
31/// The plaintext stream from the client to the server.
32writer: Writer,
33
34/// Populated when `error.TlsAlert` is returned.
35alert: ?tls.Alert = null,
36read_err: ?ReadError = null,
1637tls_version: tls.ProtocolVersion,
1738read_seq: u64,
1839write_seq: u64,
19/// The starting index of cleartext bytes inside `partially_read_buffer`.
20partial_cleartext_idx: u15,
21/// The ending index of cleartext bytes inside `partially_read_buffer` as well
22/// as the starting index of ciphertext bytes.
23partial_ciphertext_idx: u15,
24/// The ending index of ciphertext bytes inside `partially_read_buffer`.
25partial_ciphertext_end: u15,
2640/// When this is true, the stream may still not be at the end because there
27/// may be data in `partially_read_buffer`.
41/// may be data in the input buffer.
2842received_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.
3643allow_truncation_attacks: bool,
3744application_cipher: tls.ApplicationCipher,
38/// The size is enough to contain exactly one TLSCiphertext record.
39/// This buffer is segmented into four parts:
40/// 0. unused
41/// 1. cleartext
42/// 2. ciphertext
43/// 3. unused
44/// The fields `partial_cleartext_idx`, `partial_ciphertext_idx`, and
45/// `partial_ciphertext_end` describe the span of the segments.
46partially_read_buffer: [tls.max_ciphertext_record_len]u8,
47/// If non-null, ssl secrets are logged to a file. Creating such a log file allows other
48/// programs with access to that file to decrypt all traffic over this connection.
49ssl_key_log: ?struct {
45
46/// If non-null, ssl secrets are logged to a stream. Creating such a log file
47/// allows other programs with access to that file to decrypt all traffic over
48/// this connection.
49ssl_key_log: ?*SslKeyLog,
50
51pub const ReadError = error{
52 /// The alert description will be stored in `alert`.
53 TlsAlert,
54 TlsBadLength,
55 TlsBadRecordMac,
56 TlsConnectionTruncated,
57 TlsDecodeError,
58 TlsRecordOverflow,
59 TlsUnexpectedMessage,
60 TlsIllegalParameter,
61 TlsSequenceOverflow,
62 /// The buffer provided to the read function was not at least
63 /// `min_buffer_len`.
64 OutputBufferUndersize,
65};
66
67pub const SslKeyLog = struct {
5068 client_key_seq: u64,
5169 server_key_seq: u64,
5270 client_random: [32]u8,
53 file: std.fs.File,
71 writer: *Writer,
5472
5573 fn clientCounter(key_log: *@This()) u64 {
5674 defer key_log.client_key_seq += 1;
......@@ -61,51 +79,12 @@ ssl_key_log: ?struct {
6179 defer key_log.server_key_seq += 1;
6280 return key_log.server_key_seq;
6381 }
64},
65
66/// This is an example of the type that is needed by the read and write
67/// functions. It can have any fields but it must at least have these
68/// functions.
69///
70/// Note that `std.net.Stream` conforms to this interface.
71///
72/// This declaration serves as documentation only.
73pub const StreamInterface = struct {
74 /// Can be any error set.
75 pub const ReadError = error{};
76
77 /// Returns the number of bytes read. The number read may be less than the
78 /// buffer space provided. End-of-stream is indicated by a return value of 0.
79 ///
80 /// The `iovecs` parameter is mutable because so that function may to
81 /// mutate the fields in order to handle partial reads from the underlying
82 /// stream layer.
83 pub fn readv(this: @This(), iovecs: []std.posix.iovec) ReadError!usize {
84 _ = .{ this, iovecs };
85 @panic("unimplemented");
86 }
87
88 /// Can be any error set.
89 pub const WriteError = error{};
90
91 /// Returns the number of bytes read, which may be less than the buffer
92 /// space provided. A short read does not indicate end-of-stream.
93 pub fn writev(this: @This(), iovecs: []const std.posix.iovec_const) WriteError!usize {
94 _ = .{ this, iovecs };
95 @panic("unimplemented");
96 }
97
98 /// Returns the number of bytes read, which may be less than the buffer
99 /// space provided, indicating end-of-stream.
100 /// The `iovecs` parameter is mutable in case this function needs to mutate
101 /// the fields in order to handle partial writes from the underlying layer.
102 pub fn writevAll(this: @This(), iovecs: []std.posix.iovec_const) WriteError!usize {
103 // This can be implemented in terms of writev, or specialized if desired.
104 _ = .{ this, iovecs };
105 @panic("unimplemented");
106 }
10782};
10883
84/// The `Reader` supplied to `init` requires a buffer capacity
85/// at least this amount.
86pub const min_buffer_len = tls.max_ciphertext_record_len;
87
10988pub const Options = struct {
11089 /// How to perform host verification of server certificates.
11190 host: union(enum) {
......@@ -127,64 +106,85 @@ pub const Options = struct {
127106 /// Verify that the server certificate is authorized by a given ca bundle.
128107 bundle: Certificate.Bundle,
129108 },
130 /// If non-null, ssl secrets are logged to this file. Creating such a log file allows
109 /// If non-null, ssl secrets are logged to this stream. Creating such a log file allows
131110 /// other programs with access to that file to decrypt all traffic over this connection.
132 ssl_key_log_file: ?std.fs.File = null,
111 ///
112 /// Only the `writer` field is observed during the handshake (`init`).
113 /// After that, the other fields are populated.
114 ssl_key_log: ?*SslKeyLog = null,
115 /// By default, reaching the end-of-stream when reading from the server will
116 /// cause `error.TlsConnectionTruncated` to be returned, unless a close_notify
117 /// message has been received. By setting this flag to `true`, instead, the
118 /// end-of-stream will be forwarded to the application layer above TLS.
119 ///
120 /// This makes the application vulnerable to truncation attacks unless the
121 /// application layer itself verifies that the amount of data received equals
122 /// the amount of data expected, such as HTTP with the Content-Length header.
123 allow_truncation_attacks: bool = false,
124 write_buffer: []u8,
125 /// Asserted to have capacity at least `min_buffer_len`.
126 read_buffer: []u8,
127 /// Populated when `error.TlsAlert` is returned from `init`.
128 alert: ?*tls.Alert = null,
133129};
134130
135pub fn InitError(comptime Stream: type) type {
136 return std.mem.Allocator.Error || Stream.WriteError || Stream.ReadError || tls.AlertDescription.Error || error{
137 InsufficientEntropy,
138 DiskQuota,
139 LockViolation,
140 NotOpenForWriting,
141 TlsUnexpectedMessage,
142 TlsIllegalParameter,
143 TlsDecryptFailure,
144 TlsRecordOverflow,
145 TlsBadRecordMac,
146 CertificateFieldHasInvalidLength,
147 CertificateHostMismatch,
148 CertificatePublicKeyInvalid,
149 CertificateExpired,
150 CertificateFieldHasWrongDataType,
151 CertificateIssuerMismatch,
152 CertificateNotYetValid,
153 CertificateSignatureAlgorithmMismatch,
154 CertificateSignatureAlgorithmUnsupported,
155 CertificateSignatureInvalid,
156 CertificateSignatureInvalidLength,
157 CertificateSignatureNamedCurveUnsupported,
158 CertificateSignatureUnsupportedBitCount,
159 TlsCertificateNotVerified,
160 TlsBadSignatureScheme,
161 TlsBadRsaSignatureBitCount,
162 InvalidEncoding,
163 IdentityElement,
164 SignatureVerificationFailed,
165 TlsDecryptError,
166 TlsConnectionTruncated,
167 TlsDecodeError,
168 UnsupportedCertificateVersion,
169 CertificateTimeInvalid,
170 CertificateHasUnrecognizedObjectId,
171 CertificateHasInvalidBitString,
172 MessageTooLong,
173 NegativeIntoUnsigned,
174 TargetTooSmall,
175 BufferTooSmall,
176 InvalidSignature,
177 NotSquare,
178 NonCanonical,
179 WeakPublicKey,
180 };
181}
131const InitError = error{
132 WriteFailed,
133 ReadFailed,
134 InsufficientEntropy,
135 DiskQuota,
136 LockViolation,
137 NotOpenForWriting,
138 /// The alert description will be stored in `alert`.
139 TlsAlert,
140 TlsUnexpectedMessage,
141 TlsIllegalParameter,
142 TlsDecryptFailure,
143 TlsRecordOverflow,
144 TlsBadRecordMac,
145 CertificateFieldHasInvalidLength,
146 CertificateHostMismatch,
147 CertificatePublicKeyInvalid,
148 CertificateExpired,
149 CertificateFieldHasWrongDataType,
150 CertificateIssuerMismatch,
151 CertificateNotYetValid,
152 CertificateSignatureAlgorithmMismatch,
153 CertificateSignatureAlgorithmUnsupported,
154 CertificateSignatureInvalid,
155 CertificateSignatureInvalidLength,
156 CertificateSignatureNamedCurveUnsupported,
157 CertificateSignatureUnsupportedBitCount,
158 TlsCertificateNotVerified,
159 TlsBadSignatureScheme,
160 TlsBadRsaSignatureBitCount,
161 InvalidEncoding,
162 IdentityElement,
163 SignatureVerificationFailed,
164 TlsDecryptError,
165 TlsConnectionTruncated,
166 TlsDecodeError,
167 UnsupportedCertificateVersion,
168 CertificateTimeInvalid,
169 CertificateHasUnrecognizedObjectId,
170 CertificateHasInvalidBitString,
171 MessageTooLong,
172 NegativeIntoUnsigned,
173 TargetTooSmall,
174 BufferTooSmall,
175 InvalidSignature,
176 NotSquare,
177 NonCanonical,
178 WeakPublicKey,
179};
182180
183/// Initiates a TLS handshake and establishes a TLSv1.2 or TLSv1.3 session with `stream`, which
184/// must conform to `StreamInterface`.
181/// Initiates a TLS handshake and establishes a TLSv1.2 or TLSv1.3 session.
185182///
186183/// `host` is only borrowed during this function call.
187pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client {
184///
185/// `input` is asserted to have buffer capacity at least `min_buffer_len`.
186pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client {
187 assert(input.buffer.len >= min_buffer_len);
188188 const host = switch (options.host) {
189189 .no_verification => "",
190190 .explicit => |host| host,
......@@ -276,11 +276,8 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client
276276 };
277277
278278 {
279 var iovecs = [_]std.posix.iovec_const{
280 .{ .base = cleartext_header.ptr, .len = cleartext_header.len },
281 .{ .base = host.ptr, .len = host.len },
282 };
283 try stream.writevAll(iovecs[0..if (host.len == 0) 1 else 2]);
279 var iovecs: [2][]const u8 = .{ cleartext_header, host };
280 try output.writeVecAll(iovecs[0..if (host.len == 0) 1 else 2]);
284281 }
285282
286283 var tls_version: tls.ProtocolVersion = undefined;
......@@ -329,20 +326,26 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client
329326 var cleartext_fragment_start: usize = 0;
330327 var cleartext_fragment_end: usize = 0;
331328 var cleartext_bufs: [2][tls.max_ciphertext_inner_record_len]u8 = undefined;
332 var handshake_buffer: [tls.max_ciphertext_record_len]u8 = undefined;
333 var d: tls.Decoder = .{ .buf = &handshake_buffer };
334329 fragment: while (true) {
335 try d.readAtLeastOurAmt(stream, tls.record_header_len);
336 const record_header = d.buf[d.idx..][0..tls.record_header_len];
337 const record_ct = d.decode(tls.ContentType);
338 d.skip(2); // legacy_version
339 const record_len = d.decode(u16);
340 try d.readAtLeast(stream, record_len);
341 var record_decoder = try d.sub(record_len);
330 // Ensure the input buffer pointer is stable in this scope.
331 input.rebaseCapacity(tls.max_ciphertext_record_len);
332 const record_header = input.peek(tls.record_header_len) catch |err| switch (err) {
333 error.EndOfStream => return error.TlsConnectionTruncated,
334 error.ReadFailed => return error.ReadFailed,
335 };
336 const record_ct = input.takeEnumNonexhaustive(tls.ContentType, .big) catch unreachable; // already peeked
337 input.toss(2); // legacy_version
338 const record_len = input.takeInt(u16, .big) catch unreachable; // already peeked
339 if (record_len > tls.max_ciphertext_len) return error.TlsRecordOverflow;
340 const record_buffer = input.take(record_len) catch |err| switch (err) {
341 error.EndOfStream => return error.TlsConnectionTruncated,
342 error.ReadFailed => return error.ReadFailed,
343 };
344 var record_decoder: tls.Decoder = .fromTheirSlice(record_buffer);
342345 var ctd, const ct = content: switch (cipher_state) {
343346 .cleartext => .{ record_decoder, record_ct },
344347 .handshake => {
345 std.debug.assert(tls_version == .tls_1_3);
348 assert(tls_version == .tls_1_3);
346349 if (record_ct != .application_data) return error.TlsUnexpectedMessage;
347350 try record_decoder.ensure(record_len);
348351 const cleartext_buf = &cleartext_bufs[cert_buf_index % 2];
......@@ -374,7 +377,7 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client
374377 break :content .{ tls.Decoder.fromTheirSlice(@constCast(cleartext_buf[cleartext_fragment_start..cleartext_fragment_end])), ct };
375378 },
376379 .application => {
377 std.debug.assert(tls_version == .tls_1_2);
380 assert(tls_version == .tls_1_2);
378381 if (record_ct != .handshake) return error.TlsUnexpectedMessage;
379382 try record_decoder.ensure(record_len);
380383 const cleartext_buf = &cleartext_bufs[cert_buf_index % 2];
......@@ -412,14 +415,11 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client
412415 switch (ct) {
413416 .alert => {
414417 ctd.ensure(2) catch continue :fragment;
415 const level = ctd.decode(tls.AlertLevel);
416 const desc = ctd.decode(tls.AlertDescription);
417 _ = level;
418
419 // if this isn't a error alert, then it's a closure alert, which makes no sense in a handshake
420 try desc.toError();
421 // TODO: handle server-side closures
422 return error.TlsUnexpectedMessage;
418 if (options.alert) |a| a.* = .{
419 .level = ctd.decode(tls.Alert.Level),
420 .description = ctd.decode(tls.Alert.Description),
421 };
422 return error.TlsAlert;
423423 },
424424 .change_cipher_spec => {
425425 ctd.ensure(1) catch continue :fragment;
......@@ -533,7 +533,7 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client
533533 pv.master_secret = P.Hkdf.extract(&ap_derived_secret, &zeroes);
534534 const client_secret = hkdfExpandLabel(P.Hkdf, pv.handshake_secret, "c hs traffic", &hello_hash, P.Hash.digest_length);
535535 const server_secret = hkdfExpandLabel(P.Hkdf, pv.handshake_secret, "s hs traffic", &hello_hash, P.Hash.digest_length);
536 if (options.ssl_key_log_file) |key_log_file| logSecrets(key_log_file, .{
536 if (options.ssl_key_log) |key_log| logSecrets(key_log.writer, .{
537537 .client_random = &client_hello_rand,
538538 }, .{
539539 .SERVER_HANDSHAKE_TRAFFIC_SECRET = &server_secret,
......@@ -707,7 +707,7 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client
707707 &client_hello_rand,
708708 &server_hello_rand,
709709 }, 48);
710 if (options.ssl_key_log_file) |key_log_file| logSecrets(key_log_file, .{
710 if (options.ssl_key_log) |key_log| logSecrets(key_log.writer, .{
711711 .client_random = &client_hello_rand,
712712 }, .{
713713 .CLIENT_RANDOM = &master_secret,
......@@ -755,11 +755,12 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client
755755 nonce,
756756 pv.app_cipher.client_write_key,
757757 );
758 const all_msgs = client_key_exchange_msg ++ client_change_cipher_spec_msg ++ client_verify_msg;
759 var all_msgs_vec = [_]std.posix.iovec_const{
760 .{ .base = &all_msgs, .len = all_msgs.len },
758 var all_msgs_vec: [3][]const u8 = .{
759 &client_key_exchange_msg,
760 &client_change_cipher_spec_msg,
761 &client_verify_msg,
761762 };
762 try stream.writevAll(&all_msgs_vec);
763 try output.writeVecAll(&all_msgs_vec);
763764 },
764765 }
765766 write_seq += 1;
......@@ -820,15 +821,15 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client
820821 const nonce = pv.client_handshake_iv;
821822 P.AEAD.encrypt(ciphertext, auth_tag, &out_cleartext, ad, nonce, pv.client_handshake_key);
822823
823 const all_msgs = client_change_cipher_spec_msg ++ finished_msg;
824 var all_msgs_vec = [_]std.posix.iovec_const{
825 .{ .base = &all_msgs, .len = all_msgs.len },
824 var all_msgs_vec: [2][]const u8 = .{
825 &client_change_cipher_spec_msg,
826 &finished_msg,
826827 };
827 try stream.writevAll(&all_msgs_vec);
828 try output.writeVecAll(&all_msgs_vec);
828829
829830 const client_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "c ap traffic", &handshake_hash, P.Hash.digest_length);
830831 const server_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "s ap traffic", &handshake_hash, P.Hash.digest_length);
831 if (options.ssl_key_log_file) |key_log_file| logSecrets(key_log_file, .{
832 if (options.ssl_key_log) |key_log| logSecrets(key_log.writer, .{
832833 .counter = key_seq,
833834 .client_random = &client_hello_rand,
834835 }, .{
......@@ -855,8 +856,28 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client
855856 else => unreachable,
856857 },
857858 };
858 const leftover = d.rest();
859 var client: Client = .{
859 if (options.ssl_key_log) |ssl_key_log| ssl_key_log.* = .{
860 .client_key_seq = key_seq,
861 .server_key_seq = key_seq,
862 .client_random = client_hello_rand,
863 .writer = ssl_key_log.writer,
864 };
865 return .{
866 .input = input,
867 .reader = .{
868 .buffer = options.read_buffer,
869 .vtable = &.{ .stream = stream },
870 .seek = 0,
871 .end = 0,
872 },
873 .output = output,
874 .writer = .{
875 .buffer = options.write_buffer,
876 .vtable = &.{
877 .drain = drain,
878 .sendFile = Writer.unimplementedSendFile,
879 },
880 },
860881 .tls_version = tls_version,
861882 .read_seq = switch (tls_version) {
862883 .tls_1_3 => 0,
......@@ -868,22 +889,11 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client
868889 .tls_1_2 => write_seq,
869890 else => unreachable,
870891 },
871 .partial_cleartext_idx = 0,
872 .partial_ciphertext_idx = 0,
873 .partial_ciphertext_end = @intCast(leftover.len),
874892 .received_close_notify = false,
875 .allow_truncation_attacks = false,
893 .allow_truncation_attacks = options.allow_truncation_attacks,
876894 .application_cipher = app_cipher,
877 .partially_read_buffer = undefined,
878 .ssl_key_log = if (options.ssl_key_log_file) |key_log_file| .{
879 .client_key_seq = key_seq,
880 .server_key_seq = key_seq,
881 .client_random = client_hello_rand,
882 .file = key_log_file,
883 } else null,
895 .ssl_key_log = options.ssl_key_log,
884896 };
885 @memcpy(client.partially_read_buffer[0..leftover.len], leftover);
886 return client;
887897 },
888898 else => return error.TlsUnexpectedMessage,
889899 }
......@@ -897,94 +907,48 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client
897907 }
898908}
899909
900/// Sends TLS-encrypted data to `stream`, which must conform to `StreamInterface`.
901/// Returns the number of cleartext bytes sent, which may be fewer than `bytes.len`.
902pub fn write(c: *Client, stream: anytype, bytes: []const u8) !usize {
903 return writeEnd(c, stream, bytes, false);
904}
905
906/// Sends TLS-encrypted data to `stream`, which must conform to `StreamInterface`.
907pub fn writeAll(c: *Client, stream: anytype, bytes: []const u8) !void {
908 var index: usize = 0;
909 while (index < bytes.len) {
910 index += try c.write(stream, bytes[index..]);
911 }
912}
913
914/// Sends TLS-encrypted data to `stream`, which must conform to `StreamInterface`.
915/// If `end` is true, then this function additionally sends a `close_notify` alert,
916/// which is necessary for the server to distinguish between a properly finished
917/// TLS session, or a truncation attack.
918pub fn writeAllEnd(c: *Client, stream: anytype, bytes: []const u8, end: bool) !void {
919 var index: usize = 0;
920 while (index < bytes.len) {
921 index += try c.writeEnd(stream, bytes[index..], end);
910fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize {
911 const c: *Client = @fieldParentPtr("writer", w);
912 if (true) @panic("update to use the buffer and flush");
913 const sliced_data = if (splat == 0) data[0..data.len -| 1] else data;
914 const output = c.output;
915 const ciphertext_buf = try output.writableSliceGreedy(min_buffer_len);
916 var total_clear: usize = 0;
917 var ciphertext_end: usize = 0;
918 for (sliced_data) |buf| {
919 const prepared = prepareCiphertextRecord(c, ciphertext_buf[ciphertext_end..], buf, .application_data);
920 total_clear += prepared.cleartext_len;
921 ciphertext_end += prepared.ciphertext_end;
922 if (total_clear < buf.len) break;
922923 }
924 output.advance(ciphertext_end);
925 return total_clear;
923926}
924927
925/// Sends TLS-encrypted data to `stream`, which must conform to `StreamInterface`.
926/// Returns the number of cleartext bytes sent, which may be fewer than `bytes.len`.
927/// If `end` is true, then this function additionally sends a `close_notify` alert,
928/// which is necessary for the server to distinguish between a properly finished
929/// TLS session, or a truncation attack.
930pub fn writeEnd(c: *Client, stream: anytype, bytes: []const u8, end: bool) !usize {
931 var ciphertext_buf: [tls.max_ciphertext_record_len * 4]u8 = undefined;
932 var iovecs_buf: [6]std.posix.iovec_const = undefined;
933 var prepared = prepareCiphertextRecord(c, &iovecs_buf, &ciphertext_buf, bytes, .application_data);
934 if (end) {
935 prepared.iovec_end += prepareCiphertextRecord(
936 c,
937 iovecs_buf[prepared.iovec_end..],
938 ciphertext_buf[prepared.ciphertext_end..],
939 &tls.close_notify_alert,
940 .alert,
941 ).iovec_end;
942 }
943
944 const iovec_end = prepared.iovec_end;
945 const overhead_len = prepared.overhead_len;
946
947 // Ideally we would call writev exactly once here, however, we must ensure
948 // that we don't return with a record partially written.
949 var i: usize = 0;
950 var total_amt: usize = 0;
951 while (true) {
952 var amt = try stream.writev(iovecs_buf[i..iovec_end]);
953 while (amt >= iovecs_buf[i].len) {
954 const encrypted_amt = iovecs_buf[i].len;
955 total_amt += encrypted_amt - overhead_len;
956 amt -= encrypted_amt;
957 i += 1;
958 // Rely on the property that iovecs delineate records, meaning that
959 // if amt equals zero here, we have fortunately found ourselves
960 // with a short read that aligns at the record boundary.
961 if (i >= iovec_end) return total_amt;
962 // We also cannot return on a vector boundary if the final close_notify is
963 // not sent; otherwise the caller would not know to retry the call.
964 if (amt == 0 and (!end or i < iovec_end - 1)) return total_amt;
965 }
966 iovecs_buf[i].base += amt;
967 iovecs_buf[i].len -= amt;
968 }
928/// Sends a `close_notify` alert, which is necessary for the server to
929/// distinguish between a properly finished TLS session, or a truncation
930/// attack.
931pub fn end(c: *Client) Writer.Error!void {
932 const output = c.output;
933 const ciphertext_buf = try output.writableSliceGreedy(min_buffer_len);
934 const prepared = prepareCiphertextRecord(c, ciphertext_buf, &tls.close_notify_alert, .alert);
935 output.advance(prepared.cleartext_len);
936 return prepared.ciphertext_end;
969937}
970938
971939fn prepareCiphertextRecord(
972940 c: *Client,
973 iovecs: []std.posix.iovec_const,
974941 ciphertext_buf: []u8,
975942 bytes: []const u8,
976943 inner_content_type: tls.ContentType,
977944) struct {
978 iovec_end: usize,
979945 ciphertext_end: usize,
980 /// How many bytes are taken up by overhead per record.
981 overhead_len: usize,
946 cleartext_len: usize,
982947} {
983948 // Due to the trailing inner content type byte in the ciphertext, we need
984949 // an additional buffer for storing the cleartext into before encrypting.
985950 var cleartext_buf: [max_ciphertext_len]u8 = undefined;
986951 var ciphertext_end: usize = 0;
987 var iovec_end: usize = 0;
988952 var bytes_i: usize = 0;
989953 switch (c.application_cipher) {
990954 inline else => |*p| switch (c.tls_version) {
......@@ -992,18 +956,15 @@ fn prepareCiphertextRecord(
992956 const pv = &p.tls_1_3;
993957 const P = @TypeOf(p.*);
994958 const overhead_len = tls.record_header_len + P.AEAD.tag_length + 1;
995 const close_notify_alert_reserved = tls.close_notify_alert.len + overhead_len;
996959 while (true) {
997960 const encrypted_content_len: u16 = @min(
998961 bytes.len - bytes_i,
999962 tls.max_ciphertext_inner_record_len,
1000 ciphertext_buf.len -|
1001 (close_notify_alert_reserved + overhead_len + ciphertext_end),
963 ciphertext_buf.len -| (overhead_len + ciphertext_end),
1002964 );
1003965 if (encrypted_content_len == 0) return .{
1004 .iovec_end = iovec_end,
1005966 .ciphertext_end = ciphertext_end,
1006 .overhead_len = overhead_len,
967 .cleartext_len = bytes_i,
1007968 };
1008969
1009970 @memcpy(cleartext_buf[0..encrypted_content_len], bytes[bytes_i..][0..encrypted_content_len]);
......@@ -1012,7 +973,6 @@ fn prepareCiphertextRecord(
1012973 const ciphertext_len = encrypted_content_len + 1;
1013974 const cleartext = cleartext_buf[0..ciphertext_len];
1014975
1015 const record_start = ciphertext_end;
1016976 const ad = ciphertext_buf[ciphertext_end..][0..tls.record_header_len];
1017977 ad.* = .{@intFromEnum(tls.ContentType.application_data)} ++
1018978 int(u16, @intFromEnum(tls.ProtocolVersion.tls_1_2)) ++
......@@ -1030,38 +990,27 @@ fn prepareCiphertextRecord(
1030990 };
1031991 P.AEAD.encrypt(ciphertext, auth_tag, cleartext, ad, nonce, pv.client_key);
1032992 c.write_seq += 1; // TODO send key_update on overflow
1033
1034 const record = ciphertext_buf[record_start..ciphertext_end];
1035 iovecs[iovec_end] = .{
1036 .base = record.ptr,
1037 .len = record.len,
1038 };
1039 iovec_end += 1;
1040993 }
1041994 },
1042995 .tls_1_2 => {
1043996 const pv = &p.tls_1_2;
1044997 const P = @TypeOf(p.*);
1045998 const overhead_len = tls.record_header_len + P.record_iv_length + P.mac_length;
1046 const close_notify_alert_reserved = tls.close_notify_alert.len + overhead_len;
1047999 while (true) {
10481000 const message_len: u16 = @min(
10491001 bytes.len - bytes_i,
10501002 tls.max_ciphertext_inner_record_len,
1051 ciphertext_buf.len -|
1052 (close_notify_alert_reserved + overhead_len + ciphertext_end),
1003 ciphertext_buf.len -| (overhead_len + ciphertext_end),
10531004 );
10541005 if (message_len == 0) return .{
1055 .iovec_end = iovec_end,
10561006 .ciphertext_end = ciphertext_end,
1057 .overhead_len = overhead_len,
1007 .cleartext_len = bytes_i,
10581008 };
10591009
10601010 @memcpy(cleartext_buf[0..message_len], bytes[bytes_i..][0..message_len]);
10611011 bytes_i += message_len;
10621012 const cleartext = cleartext_buf[0..message_len];
10631013
1064 const record_start = ciphertext_end;
10651014 const record_header = ciphertext_buf[ciphertext_end..][0..tls.record_header_len];
10661015 ciphertext_end += tls.record_header_len;
10671016 record_header.* = .{@intFromEnum(inner_content_type)} ++
......@@ -1083,13 +1032,6 @@ fn prepareCiphertextRecord(
10831032 ciphertext_end += P.mac_length;
10841033 P.AEAD.encrypt(ciphertext, auth_tag, cleartext, ad, nonce, pv.client_write_key);
10851034 c.write_seq += 1; // TODO send key_update on overflow
1086
1087 const record = ciphertext_buf[record_start..ciphertext_end];
1088 iovecs[iovec_end] = .{
1089 .base = record.ptr,
1090 .len = record.len,
1091 };
1092 iovec_end += 1;
10931035 }
10941036 },
10951037 else => unreachable,
......@@ -1098,421 +1040,194 @@ fn prepareCiphertextRecord(
10981040}
10991041
11001042pub fn eof(c: Client) bool {
1101 return c.received_close_notify and
1102 c.partial_cleartext_idx >= c.partial_ciphertext_idx and
1103 c.partial_ciphertext_idx >= c.partial_ciphertext_end;
1104}
1105
1106/// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`.
1107/// Returns the number of bytes read, calling the underlying read function the
1108/// minimal number of times until the buffer has at least `len` bytes filled.
1109/// If the number read is less than `len` it means the stream reached the end.
1110/// Reaching the end of the stream is not an error condition.
1111pub fn readAtLeast(c: *Client, stream: anytype, buffer: []u8, len: usize) !usize {
1112 var iovecs = [1]std.posix.iovec{.{ .base = buffer.ptr, .len = buffer.len }};
1113 return readvAtLeast(c, stream, &iovecs, len);
1114}
1115
1116/// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`.
1117pub fn read(c: *Client, stream: anytype, buffer: []u8) !usize {
1118 return readAtLeast(c, stream, buffer, 1);
1119}
1120
1121/// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`.
1122/// Returns the number of bytes read. If the number read is smaller than
1123/// `buffer.len`, it means the stream reached the end. Reaching the end of the
1124/// stream is not an error condition.
1125pub fn readAll(c: *Client, stream: anytype, buffer: []u8) !usize {
1126 return readAtLeast(c, stream, buffer, buffer.len);
1127}
1128
1129/// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`.
1130/// Returns the number of bytes read. If the number read is less than the space
1131/// provided it means the stream reached the end. Reaching the end of the
1132/// stream is not an error condition.
1133/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
1134/// order to handle partial reads from the underlying stream layer.
1135pub fn readv(c: *Client, stream: anytype, iovecs: []std.posix.iovec) !usize {
1136 return readvAtLeast(c, stream, iovecs, 1);
1137}
1138
1139/// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`.
1140/// Returns the number of bytes read, calling the underlying read function the
1141/// minimal number of times until the iovecs have at least `len` bytes filled.
1142/// If the number read is less than `len` it means the stream reached the end.
1143/// Reaching the end of the stream is not an error condition.
1144/// The `iovecs` parameter is mutable because this function needs to mutate the fields in
1145/// order to handle partial reads from the underlying stream layer.
1146pub fn readvAtLeast(c: *Client, stream: anytype, iovecs: []std.posix.iovec, len: usize) !usize {
1147 if (c.eof()) return 0;
1148
1149 var off_i: usize = 0;
1150 var vec_i: usize = 0;
1151 while (true) {
1152 var amt = try c.readvAdvanced(stream, iovecs[vec_i..]);
1153 off_i += amt;
1154 if (c.eof() or off_i >= len) return off_i;
1155 while (amt >= iovecs[vec_i].len) {
1156 amt -= iovecs[vec_i].len;
1157 vec_i += 1;
1158 }
1159 iovecs[vec_i].base += amt;
1160 iovecs[vec_i].len -= amt;
1161 }
1043 return c.received_close_notify;
11621044}
11631045
1164/// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`.
1165/// Returns number of bytes that have been read, populated inside `iovecs`. A
1166/// return value of zero bytes does not mean end of stream. Instead, check the `eof()`
1167/// for the end of stream. The `eof()` may be true after any call to
1168/// `read`, including when greater than zero bytes are returned, and this
1169/// function asserts that `eof()` is `false`.
1170/// See `readv` for a higher level function that has the same, familiar API as
1171/// other read functions, such as `std.fs.File.read`.
1172pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iovec) !usize {
1173 var vp: VecPut = .{ .iovecs = iovecs };
1174
1175 // Give away the buffered cleartext we have, if any.
1176 const partial_cleartext = c.partially_read_buffer[c.partial_cleartext_idx..c.partial_ciphertext_idx];
1177 if (partial_cleartext.len > 0) {
1178 const amt: u15 = @intCast(vp.put(partial_cleartext));
1179 c.partial_cleartext_idx += amt;
1180
1181 if (c.partial_cleartext_idx == c.partial_ciphertext_idx and
1182 c.partial_ciphertext_end == c.partial_ciphertext_idx)
1183 {
1184 // The buffer is now empty.
1185 c.partial_cleartext_idx = 0;
1186 c.partial_ciphertext_idx = 0;
1187 c.partial_ciphertext_end = 0;
1188 }
1189
1190 if (c.received_close_notify) {
1191 c.partial_ciphertext_end = 0;
1192 assert(vp.total == amt);
1193 return amt;
1194 } else if (amt > 0) {
1195 // We don't need more data, so don't call read.
1196 assert(vp.total == amt);
1197 return amt;
1198 }
1046fn stream(r: *Reader, w: *Writer, limit: std.io.Limit) Reader.StreamError!usize {
1047 const c: *Client = @fieldParentPtr("reader", r);
1048 if (c.eof()) return error.EndOfStream;
1049 const input = c.input;
1050 // If at least one full encrypted record is not buffered, read once.
1051 const record_header = input.peek(tls.record_header_len) catch |err| switch (err) {
1052 error.EndOfStream => {
1053 // This is either a truncation attack, a bug in the server, or an
1054 // intentional omission of the close_notify message due to truncation
1055 // detection handled above the TLS layer.
1056 if (c.allow_truncation_attacks) {
1057 c.received_close_notify = true;
1058 return error.EndOfStream;
1059 } else {
1060 return failRead(c, error.TlsConnectionTruncated);
1061 }
1062 },
1063 error.ReadFailed => return error.ReadFailed,
1064 };
1065 const ct: tls.ContentType = @enumFromInt(record_header[0]);
1066 const legacy_version = mem.readInt(u16, record_header[1..][0..2], .big);
1067 _ = legacy_version;
1068 const record_len = mem.readInt(u16, record_header[3..][0..2], .big);
1069 if (record_len > max_ciphertext_len) return failRead(c, error.TlsRecordOverflow);
1070 const record_end = 5 + record_len;
1071 if (record_end > input.buffered().len) {
1072 input.fillMore() catch |err| switch (err) {
1073 error.EndOfStream => return failRead(c, error.TlsConnectionTruncated),
1074 error.ReadFailed => return error.ReadFailed,
1075 };
1076 if (record_end > input.buffered().len) return 0;
11991077 }
12001078
1201 assert(!c.received_close_notify);
1202
1203 // Ideally, this buffer would never be used. It is needed when `iovecs` are
1204 // too small to fit the cleartext, which may be as large as `max_ciphertext_len`.
12051079 var cleartext_stack_buffer: [max_ciphertext_len]u8 = undefined;
1206 // Temporarily stores ciphertext before decrypting it and giving it to `iovecs`.
1207 var in_stack_buffer: [max_ciphertext_len * 4]u8 = undefined;
1208 // How many bytes left in the user's buffer.
1209 const free_size = vp.freeSize();
1210 // The amount of the user's buffer that we need to repurpose for storing
1211 // ciphertext. The end of the buffer will be used for such purposes.
1212 const ciphertext_buf_len = (free_size / 2) -| in_stack_buffer.len;
1213 // The amount of the user's buffer that will be used to give cleartext. The
1214 // beginning of the buffer will be used for such purposes.
1215 const cleartext_buf_len = free_size - ciphertext_buf_len;
1216
1217 // Recoup `partially_read_buffer` space. This is necessary because it is assumed
1218 // below that `frag0` is big enough to hold at least one record.
1219 limitedOverlapCopy(c.partially_read_buffer[0..c.partial_ciphertext_end], c.partial_ciphertext_idx);
1220 c.partial_ciphertext_end -= c.partial_ciphertext_idx;
1221 c.partial_ciphertext_idx = 0;
1222 c.partial_cleartext_idx = 0;
1223 const first_iov = c.partially_read_buffer[c.partial_ciphertext_end..];
1224
1225 var ask_iovecs_buf: [2]std.posix.iovec = .{
1226 .{
1227 .base = first_iov.ptr,
1228 .len = first_iov.len,
1229 },
1230 .{
1231 .base = &in_stack_buffer,
1232 .len = in_stack_buffer.len,
1080 const cleartext, const inner_ct: tls.ContentType = cleartext: switch (c.application_cipher) {
1081 inline else => |*p| switch (c.tls_version) {
1082 .tls_1_3 => {
1083 const pv = &p.tls_1_3;
1084 const P = @TypeOf(p.*);
1085 const ad = input.take(tls.record_header_len) catch unreachable; // already peeked
1086 const ciphertext_len = record_len - P.AEAD.tag_length;
1087 const ciphertext = input.take(ciphertext_len) catch unreachable; // already peeked
1088 const auth_tag = (input.takeArray(P.AEAD.tag_length) catch unreachable).*; // already peeked
1089 const nonce = nonce: {
1090 const V = @Vector(P.AEAD.nonce_length, u8);
1091 const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8);
1092 const operand: V = pad ++ std.mem.toBytes(big(c.read_seq));
1093 break :nonce @as(V, pv.server_iv) ^ operand;
1094 };
1095 const cleartext = cleartext_stack_buffer[0..ciphertext.len];
1096 P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, pv.server_key) catch
1097 return failRead(c, error.TlsBadRecordMac);
1098 const msg = mem.trimRight(u8, cleartext, "\x00");
1099 break :cleartext .{ msg[0 .. msg.len - 1], @enumFromInt(msg[msg.len - 1]) };
1100 },
1101 .tls_1_2 => {
1102 const pv = &p.tls_1_2;
1103 const P = @TypeOf(p.*);
1104 const message_len: u16 = record_len - P.record_iv_length - P.mac_length;
1105 const ad_header = input.take(tls.record_header_len) catch unreachable; // already peeked
1106 const ad = std.mem.toBytes(big(c.read_seq)) ++
1107 ad_header[0 .. 1 + 2] ++
1108 std.mem.toBytes(big(message_len));
1109 const record_iv = (input.takeArray(P.record_iv_length) catch unreachable).*; // already peeked
1110 const masked_read_seq = c.read_seq &
1111 comptime std.math.shl(u64, std.math.maxInt(u64), 8 * P.record_iv_length);
1112 const nonce: [P.AEAD.nonce_length]u8 = nonce: {
1113 const V = @Vector(P.AEAD.nonce_length, u8);
1114 const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8);
1115 const operand: V = pad ++ @as([8]u8, @bitCast(big(masked_read_seq)));
1116 break :nonce @as(V, pv.server_write_IV ++ record_iv) ^ operand;
1117 };
1118 const ciphertext = input.take(message_len) catch unreachable; // already peeked
1119 const auth_tag = (input.takeArray(P.mac_length) catch unreachable).*; // already peeked
1120 const cleartext = cleartext_stack_buffer[0..ciphertext.len];
1121 P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, pv.server_write_key) catch
1122 return failRead(c, error.TlsBadRecordMac);
1123 break :cleartext .{ cleartext, ct };
1124 },
1125 else => unreachable,
12331126 },
12341127 };
1235
1236 // Cleartext capacity of output buffer, in records. Minimum one full record.
1237 const buf_cap = @max(cleartext_buf_len / max_ciphertext_len, 1);
1238 const wanted_read_len = buf_cap * (max_ciphertext_len + tls.record_header_len);
1239 const ask_len = @max(wanted_read_len, cleartext_stack_buffer.len) - c.partial_ciphertext_end;
1240 const ask_iovecs = limitVecs(&ask_iovecs_buf, ask_len);
1241 const actual_read_len = try stream.readv(ask_iovecs);
1242 if (actual_read_len == 0) {
1243 // This is either a truncation attack, a bug in the server, or an
1244 // intentional omission of the close_notify message due to truncation
1245 // detection handled above the TLS layer.
1246 if (c.allow_truncation_attacks) {
1247 c.received_close_notify = true;
1248 } else {
1249 return error.TlsConnectionTruncated;
1250 }
1251 }
1252
1253 // There might be more bytes inside `in_stack_buffer` that need to be processed,
1254 // but at least frag0 will have one complete ciphertext record.
1255 const frag0_end = @min(c.partially_read_buffer.len, c.partial_ciphertext_end + actual_read_len);
1256 const frag0 = c.partially_read_buffer[c.partial_ciphertext_idx..frag0_end];
1257 var frag1 = in_stack_buffer[0..actual_read_len -| first_iov.len];
1258 // We need to decipher frag0 and frag1 but there may be a ciphertext record
1259 // straddling the boundary. We can handle this with two memcpy() calls to
1260 // assemble the straddling record in between handling the two sides.
1261 var frag = frag0;
1262 var in: usize = 0;
1263 while (true) {
1264 if (in == frag.len) {
1265 // Perfect split.
1266 if (frag.ptr == frag1.ptr) {
1267 c.partial_ciphertext_end = c.partial_ciphertext_idx;
1268 return vp.total;
1269 }
1270 frag = frag1;
1271 in = 0;
1272 continue;
1273 }
1274
1275 if (in + tls.record_header_len > frag.len) {
1276 if (frag.ptr == frag1.ptr)
1277 return finishRead(c, frag, in, vp.total);
1278
1279 const first = frag[in..];
1280
1281 if (frag1.len < tls.record_header_len)
1282 return finishRead2(c, first, frag1, vp.total);
1283
1284 // A record straddles the two fragments. Copy into the now-empty first fragment.
1285 const record_len_byte_0: u16 = straddleByte(frag, frag1, in + 3);
1286 const record_len_byte_1: u16 = straddleByte(frag, frag1, in + 4);
1287 const record_len = (record_len_byte_0 << 8) | record_len_byte_1;
1288 if (record_len > max_ciphertext_len) return error.TlsRecordOverflow;
1289
1290 const full_record_len = record_len + tls.record_header_len;
1291 const second_len = full_record_len - first.len;
1292 if (frag1.len < second_len)
1293 return finishRead2(c, first, frag1, vp.total);
1294
1295 limitedOverlapCopy(frag, in);
1296 @memcpy(frag[first.len..][0..second_len], frag1[0..second_len]);
1297 frag = frag[0..full_record_len];
1298 frag1 = frag1[second_len..];
1299 in = 0;
1300 continue;
1301 }
1302 const ct: tls.ContentType = @enumFromInt(frag[in]);
1303 in += 1;
1304 const legacy_version = mem.readInt(u16, frag[in..][0..2], .big);
1305 in += 2;
1306 _ = legacy_version;
1307 const record_len = mem.readInt(u16, frag[in..][0..2], .big);
1308 if (record_len > max_ciphertext_len) return error.TlsRecordOverflow;
1309 in += 2;
1310 const end = in + record_len;
1311 if (end > frag.len) {
1312 // We need the record header on the next iteration of the loop.
1313 in -= tls.record_header_len;
1314
1315 if (frag.ptr == frag1.ptr)
1316 return finishRead(c, frag, in, vp.total);
1317
1318 // A record straddles the two fragments. Copy into the now-empty first fragment.
1319 const first = frag[in..];
1320 const full_record_len = record_len + tls.record_header_len;
1321 const second_len = full_record_len - first.len;
1322 if (frag1.len < second_len)
1323 return finishRead2(c, first, frag1, vp.total);
1324
1325 limitedOverlapCopy(frag, in);
1326 @memcpy(frag[first.len..][0..second_len], frag1[0..second_len]);
1327 frag = frag[0..full_record_len];
1328 frag1 = frag1[second_len..];
1329 in = 0;
1330 continue;
1331 }
1332 const cleartext, const inner_ct: tls.ContentType = cleartext: switch (c.application_cipher) {
1333 inline else => |*p| switch (c.tls_version) {
1334 .tls_1_3 => {
1335 const pv = &p.tls_1_3;
1336 const P = @TypeOf(p.*);
1337 const ad = frag[in - tls.record_header_len ..][0..tls.record_header_len];
1338 const ciphertext_len = record_len - P.AEAD.tag_length;
1339 const ciphertext = frag[in..][0..ciphertext_len];
1340 in += ciphertext_len;
1341 const auth_tag = frag[in..][0..P.AEAD.tag_length].*;
1342 const nonce = nonce: {
1343 const V = @Vector(P.AEAD.nonce_length, u8);
1344 const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8);
1345 const operand: V = pad ++ std.mem.toBytes(big(c.read_seq));
1346 break :nonce @as(V, pv.server_iv) ^ operand;
1347 };
1348 const out_buf = vp.peek();
1349 const cleartext_buf = if (ciphertext.len <= out_buf.len)
1350 out_buf
1351 else
1352 &cleartext_stack_buffer;
1353 const cleartext = cleartext_buf[0..ciphertext.len];
1354 P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, pv.server_key) catch
1355 return error.TlsBadRecordMac;
1356 const msg = mem.trimEnd(u8, cleartext, "\x00");
1357 break :cleartext .{ msg[0 .. msg.len - 1], @enumFromInt(msg[msg.len - 1]) };
1128 c.read_seq = std.math.add(u64, c.read_seq, 1) catch return failRead(c, error.TlsSequenceOverflow);
1129 switch (inner_ct) {
1130 .alert => {
1131 if (cleartext.len != 2) return failRead(c, error.TlsDecodeError);
1132 const alert: tls.Alert = .{
1133 .level = @enumFromInt(cleartext[0]),
1134 .description = @enumFromInt(cleartext[1]),
1135 };
1136 switch (alert.description) {
1137 .close_notify => {
1138 c.received_close_notify = true;
1139 return 0;
13581140 },
1359 .tls_1_2 => {
1360 const pv = &p.tls_1_2;
1361 const P = @TypeOf(p.*);
1362 const message_len: u16 = record_len - P.record_iv_length - P.mac_length;
1363 const ad = std.mem.toBytes(big(c.read_seq)) ++
1364 frag[in - tls.record_header_len ..][0 .. 1 + 2] ++
1365 std.mem.toBytes(big(message_len));
1366 const record_iv = frag[in..][0..P.record_iv_length].*;
1367 in += P.record_iv_length;
1368 const masked_read_seq = c.read_seq &
1369 comptime std.math.shl(u64, std.math.maxInt(u64), 8 * P.record_iv_length);
1370 const nonce: [P.AEAD.nonce_length]u8 = nonce: {
1371 const V = @Vector(P.AEAD.nonce_length, u8);
1372 const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8);
1373 const operand: V = pad ++ @as([8]u8, @bitCast(big(masked_read_seq)));
1374 break :nonce @as(V, pv.server_write_IV ++ record_iv) ^ operand;
1375 };
1376 const ciphertext = frag[in..][0..message_len];
1377 in += message_len;
1378 const auth_tag = frag[in..][0..P.mac_length].*;
1379 in += P.mac_length;
1380 const out_buf = vp.peek();
1381 const cleartext_buf = if (message_len <= out_buf.len)
1382 out_buf
1383 else
1384 &cleartext_stack_buffer;
1385 const cleartext = cleartext_buf[0..ciphertext.len];
1386 P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, pv.server_write_key) catch
1387 return error.TlsBadRecordMac;
1388 break :cleartext .{ cleartext, ct };
1141 .user_canceled => {
1142 // TODO: handle server-side closures
1143 return failRead(c, error.TlsUnexpectedMessage);
13891144 },
1390 else => unreachable,
1391 },
1392 };
1393 c.read_seq = try std.math.add(u64, c.read_seq, 1);
1394 switch (inner_ct) {
1395 .alert => {
1396 if (cleartext.len != 2) return error.TlsDecodeError;
1397 const level: tls.AlertLevel = @enumFromInt(cleartext[0]);
1398 const desc: tls.AlertDescription = @enumFromInt(cleartext[1]);
1399 if (desc == .close_notify) {
1400 c.received_close_notify = true;
1401 c.partial_ciphertext_end = c.partial_ciphertext_idx;
1402 return vp.total;
1403 }
1404 _ = level;
1405
1406 try desc.toError();
1407 // TODO: handle server-side closures
1408 return error.TlsUnexpectedMessage;
1409 },
1410 .handshake => {
1411 var ct_i: usize = 0;
1412 while (true) {
1413 const handshake_type: tls.HandshakeType = @enumFromInt(cleartext[ct_i]);
1414 ct_i += 1;
1415 const handshake_len = mem.readInt(u24, cleartext[ct_i..][0..3], .big);
1416 ct_i += 3;
1417 const next_handshake_i = ct_i + handshake_len;
1418 if (next_handshake_i > cleartext.len)
1419 return error.TlsBadLength;
1420 const handshake = cleartext[ct_i..next_handshake_i];
1421 switch (handshake_type) {
1422 .new_session_ticket => {
1423 // This client implementation ignores new session tickets.
1424 },
1425 .key_update => {
1426 switch (c.application_cipher) {
1427 inline else => |*p| {
1428 const pv = &p.tls_1_3;
1429 const P = @TypeOf(p.*);
1430 const server_secret = hkdfExpandLabel(P.Hkdf, pv.server_secret, "traffic upd", "", P.Hash.digest_length);
1431 if (c.ssl_key_log) |*key_log| logSecrets(key_log.file, .{
1432 .counter = key_log.serverCounter(),
1433 .client_random = &key_log.client_random,
1434 }, .{
1435 .SERVER_TRAFFIC_SECRET = &server_secret,
1436 });
1437 pv.server_secret = server_secret;
1438 pv.server_key = hkdfExpandLabel(P.Hkdf, server_secret, "key", "", P.AEAD.key_length);
1439 pv.server_iv = hkdfExpandLabel(P.Hkdf, server_secret, "iv", "", P.AEAD.nonce_length);
1440 },
1441 }
1442 c.read_seq = 0;
1443
1444 switch (@as(tls.KeyUpdateRequest, @enumFromInt(handshake[0]))) {
1445 .update_requested => {
1446 switch (c.application_cipher) {
1447 inline else => |*p| {
1448 const pv = &p.tls_1_3;
1449 const P = @TypeOf(p.*);
1450 const client_secret = hkdfExpandLabel(P.Hkdf, pv.client_secret, "traffic upd", "", P.Hash.digest_length);
1451 if (c.ssl_key_log) |*key_log| logSecrets(key_log.file, .{
1452 .counter = key_log.clientCounter(),
1453 .client_random = &key_log.client_random,
1454 }, .{
1455 .CLIENT_TRAFFIC_SECRET = &client_secret,
1456 });
1457 pv.client_secret = client_secret;
1458 pv.client_key = hkdfExpandLabel(P.Hkdf, client_secret, "key", "", P.AEAD.key_length);
1459 pv.client_iv = hkdfExpandLabel(P.Hkdf, client_secret, "iv", "", P.AEAD.nonce_length);
1460 },
1461 }
1462 c.write_seq = 0;
1463 },
1464 .update_not_requested => {},
1465 _ => return error.TlsIllegalParameter,
1466 }
1467 },
1468 else => {
1469 return error.TlsUnexpectedMessage;
1470 },
1471 }
1472 ct_i = next_handshake_i;
1473 if (ct_i >= cleartext.len) break;
1474 }
1475 },
1476 .application_data => {
1477 // Determine whether the output buffer or a stack
1478 // buffer was used for storing the cleartext.
1479 if (cleartext.ptr == &cleartext_stack_buffer) {
1480 // Stack buffer was used, so we must copy to the output buffer.
1481 if (c.partial_ciphertext_idx > c.partial_cleartext_idx) {
1482 // We have already run out of room in iovecs. Continue
1483 // appending to `partially_read_buffer`.
1484 @memcpy(
1485 c.partially_read_buffer[c.partial_ciphertext_idx..][0..cleartext.len],
1486 cleartext,
1487 );
1488 c.partial_ciphertext_idx = @intCast(c.partial_ciphertext_idx + cleartext.len);
1489 } else {
1490 const amt = vp.put(cleartext);
1491 if (amt < cleartext.len) {
1492 const rest = cleartext[amt..];
1493 c.partial_cleartext_idx = 0;
1494 c.partial_ciphertext_idx = @intCast(rest.len);
1495 @memcpy(c.partially_read_buffer[0..rest.len], rest);
1145 else => {
1146 c.alert = alert;
1147 return failRead(c, error.TlsAlert);
1148 },
1149 }
1150 },
1151 .handshake => {
1152 var ct_i: usize = 0;
1153 while (true) {
1154 const handshake_type: tls.HandshakeType = @enumFromInt(cleartext[ct_i]);
1155 ct_i += 1;
1156 const handshake_len = mem.readInt(u24, cleartext[ct_i..][0..3], .big);
1157 ct_i += 3;
1158 const next_handshake_i = ct_i + handshake_len;
1159 if (next_handshake_i > cleartext.len) return failRead(c, error.TlsBadLength);
1160 const handshake = cleartext[ct_i..next_handshake_i];
1161 switch (handshake_type) {
1162 .new_session_ticket => {
1163 // This client implementation ignores new session tickets.
1164 },
1165 .key_update => {
1166 switch (c.application_cipher) {
1167 inline else => |*p| {
1168 const pv = &p.tls_1_3;
1169 const P = @TypeOf(p.*);
1170 const server_secret = hkdfExpandLabel(P.Hkdf, pv.server_secret, "traffic upd", "", P.Hash.digest_length);
1171 if (c.ssl_key_log) |key_log| logSecrets(key_log.writer, .{
1172 .counter = key_log.serverCounter(),
1173 .client_random = &key_log.client_random,
1174 }, .{
1175 .SERVER_TRAFFIC_SECRET = &server_secret,
1176 });
1177 pv.server_secret = server_secret;
1178 pv.server_key = hkdfExpandLabel(P.Hkdf, server_secret, "key", "", P.AEAD.key_length);
1179 pv.server_iv = hkdfExpandLabel(P.Hkdf, server_secret, "iv", "", P.AEAD.nonce_length);
1180 },
1181 }
1182 c.read_seq = 0;
1183
1184 switch (@as(tls.KeyUpdateRequest, @enumFromInt(handshake[0]))) {
1185 .update_requested => {
1186 switch (c.application_cipher) {
1187 inline else => |*p| {
1188 const pv = &p.tls_1_3;
1189 const P = @TypeOf(p.*);
1190 const client_secret = hkdfExpandLabel(P.Hkdf, pv.client_secret, "traffic upd", "", P.Hash.digest_length);
1191 if (c.ssl_key_log) |key_log| logSecrets(key_log.writer, .{
1192 .counter = key_log.clientCounter(),
1193 .client_random = &key_log.client_random,
1194 }, .{
1195 .CLIENT_TRAFFIC_SECRET = &client_secret,
1196 });
1197 pv.client_secret = client_secret;
1198 pv.client_key = hkdfExpandLabel(P.Hkdf, client_secret, "key", "", P.AEAD.key_length);
1199 pv.client_iv = hkdfExpandLabel(P.Hkdf, client_secret, "iv", "", P.AEAD.nonce_length);
1200 },
1201 }
1202 c.write_seq = 0;
1203 },
1204 .update_not_requested => {},
1205 _ => return failRead(c, error.TlsIllegalParameter),
14961206 }
1497 }
1498 } else {
1499 // Output buffer was used directly which means no
1500 // memory copying needs to occur, and we can move
1501 // on to the next ciphertext record.
1502 vp.next(cleartext.len);
1207 },
1208 else => return failRead(c, error.TlsUnexpectedMessage),
15031209 }
1504 },
1505 else => return error.TlsUnexpectedMessage,
1506 }
1507 in = end;
1210 ct_i = next_handshake_i;
1211 if (ct_i >= cleartext.len) break;
1212 }
1213 return 0;
1214 },
1215 .application_data => {
1216 if (@intFromEnum(limit) < cleartext.len) return failRead(c, error.OutputBufferUndersize);
1217 try w.writeAll(cleartext);
1218 return cleartext.len;
1219 },
1220 else => return failRead(c, error.TlsUnexpectedMessage),
15081221 }
15091222}
15101223
1511fn logSecrets(key_log_file: std.fs.File, context: anytype, secrets: anytype) void {
1512 const locked = if (key_log_file.lock(.exclusive)) |_| true else |_| false;
1513 defer if (locked) key_log_file.unlock();
1514 key_log_file.seekFromEnd(0) catch {};
1515 inline for (@typeInfo(@TypeOf(secrets)).@"struct".fields) |field| key_log_file.deprecatedWriter().print("{s}" ++
1224fn failRead(c: *Client, err: ReadError) error{ReadFailed} {
1225 c.read_err = err;
1226 return error.ReadFailed;
1227}
1228
1229fn logSecrets(w: *Writer, context: anytype, secrets: anytype) void {
1230 inline for (@typeInfo(@TypeOf(secrets)).@"struct".fields) |field| w.print("{s}" ++
15161231 (if (@hasField(@TypeOf(context), "counter")) "_{d}" else "") ++ " {x} {x}\n", .{field.name} ++
15171232 (if (@hasField(@TypeOf(context), "counter")) .{context.counter} else .{}) ++ .{
15181233 context.client_random,
......@@ -1520,62 +1235,6 @@ fn logSecrets(key_log_file: std.fs.File, context: anytype, secrets: anytype) voi
15201235 }) catch {};
15211236}
15221237
1523fn finishRead(c: *Client, frag: []const u8, in: usize, out: usize) usize {
1524 const saved_buf = frag[in..];
1525 if (c.partial_ciphertext_idx > c.partial_cleartext_idx) {
1526 // There is cleartext at the beginning already which we need to preserve.
1527 c.partial_ciphertext_end = @intCast(c.partial_ciphertext_idx + saved_buf.len);
1528 @memcpy(c.partially_read_buffer[c.partial_ciphertext_idx..][0..saved_buf.len], saved_buf);
1529 } else {
1530 c.partial_cleartext_idx = 0;
1531 c.partial_ciphertext_idx = 0;
1532 c.partial_ciphertext_end = @intCast(saved_buf.len);
1533 @memcpy(c.partially_read_buffer[0..saved_buf.len], saved_buf);
1534 }
1535 return out;
1536}
1537
1538/// Note that `first` usually overlaps with `c.partially_read_buffer`.
1539fn finishRead2(c: *Client, first: []const u8, frag1: []const u8, out: usize) usize {
1540 if (c.partial_ciphertext_idx > c.partial_cleartext_idx) {
1541 // There is cleartext at the beginning already which we need to preserve.
1542 c.partial_ciphertext_end = @intCast(c.partial_ciphertext_idx + first.len + frag1.len);
1543 // TODO: eliminate this call to copyForwards
1544 std.mem.copyForwards(u8, c.partially_read_buffer[c.partial_ciphertext_idx..][0..first.len], first);
1545 @memcpy(c.partially_read_buffer[c.partial_ciphertext_idx + first.len ..][0..frag1.len], frag1);
1546 } else {
1547 c.partial_cleartext_idx = 0;
1548 c.partial_ciphertext_idx = 0;
1549 c.partial_ciphertext_end = @intCast(first.len + frag1.len);
1550 // TODO: eliminate this call to copyForwards
1551 std.mem.copyForwards(u8, c.partially_read_buffer[0..first.len], first);
1552 @memcpy(c.partially_read_buffer[first.len..][0..frag1.len], frag1);
1553 }
1554 return out;
1555}
1556
1557fn limitedOverlapCopy(frag: []u8, in: usize) void {
1558 const first = frag[in..];
1559 if (first.len <= in) {
1560 // A single, non-overlapping memcpy suffices.
1561 @memcpy(frag[0..first.len], first);
1562 } else {
1563 // One memcpy call would overlap, so just do this instead.
1564 std.mem.copyForwards(u8, frag, first);
1565 }
1566}
1567
1568fn straddleByte(s1: []const u8, s2: []const u8, index: usize) u8 {
1569 if (index < s1.len) {
1570 return s1[index];
1571 } else {
1572 return s2[index - s1.len];
1573 }
1574}
1575
1576const builtin = @import("builtin");
1577const native_endian = builtin.cpu.arch.endian();
1578
15791238fn big(x: anytype) @TypeOf(x) {
15801239 return switch (native_endian) {
15811240 .big => x,
......@@ -1836,81 +1495,6 @@ const CertificatePublicKey = struct {
18361495 }
18371496};
18381497
1839/// Abstraction for sending multiple byte buffers to a slice of iovecs.
1840const VecPut = struct {
1841 iovecs: []const std.posix.iovec,
1842 idx: usize = 0,
1843 off: usize = 0,
1844 total: usize = 0,
1845
1846 /// Returns the amount actually put which is always equal to bytes.len
1847 /// unless the vectors ran out of space.
1848 fn put(vp: *VecPut, bytes: []const u8) usize {
1849 if (vp.idx >= vp.iovecs.len) return 0;
1850 var bytes_i: usize = 0;
1851 while (true) {
1852 const v = vp.iovecs[vp.idx];
1853 const dest = v.base[vp.off..v.len];
1854 const src = bytes[bytes_i..][0..@min(dest.len, bytes.len - bytes_i)];
1855 @memcpy(dest[0..src.len], src);
1856 bytes_i += src.len;
1857 vp.off += src.len;
1858 if (vp.off >= v.len) {
1859 vp.off = 0;
1860 vp.idx += 1;
1861 if (vp.idx >= vp.iovecs.len) {
1862 vp.total += bytes_i;
1863 return bytes_i;
1864 }
1865 }
1866 if (bytes_i >= bytes.len) {
1867 vp.total += bytes_i;
1868 return bytes_i;
1869 }
1870 }
1871 }
1872
1873 /// Returns the next buffer that consecutive bytes can go into.
1874 fn peek(vp: VecPut) []u8 {
1875 if (vp.idx >= vp.iovecs.len) return &.{};
1876 const v = vp.iovecs[vp.idx];
1877 return v.base[vp.off..v.len];
1878 }
1879
1880 // After writing to the result of peek(), one can call next() to
1881 // advance the cursor.
1882 fn next(vp: *VecPut, len: usize) void {
1883 vp.total += len;
1884 vp.off += len;
1885 if (vp.off >= vp.iovecs[vp.idx].len) {
1886 vp.off = 0;
1887 vp.idx += 1;
1888 }
1889 }
1890
1891 fn freeSize(vp: VecPut) usize {
1892 if (vp.idx >= vp.iovecs.len) return 0;
1893 var total: usize = 0;
1894 total += vp.iovecs[vp.idx].len - vp.off;
1895 if (vp.idx + 1 >= vp.iovecs.len) return total;
1896 for (vp.iovecs[vp.idx + 1 ..]) |v| total += v.len;
1897 return total;
1898 }
1899};
1900
1901/// Limit iovecs to a specific byte size.
1902fn limitVecs(iovecs: []std.posix.iovec, len: usize) []std.posix.iovec {
1903 var bytes_left: usize = len;
1904 for (iovecs, 0..) |*iovec, vec_i| {
1905 if (bytes_left <= iovec.len) {
1906 iovec.len = bytes_left;
1907 return iovecs[0 .. vec_i + 1];
1908 }
1909 bytes_left -= iovec.len;
1910 }
1911 return iovecs;
1912}
1913
19141498/// The priority order here is chosen based on what crypto algorithms Zig has
19151499/// available in the standard library as well as what is faster. Following are
19161500/// a few data points on the relative performance of these algorithms.
......@@ -1954,7 +1538,3 @@ else
19541538 .AES_256_GCM_SHA384,
19551539 .ECDHE_RSA_WITH_AES_256_GCM_SHA384,
19561540 });
1957
1958test {
1959 _ = StreamInterface;
1960}
lib/std/http.zig+5-14
......@@ -343,10 +343,9 @@ pub const Reader = struct {
343343 /// read from `in`.
344344 trailers: []const u8 = &.{},
345345 body_err: ?BodyError = null,
346 /// Stolen from `in`.
347 head_buffer: []u8 = &.{},
348
349 pub const max_chunk_header_len = 22;
346 /// Determines at which point `error.HttpHeadersOversize` occurs, as well
347 /// as the minimum buffer capacity of `in`.
348 max_head_len: usize,
350349
351350 pub const RemainingChunkLen = enum(u64) {
352351 head = 0,
......@@ -398,19 +397,11 @@ pub const Reader = struct {
398397 ReadFailed,
399398 };
400399
401 pub fn restituteHeadBuffer(reader: *Reader) void {
402 reader.in.restitute(reader.head_buffer.len);
403 reader.head_buffer.len = 0;
404 }
405
406 /// Buffers the entire head into `head_buffer`, invalidating the previous
407 /// `head_buffer`, if any.
400 /// Buffers the entire head.
408401 pub fn receiveHead(reader: *Reader) HeadError!void {
409402 reader.trailers = &.{};
410403 const in = reader.in;
411 in.restitute(reader.head_buffer.len);
412 reader.head_buffer.len = 0;
413 in.rebase();
404 try in.rebase(reader.max_head_len);
414405 var hp: HeadParser = .{};
415406 var head_end: usize = 0;
416407 while (true) {