| ... | @@ -4,6 +4,20 @@ | ... | @@ -4,6 +4,20 @@ |
| 4 | //! concatenated together in the `bytes` array. The `map` field contains an | 4 | //! concatenated together in the `bytes` array. The `map` field contains an |
| 5 | //! index from the DER-encoded subject name to the index of the containing | 5 | //! index from the DER-encoded subject name to the index of the containing |
| 6 | //! certificate within `bytes`. | 6 | //! certificate within `bytes`. |
| | 7 | const Bundle = @This(); |
| | 8 | const builtin = @import("builtin"); |
| | 9 | |
| | 10 | const std = @import("../../std.zig"); |
| | 11 | const Io = std.Io; |
| | 12 | const assert = std.debug.assert; |
| | 13 | const fs = std.fs; |
| | 14 | const mem = std.mem; |
| | 15 | const crypto = std.crypto; |
| | 16 | const Allocator = std.mem.Allocator; |
| | 17 | const Certificate = std.crypto.Certificate; |
| | 18 | const der = Certificate.der; |
| | 19 | |
| | 20 | const base64 = std.base64.standard.decoderWithIgnore(" \t\r\n"); |
| 7 | | 21 | |
| 8 | /// The key is the contents slice of the subject. | 22 | /// The key is the contents slice of the subject. |
| 9 | map: std.HashMapUnmanaged(der.Element.Slice, u32, MapContext, std.hash_map.default_max_load_percentage) = .empty, | 23 | map: 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 be | 71 | /// 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. |
| 59 | pub fn rescan(cb: *Bundle, gpa: Allocator) RescanError!void { | 73 | pub 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#L19 | 82 | // 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; |
| 77 | | 91 | |
| 78 | const RescanLinuxError = AddCertsFromFilePathError || AddCertsFromDirPathError; | 92 | const RescanLinuxError = AddCertsFromFilePathError || AddCertsFromDirPathError; |
| 79 | | 93 | |
| 80 | fn rescanLinux(cb: *Bundle, gpa: Allocator) RescanLinuxError!void { | 94 | fn 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 { |
| 100 | | 114 | |
| 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 | } |
| 110 | | 124 | |
| 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 { |
| 121 | | 135 | |
| 122 | const RescanWithPathError = AddCertsFromFilePathError; | 136 | const RescanWithPathError = AddCertsFromFilePathError; |
| 123 | | 137 | |
| 124 | fn rescanWithPath(cb: *Bundle, gpa: Allocator, cert_file_path: []const u8) RescanWithPathError!void { | 138 | fn 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 | } |
| 130 | | 144 | |
| ... | @@ -160,28 +174,30 @@ pub const AddCertsFromDirPathError = fs.File.OpenError || AddCertsFromDirError; | ... | @@ -160,28 +174,30 @@ pub const AddCertsFromDirPathError = fs.File.OpenError || AddCertsFromDirError; |
| 160 | pub fn addCertsFromDirPath( | 174 | pub 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 | } |
| 170 | | 185 | |
| 171 | pub fn addCertsFromDirPathAbsolute( | 186 | pub 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 | } |
| 181 | | 197 | |
| 182 | pub const AddCertsFromDirError = AddCertsFromFilePathError; | 198 | pub const AddCertsFromDirError = AddCertsFromFilePathError; |
| 183 | | 199 | |
| 184 | pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, iterable_dir: fs.Dir) AddCertsFromDirError!void { | 200 | pub 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 | } |
| 191 | | 207 | |
| 192 | try addCertsFromFilePath(cb, gpa, iterable_dir, entry.name); | 208 | try addCertsFromFilePath(cb, gpa, io, iterable_dir.adaptToNewApi(), entry.name); |
| 193 | } | 209 | } |
| 194 | } | 210 | } |
| 195 | | 211 | |
| 196 | pub const AddCertsFromFilePathError = fs.File.OpenError || AddCertsFromFileError; | 212 | pub const AddCertsFromFilePathError = fs.File.OpenError || AddCertsFromFileError || Io.Clock.Error; |
| 197 | | 213 | |
| 198 | pub fn addCertsFromFilePathAbsolute( | 214 | pub 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 | } |
| 208 | | 226 | |
| 209 | pub fn addCertsFromFilePath( | 227 | pub 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 | } |
| 219 | | 240 | |
| 220 | pub const AddCertsFromFileError = Allocator.Error || | 241 | pub 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 }; |
| 226 | | 247 | |
| 227 | pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file: fs.File) AddCertsFromFileError!void { | 248 | pub 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(); |
| 229 | | 250 | |
| 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 space | 252 | // 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]; |
| 241 | | 264 | |
| 242 | const begin_marker = "-----BEGIN CERTIFICATE-----"; | 265 | const begin_marker = "-----BEGIN CERTIFICATE-----"; |
| 243 | const end_marker = "-----END CERTIFICATE-----"; | 266 | const end_marker = "-----END CERTIFICATE-----"; |
| 244 | | 267 | |
| 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 | } |
| 290 | | 311 | |
| 291 | const builtin = @import("builtin"); | | |
| 292 | const std = @import("../../std.zig"); | | |
| 293 | const assert = std.debug.assert; | | |
| 294 | const fs = std.fs; | | |
| 295 | const mem = std.mem; | | |
| 296 | const crypto = std.crypto; | | |
| 297 | const Allocator = std.mem.Allocator; | | |
| 298 | const Certificate = std.crypto.Certificate; | | |
| 299 | const der = Certificate.der; | | |
| 300 | const Bundle = @This(); | | |
| 301 | | | |
| 302 | const base64 = std.base64.standard.decoderWithIgnore(" \t\r\n"); | | |
| 303 | | | |
| 304 | const MapContext = struct { | 312 | const MapContext = struct { |
| 305 | cb: *const Bundle, | 313 | cb: *const Bundle, |
| 306 | | 314 | |
| ... | @@ -321,8 +329,11 @@ const MapContext = struct { | ... | @@ -321,8 +329,11 @@ const MapContext = struct { |
| 321 | test "scan for OS-provided certificates" { | 329 | test "scan for OS-provided certificates" { |
| 322 | if (builtin.os.tag == .wasi) return error.SkipZigTest; | 330 | if (builtin.os.tag == .wasi) return error.SkipZigTest; |
| 323 | | 331 | |
| | 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); |
| 326 | | 337 | |
| 327 | try bundle.rescan(std.testing.allocator); | 338 | try bundle.rescan(gpa, io); |
| 328 | } | 339 | } |