Skip to content

Commit e78a1b7

Browse files
Find system-installed root SSL certificates on macOS
1 parent bb15e40 commit e78a1b7

File tree

1 file changed

+76
-5
lines changed

1 file changed

+76
-5
lines changed

lib/std/crypto/Certificate/Bundle.zig

Lines changed: 76 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -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+
93136
pub 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+
182253
test "scan for OS-provided certificates" {
183254
if (builtin.os.tag == .wasi) return error.SkipZigTest;
184255

0 commit comments

Comments
 (0)