authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-09 15:24:11-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2025-10-29 06:20:49-07:00
log63801c4b058feae3a9b6ca2bbe8effeae267abbc
tree7af2caea39065bb7aa02903204e1f1735aec8c8b
parentfcac8617b4e3c8fcb3a8c888fe77fa8bff1c2057

std.crypto.Certificate.Bundle: remove use of File.readAll


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

lib/std/crypto/Certificate/Bundle.zig+56-45
...@@ -4,6 +4,20 @@...@@ -4,6 +4,20 @@
4//! concatenated together in the `bytes` array. The `map` field contains an4//! concatenated together in the `bytes` array. The `map` field contains an
5//! index from the DER-encoded subject name to the index of the containing5//! index from the DER-encoded subject name to the index of the containing
6//! certificate within `bytes`.6//! certificate within `bytes`.
7const Bundle = @This();
8const builtin = @import("builtin");
9
10const std = @import("../../std.zig");
11const Io = std.Io;
12const assert = std.debug.assert;
13const fs = std.fs;
14const mem = std.mem;
15const crypto = std.crypto;
16const Allocator = std.mem.Allocator;
17const Certificate = std.crypto.Certificate;
18const der = Certificate.der;
19
20const base64 = std.base64.standard.decoderWithIgnore(" \t\r\n");
721
8/// The key is the contents slice of the subject.22/// The key is the contents slice of the subject.
9map: std.HashMapUnmanaged(der.Element.Slice, u32, MapContext, std.hash_map.default_max_load_percentage) = .empty,23map: std.HashMapUnmanaged(der.Element.Slice, u32, MapContext, std.hash_map.default_max_load_percentage) = .empty,
...@@ -56,17 +70,17 @@ pub const RescanError = RescanLinuxError || RescanMacError || RescanWithPathErro...@@ -56,17 +70,17 @@ pub const RescanError = RescanLinuxError || RescanMacError || RescanWithPathErro
56/// file system standard locations for certificates.70/// file system standard locations for certificates.
57/// For operating systems that do not have standard CA installations to be71/// For operating systems that do not have standard CA installations to be
58/// found, this function clears the set of certificates.72/// found, this function clears the set of certificates.
59pub fn rescan(cb: *Bundle, gpa: Allocator) RescanError!void {73pub fn rescan(cb: *Bundle, gpa: Allocator, io: Io) RescanError!void {
60 switch (builtin.os.tag) {74 switch (builtin.os.tag) {
61 .linux => return rescanLinux(cb, gpa),75 .linux => return rescanLinux(cb, gpa, io),
62 .macos => return rescanMac(cb, gpa),76 .macos => return rescanMac(cb, gpa),
63 .freebsd, .openbsd => return rescanWithPath(cb, gpa, "/etc/ssl/cert.pem"),77 .freebsd, .openbsd => return rescanWithPath(cb, gpa, io, "/etc/ssl/cert.pem"),
64 .netbsd => return rescanWithPath(cb, gpa, "/etc/openssl/certs/ca-certificates.crt"),78 .netbsd => return rescanWithPath(cb, gpa, io, "/etc/openssl/certs/ca-certificates.crt"),
65 .dragonfly => return rescanWithPath(cb, gpa, "/usr/local/etc/ssl/cert.pem"),79 .dragonfly => return rescanWithPath(cb, gpa, io, "/usr/local/etc/ssl/cert.pem"),
66 .illumos => return rescanWithPath(cb, gpa, "/etc/ssl/cacert.pem"),80 .illumos => return rescanWithPath(cb, gpa, io, "/etc/ssl/cacert.pem"),
67 .haiku => return rescanWithPath(cb, gpa, "/boot/system/data/ssl/CARootCertificates.pem"),81 .haiku => return rescanWithPath(cb, gpa, io, "/boot/system/data/ssl/CARootCertificates.pem"),
68 // https://github.com/SerenityOS/serenity/blob/222acc9d389bc6b490d4c39539761b043a4bfcb0/Ports/ca-certificates/package.sh#L1982 // https://github.com/SerenityOS/serenity/blob/222acc9d389bc6b490d4c39539761b043a4bfcb0/Ports/ca-certificates/package.sh#L19
69 .serenity => return rescanWithPath(cb, gpa, "/etc/ssl/certs/ca-certificates.crt"),83 .serenity => return rescanWithPath(cb, gpa, io, "/etc/ssl/certs/ca-certificates.crt"),
70 .windows => return rescanWindows(cb, gpa),84 .windows => return rescanWindows(cb, gpa),
71 else => {},85 else => {},
72 }86 }
...@@ -77,7 +91,7 @@ const RescanMacError = @import("Bundle/macos.zig").RescanMacError;...@@ -77,7 +91,7 @@ const RescanMacError = @import("Bundle/macos.zig").RescanMacError;
7791
78const RescanLinuxError = AddCertsFromFilePathError || AddCertsFromDirPathError;92const RescanLinuxError = AddCertsFromFilePathError || AddCertsFromDirPathError;
7993
80fn rescanLinux(cb: *Bundle, gpa: Allocator) RescanLinuxError!void {94fn rescanLinux(cb: *Bundle, gpa: Allocator, io: Io) RescanLinuxError!void {
81 // Possible certificate files; stop after finding one.95 // Possible certificate files; stop after finding one.
82 const cert_file_paths = [_][]const u8{96 const cert_file_paths = [_][]const u8{
83 "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc.97 "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc.
...@@ -100,7 +114,7 @@ fn rescanLinux(cb: *Bundle, gpa: Allocator) RescanLinuxError!void {...@@ -100,7 +114,7 @@ fn rescanLinux(cb: *Bundle, gpa: Allocator) RescanLinuxError!void {
100114
101 scan: {115 scan: {
102 for (cert_file_paths) |cert_file_path| {116 for (cert_file_paths) |cert_file_path| {
103 if (addCertsFromFilePathAbsolute(cb, gpa, cert_file_path)) |_| {117 if (addCertsFromFilePathAbsolute(cb, gpa, io, cert_file_path)) |_| {
104 break :scan;118 break :scan;
105 } else |err| switch (err) {119 } else |err| switch (err) {
106 error.FileNotFound => continue,120 error.FileNotFound => continue,
...@@ -109,7 +123,7 @@ fn rescanLinux(cb: *Bundle, gpa: Allocator) RescanLinuxError!void {...@@ -109,7 +123,7 @@ fn rescanLinux(cb: *Bundle, gpa: Allocator) RescanLinuxError!void {
109 }123 }
110124
111 for (cert_dir_paths) |cert_dir_path| {125 for (cert_dir_paths) |cert_dir_path| {
112 addCertsFromDirPathAbsolute(cb, gpa, cert_dir_path) catch |err| switch (err) {126 addCertsFromDirPathAbsolute(cb, gpa, io, cert_dir_path) catch |err| switch (err) {
113 error.FileNotFound => continue,127 error.FileNotFound => continue,
114 else => |e| return e,128 else => |e| return e,
115 };129 };
...@@ -121,10 +135,10 @@ fn rescanLinux(cb: *Bundle, gpa: Allocator) RescanLinuxError!void {...@@ -121,10 +135,10 @@ fn rescanLinux(cb: *Bundle, gpa: Allocator) RescanLinuxError!void {
121135
122const RescanWithPathError = AddCertsFromFilePathError;136const RescanWithPathError = AddCertsFromFilePathError;
123137
124fn rescanWithPath(cb: *Bundle, gpa: Allocator, cert_file_path: []const u8) RescanWithPathError!void {138fn rescanWithPath(cb: *Bundle, gpa: Allocator, io: Io, cert_file_path: []const u8) RescanWithPathError!void {
125 cb.bytes.clearRetainingCapacity();139 cb.bytes.clearRetainingCapacity();
126 cb.map.clearRetainingCapacity();140 cb.map.clearRetainingCapacity();
127 try addCertsFromFilePathAbsolute(cb, gpa, cert_file_path);141 try addCertsFromFilePathAbsolute(cb, gpa, io, cert_file_path);
128 cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len);142 cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len);
129}143}
130144
...@@ -160,28 +174,30 @@ pub const AddCertsFromDirPathError = fs.File.OpenError || AddCertsFromDirError;...@@ -160,28 +174,30 @@ pub const AddCertsFromDirPathError = fs.File.OpenError || AddCertsFromDirError;
160pub fn addCertsFromDirPath(174pub fn addCertsFromDirPath(
161 cb: *Bundle,175 cb: *Bundle,
162 gpa: Allocator,176 gpa: Allocator,
177 io: Io,
163 dir: fs.Dir,178 dir: fs.Dir,
164 sub_dir_path: []const u8,179 sub_dir_path: []const u8,
165) AddCertsFromDirPathError!void {180) AddCertsFromDirPathError!void {
166 var iterable_dir = try dir.openDir(sub_dir_path, .{ .iterate = true });181 var iterable_dir = try dir.openDir(sub_dir_path, .{ .iterate = true });
167 defer iterable_dir.close();182 defer iterable_dir.close();
168 return addCertsFromDir(cb, gpa, iterable_dir);183 return addCertsFromDir(cb, gpa, io, iterable_dir);
169}184}
170185
171pub fn addCertsFromDirPathAbsolute(186pub fn addCertsFromDirPathAbsolute(
172 cb: *Bundle,187 cb: *Bundle,
173 gpa: Allocator,188 gpa: Allocator,
189 io: Io,
174 abs_dir_path: []const u8,190 abs_dir_path: []const u8,
175) AddCertsFromDirPathError!void {191) AddCertsFromDirPathError!void {
176 assert(fs.path.isAbsolute(abs_dir_path));192 assert(fs.path.isAbsolute(abs_dir_path));
177 var iterable_dir = try fs.openDirAbsolute(abs_dir_path, .{ .iterate = true });193 var iterable_dir = try fs.openDirAbsolute(abs_dir_path, .{ .iterate = true });
178 defer iterable_dir.close();194 defer iterable_dir.close();
179 return addCertsFromDir(cb, gpa, iterable_dir);195 return addCertsFromDir(cb, gpa, io, iterable_dir);
180}196}
181197
182pub const AddCertsFromDirError = AddCertsFromFilePathError;198pub const AddCertsFromDirError = AddCertsFromFilePathError;
183199
184pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, iterable_dir: fs.Dir) AddCertsFromDirError!void {200pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, io: Io, iterable_dir: fs.Dir) AddCertsFromDirError!void {
185 var it = iterable_dir.iterate();201 var it = iterable_dir.iterate();
186 while (try it.next()) |entry| {202 while (try it.next()) |entry| {
187 switch (entry.kind) {203 switch (entry.kind) {
...@@ -189,32 +205,37 @@ pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, iterable_dir: fs.Dir) AddCer...@@ -189,32 +205,37 @@ pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, iterable_dir: fs.Dir) AddCer
189 else => continue,205 else => continue,
190 }206 }
191207
192 try addCertsFromFilePath(cb, gpa, iterable_dir, entry.name);208 try addCertsFromFilePath(cb, gpa, io, iterable_dir.adaptToNewApi(), entry.name);
193 }209 }
194}210}
195211
196pub const AddCertsFromFilePathError = fs.File.OpenError || AddCertsFromFileError;212pub const AddCertsFromFilePathError = fs.File.OpenError || AddCertsFromFileError || Io.Clock.Error;
197213
198pub fn addCertsFromFilePathAbsolute(214pub fn addCertsFromFilePathAbsolute(
199 cb: *Bundle,215 cb: *Bundle,
200 gpa: Allocator,216 gpa: Allocator,
217 io: Io,
201 abs_file_path: []const u8,218 abs_file_path: []const u8,
202) AddCertsFromFilePathError!void {219) AddCertsFromFilePathError!void {
203 assert(fs.path.isAbsolute(abs_file_path));220 const now = try Io.Clock.real.now(io);
204 var file = try fs.openFileAbsolute(abs_file_path, .{});221 var file = try fs.openFileAbsolute(abs_file_path, .{});
205 defer file.close();222 defer file.close();
206 return addCertsFromFile(cb, gpa, file);223 var file_reader = file.reader(io, &.{});
224 return addCertsFromFile(cb, gpa, &file_reader, now.toSeconds());
207}225}
208226
209pub fn addCertsFromFilePath(227pub fn addCertsFromFilePath(
210 cb: *Bundle,228 cb: *Bundle,
211 gpa: Allocator,229 gpa: Allocator,
212 dir: fs.Dir,230 io: Io,
231 dir: Io.Dir,
213 sub_file_path: []const u8,232 sub_file_path: []const u8,
214) AddCertsFromFilePathError!void {233) AddCertsFromFilePathError!void {
215 var file = try dir.openFile(sub_file_path, .{});234 const now = try Io.Clock.real.now(io);
216 defer file.close();235 var file = try dir.openFile(io, sub_file_path, .{});
217 return addCertsFromFile(cb, gpa, file);236 defer file.close(io);
237 var file_reader = file.reader(io, &.{});
238 return addCertsFromFile(cb, gpa, &file_reader, now.toSeconds());
218}239}
219240
220pub const AddCertsFromFileError = Allocator.Error ||241pub const AddCertsFromFileError = Allocator.Error ||
...@@ -222,10 +243,10 @@ pub const AddCertsFromFileError = Allocator.Error ||...@@ -222,10 +243,10 @@ pub const AddCertsFromFileError = Allocator.Error ||
222 fs.File.ReadError ||243 fs.File.ReadError ||
223 ParseCertError ||244 ParseCertError ||
224 std.base64.Error ||245 std.base64.Error ||
225 error{ CertificateAuthorityBundleTooBig, MissingEndCertificateMarker };246 error{ CertificateAuthorityBundleTooBig, MissingEndCertificateMarker, Streaming };
226247
227pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file: fs.File) AddCertsFromFileError!void {248pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file_reader: *Io.File.Reader, now_sec: i64) AddCertsFromFileError!void {
228 const size = try file.getEndPos();249 const size = try file_reader.getSize();
229250
230 // We borrow `bytes` as a temporary buffer for the base64-encoded data.251 // We borrow `bytes` as a temporary buffer for the base64-encoded data.
231 // This is possible by computing the decoded length and reserving the space252 // This is possible by computing the decoded length and reserving the space
...@@ -236,14 +257,14 @@ pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file: fs.File) AddCertsFrom...@@ -236,14 +257,14 @@ pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file: fs.File) AddCertsFrom
236 try cb.bytes.ensureUnusedCapacity(gpa, needed_capacity);257 try cb.bytes.ensureUnusedCapacity(gpa, needed_capacity);
237 const end_reserved: u32 = @intCast(cb.bytes.items.len + decoded_size_upper_bound);258 const end_reserved: u32 = @intCast(cb.bytes.items.len + decoded_size_upper_bound);
238 const buffer = cb.bytes.allocatedSlice()[end_reserved..];259 const buffer = cb.bytes.allocatedSlice()[end_reserved..];
239 const end_index = try file.readAll(buffer);260 const end_index = file_reader.interface.readSliceShort(buffer) catch |err| switch (err) {
261 error.ReadFailed => return file_reader.err.?,
262 };
240 const encoded_bytes = buffer[0..end_index];263 const encoded_bytes = buffer[0..end_index];
241264
242 const begin_marker = "-----BEGIN CERTIFICATE-----";265 const begin_marker = "-----BEGIN CERTIFICATE-----";
243 const end_marker = "-----END CERTIFICATE-----";266 const end_marker = "-----END CERTIFICATE-----";
244267
245 const now_sec = std.time.timestamp();
246
247 var start_index: usize = 0;268 var start_index: usize = 0;
248 while (mem.indexOfPos(u8, encoded_bytes, start_index, begin_marker)) |begin_marker_start| {269 while (mem.indexOfPos(u8, encoded_bytes, start_index, begin_marker)) |begin_marker_start| {
249 const cert_start = begin_marker_start + begin_marker.len;270 const cert_start = begin_marker_start + begin_marker.len;
...@@ -288,19 +309,6 @@ pub fn parseCert(cb: *Bundle, gpa: Allocator, decoded_start: u32, now_sec: i64)...@@ -288,19 +309,6 @@ pub fn parseCert(cb: *Bundle, gpa: Allocator, decoded_start: u32, now_sec: i64)
288 }309 }
289}310}
290311
291const builtin = @import("builtin");
292const std = @import("../../std.zig");
293const assert = std.debug.assert;
294const fs = std.fs;
295const mem = std.mem;
296const crypto = std.crypto;
297const Allocator = std.mem.Allocator;
298const Certificate = std.crypto.Certificate;
299const der = Certificate.der;
300const Bundle = @This();
301
302const base64 = std.base64.standard.decoderWithIgnore(" \t\r\n");
303
304const MapContext = struct {312const MapContext = struct {
305 cb: *const Bundle,313 cb: *const Bundle,
306314
...@@ -321,8 +329,11 @@ const MapContext = struct {...@@ -321,8 +329,11 @@ const MapContext = struct {
321test "scan for OS-provided certificates" {329test "scan for OS-provided certificates" {
322 if (builtin.os.tag == .wasi) return error.SkipZigTest;330 if (builtin.os.tag == .wasi) return error.SkipZigTest;
323331
332 const io = std.testing.io;
333 const gpa = std.testing.allocator;
334
324 var bundle: Bundle = .{};335 var bundle: Bundle = .{};
325 defer bundle.deinit(std.testing.allocator);336 defer bundle.deinit(gpa);
326337
327 try bundle.rescan(std.testing.allocator);338 try bundle.rescan(gpa, io);
328}339}
lib/std/http/Client.zig+3-1
...@@ -1666,6 +1666,8 @@ pub fn request(...@@ -1666,6 +1666,8 @@ pub fn request(
1666 uri: Uri,1666 uri: Uri,
1667 options: RequestOptions,1667 options: RequestOptions,
1668) RequestError!Request {1668) RequestError!Request {
1669 const io = client.io;
1670
1669 if (std.debug.runtime_safety) {1671 if (std.debug.runtime_safety) {
1670 for (options.extra_headers) |header| {1672 for (options.extra_headers) |header| {
1671 assert(header.name.len != 0);1673 assert(header.name.len != 0);
...@@ -1689,7 +1691,7 @@ pub fn request(...@@ -1689,7 +1691,7 @@ pub fn request(
1689 defer client.ca_bundle_mutex.unlock();1691 defer client.ca_bundle_mutex.unlock();
16901692
1691 if (client.next_https_rescan_certs) {1693 if (client.next_https_rescan_certs) {
1692 client.ca_bundle.rescan(client.allocator) catch1694 client.ca_bundle.rescan(client.allocator, io) catch
1693 return error.CertificateBundleLoadFailure;1695 return error.CertificateBundleLoadFailure;
1694 @atomicStore(bool, &client.next_https_rescan_certs, false, .release);1696 @atomicStore(bool, &client.next_https_rescan_certs, false, .release);
1695 }1697 }