master
  1const std = @import("std");
  2const Io = std.Io;
  3const assert = std.debug.assert;
  4const fs = std.fs;
  5const mem = std.mem;
  6const Allocator = std.mem.Allocator;
  7const Bundle = @import("../Bundle.zig");
  8
  9pub const RescanMacError = Allocator.Error || fs.File.OpenError || fs.File.ReadError || fs.File.SeekError || Bundle.ParseCertError || error{EndOfStream};
 10
 11pub fn rescanMac(cb: *Bundle, gpa: Allocator, io: Io, now: Io.Timestamp) RescanMacError!void {
 12    cb.bytes.clearRetainingCapacity();
 13    cb.map.clearRetainingCapacity();
 14
 15    const keychain_paths = [2][]const u8{
 16        "/System/Library/Keychains/SystemRootCertificates.keychain",
 17        "/Library/Keychains/System.keychain",
 18    };
 19
 20    _ = io; // TODO migrate file system to use std.Io
 21    for (keychain_paths) |keychain_path| {
 22        const bytes = std.fs.cwd().readFileAlloc(keychain_path, gpa, .limited(std.math.maxInt(u32))) catch |err| switch (err) {
 23            error.StreamTooLong => return error.FileTooBig,
 24            else => |e| return e,
 25        };
 26        defer gpa.free(bytes);
 27
 28        var reader: Io.Reader = .fixed(bytes);
 29        scanReader(cb, gpa, &reader, now.toSeconds()) catch |err| switch (err) {
 30            error.ReadFailed => unreachable, // prebuffered
 31            else => |e| return e,
 32        };
 33    }
 34
 35    cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len);
 36}
 37
 38fn scanReader(cb: *Bundle, gpa: Allocator, reader: *Io.Reader, now_sec: i64) !void {
 39    const db_header = try reader.takeStruct(ApplDbHeader, .big);
 40    assert(mem.eql(u8, &db_header.signature, "kych"));
 41
 42    reader.seek = db_header.schema_offset;
 43
 44    const db_schema = try reader.takeStruct(ApplDbSchema, .big);
 45
 46    var table_list = try gpa.alloc(u32, db_schema.table_count);
 47    defer gpa.free(table_list);
 48
 49    var table_idx: u32 = 0;
 50    while (table_idx < table_list.len) : (table_idx += 1) {
 51        table_list[table_idx] = try reader.takeInt(u32, .big);
 52    }
 53
 54    for (table_list) |table_offset| {
 55        reader.seek = db_header.schema_offset + table_offset;
 56
 57        const table_header = try reader.takeStruct(TableHeader, .big);
 58
 59        if (@as(std.c.DB_RECORDTYPE, @enumFromInt(table_header.table_id)) != .X509_CERTIFICATE) {
 60            continue;
 61        }
 62
 63        var record_list = try gpa.alloc(u32, table_header.record_count);
 64        defer gpa.free(record_list);
 65
 66        var record_idx: u32 = 0;
 67        while (record_idx < record_list.len) : (record_idx += 1) {
 68            record_list[record_idx] = try reader.takeInt(u32, .big);
 69        }
 70
 71        for (record_list) |record_offset| {
 72            // An offset of zero means that the record is not present.
 73            // An offset that is not 4-byte-aligned is invalid.
 74            if (record_offset == 0 or record_offset % 4 != 0) continue;
 75
 76            reader.seek = db_header.schema_offset + table_offset + record_offset;
 77
 78            const cert_header = try reader.takeStruct(X509CertHeader, .big);
 79
 80            if (cert_header.cert_size == 0) continue;
 81
 82            const cert_start: u32 = @intCast(cb.bytes.items.len);
 83            const dest_buf = try cb.bytes.addManyAsSlice(gpa, cert_header.cert_size);
 84            try reader.readSliceAll(dest_buf);
 85
 86            try cb.parseCert(gpa, cert_start, now_sec);
 87        }
 88    }
 89}
 90
 91const ApplDbHeader = extern struct {
 92    signature: [4]u8,
 93    version: u32,
 94    header_size: u32,
 95    schema_offset: u32,
 96    auth_offset: u32,
 97};
 98
 99const ApplDbSchema = extern struct {
100    schema_size: u32,
101    table_count: u32,
102};
103
104const TableHeader = extern struct {
105    table_size: u32,
106    table_id: u32,
107    record_count: u32,
108    records: u32,
109    indexes_offset: u32,
110    free_list_head: u32,
111    record_numbers_count: u32,
112};
113
114const X509CertHeader = extern struct {
115    record_size: u32,
116    record_number: u32,
117    unknown1: u32,
118    unknown2: u32,
119    cert_size: u32,
120    unknown3: u32,
121    cert_type: u32,
122    cert_encoding: u32,
123    print_name: u32,
124    alias: u32,
125    subject: u32,
126    issuer: u32,
127    serial_number: u32,
128    subject_key_identifier: u32,
129    public_key_hash: u32,
130};