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 {...@@ -1306,31 +1306,6 @@ pub fn defaultRebase(r: *Reader, capacity: usize) RebaseError!void {
1306 r.end = data.len;1306 r.end = data.len;
1307}1307}
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
1334test fixed {1309test fixed {
1335 var r: Reader = .fixed("a\x02");1310 var r: Reader = .fixed("a\x02");
1336 try testing.expect((try r.takeByte()) == 'a');1311 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{...@@ -49,8 +49,8 @@ pub const hello_retry_request_sequence = [32]u8{
49};49};
5050
51pub const close_notify_alert = [_]u8{51pub const close_notify_alert = [_]u8{
52 @intFromEnum(AlertLevel.warning),52 @intFromEnum(Alert.Level.warning),
53 @intFromEnum(AlertDescription.close_notify),53 @intFromEnum(Alert.Description.close_notify),
54};54};
5555
56pub const ProtocolVersion = enum(u16) {56pub const ProtocolVersion = enum(u16) {
...@@ -138,103 +138,108 @@ pub const ExtensionType = enum(u16) {...@@ -138,103 +138,108 @@ pub const ExtensionType = enum(u16) {
138 _,138 _,
139};139};
140140
141pub const AlertLevel = enum(u8) {141pub const Alert = struct {
142 warning = 1,142 level: Level,
143 fatal = 2,143 description: Description,
144 _,
145};
146144
147pub const AlertDescription = enum(u8) {145 pub const Level = enum(u8) {
148 pub const Error = error{146 warning = 1,
149 TlsAlertUnexpectedMessage,147 fatal = 2,
150 TlsAlertBadRecordMac,148 _,
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,
175 };149 };
176150
177 close_notify = 0,151 pub const Description = enum(u8) {
178 unexpected_message = 10,152 pub const Error = error{
179 bad_record_mac = 20,153 TlsAlertUnexpectedMessage,
180 record_overflow = 22,154 TlsAlertBadRecordMac,
181 handshake_failure = 40,155 TlsAlertRecordOverflow,
182 bad_certificate = 42,156 TlsAlertHandshakeFailure,
183 unsupported_certificate = 43,157 TlsAlertBadCertificate,
184 certificate_revoked = 44,158 TlsAlertUnsupportedCertificate,
185 certificate_expired = 45,159 TlsAlertCertificateRevoked,
186 certificate_unknown = 46,160 TlsAlertCertificateExpired,
187 illegal_parameter = 47,161 TlsAlertCertificateUnknown,
188 unknown_ca = 48,162 TlsAlertIllegalParameter,
189 access_denied = 49,163 TlsAlertUnknownCa,
190 decode_error = 50,164 TlsAlertAccessDenied,
191 decrypt_error = 51,165 TlsAlertDecodeError,
192 protocol_version = 70,166 TlsAlertDecryptError,
193 insufficient_security = 71,167 TlsAlertProtocolVersion,
194 internal_error = 80,168 TlsAlertInsufficientSecurity,
195 inappropriate_fallback = 86,169 TlsAlertInternalError,
196 user_canceled = 90,170 TlsAlertInappropriateFallback,
197 missing_extension = 109,171 TlsAlertMissingExtension,
198 unsupported_extension = 110,172 TlsAlertUnsupportedExtension,
199 unrecognized_name = 112,173 TlsAlertUnrecognizedName,
200 bad_certificate_status_response = 113,174 TlsAlertBadCertificateStatusResponse,
201 unknown_psk_identity = 115,175 TlsAlertUnknownPskIdentity,
202 certificate_required = 116,176 TlsAlertCertificateRequired,
203 no_application_protocol = 120,177 TlsAlertNoApplicationProtocol,
204 _,178 TlsAlertUnknown,
179 };
205180
206 pub fn toError(alert: AlertDescription) Error!void {181 close_notify = 0,
207 switch (alert) {182 unexpected_message = 10,
208 .close_notify => {}, // not an error183 bad_record_mac = 20,
209 .unexpected_message => return error.TlsAlertUnexpectedMessage,184 record_overflow = 22,
210 .bad_record_mac => return error.TlsAlertBadRecordMac,185 handshake_failure = 40,
211 .record_overflow => return error.TlsAlertRecordOverflow,186 bad_certificate = 42,
212 .handshake_failure => return error.TlsAlertHandshakeFailure,187 unsupported_certificate = 43,
213 .bad_certificate => return error.TlsAlertBadCertificate,188 certificate_revoked = 44,
214 .unsupported_certificate => return error.TlsAlertUnsupportedCertificate,189 certificate_expired = 45,
215 .certificate_revoked => return error.TlsAlertCertificateRevoked,190 certificate_unknown = 46,
216 .certificate_expired => return error.TlsAlertCertificateExpired,191 illegal_parameter = 47,
217 .certificate_unknown => return error.TlsAlertCertificateUnknown,192 unknown_ca = 48,
218 .illegal_parameter => return error.TlsAlertIllegalParameter,193 access_denied = 49,
219 .unknown_ca => return error.TlsAlertUnknownCa,194 decode_error = 50,
220 .access_denied => return error.TlsAlertAccessDenied,195 decrypt_error = 51,
221 .decode_error => return error.TlsAlertDecodeError,196 protocol_version = 70,
222 .decrypt_error => return error.TlsAlertDecryptError,197 insufficient_security = 71,
223 .protocol_version => return error.TlsAlertProtocolVersion,198 internal_error = 80,
224 .insufficient_security => return error.TlsAlertInsufficientSecurity,199 inappropriate_fallback = 86,
225 .internal_error => return error.TlsAlertInternalError,200 user_canceled = 90,
226 .inappropriate_fallback => return error.TlsAlertInappropriateFallback,201 missing_extension = 109,
227 .user_canceled => {}, // not an error202 unsupported_extension = 110,
228 .missing_extension => return error.TlsAlertMissingExtension,203 unrecognized_name = 112,
229 .unsupported_extension => return error.TlsAlertUnsupportedExtension,204 bad_certificate_status_response = 113,
230 .unrecognized_name => return error.TlsAlertUnrecognizedName,205 unknown_psk_identity = 115,
231 .bad_certificate_status_response => return error.TlsAlertBadCertificateStatusResponse,206 certificate_required = 116,
232 .unknown_psk_identity => return error.TlsAlertUnknownPskIdentity,207 no_application_protocol = 120,
233 .certificate_required => return error.TlsAlertCertificateRequired,208 _,
234 .no_application_protocol => return error.TlsAlertNoApplicationProtocol,209
235 _ => return error.TlsAlertUnknown,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 }
236 }241 }
237 }242 };
238};243};
239244
240pub const SignatureScheme = enum(u16) {245pub const SignatureScheme = enum(u16) {
...@@ -650,7 +655,7 @@ pub const Decoder = struct {...@@ -650,7 +655,7 @@ pub const Decoder = struct {
650 }655 }
651656
652 /// Use this function to increase `their_end`.657 /// 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 {
654 assert(!d.disable_reads);659 assert(!d.disable_reads);
655 const existing_amt = d.cap - d.idx;660 const existing_amt = d.cap - d.idx;
656 d.their_end = d.idx + their_amt;661 d.their_end = d.idx + their_amt;
...@@ -658,14 +663,16 @@ pub const Decoder = struct {...@@ -658,14 +663,16 @@ pub const Decoder = struct {
658 const request_amt = their_amt - existing_amt;663 const request_amt = their_amt - existing_amt;
659 const dest = d.buf[d.cap..];664 const dest = d.buf[d.cap..];
660 if (request_amt > dest.len) return error.TlsRecordOverflow;665 if (request_amt > dest.len) return error.TlsRecordOverflow;
661 const actual_amt = try stream.readAtLeast(dest, request_amt);666 stream.readSlice(dest[0..request_amt]) catch |err| switch (err) {
662 if (actual_amt < request_amt) return error.TlsConnectionTruncated;667 error.EndOfStream => return error.TlsConnectionTruncated,
663 d.cap += actual_amt;668 error.ReadFailed => return error.ReadFailed,
669 };
670 d.cap += request_amt;
664 }671 }
665672
666 /// Same as `readAtLeast` but also increases `our_end` by exactly `our_amt`.673 /// Same as `readAtLeast` but also increases `our_end` by exactly `our_amt`.
667 /// Use when `our_amt` is calculated by us, not by them.674 /// 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 {
669 assert(!d.disable_reads);676 assert(!d.disable_reads);
670 try readAtLeast(d, stream, our_amt);677 try readAtLeast(d, stream, our_amt);
671 d.our_end = d.idx + our_amt;678 d.our_end = d.idx + our_amt;
lib/std/crypto/tls/Client.zig+387-807
...@@ -1,11 +1,15 @@...@@ -1,11 +1,15 @@
1const builtin = @import("builtin");
2const native_endian = builtin.cpu.arch.endian();
3
1const std = @import("../../std.zig");4const std = @import("../../std.zig");
2const tls = std.crypto.tls;5const tls = std.crypto.tls;
3const Client = @This();6const Client = @This();
4const net = std.net;
5const mem = std.mem;7const mem = std.mem;
6const crypto = std.crypto;8const crypto = std.crypto;
7const assert = std.debug.assert;9const assert = std.debug.assert;
8const Certificate = std.crypto.Certificate;10const Certificate = std.crypto.Certificate;
11const Reader = std.io.Reader;
12const Writer = std.io.Writer;
913
10const max_ciphertext_len = tls.max_ciphertext_len;14const max_ciphertext_len = tls.max_ciphertext_len;
11const hmacExpandLabel = tls.hmacExpandLabel;15const hmacExpandLabel = tls.hmacExpandLabel;
...@@ -13,44 +17,58 @@ const hkdfExpandLabel = tls.hkdfExpandLabel;...@@ -13,44 +17,58 @@ const hkdfExpandLabel = tls.hkdfExpandLabel;
13const int = tls.int;17const int = tls.int;
14const array = tls.array;18const 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,
16tls_version: tls.ProtocolVersion,37tls_version: tls.ProtocolVersion,
17read_seq: u64,38read_seq: u64,
18write_seq: u64,39write_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,
26/// When this is true, the stream may still not be at the end because there40/// 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.
28received_close_notify: bool,42received_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,43allow_truncation_attacks: bool,
37application_cipher: tls.ApplicationCipher,44application_cipher: tls.ApplicationCipher,
38/// The size is enough to contain exactly one TLSCiphertext record.45
39/// This buffer is segmented into four parts:46/// If non-null, ssl secrets are logged to a stream. Creating such a log file
40/// 0. unused47/// allows other programs with access to that file to decrypt all traffic over
41/// 1. cleartext48/// this connection.
42/// 2. ciphertext49ssl_key_log: ?*SslKeyLog,
43/// 3. unused50
44/// The fields `partial_cleartext_idx`, `partial_ciphertext_idx`, and51pub const ReadError = error{
45/// `partial_ciphertext_end` describe the span of the segments.52 /// The alert description will be stored in `alert`.
46partially_read_buffer: [tls.max_ciphertext_record_len]u8,53 TlsAlert,
47/// If non-null, ssl secrets are logged to a file. Creating such a log file allows other54 TlsBadLength,
48/// programs with access to that file to decrypt all traffic over this connection.55 TlsBadRecordMac,
49ssl_key_log: ?struct {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 {
50 client_key_seq: u64,68 client_key_seq: u64,
51 server_key_seq: u64,69 server_key_seq: u64,
52 client_random: [32]u8,70 client_random: [32]u8,
53 file: std.fs.File,71 writer: *Writer,
5472
55 fn clientCounter(key_log: *@This()) u64 {73 fn clientCounter(key_log: *@This()) u64 {
56 defer key_log.client_key_seq += 1;74 defer key_log.client_key_seq += 1;
...@@ -61,51 +79,12 @@ ssl_key_log: ?struct {...@@ -61,51 +79,12 @@ ssl_key_log: ?struct {
61 defer key_log.server_key_seq += 1;79 defer key_log.server_key_seq += 1;
62 return key_log.server_key_seq;80 return key_log.server_key_seq;
63 }81 }
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 }
107};82};
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
109pub const Options = struct {88pub const Options = struct {
110 /// How to perform host verification of server certificates.89 /// How to perform host verification of server certificates.
111 host: union(enum) {90 host: union(enum) {
...@@ -127,64 +106,85 @@ pub const Options = struct {...@@ -127,64 +106,85 @@ pub const Options = struct {
127 /// Verify that the server certificate is authorized by a given ca bundle.106 /// Verify that the server certificate is authorized by a given ca bundle.
128 bundle: Certificate.Bundle,107 bundle: Certificate.Bundle,
129 },108 },
130 /// If non-null, ssl secrets are logged to this file. Creating such a log file allows109 /// If non-null, ssl secrets are logged to this stream. Creating such a log file allows
131 /// other programs with access to that file to decrypt all traffic over this connection.110 /// 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,
133};129};
134130
135pub fn InitError(comptime Stream: type) type {131const InitError = error{
136 return std.mem.Allocator.Error || Stream.WriteError || Stream.ReadError || tls.AlertDescription.Error || error{132 WriteFailed,
137 InsufficientEntropy,133 ReadFailed,
138 DiskQuota,134 InsufficientEntropy,
139 LockViolation,135 DiskQuota,
140 NotOpenForWriting,136 LockViolation,
141 TlsUnexpectedMessage,137 NotOpenForWriting,
142 TlsIllegalParameter,138 /// The alert description will be stored in `alert`.
143 TlsDecryptFailure,139 TlsAlert,
144 TlsRecordOverflow,140 TlsUnexpectedMessage,
145 TlsBadRecordMac,141 TlsIllegalParameter,
146 CertificateFieldHasInvalidLength,142 TlsDecryptFailure,
147 CertificateHostMismatch,143 TlsRecordOverflow,
148 CertificatePublicKeyInvalid,144 TlsBadRecordMac,
149 CertificateExpired,145 CertificateFieldHasInvalidLength,
150 CertificateFieldHasWrongDataType,146 CertificateHostMismatch,
151 CertificateIssuerMismatch,147 CertificatePublicKeyInvalid,
152 CertificateNotYetValid,148 CertificateExpired,
153 CertificateSignatureAlgorithmMismatch,149 CertificateFieldHasWrongDataType,
154 CertificateSignatureAlgorithmUnsupported,150 CertificateIssuerMismatch,
155 CertificateSignatureInvalid,151 CertificateNotYetValid,
156 CertificateSignatureInvalidLength,152 CertificateSignatureAlgorithmMismatch,
157 CertificateSignatureNamedCurveUnsupported,153 CertificateSignatureAlgorithmUnsupported,
158 CertificateSignatureUnsupportedBitCount,154 CertificateSignatureInvalid,
159 TlsCertificateNotVerified,155 CertificateSignatureInvalidLength,
160 TlsBadSignatureScheme,156 CertificateSignatureNamedCurveUnsupported,
161 TlsBadRsaSignatureBitCount,157 CertificateSignatureUnsupportedBitCount,
162 InvalidEncoding,158 TlsCertificateNotVerified,
163 IdentityElement,159 TlsBadSignatureScheme,
164 SignatureVerificationFailed,160 TlsBadRsaSignatureBitCount,
165 TlsDecryptError,161 InvalidEncoding,
166 TlsConnectionTruncated,162 IdentityElement,
167 TlsDecodeError,163 SignatureVerificationFailed,
168 UnsupportedCertificateVersion,164 TlsDecryptError,
169 CertificateTimeInvalid,165 TlsConnectionTruncated,
170 CertificateHasUnrecognizedObjectId,166 TlsDecodeError,
171 CertificateHasInvalidBitString,167 UnsupportedCertificateVersion,
172 MessageTooLong,168 CertificateTimeInvalid,
173 NegativeIntoUnsigned,169 CertificateHasUnrecognizedObjectId,
174 TargetTooSmall,170 CertificateHasInvalidBitString,
175 BufferTooSmall,171 MessageTooLong,
176 InvalidSignature,172 NegativeIntoUnsigned,
177 NotSquare,173 TargetTooSmall,
178 NonCanonical,174 BufferTooSmall,
179 WeakPublicKey,175 InvalidSignature,
180 };176 NotSquare,
181}177 NonCanonical,
178 WeakPublicKey,
179};
182180
183/// Initiates a TLS handshake and establishes a TLSv1.2 or TLSv1.3 session with `stream`, which181/// Initiates a TLS handshake and establishes a TLSv1.2 or TLSv1.3 session.
184/// must conform to `StreamInterface`.
185///182///
186/// `host` is only borrowed during this function call.183/// `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);
188 const host = switch (options.host) {188 const host = switch (options.host) {
189 .no_verification => "",189 .no_verification => "",
190 .explicit => |host| host,190 .explicit => |host| host,
...@@ -276,11 +276,8 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client...@@ -276,11 +276,8 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client
276 };276 };
277277
278 {278 {
279 var iovecs = [_]std.posix.iovec_const{279 var iovecs: [2][]const u8 = .{ cleartext_header, host };
280 .{ .base = cleartext_header.ptr, .len = cleartext_header.len },280 try output.writeVecAll(iovecs[0..if (host.len == 0) 1 else 2]);
281 .{ .base = host.ptr, .len = host.len },
282 };
283 try stream.writevAll(iovecs[0..if (host.len == 0) 1 else 2]);
284 }281 }
285282
286 var tls_version: tls.ProtocolVersion = undefined;283 var tls_version: tls.ProtocolVersion = undefined;
...@@ -329,20 +326,26 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client...@@ -329,20 +326,26 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client
329 var cleartext_fragment_start: usize = 0;326 var cleartext_fragment_start: usize = 0;
330 var cleartext_fragment_end: usize = 0;327 var cleartext_fragment_end: usize = 0;
331 var cleartext_bufs: [2][tls.max_ciphertext_inner_record_len]u8 = undefined;328 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 };
334 fragment: while (true) {329 fragment: while (true) {
335 try d.readAtLeastOurAmt(stream, tls.record_header_len);330 // Ensure the input buffer pointer is stable in this scope.
336 const record_header = d.buf[d.idx..][0..tls.record_header_len];331 input.rebaseCapacity(tls.max_ciphertext_record_len);
337 const record_ct = d.decode(tls.ContentType);332 const record_header = input.peek(tls.record_header_len) catch |err| switch (err) {
338 d.skip(2); // legacy_version333 error.EndOfStream => return error.TlsConnectionTruncated,
339 const record_len = d.decode(u16);334 error.ReadFailed => return error.ReadFailed,
340 try d.readAtLeast(stream, record_len);335 };
341 var record_decoder = try d.sub(record_len);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);
342 var ctd, const ct = content: switch (cipher_state) {345 var ctd, const ct = content: switch (cipher_state) {
343 .cleartext => .{ record_decoder, record_ct },346 .cleartext => .{ record_decoder, record_ct },
344 .handshake => {347 .handshake => {
345 std.debug.assert(tls_version == .tls_1_3);348 assert(tls_version == .tls_1_3);
346 if (record_ct != .application_data) return error.TlsUnexpectedMessage;349 if (record_ct != .application_data) return error.TlsUnexpectedMessage;
347 try record_decoder.ensure(record_len);350 try record_decoder.ensure(record_len);
348 const cleartext_buf = &cleartext_bufs[cert_buf_index % 2];351 const cleartext_buf = &cleartext_bufs[cert_buf_index % 2];
...@@ -374,7 +377,7 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client...@@ -374,7 +377,7 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client
374 break :content .{ tls.Decoder.fromTheirSlice(@constCast(cleartext_buf[cleartext_fragment_start..cleartext_fragment_end])), ct };377 break :content .{ tls.Decoder.fromTheirSlice(@constCast(cleartext_buf[cleartext_fragment_start..cleartext_fragment_end])), ct };
375 },378 },
376 .application => {379 .application => {
377 std.debug.assert(tls_version == .tls_1_2);380 assert(tls_version == .tls_1_2);
378 if (record_ct != .handshake) return error.TlsUnexpectedMessage;381 if (record_ct != .handshake) return error.TlsUnexpectedMessage;
379 try record_decoder.ensure(record_len);382 try record_decoder.ensure(record_len);
380 const cleartext_buf = &cleartext_bufs[cert_buf_index % 2];383 const cleartext_buf = &cleartext_bufs[cert_buf_index % 2];
...@@ -412,14 +415,11 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client...@@ -412,14 +415,11 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client
412 switch (ct) {415 switch (ct) {
413 .alert => {416 .alert => {
414 ctd.ensure(2) catch continue :fragment;417 ctd.ensure(2) catch continue :fragment;
415 const level = ctd.decode(tls.AlertLevel);418 if (options.alert) |a| a.* = .{
416 const desc = ctd.decode(tls.AlertDescription);419 .level = ctd.decode(tls.Alert.Level),
417 _ = level;420 .description = ctd.decode(tls.Alert.Description),
418421 };
419 // if this isn't a error alert, then it's a closure alert, which makes no sense in a handshake422 return error.TlsAlert;
420 try desc.toError();
421 // TODO: handle server-side closures
422 return error.TlsUnexpectedMessage;
423 },423 },
424 .change_cipher_spec => {424 .change_cipher_spec => {
425 ctd.ensure(1) catch continue :fragment;425 ctd.ensure(1) catch continue :fragment;
...@@ -533,7 +533,7 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client...@@ -533,7 +533,7 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client
533 pv.master_secret = P.Hkdf.extract(&ap_derived_secret, &zeroes);533 pv.master_secret = P.Hkdf.extract(&ap_derived_secret, &zeroes);
534 const client_secret = hkdfExpandLabel(P.Hkdf, pv.handshake_secret, "c hs traffic", &hello_hash, P.Hash.digest_length);534 const client_secret = hkdfExpandLabel(P.Hkdf, pv.handshake_secret, "c hs traffic", &hello_hash, P.Hash.digest_length);
535 const server_secret = hkdfExpandLabel(P.Hkdf, pv.handshake_secret, "s hs traffic", &hello_hash, P.Hash.digest_length);535 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, .{
537 .client_random = &client_hello_rand,537 .client_random = &client_hello_rand,
538 }, .{538 }, .{
539 .SERVER_HANDSHAKE_TRAFFIC_SECRET = &server_secret,539 .SERVER_HANDSHAKE_TRAFFIC_SECRET = &server_secret,
...@@ -707,7 +707,7 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client...@@ -707,7 +707,7 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client
707 &client_hello_rand,707 &client_hello_rand,
708 &server_hello_rand,708 &server_hello_rand,
709 }, 48);709 }, 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, .{
711 .client_random = &client_hello_rand,711 .client_random = &client_hello_rand,
712 }, .{712 }, .{
713 .CLIENT_RANDOM = &master_secret,713 .CLIENT_RANDOM = &master_secret,
...@@ -755,11 +755,12 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client...@@ -755,11 +755,12 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client
755 nonce,755 nonce,
756 pv.app_cipher.client_write_key,756 pv.app_cipher.client_write_key,
757 );757 );
758 const all_msgs = client_key_exchange_msg ++ client_change_cipher_spec_msg ++ client_verify_msg;758 var all_msgs_vec: [3][]const u8 = .{
759 var all_msgs_vec = [_]std.posix.iovec_const{759 &client_key_exchange_msg,
760 .{ .base = &all_msgs, .len = all_msgs.len },760 &client_change_cipher_spec_msg,
761 &client_verify_msg,
761 };762 };
762 try stream.writevAll(&all_msgs_vec);763 try output.writeVecAll(&all_msgs_vec);
763 },764 },
764 }765 }
765 write_seq += 1;766 write_seq += 1;
...@@ -820,15 +821,15 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client...@@ -820,15 +821,15 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client
820 const nonce = pv.client_handshake_iv;821 const nonce = pv.client_handshake_iv;
821 P.AEAD.encrypt(ciphertext, auth_tag, &out_cleartext, ad, nonce, pv.client_handshake_key);822 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: [2][]const u8 = .{
824 var all_msgs_vec = [_]std.posix.iovec_const{825 &client_change_cipher_spec_msg,
825 .{ .base = &all_msgs, .len = all_msgs.len },826 &finished_msg,
826 };827 };
827 try stream.writevAll(&all_msgs_vec);828 try output.writeVecAll(&all_msgs_vec);
828829
829 const client_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "c ap traffic", &handshake_hash, P.Hash.digest_length);830 const client_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "c ap traffic", &handshake_hash, P.Hash.digest_length);
830 const server_secret = hkdfExpandLabel(P.Hkdf, pv.master_secret, "s ap traffic", &handshake_hash, P.Hash.digest_length);831 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, .{
832 .counter = key_seq,833 .counter = key_seq,
833 .client_random = &client_hello_rand,834 .client_random = &client_hello_rand,
834 }, .{835 }, .{
...@@ -855,8 +856,28 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client...@@ -855,8 +856,28 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client
855 else => unreachable,856 else => unreachable,
856 },857 },
857 };858 };
858 const leftover = d.rest();859 if (options.ssl_key_log) |ssl_key_log| ssl_key_log.* = .{
859 var client: Client = .{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 },
860 .tls_version = tls_version,881 .tls_version = tls_version,
861 .read_seq = switch (tls_version) {882 .read_seq = switch (tls_version) {
862 .tls_1_3 => 0,883 .tls_1_3 => 0,
...@@ -868,22 +889,11 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client...@@ -868,22 +889,11 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client
868 .tls_1_2 => write_seq,889 .tls_1_2 => write_seq,
869 else => unreachable,890 else => unreachable,
870 },891 },
871 .partial_cleartext_idx = 0,
872 .partial_ciphertext_idx = 0,
873 .partial_ciphertext_end = @intCast(leftover.len),
874 .received_close_notify = false,892 .received_close_notify = false,
875 .allow_truncation_attacks = false,893 .allow_truncation_attacks = options.allow_truncation_attacks,
876 .application_cipher = app_cipher,894 .application_cipher = app_cipher,
877 .partially_read_buffer = undefined,895 .ssl_key_log = options.ssl_key_log,
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,
884 };896 };
885 @memcpy(client.partially_read_buffer[0..leftover.len], leftover);
886 return client;
887 },897 },
888 else => return error.TlsUnexpectedMessage,898 else => return error.TlsUnexpectedMessage,
889 }899 }
...@@ -897,94 +907,48 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client...@@ -897,94 +907,48 @@ pub fn init(stream: anytype, options: Options) InitError(@TypeOf(stream))!Client
897 }907 }
898}908}
899909
900/// Sends TLS-encrypted data to `stream`, which must conform to `StreamInterface`.910fn drain(w: *Writer, data: []const []const u8, splat: usize) Writer.Error!usize {
901/// Returns the number of cleartext bytes sent, which may be fewer than `bytes.len`.911 const c: *Client = @fieldParentPtr("writer", w);
902pub fn write(c: *Client, stream: anytype, bytes: []const u8) !usize {912 if (true) @panic("update to use the buffer and flush");
903 return writeEnd(c, stream, bytes, false);913 const sliced_data = if (splat == 0) data[0..data.len -| 1] else data;
904}914 const output = c.output;
905915 const ciphertext_buf = try output.writableSliceGreedy(min_buffer_len);
906/// Sends TLS-encrypted data to `stream`, which must conform to `StreamInterface`.916 var total_clear: usize = 0;
907pub fn writeAll(c: *Client, stream: anytype, bytes: []const u8) !void {917 var ciphertext_end: usize = 0;
908 var index: usize = 0;918 for (sliced_data) |buf| {
909 while (index < bytes.len) {919 const prepared = prepareCiphertextRecord(c, ciphertext_buf[ciphertext_end..], buf, .application_data);
910 index += try c.write(stream, bytes[index..]);920 total_clear += prepared.cleartext_len;
911 }921 ciphertext_end += prepared.ciphertext_end;
912}922 if (total_clear < buf.len) break;
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);
922 }923 }
924 output.advance(ciphertext_end);
925 return total_clear;
923}926}
924927
925/// Sends TLS-encrypted data to `stream`, which must conform to `StreamInterface`.928/// Sends a `close_notify` alert, which is necessary for the server to
926/// Returns the number of cleartext bytes sent, which may be fewer than `bytes.len`.929/// distinguish between a properly finished TLS session, or a truncation
927/// If `end` is true, then this function additionally sends a `close_notify` alert,930/// attack.
928/// which is necessary for the server to distinguish between a properly finished931pub fn end(c: *Client) Writer.Error!void {
929/// TLS session, or a truncation attack.932 const output = c.output;
930pub fn writeEnd(c: *Client, stream: anytype, bytes: []const u8, end: bool) !usize {933 const ciphertext_buf = try output.writableSliceGreedy(min_buffer_len);
931 var ciphertext_buf: [tls.max_ciphertext_record_len * 4]u8 = undefined;934 const prepared = prepareCiphertextRecord(c, ciphertext_buf, &tls.close_notify_alert, .alert);
932 var iovecs_buf: [6]std.posix.iovec_const = undefined;935 output.advance(prepared.cleartext_len);
933 var prepared = prepareCiphertextRecord(c, &iovecs_buf, &ciphertext_buf, bytes, .application_data);936 return prepared.ciphertext_end;
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 }
969}937}
970938
971fn prepareCiphertextRecord(939fn prepareCiphertextRecord(
972 c: *Client,940 c: *Client,
973 iovecs: []std.posix.iovec_const,
974 ciphertext_buf: []u8,941 ciphertext_buf: []u8,
975 bytes: []const u8,942 bytes: []const u8,
976 inner_content_type: tls.ContentType,943 inner_content_type: tls.ContentType,
977) struct {944) struct {
978 iovec_end: usize,
979 ciphertext_end: usize,945 ciphertext_end: usize,
980 /// How many bytes are taken up by overhead per record.946 cleartext_len: usize,
981 overhead_len: usize,
982} {947} {
983 // Due to the trailing inner content type byte in the ciphertext, we need948 // Due to the trailing inner content type byte in the ciphertext, we need
984 // an additional buffer for storing the cleartext into before encrypting.949 // an additional buffer for storing the cleartext into before encrypting.
985 var cleartext_buf: [max_ciphertext_len]u8 = undefined;950 var cleartext_buf: [max_ciphertext_len]u8 = undefined;
986 var ciphertext_end: usize = 0;951 var ciphertext_end: usize = 0;
987 var iovec_end: usize = 0;
988 var bytes_i: usize = 0;952 var bytes_i: usize = 0;
989 switch (c.application_cipher) {953 switch (c.application_cipher) {
990 inline else => |*p| switch (c.tls_version) {954 inline else => |*p| switch (c.tls_version) {
...@@ -992,18 +956,15 @@ fn prepareCiphertextRecord(...@@ -992,18 +956,15 @@ fn prepareCiphertextRecord(
992 const pv = &p.tls_1_3;956 const pv = &p.tls_1_3;
993 const P = @TypeOf(p.*);957 const P = @TypeOf(p.*);
994 const overhead_len = tls.record_header_len + P.AEAD.tag_length + 1;958 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;
996 while (true) {959 while (true) {
997 const encrypted_content_len: u16 = @min(960 const encrypted_content_len: u16 = @min(
998 bytes.len - bytes_i,961 bytes.len - bytes_i,
999 tls.max_ciphertext_inner_record_len,962 tls.max_ciphertext_inner_record_len,
1000 ciphertext_buf.len -|963 ciphertext_buf.len -| (overhead_len + ciphertext_end),
1001 (close_notify_alert_reserved + overhead_len + ciphertext_end),
1002 );964 );
1003 if (encrypted_content_len == 0) return .{965 if (encrypted_content_len == 0) return .{
1004 .iovec_end = iovec_end,
1005 .ciphertext_end = ciphertext_end,966 .ciphertext_end = ciphertext_end,
1006 .overhead_len = overhead_len,967 .cleartext_len = bytes_i,
1007 };968 };
1008969
1009 @memcpy(cleartext_buf[0..encrypted_content_len], bytes[bytes_i..][0..encrypted_content_len]);970 @memcpy(cleartext_buf[0..encrypted_content_len], bytes[bytes_i..][0..encrypted_content_len]);
...@@ -1012,7 +973,6 @@ fn prepareCiphertextRecord(...@@ -1012,7 +973,6 @@ fn prepareCiphertextRecord(
1012 const ciphertext_len = encrypted_content_len + 1;973 const ciphertext_len = encrypted_content_len + 1;
1013 const cleartext = cleartext_buf[0..ciphertext_len];974 const cleartext = cleartext_buf[0..ciphertext_len];
1014975
1015 const record_start = ciphertext_end;
1016 const ad = ciphertext_buf[ciphertext_end..][0..tls.record_header_len];976 const ad = ciphertext_buf[ciphertext_end..][0..tls.record_header_len];
1017 ad.* = .{@intFromEnum(tls.ContentType.application_data)} ++977 ad.* = .{@intFromEnum(tls.ContentType.application_data)} ++
1018 int(u16, @intFromEnum(tls.ProtocolVersion.tls_1_2)) ++978 int(u16, @intFromEnum(tls.ProtocolVersion.tls_1_2)) ++
...@@ -1030,38 +990,27 @@ fn prepareCiphertextRecord(...@@ -1030,38 +990,27 @@ fn prepareCiphertextRecord(
1030 };990 };
1031 P.AEAD.encrypt(ciphertext, auth_tag, cleartext, ad, nonce, pv.client_key);991 P.AEAD.encrypt(ciphertext, auth_tag, cleartext, ad, nonce, pv.client_key);
1032 c.write_seq += 1; // TODO send key_update on overflow992 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;
1040 }993 }
1041 },994 },
1042 .tls_1_2 => {995 .tls_1_2 => {
1043 const pv = &p.tls_1_2;996 const pv = &p.tls_1_2;
1044 const P = @TypeOf(p.*);997 const P = @TypeOf(p.*);
1045 const overhead_len = tls.record_header_len + P.record_iv_length + P.mac_length;998 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;
1047 while (true) {999 while (true) {
1048 const message_len: u16 = @min(1000 const message_len: u16 = @min(
1049 bytes.len - bytes_i,1001 bytes.len - bytes_i,
1050 tls.max_ciphertext_inner_record_len,1002 tls.max_ciphertext_inner_record_len,
1051 ciphertext_buf.len -|1003 ciphertext_buf.len -| (overhead_len + ciphertext_end),
1052 (close_notify_alert_reserved + overhead_len + ciphertext_end),
1053 );1004 );
1054 if (message_len == 0) return .{1005 if (message_len == 0) return .{
1055 .iovec_end = iovec_end,
1056 .ciphertext_end = ciphertext_end,1006 .ciphertext_end = ciphertext_end,
1057 .overhead_len = overhead_len,1007 .cleartext_len = bytes_i,
1058 };1008 };
10591009
1060 @memcpy(cleartext_buf[0..message_len], bytes[bytes_i..][0..message_len]);1010 @memcpy(cleartext_buf[0..message_len], bytes[bytes_i..][0..message_len]);
1061 bytes_i += message_len;1011 bytes_i += message_len;
1062 const cleartext = cleartext_buf[0..message_len];1012 const cleartext = cleartext_buf[0..message_len];
10631013
1064 const record_start = ciphertext_end;
1065 const record_header = ciphertext_buf[ciphertext_end..][0..tls.record_header_len];1014 const record_header = ciphertext_buf[ciphertext_end..][0..tls.record_header_len];
1066 ciphertext_end += tls.record_header_len;1015 ciphertext_end += tls.record_header_len;
1067 record_header.* = .{@intFromEnum(inner_content_type)} ++1016 record_header.* = .{@intFromEnum(inner_content_type)} ++
...@@ -1083,13 +1032,6 @@ fn prepareCiphertextRecord(...@@ -1083,13 +1032,6 @@ fn prepareCiphertextRecord(
1083 ciphertext_end += P.mac_length;1032 ciphertext_end += P.mac_length;
1084 P.AEAD.encrypt(ciphertext, auth_tag, cleartext, ad, nonce, pv.client_write_key);1033 P.AEAD.encrypt(ciphertext, auth_tag, cleartext, ad, nonce, pv.client_write_key);
1085 c.write_seq += 1; // TODO send key_update on overflow1034 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;
1093 }1035 }
1094 },1036 },
1095 else => unreachable,1037 else => unreachable,
...@@ -1098,421 +1040,194 @@ fn prepareCiphertextRecord(...@@ -1098,421 +1040,194 @@ fn prepareCiphertextRecord(
1098}1040}
10991041
1100pub fn eof(c: Client) bool {1042pub fn eof(c: Client) bool {
1101 return c.received_close_notify and1043 return c.received_close_notify;
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 }
1162}1044}
11631045
1164/// Receives TLS-encrypted data from `stream`, which must conform to `StreamInterface`.1046fn stream(r: *Reader, w: *Writer, limit: std.io.Limit) Reader.StreamError!usize {
1165/// Returns number of bytes that have been read, populated inside `iovecs`. A1047 const c: *Client = @fieldParentPtr("reader", r);
1166/// return value of zero bytes does not mean end of stream. Instead, check the `eof()`1048 if (c.eof()) return error.EndOfStream;
1167/// for the end of stream. The `eof()` may be true after any call to1049 const input = c.input;
1168/// `read`, including when greater than zero bytes are returned, and this1050 // If at least one full encrypted record is not buffered, read once.
1169/// function asserts that `eof()` is `false`.1051 const record_header = input.peek(tls.record_header_len) catch |err| switch (err) {
1170/// See `readv` for a higher level function that has the same, familiar API as1052 error.EndOfStream => {
1171/// other read functions, such as `std.fs.File.read`.1053 // This is either a truncation attack, a bug in the server, or an
1172pub fn readvAdvanced(c: *Client, stream: anytype, iovecs: []const std.posix.iovec) !usize {1054 // intentional omission of the close_notify message due to truncation
1173 var vp: VecPut = .{ .iovecs = iovecs };1055 // detection handled above the TLS layer.
11741056 if (c.allow_truncation_attacks) {
1175 // Give away the buffered cleartext we have, if any.1057 c.received_close_notify = true;
1176 const partial_cleartext = c.partially_read_buffer[c.partial_cleartext_idx..c.partial_ciphertext_idx];1058 return error.EndOfStream;
1177 if (partial_cleartext.len > 0) {1059 } else {
1178 const amt: u15 = @intCast(vp.put(partial_cleartext));1060 return failRead(c, error.TlsConnectionTruncated);
1179 c.partial_cleartext_idx += amt;1061 }
11801062 },
1181 if (c.partial_cleartext_idx == c.partial_ciphertext_idx and1063 error.ReadFailed => return error.ReadFailed,
1182 c.partial_ciphertext_end == c.partial_ciphertext_idx)1064 };
1183 {1065 const ct: tls.ContentType = @enumFromInt(record_header[0]);
1184 // The buffer is now empty.1066 const legacy_version = mem.readInt(u16, record_header[1..][0..2], .big);
1185 c.partial_cleartext_idx = 0;1067 _ = legacy_version;
1186 c.partial_ciphertext_idx = 0;1068 const record_len = mem.readInt(u16, record_header[3..][0..2], .big);
1187 c.partial_ciphertext_end = 0;1069 if (record_len > max_ciphertext_len) return failRead(c, error.TlsRecordOverflow);
1188 }1070 const record_end = 5 + record_len;
11891071 if (record_end > input.buffered().len) {
1190 if (c.received_close_notify) {1072 input.fillMore() catch |err| switch (err) {
1191 c.partial_ciphertext_end = 0;1073 error.EndOfStream => return failRead(c, error.TlsConnectionTruncated),
1192 assert(vp.total == amt);1074 error.ReadFailed => return error.ReadFailed,
1193 return amt;1075 };
1194 } else if (amt > 0) {1076 if (record_end > input.buffered().len) return 0;
1195 // We don't need more data, so don't call read.
1196 assert(vp.total == amt);
1197 return amt;
1198 }
1199 }1077 }
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`.
1205 var cleartext_stack_buffer: [max_ciphertext_len]u8 = undefined;1079 var cleartext_stack_buffer: [max_ciphertext_len]u8 = undefined;
1206 // Temporarily stores ciphertext before decrypting it and giving it to `iovecs`.1080 const cleartext, const inner_ct: tls.ContentType = cleartext: switch (c.application_cipher) {
1207 var in_stack_buffer: [max_ciphertext_len * 4]u8 = undefined;1081 inline else => |*p| switch (c.tls_version) {
1208 // How many bytes left in the user's buffer.1082 .tls_1_3 => {
1209 const free_size = vp.freeSize();1083 const pv = &p.tls_1_3;
1210 // The amount of the user's buffer that we need to repurpose for storing1084 const P = @TypeOf(p.*);
1211 // ciphertext. The end of the buffer will be used for such purposes.1085 const ad = input.take(tls.record_header_len) catch unreachable; // already peeked
1212 const ciphertext_buf_len = (free_size / 2) -| in_stack_buffer.len;1086 const ciphertext_len = record_len - P.AEAD.tag_length;
1213 // The amount of the user's buffer that will be used to give cleartext. The1087 const ciphertext = input.take(ciphertext_len) catch unreachable; // already peeked
1214 // beginning of the buffer will be used for such purposes.1088 const auth_tag = (input.takeArray(P.AEAD.tag_length) catch unreachable).*; // already peeked
1215 const cleartext_buf_len = free_size - ciphertext_buf_len;1089 const nonce = nonce: {
12161090 const V = @Vector(P.AEAD.nonce_length, u8);
1217 // Recoup `partially_read_buffer` space. This is necessary because it is assumed1091 const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8);
1218 // below that `frag0` is big enough to hold at least one record.1092 const operand: V = pad ++ std.mem.toBytes(big(c.read_seq));
1219 limitedOverlapCopy(c.partially_read_buffer[0..c.partial_ciphertext_end], c.partial_ciphertext_idx);1093 break :nonce @as(V, pv.server_iv) ^ operand;
1220 c.partial_ciphertext_end -= c.partial_ciphertext_idx;1094 };
1221 c.partial_ciphertext_idx = 0;1095 const cleartext = cleartext_stack_buffer[0..ciphertext.len];
1222 c.partial_cleartext_idx = 0;1096 P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, pv.server_key) catch
1223 const first_iov = c.partially_read_buffer[c.partial_ciphertext_end..];1097 return failRead(c, error.TlsBadRecordMac);
12241098 const msg = mem.trimRight(u8, cleartext, "\x00");
1225 var ask_iovecs_buf: [2]std.posix.iovec = .{1099 break :cleartext .{ msg[0 .. msg.len - 1], @enumFromInt(msg[msg.len - 1]) };
1226 .{1100 },
1227 .base = first_iov.ptr,1101 .tls_1_2 => {
1228 .len = first_iov.len,1102 const pv = &p.tls_1_2;
1229 },1103 const P = @TypeOf(p.*);
1230 .{1104 const message_len: u16 = record_len - P.record_iv_length - P.mac_length;
1231 .base = &in_stack_buffer,1105 const ad_header = input.take(tls.record_header_len) catch unreachable; // already peeked
1232 .len = in_stack_buffer.len,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,
1233 },1126 },
1234 };1127 };
12351128 c.read_seq = std.math.add(u64, c.read_seq, 1) catch return failRead(c, error.TlsSequenceOverflow);
1236 // Cleartext capacity of output buffer, in records. Minimum one full record.1129 switch (inner_ct) {
1237 const buf_cap = @max(cleartext_buf_len / max_ciphertext_len, 1);1130 .alert => {
1238 const wanted_read_len = buf_cap * (max_ciphertext_len + tls.record_header_len);1131 if (cleartext.len != 2) return failRead(c, error.TlsDecodeError);
1239 const ask_len = @max(wanted_read_len, cleartext_stack_buffer.len) - c.partial_ciphertext_end;1132 const alert: tls.Alert = .{
1240 const ask_iovecs = limitVecs(&ask_iovecs_buf, ask_len);1133 .level = @enumFromInt(cleartext[0]),
1241 const actual_read_len = try stream.readv(ask_iovecs);1134 .description = @enumFromInt(cleartext[1]),
1242 if (actual_read_len == 0) {1135 };
1243 // This is either a truncation attack, a bug in the server, or an1136 switch (alert.description) {
1244 // intentional omission of the close_notify message due to truncation1137 .close_notify => {
1245 // detection handled above the TLS layer.1138 c.received_close_notify = true;
1246 if (c.allow_truncation_attacks) {1139 return 0;
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]) };
1358 },1140 },
1359 .tls_1_2 => {1141 .user_canceled => {
1360 const pv = &p.tls_1_2;1142 // TODO: handle server-side closures
1361 const P = @TypeOf(p.*);1143 return failRead(c, error.TlsUnexpectedMessage);
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 };
1389 },1144 },
1390 else => unreachable,1145 else => {
1391 },1146 c.alert = alert;
1392 };1147 return failRead(c, error.TlsAlert);
1393 c.read_seq = try std.math.add(u64, c.read_seq, 1);1148 },
1394 switch (inner_ct) {1149 }
1395 .alert => {1150 },
1396 if (cleartext.len != 2) return error.TlsDecodeError;1151 .handshake => {
1397 const level: tls.AlertLevel = @enumFromInt(cleartext[0]);1152 var ct_i: usize = 0;
1398 const desc: tls.AlertDescription = @enumFromInt(cleartext[1]);1153 while (true) {
1399 if (desc == .close_notify) {1154 const handshake_type: tls.HandshakeType = @enumFromInt(cleartext[ct_i]);
1400 c.received_close_notify = true;1155 ct_i += 1;
1401 c.partial_ciphertext_end = c.partial_ciphertext_idx;1156 const handshake_len = mem.readInt(u24, cleartext[ct_i..][0..3], .big);
1402 return vp.total;1157 ct_i += 3;
1403 }1158 const next_handshake_i = ct_i + handshake_len;
1404 _ = level;1159 if (next_handshake_i > cleartext.len) return failRead(c, error.TlsBadLength);
14051160 const handshake = cleartext[ct_i..next_handshake_i];
1406 try desc.toError();1161 switch (handshake_type) {
1407 // TODO: handle server-side closures1162 .new_session_ticket => {
1408 return error.TlsUnexpectedMessage;1163 // This client implementation ignores new session tickets.
1409 },1164 },
1410 .handshake => {1165 .key_update => {
1411 var ct_i: usize = 0;1166 switch (c.application_cipher) {
1412 while (true) {1167 inline else => |*p| {
1413 const handshake_type: tls.HandshakeType = @enumFromInt(cleartext[ct_i]);1168 const pv = &p.tls_1_3;
1414 ct_i += 1;1169 const P = @TypeOf(p.*);
1415 const handshake_len = mem.readInt(u24, cleartext[ct_i..][0..3], .big);1170 const server_secret = hkdfExpandLabel(P.Hkdf, pv.server_secret, "traffic upd", "", P.Hash.digest_length);
1416 ct_i += 3;1171 if (c.ssl_key_log) |key_log| logSecrets(key_log.writer, .{
1417 const next_handshake_i = ct_i + handshake_len;1172 .counter = key_log.serverCounter(),
1418 if (next_handshake_i > cleartext.len)1173 .client_random = &key_log.client_random,
1419 return error.TlsBadLength;1174 }, .{
1420 const handshake = cleartext[ct_i..next_handshake_i];1175 .SERVER_TRAFFIC_SECRET = &server_secret,
1421 switch (handshake_type) {1176 });
1422 .new_session_ticket => {1177 pv.server_secret = server_secret;
1423 // This client implementation ignores new session tickets.1178 pv.server_key = hkdfExpandLabel(P.Hkdf, server_secret, "key", "", P.AEAD.key_length);
1424 },1179 pv.server_iv = hkdfExpandLabel(P.Hkdf, server_secret, "iv", "", P.AEAD.nonce_length);
1425 .key_update => {1180 },
1426 switch (c.application_cipher) {1181 }
1427 inline else => |*p| {1182 c.read_seq = 0;
1428 const pv = &p.tls_1_3;1183
1429 const P = @TypeOf(p.*);1184 switch (@as(tls.KeyUpdateRequest, @enumFromInt(handshake[0]))) {
1430 const server_secret = hkdfExpandLabel(P.Hkdf, pv.server_secret, "traffic upd", "", P.Hash.digest_length);1185 .update_requested => {
1431 if (c.ssl_key_log) |*key_log| logSecrets(key_log.file, .{1186 switch (c.application_cipher) {
1432 .counter = key_log.serverCounter(),1187 inline else => |*p| {
1433 .client_random = &key_log.client_random,1188 const pv = &p.tls_1_3;
1434 }, .{1189 const P = @TypeOf(p.*);
1435 .SERVER_TRAFFIC_SECRET = &server_secret,1190 const client_secret = hkdfExpandLabel(P.Hkdf, pv.client_secret, "traffic upd", "", P.Hash.digest_length);
1436 });1191 if (c.ssl_key_log) |key_log| logSecrets(key_log.writer, .{
1437 pv.server_secret = server_secret;1192 .counter = key_log.clientCounter(),
1438 pv.server_key = hkdfExpandLabel(P.Hkdf, server_secret, "key", "", P.AEAD.key_length);1193 .client_random = &key_log.client_random,
1439 pv.server_iv = hkdfExpandLabel(P.Hkdf, server_secret, "iv", "", P.AEAD.nonce_length);1194 }, .{
1440 },1195 .CLIENT_TRAFFIC_SECRET = &client_secret,
1441 }1196 });
1442 c.read_seq = 0;1197 pv.client_secret = client_secret;
14431198 pv.client_key = hkdfExpandLabel(P.Hkdf, client_secret, "key", "", P.AEAD.key_length);
1444 switch (@as(tls.KeyUpdateRequest, @enumFromInt(handshake[0]))) {1199 pv.client_iv = hkdfExpandLabel(P.Hkdf, client_secret, "iv", "", P.AEAD.nonce_length);
1445 .update_requested => {1200 },
1446 switch (c.application_cipher) {1201 }
1447 inline else => |*p| {1202 c.write_seq = 0;
1448 const pv = &p.tls_1_3;1203 },
1449 const P = @TypeOf(p.*);1204 .update_not_requested => {},
1450 const client_secret = hkdfExpandLabel(P.Hkdf, pv.client_secret, "traffic upd", "", P.Hash.digest_length);1205 _ => return failRead(c, error.TlsIllegalParameter),
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);
1496 }1206 }
1497 }1207 },
1498 } else {1208 else => return failRead(c, error.TlsUnexpectedMessage),
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);
1503 }1209 }
1504 },1210 ct_i = next_handshake_i;
1505 else => return error.TlsUnexpectedMessage,1211 if (ct_i >= cleartext.len) break;
1506 }1212 }
1507 in = end;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),
1508 }1221 }
1509}1222}
15101223
1511fn logSecrets(key_log_file: std.fs.File, context: anytype, secrets: anytype) void {1224fn failRead(c: *Client, err: ReadError) error{ReadFailed} {
1512 const locked = if (key_log_file.lock(.exclusive)) |_| true else |_| false;1225 c.read_err = err;
1513 defer if (locked) key_log_file.unlock();1226 return error.ReadFailed;
1514 key_log_file.seekFromEnd(0) catch {};1227}
1515 inline for (@typeInfo(@TypeOf(secrets)).@"struct".fields) |field| key_log_file.deprecatedWriter().print("{s}" ++1228
1229fn logSecrets(w: *Writer, context: anytype, secrets: anytype) void {
1230 inline for (@typeInfo(@TypeOf(secrets)).@"struct".fields) |field| w.print("{s}" ++
1516 (if (@hasField(@TypeOf(context), "counter")) "_{d}" else "") ++ " {x} {x}\n", .{field.name} ++1231 (if (@hasField(@TypeOf(context), "counter")) "_{d}" else "") ++ " {x} {x}\n", .{field.name} ++
1517 (if (@hasField(@TypeOf(context), "counter")) .{context.counter} else .{}) ++ .{1232 (if (@hasField(@TypeOf(context), "counter")) .{context.counter} else .{}) ++ .{
1518 context.client_random,1233 context.client_random,
...@@ -1520,62 +1235,6 @@ fn logSecrets(key_log_file: std.fs.File, context: anytype, secrets: anytype) voi...@@ -1520,62 +1235,6 @@ fn logSecrets(key_log_file: std.fs.File, context: anytype, secrets: anytype) voi
1520 }) catch {};1235 }) catch {};
1521}1236}
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
1579fn big(x: anytype) @TypeOf(x) {1238fn big(x: anytype) @TypeOf(x) {
1580 return switch (native_endian) {1239 return switch (native_endian) {
1581 .big => x,1240 .big => x,
...@@ -1836,81 +1495,6 @@ const CertificatePublicKey = struct {...@@ -1836,81 +1495,6 @@ const CertificatePublicKey = struct {
1836 }1495 }
1837};1496};
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
1914/// The priority order here is chosen based on what crypto algorithms Zig has1498/// The priority order here is chosen based on what crypto algorithms Zig has
1915/// available in the standard library as well as what is faster. Following are1499/// available in the standard library as well as what is faster. Following are
1916/// a few data points on the relative performance of these algorithms.1500/// a few data points on the relative performance of these algorithms.
...@@ -1954,7 +1538,3 @@ else...@@ -1954,7 +1538,3 @@ else
1954 .AES_256_GCM_SHA384,1538 .AES_256_GCM_SHA384,
1955 .ECDHE_RSA_WITH_AES_256_GCM_SHA384,1539 .ECDHE_RSA_WITH_AES_256_GCM_SHA384,
1956 });1540 });
1957
1958test {
1959 _ = StreamInterface;
1960}
lib/std/http.zig+5-14
...@@ -343,10 +343,9 @@ pub const Reader = struct {...@@ -343,10 +343,9 @@ pub const Reader = struct {
343 /// read from `in`.343 /// read from `in`.
344 trailers: []const u8 = &.{},344 trailers: []const u8 = &.{},
345 body_err: ?BodyError = null,345 body_err: ?BodyError = null,
346 /// Stolen from `in`.346 /// Determines at which point `error.HttpHeadersOversize` occurs, as well
347 head_buffer: []u8 = &.{},347 /// as the minimum buffer capacity of `in`.
348348 max_head_len: usize,
349 pub const max_chunk_header_len = 22;
350349
351 pub const RemainingChunkLen = enum(u64) {350 pub const RemainingChunkLen = enum(u64) {
352 head = 0,351 head = 0,
...@@ -398,19 +397,11 @@ pub const Reader = struct {...@@ -398,19 +397,11 @@ pub const Reader = struct {
398 ReadFailed,397 ReadFailed,
399 };398 };
400399
401 pub fn restituteHeadBuffer(reader: *Reader) void {400 /// Buffers the entire head.
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.
408 pub fn receiveHead(reader: *Reader) HeadError!void {401 pub fn receiveHead(reader: *Reader) HeadError!void {
409 reader.trailers = &.{};402 reader.trailers = &.{};
410 const in = reader.in;403 const in = reader.in;
411 in.restitute(reader.head_buffer.len);404 try in.rebase(reader.max_head_len);
412 reader.head_buffer.len = 0;
413 in.rebase();
414 var hp: HeadParser = .{};405 var hp: HeadParser = .{};
415 var head_end: usize = 0;406 var head_end: usize = 0;
416 while (true) {407 while (true) {