@@ -60,9 +60,7 @@ 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
}
@@ -90,6 +88,51 @@ pub fn rescanLinux(cb: *Bundle, gpa: Allocator) !void {
90
88
cb .bytes .shrinkAndFree (gpa , cb .bytes .items .len );
91
89
}
92
90
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
+
93
136
pub fn addCertsFromFile (
94
137
cb : * Bundle ,
95
138
gpa : Allocator ,
@@ -112,7 +155,14 @@ pub fn addCertsFromFile(
112
155
const buffer = cb .bytes .allocatedSlice ()[end_reserved .. ];
113
156
const end_index = try file .readAll (buffer );
114
157
const encoded_bytes = buffer [0.. end_index ];
158
+ return addCertsFromBytes (cb , gpa , encoded_bytes );
159
+ }
115
160
161
+ pub fn addCertsFromBytes (
162
+ cb : * Bundle ,
163
+ gpa : Allocator ,
164
+ encoded_bytes : []const u8 ,
165
+ ) ! void {
116
166
const begin_marker = "-----BEGIN CERTIFICATE-----" ;
117
167
const end_marker = "-----END CERTIFICATE-----" ;
118
168
@@ -132,10 +182,13 @@ pub fn addCertsFromFile(
132
182
// the subject name, we pre-parse all of them to make sure and only
133
183
// include in the bundle ones that we know will parse. This way we can
134
184
// use `catch unreachable` later.
135
- const parsed_cert = try Certificate .parse (.{
185
+ const parsed_cert = Certificate .parse (.{
136
186
.buffer = cb .bytes .items ,
137
187
.index = decoded_start ,
138
- });
188
+ }) catch | err | switch (err ) {
189
+ error .UnsupportedCertificateVersion = > continue ,
190
+ else = > | e | return e ,
191
+ };
139
192
if (now_sec > parsed_cert .validity .not_after ) {
140
193
// Ignore expired cert.
141
194
cb .bytes .items .len = decoded_start ;
@@ -179,6 +232,24 @@ const MapContext = struct {
179
232
}
180
233
};
181
234
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
+
182
253
test "scan for OS-provided certificates" {
183
254
if (builtin .os .tag == .wasi ) return error .SkipZigTest ;
184
255
0 commit comments