authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-08-07 19:47:56-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-08-07 19:55:40-07:00
log8721efece4f6cf04341626909d9118d9e1c5bf4c
tree91401f88402027028de52d09d34e19fb01496676
parentd7bf60882183dc7479f8cdc2fb756f1dba726167

std.crypto.tls.Client: always write to buffer

simplifies the logic & makes it respect limit

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

lib/std/Io/Reader.zig+1-3
...@@ -25,9 +25,7 @@ pub const VTable = struct {...@@ -25,9 +25,7 @@ pub const VTable = struct {
25 ///25 ///
26 /// Returns the number of bytes written, which will be at minimum `0` and26 /// Returns the number of bytes written, which will be at minimum `0` and
27 /// at most `limit`. The number returned, including zero, does not indicate27 /// at most `limit`. The number returned, including zero, does not indicate
28 /// end of stream. `limit` is guaranteed to be at least as large as the28 /// end of stream.
29 /// buffer capacity of `w`, a value whose minimum size is determined by the
30 /// stream implementation.
31 ///29 ///
32 /// The reader's internal logical seek position moves forward in accordance30 /// The reader's internal logical seek position moves forward in accordance
33 /// with the number of bytes returned from this function.31 /// with the number of bytes returned from this function.
lib/std/crypto/tls/Client.zig+45-23
...@@ -61,9 +61,6 @@ pub const ReadError = error{...@@ -61,9 +61,6 @@ pub const ReadError = error{
61 TlsUnexpectedMessage,61 TlsUnexpectedMessage,
62 TlsIllegalParameter,62 TlsIllegalParameter,
63 TlsSequenceOverflow,63 TlsSequenceOverflow,
64 /// The buffer provided to the read function was not at least
65 /// `min_buffer_len`.
66 OutputBufferUndersize,
67};64};
6865
69pub const SslKeyLog = struct {66pub const SslKeyLog = struct {
...@@ -372,7 +369,8 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client...@@ -372,7 +369,8 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
372 };369 };
373 P.AEAD.decrypt(cleartext, ciphertext, auth_tag, record_header, nonce, pv.server_handshake_key) catch370 P.AEAD.decrypt(cleartext, ciphertext, auth_tag, record_header, nonce, pv.server_handshake_key) catch
374 return error.TlsBadRecordMac;371 return error.TlsBadRecordMac;
375 cleartext_fragment_end += std.mem.trimEnd(u8, cleartext, "\x00").len;372 // TODO use scalar, non-slice version
373 cleartext_fragment_end += mem.trimEnd(u8, cleartext, "\x00").len;
376 },374 },
377 }375 }
378 read_seq += 1;376 read_seq += 1;
...@@ -395,9 +393,9 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client...@@ -395,9 +393,9 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
395 const cleartext_fragment_buf = cleartext_buf[cleartext_fragment_end..];393 const cleartext_fragment_buf = cleartext_buf[cleartext_fragment_end..];
396 if (message_len > cleartext_fragment_buf.len) return error.TlsRecordOverflow;394 if (message_len > cleartext_fragment_buf.len) return error.TlsRecordOverflow;
397 const cleartext = cleartext_fragment_buf[0..message_len];395 const cleartext = cleartext_fragment_buf[0..message_len];
398 const ad = std.mem.toBytes(big(read_seq)) ++396 const ad = mem.toBytes(big(read_seq)) ++
399 record_header[0 .. 1 + 2] ++397 record_header[0 .. 1 + 2] ++
400 std.mem.toBytes(big(message_len));398 mem.toBytes(big(message_len));
401 const record_iv = record_decoder.array(P.record_iv_length).*;399 const record_iv = record_decoder.array(P.record_iv_length).*;
402 const masked_read_seq = read_seq &400 const masked_read_seq = read_seq &
403 comptime std.math.shl(u64, std.math.maxInt(u64), 8 * P.record_iv_length);401 comptime std.math.shl(u64, std.math.maxInt(u64), 8 * P.record_iv_length);
...@@ -738,7 +736,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client...@@ -738,7 +736,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
738 &.{ "server finished", &p.transcript_hash.finalResult() },736 &.{ "server finished", &p.transcript_hash.finalResult() },
739 P.verify_data_length,737 P.verify_data_length,
740 ),738 ),
741 .app_cipher = std.mem.bytesToValue(P.Tls_1_2, &key_block),739 .app_cipher = mem.bytesToValue(P.Tls_1_2, &key_block),
742 } };740 } };
743 const pv = &p.version.tls_1_2;741 const pv = &p.version.tls_1_2;
744 const nonce: [P.AEAD.nonce_length]u8 = nonce: {742 const nonce: [P.AEAD.nonce_length]u8 = nonce: {
...@@ -756,7 +754,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client...@@ -756,7 +754,7 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
756 client_verify_cleartext.len ..][0..client_verify_cleartext.len],754 client_verify_cleartext.len ..][0..client_verify_cleartext.len],
757 client_verify_msg[client_verify_msg.len - P.mac_length ..][0..P.mac_length],755 client_verify_msg[client_verify_msg.len - P.mac_length ..][0..P.mac_length],
758 &client_verify_cleartext,756 &client_verify_cleartext,
759 std.mem.toBytes(big(write_seq)) ++ client_verify_msg[0 .. 1 + 2] ++ int(u16, client_verify_cleartext.len),757 mem.toBytes(big(write_seq)) ++ client_verify_msg[0 .. 1 + 2] ++ int(u16, client_verify_cleartext.len),
760 nonce,758 nonce,
761 pv.app_cipher.client_write_key,759 pv.app_cipher.client_write_key,
762 );760 );
...@@ -873,7 +871,10 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client...@@ -873,7 +871,10 @@ pub fn init(input: *Reader, output: *Writer, options: Options) InitError!Client
873 .input = input,871 .input = input,
874 .reader = .{872 .reader = .{
875 .buffer = options.read_buffer,873 .buffer = options.read_buffer,
876 .vtable = &.{ .stream = stream },874 .vtable = &.{
875 .stream = stream,
876 .readVec = readVec,
877 },
877 .seek = 0,878 .seek = 0,
878 .end = 0,879 .end = 0,
879 },880 },
...@@ -1017,7 +1018,7 @@ fn prepareCiphertextRecord(...@@ -1017,7 +1018,7 @@ fn prepareCiphertextRecord(
1017 const nonce = nonce: {1018 const nonce = nonce: {
1018 const V = @Vector(P.AEAD.nonce_length, u8);1019 const V = @Vector(P.AEAD.nonce_length, u8);
1019 const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8);1020 const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8);
1020 const operand: V = pad ++ std.mem.toBytes(big(c.write_seq));1021 const operand: V = pad ++ mem.toBytes(big(c.write_seq));
1021 break :nonce @as(V, pv.client_iv) ^ operand;1022 break :nonce @as(V, pv.client_iv) ^ operand;
1022 };1023 };
1023 P.AEAD.encrypt(ciphertext, auth_tag, cleartext, ad, nonce, pv.client_key);1024 P.AEAD.encrypt(ciphertext, auth_tag, cleartext, ad, nonce, pv.client_key);
...@@ -1048,7 +1049,7 @@ fn prepareCiphertextRecord(...@@ -1048,7 +1049,7 @@ fn prepareCiphertextRecord(
1048 record_header.* = .{@intFromEnum(inner_content_type)} ++1049 record_header.* = .{@intFromEnum(inner_content_type)} ++
1049 int(u16, @intFromEnum(tls.ProtocolVersion.tls_1_2)) ++1050 int(u16, @intFromEnum(tls.ProtocolVersion.tls_1_2)) ++
1050 int(u16, P.record_iv_length + message_len + P.mac_length);1051 int(u16, P.record_iv_length + message_len + P.mac_length);
1051 const ad = std.mem.toBytes(big(c.write_seq)) ++ record_header[0 .. 1 + 2] ++ int(u16, message_len);1052 const ad = mem.toBytes(big(c.write_seq)) ++ record_header[0 .. 1 + 2] ++ int(u16, message_len);
1052 const record_iv = ciphertext_buf[ciphertext_end..][0..P.record_iv_length];1053 const record_iv = ciphertext_buf[ciphertext_end..][0..P.record_iv_length];
1053 ciphertext_end += P.record_iv_length;1054 ciphertext_end += P.record_iv_length;
1054 const nonce: [P.AEAD.nonce_length]u8 = nonce: {1055 const nonce: [P.AEAD.nonce_length]u8 = nonce: {
...@@ -1076,7 +1077,22 @@ pub fn eof(c: Client) bool {...@@ -1076,7 +1077,22 @@ pub fn eof(c: Client) bool {
1076}1077}
10771078
1078fn stream(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize {1079fn stream(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize {
1080 // This function writes exclusively to the buffer.
1081 _ = w;
1082 _ = limit;
1083 const c: *Client = @alignCast(@fieldParentPtr("reader", r));
1084 return readIndirect(c);
1085}
1086
1087fn readVec(r: *Reader, data: [][]u8) Reader.Error!usize {
1088 // This function writes exclusively to the buffer.
1089 _ = data;
1079 const c: *Client = @alignCast(@fieldParentPtr("reader", r));1090 const c: *Client = @alignCast(@fieldParentPtr("reader", r));
1091 return readIndirect(c);
1092}
1093
1094fn readIndirect(c: *Client) Reader.Error!usize {
1095 const r = &c.reader;
1080 if (c.eof()) return error.EndOfStream;1096 if (c.eof()) return error.EndOfStream;
1081 const input = c.input;1097 const input = c.input;
1082 // If at least one full encrypted record is not buffered, read once.1098 // If at least one full encrypted record is not buffered, read once.
...@@ -1108,8 +1124,13 @@ fn stream(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize...@@ -1108,8 +1124,13 @@ fn stream(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize
1108 if (record_end > input.buffered().len) return 0;1124 if (record_end > input.buffered().len) return 0;
1109 }1125 }
11101126
1111 var cleartext_stack_buffer: [max_ciphertext_len]u8 = undefined;1127 if (r.seek == r.end) {
1112 const cleartext, const inner_ct: tls.ContentType = cleartext: switch (c.application_cipher) {1128 r.seek = 0;
1129 r.end = 0;
1130 }
1131 const cleartext_buffer = r.buffer[r.end..];
1132
1133 const cleartext_len, const inner_ct: tls.ContentType = cleartext: switch (c.application_cipher) {
1113 inline else => |*p| switch (c.tls_version) {1134 inline else => |*p| switch (c.tls_version) {
1114 .tls_1_3 => {1135 .tls_1_3 => {
1115 const pv = &p.tls_1_3;1136 const pv = &p.tls_1_3;
...@@ -1121,23 +1142,24 @@ fn stream(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize...@@ -1121,23 +1142,24 @@ fn stream(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize
1121 const nonce = nonce: {1142 const nonce = nonce: {
1122 const V = @Vector(P.AEAD.nonce_length, u8);1143 const V = @Vector(P.AEAD.nonce_length, u8);
1123 const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8);1144 const pad = [1]u8{0} ** (P.AEAD.nonce_length - 8);
1124 const operand: V = pad ++ std.mem.toBytes(big(c.read_seq));1145 const operand: V = pad ++ mem.toBytes(big(c.read_seq));
1125 break :nonce @as(V, pv.server_iv) ^ operand;1146 break :nonce @as(V, pv.server_iv) ^ operand;
1126 };1147 };
1127 const cleartext = cleartext_stack_buffer[0..ciphertext.len];1148 const cleartext = cleartext_buffer[0..ciphertext.len];
1128 P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, pv.server_key) catch1149 P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, pv.server_key) catch
1129 return failRead(c, error.TlsBadRecordMac);1150 return failRead(c, error.TlsBadRecordMac);
1151 // TODO use scalar, non-slice version
1130 const msg = mem.trimRight(u8, cleartext, "\x00");1152 const msg = mem.trimRight(u8, cleartext, "\x00");
1131 break :cleartext .{ msg[0 .. msg.len - 1], @enumFromInt(msg[msg.len - 1]) };1153 break :cleartext .{ msg.len - 1, @enumFromInt(msg[msg.len - 1]) };
1132 },1154 },
1133 .tls_1_2 => {1155 .tls_1_2 => {
1134 const pv = &p.tls_1_2;1156 const pv = &p.tls_1_2;
1135 const P = @TypeOf(p.*);1157 const P = @TypeOf(p.*);
1136 const message_len: u16 = record_len - P.record_iv_length - P.mac_length;1158 const message_len: u16 = record_len - P.record_iv_length - P.mac_length;
1137 const ad_header = input.take(tls.record_header_len) catch unreachable; // already peeked1159 const ad_header = input.take(tls.record_header_len) catch unreachable; // already peeked
1138 const ad = std.mem.toBytes(big(c.read_seq)) ++1160 const ad = mem.toBytes(big(c.read_seq)) ++
1139 ad_header[0 .. 1 + 2] ++1161 ad_header[0 .. 1 + 2] ++
1140 std.mem.toBytes(big(message_len));1162 mem.toBytes(big(message_len));
1141 const record_iv = (input.takeArray(P.record_iv_length) catch unreachable).*; // already peeked1163 const record_iv = (input.takeArray(P.record_iv_length) catch unreachable).*; // already peeked
1142 const masked_read_seq = c.read_seq &1164 const masked_read_seq = c.read_seq &
1143 comptime std.math.shl(u64, std.math.maxInt(u64), 8 * P.record_iv_length);1165 comptime std.math.shl(u64, std.math.maxInt(u64), 8 * P.record_iv_length);
...@@ -1149,14 +1171,15 @@ fn stream(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize...@@ -1149,14 +1171,15 @@ fn stream(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize
1149 };1171 };
1150 const ciphertext = input.take(message_len) catch unreachable; // already peeked1172 const ciphertext = input.take(message_len) catch unreachable; // already peeked
1151 const auth_tag = (input.takeArray(P.mac_length) catch unreachable).*; // already peeked1173 const auth_tag = (input.takeArray(P.mac_length) catch unreachable).*; // already peeked
1152 const cleartext = cleartext_stack_buffer[0..ciphertext.len];1174 const cleartext = cleartext_buffer[0..ciphertext.len];
1153 P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, pv.server_write_key) catch1175 P.AEAD.decrypt(cleartext, ciphertext, auth_tag, ad, nonce, pv.server_write_key) catch
1154 return failRead(c, error.TlsBadRecordMac);1176 return failRead(c, error.TlsBadRecordMac);
1155 break :cleartext .{ cleartext, ct };1177 break :cleartext .{ cleartext.len, ct };
1156 },1178 },
1157 else => unreachable,1179 else => unreachable,
1158 },1180 },
1159 };1181 };
1182 const cleartext = cleartext_buffer[0..cleartext_len];
1160 c.read_seq = std.math.add(u64, c.read_seq, 1) catch return failRead(c, error.TlsSequenceOverflow);1183 c.read_seq = std.math.add(u64, c.read_seq, 1) catch return failRead(c, error.TlsSequenceOverflow);
1161 switch (inner_ct) {1184 switch (inner_ct) {
1162 .alert => {1185 .alert => {
...@@ -1245,9 +1268,8 @@ fn stream(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize...@@ -1245,9 +1268,8 @@ fn stream(r: *Reader, w: *Writer, limit: std.Io.Limit) Reader.StreamError!usize
1245 return 0;1268 return 0;
1246 },1269 },
1247 .application_data => {1270 .application_data => {
1248 if (@intFromEnum(limit) < cleartext.len) return failRead(c, error.OutputBufferUndersize);1271 r.end += cleartext.len;
1249 try w.writeAll(cleartext);1272 return 0;
1250 return cleartext.len;
1251 },1273 },
1252 else => return failRead(c, error.TlsUnexpectedMessage),1274 else => return failRead(c, error.TlsUnexpectedMessage),
1253 }1275 }