aboutsummaryrefslogtreecommitdiff
path: root/lib
diff options
context:
space:
mode:
authorfn ⌃ ⌥ <70830482+FnControlOption@users.noreply.github.com>2023-01-16 14:34:04 -0800
committerGitHub <noreply@github.com>2023-01-16 22:34:04 +0000
commite45b471ad35828c5a435b6df7b672f83e8e69b6a (patch)
tree3c3fdeae57c337db7b2b39fafb01bc2dce1568f4 /lib
parentb42bd759a7bdb6bc1d84c640d68e5384dc35776d (diff)
downloadzig-e45b471ad35828c5a435b6df7b672f83e8e69b6a.tar.gz
zig-e45b471ad35828c5a435b6df7b672f83e8e69b6a.zip
Find system-installed root SSL certificates on macOS (#14325)
Diffstat (limited to 'lib')
-rw-r--r--lib/std/crypto/Certificate/Bundle.zig48
-rw-r--r--lib/std/crypto/Certificate/Bundle/macos.zig136
-rw-r--r--lib/std/io/reader.zig9
3 files changed, 171 insertions, 22 deletions
diff --git a/lib/std/crypto/Certificate/Bundle.zig b/lib/std/crypto/Certificate/Bundle.zig
index 16b0329a2d..a8b3851b05 100644
--- a/lib/std/crypto/Certificate/Bundle.zig
+++ b/lib/std/crypto/Certificate/Bundle.zig
@@ -60,13 +60,13 @@ pub fn rescan(cb: *Bundle, gpa: Allocator) !void {
.windows => {
// TODO
},
- .macos => {
- // TODO
- },
+ .macos => return rescanMac(cb, gpa),
else => {},
}
}
+pub const rescanMac = @import("Bundle/macos.zig").rescanMac;
+
pub fn rescanLinux(cb: *Bundle, gpa: Allocator) !void {
// Possible certificate files; stop after finding one.
const cert_file_paths = [_][]const u8{
@@ -195,25 +195,29 @@ pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file: fs.File) !void {
const decoded_start = @intCast(u32, cb.bytes.items.len);
const dest_buf = cb.bytes.allocatedSlice()[decoded_start..];
cb.bytes.items.len += try base64.decode(dest_buf, encoded_cert);
- // Even though we could only partially parse the certificate to find
- // the subject name, we pre-parse all of them to make sure and only
- // include in the bundle ones that we know will parse. This way we can
- // use `catch unreachable` later.
- const parsed_cert = try Certificate.parse(.{
- .buffer = cb.bytes.items,
- .index = decoded_start,
- });
- if (now_sec > parsed_cert.validity.not_after) {
- // Ignore expired cert.
- cb.bytes.items.len = decoded_start;
- continue;
- }
- const gop = try cb.map.getOrPutContext(gpa, parsed_cert.subject_slice, .{ .cb = cb });
- if (gop.found_existing) {
- cb.bytes.items.len = decoded_start;
- } else {
- gop.value_ptr.* = decoded_start;
- }
+ try cb.parseCert(gpa, decoded_start, now_sec);
+ }
+}
+
+pub fn parseCert(cb: *Bundle, gpa: Allocator, decoded_start: u32, now_sec: i64) !void {
+ // Even though we could only partially parse the certificate to find
+ // the subject name, we pre-parse all of them to make sure and only
+ // include in the bundle ones that we know will parse. This way we can
+ // use `catch unreachable` later.
+ const parsed_cert = try Certificate.parse(.{
+ .buffer = cb.bytes.items,
+ .index = decoded_start,
+ });
+ if (now_sec > parsed_cert.validity.not_after) {
+ // Ignore expired cert.
+ cb.bytes.items.len = decoded_start;
+ return;
+ }
+ const gop = try cb.map.getOrPutContext(gpa, parsed_cert.subject_slice, .{ .cb = cb });
+ if (gop.found_existing) {
+ cb.bytes.items.len = decoded_start;
+ } else {
+ gop.value_ptr.* = decoded_start;
}
}
diff --git a/lib/std/crypto/Certificate/Bundle/macos.zig b/lib/std/crypto/Certificate/Bundle/macos.zig
new file mode 100644
index 0000000000..8268a67a48
--- /dev/null
+++ b/lib/std/crypto/Certificate/Bundle/macos.zig
@@ -0,0 +1,136 @@
+const std = @import("std");
+const assert = std.debug.assert;
+const mem = std.mem;
+const fs = std.fs;
+const Allocator = std.mem.Allocator;
+const Bundle = @import("../Bundle.zig");
+
+pub fn rescanMac(cb: *Bundle, gpa: Allocator) !void {
+ const file = try fs.openFileAbsolute("/System/Library/Keychains/SystemRootCertificates.keychain", .{});
+ defer file.close();
+
+ const bytes = try file.readToEndAlloc(gpa, std.math.maxInt(u32));
+ defer gpa.free(bytes);
+
+ var stream = std.io.fixedBufferStream(bytes);
+ const reader = stream.reader();
+
+ const db_header = try reader.readStructBig(ApplDbHeader);
+ assert(mem.eql(u8, "kych", &@bitCast([4]u8, db_header.signature)));
+
+ try stream.seekTo(db_header.schema_offset);
+
+ const db_schema = try reader.readStructBig(ApplDbSchema);
+
+ var table_list = try gpa.alloc(u32, db_schema.table_count);
+ defer gpa.free(table_list);
+
+ var table_idx: u32 = 0;
+ while (table_idx < table_list.len) : (table_idx += 1) {
+ table_list[table_idx] = try reader.readIntBig(u32);
+ }
+
+ const now_sec = std.time.timestamp();
+
+ for (table_list) |table_offset| {
+ try stream.seekTo(db_header.schema_offset + table_offset);
+
+ const table_header = try reader.readStructBig(TableHeader);
+
+ if (@intToEnum(TableId, table_header.table_id) != TableId.CSSM_DL_DB_RECORD_X509_CERTIFICATE) {
+ continue;
+ }
+
+ var record_list = try gpa.alloc(u32, table_header.record_count);
+ defer gpa.free(record_list);
+
+ var record_idx: u32 = 0;
+ while (record_idx < record_list.len) : (record_idx += 1) {
+ record_list[record_idx] = try reader.readIntBig(u32);
+ }
+
+ for (record_list) |record_offset| {
+ try stream.seekTo(db_header.schema_offset + table_offset + record_offset);
+
+ const cert_header = try reader.readStructBig(X509CertHeader);
+
+ try cb.bytes.ensureUnusedCapacity(gpa, cert_header.cert_size);
+
+ const cert_start = @intCast(u32, cb.bytes.items.len);
+ const dest_buf = cb.bytes.allocatedSlice()[cert_start..];
+ cb.bytes.items.len += try reader.readAtLeast(dest_buf, cert_header.cert_size);
+
+ try cb.parseCert(gpa, cert_start, now_sec);
+ }
+ }
+}
+
+const ApplDbHeader = extern struct {
+ signature: @Vector(4, u8),
+ version: u32,
+ header_size: u32,
+ schema_offset: u32,
+ auth_offset: u32,
+};
+
+const ApplDbSchema = extern struct {
+ schema_size: u32,
+ table_count: u32,
+};
+
+const TableHeader = extern struct {
+ table_size: u32,
+ table_id: u32,
+ record_count: u32,
+ records: u32,
+ indexes_offset: u32,
+ free_list_head: u32,
+ record_numbers_count: u32,
+};
+
+const TableId = enum(u32) {
+ CSSM_DL_DB_SCHEMA_INFO = 0x00000000,
+ CSSM_DL_DB_SCHEMA_INDEXES = 0x00000001,
+ CSSM_DL_DB_SCHEMA_ATTRIBUTES = 0x00000002,
+ CSSM_DL_DB_SCHEMA_PARSING_MODULE = 0x00000003,
+
+ CSSM_DL_DB_RECORD_ANY = 0x0000000a,
+ CSSM_DL_DB_RECORD_CERT = 0x0000000b,
+ CSSM_DL_DB_RECORD_CRL = 0x0000000c,
+ CSSM_DL_DB_RECORD_POLICY = 0x0000000d,
+ CSSM_DL_DB_RECORD_GENERIC = 0x0000000e,
+ CSSM_DL_DB_RECORD_PUBLIC_KEY = 0x0000000f,
+ CSSM_DL_DB_RECORD_PRIVATE_KEY = 0x00000010,
+ CSSM_DL_DB_RECORD_SYMMETRIC_KEY = 0x00000011,
+ CSSM_DL_DB_RECORD_ALL_KEYS = 0x00000012,
+
+ CSSM_DL_DB_RECORD_GENERIC_PASSWORD = 0x80000000,
+ CSSM_DL_DB_RECORD_INTERNET_PASSWORD = 0x80000001,
+ CSSM_DL_DB_RECORD_APPLESHARE_PASSWORD = 0x80000002,
+ CSSM_DL_DB_RECORD_USER_TRUST = 0x80000003,
+ CSSM_DL_DB_RECORD_X509_CRL = 0x80000004,
+ CSSM_DL_DB_RECORD_UNLOCK_REFERRAL = 0x80000005,
+ CSSM_DL_DB_RECORD_EXTENDED_ATTRIBUTE = 0x80000006,
+ CSSM_DL_DB_RECORD_X509_CERTIFICATE = 0x80001000,
+ CSSM_DL_DB_RECORD_METADATA = 0x80008000,
+
+ _,
+};
+
+const X509CertHeader = extern struct {
+ record_size: u32,
+ record_number: u32,
+ unknown1: u32,
+ unknown2: u32,
+ cert_size: u32,
+ unknown3: u32,
+ cert_type: u32,
+ cert_encoding: u32,
+ print_name: u32,
+ alias: u32,
+ subject: u32,
+ issuer: u32,
+ serial_number: u32,
+ subject_key_identifier: u32,
+ public_key_hash: u32,
+};
diff --git a/lib/std/io/reader.zig b/lib/std/io/reader.zig
index f9d3085a29..65b7a086c5 100644
--- a/lib/std/io/reader.zig
+++ b/lib/std/io/reader.zig
@@ -3,6 +3,7 @@ const math = std.math;
const assert = std.debug.assert;
const mem = std.mem;
const testing = std.testing;
+const native_endian = @import("builtin").target.cpu.arch.endian();
pub fn Reader(
comptime Context: type,
@@ -351,6 +352,14 @@ pub fn Reader(
return res[0];
}
+ pub fn readStructBig(self: Self, comptime T: type) !T {
+ var res = try self.readStruct(T);
+ if (native_endian != std.builtin.Endian.Big) {
+ mem.byteSwapAllFields(T, &res);
+ }
+ return res;
+ }
+
/// Reads an integer with the same size as the given enum's tag type. If the integer matches
/// an enum tag, casts the integer to the enum tag and returns it. Otherwise, returns an error.
/// TODO optimization taking advantage of most fields being in order