| author | |
| committer | |
| log | e45b471ad35828c5a435b6df7b672f83e8e69b6a |
| tree | 3c3fdeae57c337db7b2b39fafb01bc2dce1568f4 |
| parent | b42bd759a7bdb6bc1d84c640d68e5384dc35776d |
| signature |
4 files changed, 173 insertions(+), 36 deletions(-)
lib/std/crypto/Certificate/Bundle.zig+26-22| ... | ... | @@ -60,13 +60,13 @@ pub fn rescan(cb: *Bundle, gpa: Allocator) !void { |
| 60 | 60 | .windows => { |
| 61 | 61 | // TODO |
| 62 | 62 | }, |
| 63 | .macos => { | |
| 64 | // TODO | |
| 65 | }, | |
| 63 | .macos => return rescanMac(cb, gpa), | |
| 66 | 64 | else => {}, |
| 67 | 65 | } |
| 68 | 66 | } |
| 69 | 67 | |
| 68 | pub const rescanMac = @import("Bundle/macos.zig").rescanMac; | |
| 69 | ||
| 70 | 70 | pub fn rescanLinux(cb: *Bundle, gpa: Allocator) !void { |
| 71 | 71 | // Possible certificate files; stop after finding one. |
| 72 | 72 | const cert_file_paths = [_][]const u8{ |
| ... | ... | @@ -195,25 +195,29 @@ pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file: fs.File) !void { |
| 195 | 195 | const decoded_start = @intCast(u32, cb.bytes.items.len); |
| 196 | 196 | const dest_buf = cb.bytes.allocatedSlice()[decoded_start..]; |
| 197 | 197 | cb.bytes.items.len += try base64.decode(dest_buf, encoded_cert); |
| 198 | // Even though we could only partially parse the certificate to find | |
| 199 | // the subject name, we pre-parse all of them to make sure and only | |
| 200 | // include in the bundle ones that we know will parse. This way we can | |
| 201 | // use `catch unreachable` later. | |
| 202 | const parsed_cert = try Certificate.parse(.{ | |
| 203 | .buffer = cb.bytes.items, | |
| 204 | .index = decoded_start, | |
| 205 | }); | |
| 206 | if (now_sec > parsed_cert.validity.not_after) { | |
| 207 | // Ignore expired cert. | |
| 208 | cb.bytes.items.len = decoded_start; | |
| 209 | continue; | |
| 210 | } | |
| 211 | const gop = try cb.map.getOrPutContext(gpa, parsed_cert.subject_slice, .{ .cb = cb }); | |
| 212 | if (gop.found_existing) { | |
| 213 | cb.bytes.items.len = decoded_start; | |
| 214 | } else { | |
| 215 | gop.value_ptr.* = decoded_start; | |
| 216 | } | |
| 198 | try cb.parseCert(gpa, decoded_start, now_sec); | |
| 199 | } | |
| 200 | } | |
| 201 | ||
| 202 | pub fn parseCert(cb: *Bundle, gpa: Allocator, decoded_start: u32, now_sec: i64) !void { | |
| 203 | // Even though we could only partially parse the certificate to find | |
| 204 | // the subject name, we pre-parse all of them to make sure and only | |
| 205 | // include in the bundle ones that we know will parse. This way we can | |
| 206 | // use `catch unreachable` later. | |
| 207 | const parsed_cert = try Certificate.parse(.{ | |
| 208 | .buffer = cb.bytes.items, | |
| 209 | .index = decoded_start, | |
| 210 | }); | |
| 211 | if (now_sec > parsed_cert.validity.not_after) { | |
| 212 | // Ignore expired cert. | |
| 213 | cb.bytes.items.len = decoded_start; | |
| 214 | return; | |
| 215 | } | |
| 216 | const gop = try cb.map.getOrPutContext(gpa, parsed_cert.subject_slice, .{ .cb = cb }); | |
| 217 | if (gop.found_existing) { | |
| 218 | cb.bytes.items.len = decoded_start; | |
| 219 | } else { | |
| 220 | gop.value_ptr.* = decoded_start; | |
| 217 | 221 | } |
| 218 | 222 | } |
| 219 | 223 |
lib/std/crypto/Certificate/Bundle/macos.zig created+136| ... | ... | @@ -0,0 +1,136 @@ |
| 1 | const std = @import("std"); | |
| 2 | const assert = std.debug.assert; | |
| 3 | const mem = std.mem; | |
| 4 | const fs = std.fs; | |
| 5 | const Allocator = std.mem.Allocator; | |
| 6 | const Bundle = @import("../Bundle.zig"); | |
| 7 | ||
| 8 | pub fn rescanMac(cb: *Bundle, gpa: Allocator) !void { | |
| 9 | const file = try fs.openFileAbsolute("/System/Library/Keychains/SystemRootCertificates.keychain", .{}); | |
| 10 | defer file.close(); | |
| 11 | ||
| 12 | const bytes = try file.readToEndAlloc(gpa, std.math.maxInt(u32)); | |
| 13 | defer gpa.free(bytes); | |
| 14 | ||
| 15 | var stream = std.io.fixedBufferStream(bytes); | |
| 16 | const reader = stream.reader(); | |
| 17 | ||
| 18 | const db_header = try reader.readStructBig(ApplDbHeader); | |
| 19 | assert(mem.eql(u8, "kych", &@bitCast([4]u8, db_header.signature))); | |
| 20 | ||
| 21 | try stream.seekTo(db_header.schema_offset); | |
| 22 | ||
| 23 | const db_schema = try reader.readStructBig(ApplDbSchema); | |
| 24 | ||
| 25 | var table_list = try gpa.alloc(u32, db_schema.table_count); | |
| 26 | defer gpa.free(table_list); | |
| 27 | ||
| 28 | var table_idx: u32 = 0; | |
| 29 | while (table_idx < table_list.len) : (table_idx += 1) { | |
| 30 | table_list[table_idx] = try reader.readIntBig(u32); | |
| 31 | } | |
| 32 | ||
| 33 | const now_sec = std.time.timestamp(); | |
| 34 | ||
| 35 | for (table_list) |table_offset| { | |
| 36 | try stream.seekTo(db_header.schema_offset + table_offset); | |
| 37 | ||
| 38 | const table_header = try reader.readStructBig(TableHeader); | |
| 39 | ||
| 40 | if (@intToEnum(TableId, table_header.table_id) != TableId.CSSM_DL_DB_RECORD_X509_CERTIFICATE) { | |
| 41 | continue; | |
| 42 | } | |
| 43 | ||
| 44 | var record_list = try gpa.alloc(u32, table_header.record_count); | |
| 45 | defer gpa.free(record_list); | |
| 46 | ||
| 47 | var record_idx: u32 = 0; | |
| 48 | while (record_idx < record_list.len) : (record_idx += 1) { | |
| 49 | record_list[record_idx] = try reader.readIntBig(u32); | |
| 50 | } | |
| 51 | ||
| 52 | for (record_list) |record_offset| { | |
| 53 | try stream.seekTo(db_header.schema_offset + table_offset + record_offset); | |
| 54 | ||
| 55 | const cert_header = try reader.readStructBig(X509CertHeader); | |
| 56 | ||
| 57 | try cb.bytes.ensureUnusedCapacity(gpa, cert_header.cert_size); | |
| 58 | ||
| 59 | const cert_start = @intCast(u32, cb.bytes.items.len); | |
| 60 | const dest_buf = cb.bytes.allocatedSlice()[cert_start..]; | |
| 61 | cb.bytes.items.len += try reader.readAtLeast(dest_buf, cert_header.cert_size); | |
| 62 | ||
| 63 | try cb.parseCert(gpa, cert_start, now_sec); | |
| 64 | } | |
| 65 | } | |
| 66 | } | |
| 67 | ||
| 68 | const ApplDbHeader = extern struct { | |
| 69 | signature: @Vector(4, u8), | |
| 70 | version: u32, | |
| 71 | header_size: u32, | |
| 72 | schema_offset: u32, | |
| 73 | auth_offset: u32, | |
| 74 | }; | |
| 75 | ||
| 76 | const ApplDbSchema = extern struct { | |
| 77 | schema_size: u32, | |
| 78 | table_count: u32, | |
| 79 | }; | |
| 80 | ||
| 81 | const TableHeader = extern struct { | |
| 82 | table_size: u32, | |
| 83 | table_id: u32, | |
| 84 | record_count: u32, | |
| 85 | records: u32, | |
| 86 | indexes_offset: u32, | |
| 87 | free_list_head: u32, | |
| 88 | record_numbers_count: u32, | |
| 89 | }; | |
| 90 | ||
| 91 | const TableId = enum(u32) { | |
| 92 | CSSM_DL_DB_SCHEMA_INFO = 0x00000000, | |
| 93 | CSSM_DL_DB_SCHEMA_INDEXES = 0x00000001, | |
| 94 | CSSM_DL_DB_SCHEMA_ATTRIBUTES = 0x00000002, | |
| 95 | CSSM_DL_DB_SCHEMA_PARSING_MODULE = 0x00000003, | |
| 96 | ||
| 97 | CSSM_DL_DB_RECORD_ANY = 0x0000000a, | |
| 98 | CSSM_DL_DB_RECORD_CERT = 0x0000000b, | |
| 99 | CSSM_DL_DB_RECORD_CRL = 0x0000000c, | |
| 100 | CSSM_DL_DB_RECORD_POLICY = 0x0000000d, | |
| 101 | CSSM_DL_DB_RECORD_GENERIC = 0x0000000e, | |
| 102 | CSSM_DL_DB_RECORD_PUBLIC_KEY = 0x0000000f, | |
| 103 | CSSM_DL_DB_RECORD_PRIVATE_KEY = 0x00000010, | |
| 104 | CSSM_DL_DB_RECORD_SYMMETRIC_KEY = 0x00000011, | |
| 105 | CSSM_DL_DB_RECORD_ALL_KEYS = 0x00000012, | |
| 106 | ||
| 107 | CSSM_DL_DB_RECORD_GENERIC_PASSWORD = 0x80000000, | |
| 108 | CSSM_DL_DB_RECORD_INTERNET_PASSWORD = 0x80000001, | |
| 109 | CSSM_DL_DB_RECORD_APPLESHARE_PASSWORD = 0x80000002, | |
| 110 | CSSM_DL_DB_RECORD_USER_TRUST = 0x80000003, | |
| 111 | CSSM_DL_DB_RECORD_X509_CRL = 0x80000004, | |
| 112 | CSSM_DL_DB_RECORD_UNLOCK_REFERRAL = 0x80000005, | |
| 113 | CSSM_DL_DB_RECORD_EXTENDED_ATTRIBUTE = 0x80000006, | |
| 114 | CSSM_DL_DB_RECORD_X509_CERTIFICATE = 0x80001000, | |
| 115 | CSSM_DL_DB_RECORD_METADATA = 0x80008000, | |
| 116 | ||
| 117 | _, | |
| 118 | }; | |
| 119 | ||
| 120 | const X509CertHeader = extern struct { | |
| 121 | record_size: u32, | |
| 122 | record_number: u32, | |
| 123 | unknown1: u32, | |
| 124 | unknown2: u32, | |
| 125 | cert_size: u32, | |
| 126 | unknown3: u32, | |
| 127 | cert_type: u32, | |
| 128 | cert_encoding: u32, | |
| 129 | print_name: u32, | |
| 130 | alias: u32, | |
| 131 | subject: u32, | |
| 132 | issuer: u32, | |
| 133 | serial_number: u32, | |
| 134 | subject_key_identifier: u32, | |
| 135 | public_key_hash: u32, | |
| 136 | }; |
lib/std/io/reader.zig+9| ... | ... | @@ -3,6 +3,7 @@ const math = std.math; |
| 3 | 3 | const assert = std.debug.assert; |
| 4 | 4 | const mem = std.mem; |
| 5 | 5 | const testing = std.testing; |
| 6 | const native_endian = @import("builtin").target.cpu.arch.endian(); | |
| 6 | 7 | |
| 7 | 8 | pub fn Reader( |
| 8 | 9 | comptime Context: type, |
| ... | ... | @@ -351,6 +352,14 @@ pub fn Reader( |
| 351 | 352 | return res[0]; |
| 352 | 353 | } |
| 353 | 354 | |
| 355 | pub fn readStructBig(self: Self, comptime T: type) !T { | |
| 356 | var res = try self.readStruct(T); | |
| 357 | if (native_endian != std.builtin.Endian.Big) { | |
| 358 | mem.byteSwapAllFields(T, &res); | |
| 359 | } | |
| 360 | return res; | |
| 361 | } | |
| 362 | ||
| 354 | 363 | /// Reads an integer with the same size as the given enum's tag type. If the integer matches |
| 355 | 364 | /// an enum tag, casts the integer to the enum tag and returns it. Otherwise, returns an error. |
| 356 | 365 | /// TODO optimization taking advantage of most fields being in order |
src/link/MachO/fat.zig+2-14| ... | ... | @@ -1,9 +1,7 @@ |
| 1 | 1 | const std = @import("std"); |
| 2 | const builtin = @import("builtin"); | |
| 3 | 2 | const log = std.log.scoped(.archive); |
| 4 | 3 | const macho = std.macho; |
| 5 | 4 | const mem = std.mem; |
| 6 | const native_endian = builtin.target.cpu.arch.endian(); | |
| 7 | 5 | |
| 8 | 6 | pub fn decodeArch(cputype: macho.cpu_type_t, comptime logError: bool) !std.Target.Cpu.Arch { |
| 9 | 7 | const cpu_arch: std.Target.Cpu.Arch = switch (cputype) { |
| ... | ... | @@ -19,23 +17,13 @@ pub fn decodeArch(cputype: macho.cpu_type_t, comptime logError: bool) !std.Targe |
| 19 | 17 | return cpu_arch; |
| 20 | 18 | } |
| 21 | 19 | |
| 22 | fn readFatStruct(reader: anytype, comptime T: type) !T { | |
| 23 | // Fat structures (fat_header & fat_arch) are always written and read to/from | |
| 24 | // disk in big endian order. | |
| 25 | var res = try reader.readStruct(T); | |
| 26 | if (native_endian != std.builtin.Endian.Big) { | |
| 27 | mem.byteSwapAllFields(T, &res); | |
| 28 | } | |
| 29 | return res; | |
| 30 | } | |
| 31 | ||
| 32 | 20 | pub fn getLibraryOffset(reader: anytype, cpu_arch: std.Target.Cpu.Arch) !u64 { |
| 33 | const fat_header = try readFatStruct(reader, macho.fat_header); | |
| 21 | const fat_header = try reader.readStructBig(macho.fat_header); | |
| 34 | 22 | if (fat_header.magic != macho.FAT_MAGIC) return 0; |
| 35 | 23 | |
| 36 | 24 | var fat_arch_index: u32 = 0; |
| 37 | 25 | while (fat_arch_index < fat_header.nfat_arch) : (fat_arch_index += 1) { |
| 38 | const fat_arch = try readFatStruct(reader, macho.fat_arch); | |
| 26 | const fat_arch = try reader.readStructBig(macho.fat_arch); | |
| 39 | 27 | // If we come across an architecture that we do not know how to handle, that's |
| 40 | 28 | // fine because we can keep looking for one that might match. |
| 41 | 29 | const lib_arch = decodeArch(fat_arch.cputype, false) catch |err| switch (err) { |