@@ -60,9 +60,7 @@ pub fn rescan(cb: *Bundle, gpa: Allocator) !void {
6060 .windows = > {
6161 // TODO
6262 },
63- .macos = > {
64- // TODO
65- },
63+ .macos = > return rescanMac (cb , gpa , .{}),
6664 else = > {},
6765 }
6866}
@@ -90,6 +88,51 @@ pub fn rescanLinux(cb: *Bundle, gpa: Allocator) !void {
9088 cb .bytes .shrinkAndFree (gpa , cb .bytes .items .len );
9189}
9290
91+ pub fn rescanMac (
92+ cb : * Bundle ,
93+ gpa : Allocator ,
94+ options : struct { verbose : bool = false },
95+ ) ! void {
96+ const argv = [_ ][]const u8 {
97+ "/usr/bin/security" ,
98+ "find-certificate" ,
99+ "-a" ,
100+ "-p" ,
101+ "/System/Library/Keychains/SystemRootCertificates.keychain" ,
102+ };
103+
104+ const exec_result = std .ChildProcess .exec (.{
105+ .allocator = gpa ,
106+ .argv = & argv ,
107+ .max_output_bytes = 1024 * 1024 ,
108+ }) catch | err | switch (err ) {
109+ error .OutOfMemory = > return error .OutOfMemory ,
110+ else = > {
111+ printVerboseInvocation (& argv , options .verbose , null );
112+ return error .UnableToSpawnSecurity ;
113+ },
114+ };
115+ defer {
116+ gpa .free (exec_result .stdout );
117+ gpa .free (exec_result .stderr );
118+ }
119+ switch (exec_result .term ) {
120+ .Exited = > | code | if (code != 0 ) {
121+ printVerboseInvocation (& argv , options .verbose , exec_result .stderr );
122+ return error .SecurityExitCode ;
123+ },
124+ else = > {
125+ printVerboseInvocation (& argv , options .verbose , exec_result .stderr );
126+ return error .SecurityCrashed ;
127+ },
128+ }
129+
130+ const encoded_bytes = exec_result .stdout ;
131+ const decoded_size_upper_bound = encoded_bytes .len / 4 * 3 ;
132+ try cb .bytes .ensureUnusedCapacity (gpa , decoded_size_upper_bound );
133+ return addCertsFromBytes (cb , gpa , encoded_bytes );
134+ }
135+
93136pub fn addCertsFromFile (
94137 cb : * Bundle ,
95138 gpa : Allocator ,
@@ -112,7 +155,14 @@ pub fn addCertsFromFile(
112155 const buffer = cb .bytes .allocatedSlice ()[end_reserved .. ];
113156 const end_index = try file .readAll (buffer );
114157 const encoded_bytes = buffer [0.. end_index ];
158+ return addCertsFromBytes (cb , gpa , encoded_bytes );
159+ }
115160
161+ pub fn addCertsFromBytes (
162+ cb : * Bundle ,
163+ gpa : Allocator ,
164+ encoded_bytes : []const u8 ,
165+ ) ! void {
116166 const begin_marker = "-----BEGIN CERTIFICATE-----" ;
117167 const end_marker = "-----END CERTIFICATE-----" ;
118168
@@ -132,10 +182,13 @@ pub fn addCertsFromFile(
132182 // the subject name, we pre-parse all of them to make sure and only
133183 // include in the bundle ones that we know will parse. This way we can
134184 // use `catch unreachable` later.
135- const parsed_cert = try Certificate .parse (.{
185+ const parsed_cert = Certificate .parse (.{
136186 .buffer = cb .bytes .items ,
137187 .index = decoded_start ,
138- });
188+ }) catch | err | switch (err ) {
189+ error .UnsupportedCertificateVersion = > continue ,
190+ else = > | e | return e ,
191+ };
139192 if (now_sec > parsed_cert .validity .not_after ) {
140193 // Ignore expired cert.
141194 cb .bytes .items .len = decoded_start ;
@@ -179,6 +232,24 @@ const MapContext = struct {
179232 }
180233};
181234
235+ fn printVerboseInvocation (
236+ argv : []const []const u8 ,
237+ verbose : bool ,
238+ stderr : ? []const u8 ,
239+ ) void {
240+ if (! verbose ) return ;
241+
242+ std .debug .print ("Zig attempted to find system-installed root SSL certificates by executing this command:\n " , .{});
243+ for (argv ) | arg , i | {
244+ if (i != 0 ) std .debug .print (" " , .{});
245+ std .debug .print ("{s}" , .{arg });
246+ }
247+ std .debug .print ("\n " , .{});
248+ if (stderr ) | s | {
249+ std .debug .print ("Output:\n ==========\n {s}\n ==========\n " , .{s });
250+ }
251+ }
252+
182253test "scan for OS-provided certificates" {
183254 if (builtin .os .tag == .wasi ) return error .SkipZigTest ;
184255
0 commit comments