authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-16 00:24:03-05:00
committergravatar for noreply@github.comGitHub <noreply@github.com> 2023-01-16 00:24:03-05:00
logd389dba04f66deb3dd1831ed96b6843a9eee1309
tree5415ff8e6d75487f8c8c39fe356a1dcfc59ff1e4
parent9856bea34e9d7eca6dfc2e883d46f24a53265dc6
parente3505c0a5abcacdc17aeb4a12a58e4e12e0a7a59
signaturebadge-question-mark Signed by PGP key 4AEE18F83AFDEB23

Merge pull request #14329 from ziglang/v1-ssl-certificates

support v1/v2 SSL certificates and search more Linux directories for certificates

2 files changed, 106 insertions(+), 16 deletions(-)

lib/std/crypto/Certificate.zig+30-8
......@@ -3,6 +3,8 @@ index: u32,
33
44pub const Bundle = @import("Certificate/Bundle.zig");
55
6pub const Version = enum { v1, v2, v3 };
7
68pub const Algorithm = enum {
79 sha1WithRSAEncryption,
810 sha224WithRSAEncryption,
......@@ -130,6 +132,7 @@ pub const Parsed = struct {
130132 message_slice: Slice,
131133 subject_alt_name_slice: Slice,
132134 validity: Validity,
135 version: Version,
133136
134137 pub const PubKeyAlgo = union(AlgorithmCategory) {
135138 rsaEncryption: void,
......@@ -299,9 +302,12 @@ pub fn parse(cert: Certificate) !Parsed {
299302 const cert_bytes = cert.buffer;
300303 const certificate = try der.Element.parse(cert_bytes, cert.index);
301304 const tbs_certificate = try der.Element.parse(cert_bytes, certificate.slice.start);
302 const version = try der.Element.parse(cert_bytes, tbs_certificate.slice.start);
303 try checkVersion(cert_bytes, version);
304 const serial_number = try der.Element.parse(cert_bytes, version.slice.end);
305 const version_elem = try der.Element.parse(cert_bytes, tbs_certificate.slice.start);
306 const version = try parseVersion(cert_bytes, version_elem);
307 const serial_number = if (@bitCast(u8, version_elem.identifier) == 0xa0)
308 try der.Element.parse(cert_bytes, version_elem.slice.end)
309 else
310 version_elem;
305311 // RFC 5280, section 4.1.2.3:
306312 // "This field MUST contain the same algorithm identifier as
307313 // the signatureAlgorithm field in the sequence Certificate."
......@@ -370,6 +376,9 @@ pub fn parse(cert: Certificate) !Parsed {
370376 // Extensions
371377 var subject_alt_name_slice = der.Element.Slice.empty;
372378 ext: {
379 if (version == .v1)
380 break :ext;
381
373382 if (pub_key_info.slice.end >= tbs_certificate.slice.end)
374383 break :ext;
375384
......@@ -415,6 +424,7 @@ pub fn parse(cert: Certificate) !Parsed {
415424 .not_after = not_after_utc,
416425 },
417426 .subject_alt_name_slice = subject_alt_name_slice,
427 .version = version,
418428 };
419429}
420430
......@@ -588,12 +598,24 @@ fn parseEnum(comptime E: type, bytes: []const u8, element: der.Element) !E {
588598 return E.map.get(oid_bytes) orelse return error.CertificateHasUnrecognizedObjectId;
589599}
590600
591pub fn checkVersion(bytes: []const u8, version: der.Element) !void {
592 if (@bitCast(u8, version.identifier) != 0xa0 or
593 !mem.eql(u8, bytes[version.slice.start..version.slice.end], "\x02\x01\x02"))
594 {
595 return error.UnsupportedCertificateVersion;
601pub fn parseVersion(bytes: []const u8, version_elem: der.Element) !Version {
602 if (@bitCast(u8, version_elem.identifier) != 0xa0)
603 return .v1;
604
605 if (version_elem.slice.end - version_elem.slice.start != 3)
606 return error.CertificateFieldHasInvalidLength;
607
608 const encoded_version = bytes[version_elem.slice.start..version_elem.slice.end];
609
610 if (mem.eql(u8, encoded_version, "\x02\x01\x02")) {
611 return .v3;
612 } else if (mem.eql(u8, encoded_version, "\x02\x01\x01")) {
613 return .v2;
614 } else if (mem.eql(u8, encoded_version, "\x02\x01\x00")) {
615 return .v1;
596616 }
617
618 return error.UnsupportedCertificateVersion;
597619}
598620
599621fn verifyRsa(
lib/std/crypto/Certificate/Bundle.zig+76-8
......@@ -68,29 +68,93 @@ pub fn rescan(cb: *Bundle, gpa: Allocator) !void {
6868}
6969
7070pub fn rescanLinux(cb: *Bundle, gpa: Allocator) !void {
71 var dir = fs.openIterableDirAbsolute("/etc/ssl/certs", .{}) catch |err| switch (err) {
72 error.FileNotFound => return,
73 else => |e| return e,
71 // Possible certificate files; stop after finding one.
72 const cert_file_paths = [_][]const u8{
73 "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc.
74 "/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL 6
75 "/etc/ssl/ca-bundle.pem", // OpenSUSE
76 "/etc/pki/tls/cacert.pem", // OpenELEC
77 "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS/RHEL 7
78 "/etc/ssl/cert.pem", // Alpine Linux
79 };
80
81 // Possible directories with certificate files; all will be read.
82 const cert_dir_paths = [_][]const u8{
83 "/etc/ssl/certs", // SLES10/SLES11
84 "/etc/pki/tls/certs", // Fedora/RHEL
85 "/system/etc/security/cacerts", // Android
7486 };
75 defer dir.close();
7687
7788 cb.bytes.clearRetainingCapacity();
7889 cb.map.clearRetainingCapacity();
7990
80 var it = dir.iterate();
91 scan: {
92 for (cert_file_paths) |cert_file_path| {
93 if (addCertsFromFilePathAbsolute(cb, gpa, cert_file_path)) |_| {
94 break :scan;
95 } else |err| switch (err) {
96 error.FileNotFound => continue,
97 else => |e| return e,
98 }
99 }
100
101 for (cert_dir_paths) |cert_dir_path| {
102 addCertsFromDirPathAbsolute(cb, gpa, cert_dir_path) catch |err| switch (err) {
103 error.FileNotFound => continue,
104 else => |e| return e,
105 };
106 }
107 }
108
109 cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len);
110}
111
112pub fn addCertsFromDirPath(
113 cb: *Bundle,
114 gpa: Allocator,
115 dir: fs.Dir,
116 sub_dir_path: []const u8,
117) !void {
118 var iterable_dir = try dir.openIterableDir(sub_dir_path, .{});
119 defer iterable_dir.close();
120 return addCertsFromDir(cb, gpa, iterable_dir);
121}
122
123pub fn addCertsFromDirPathAbsolute(
124 cb: *Bundle,
125 gpa: Allocator,
126 abs_dir_path: []const u8,
127) !void {
128 assert(fs.path.isAbsolute(abs_dir_path));
129 var iterable_dir = try fs.openIterableDirAbsolute(abs_dir_path, .{});
130 defer iterable_dir.close();
131 return addCertsFromDir(cb, gpa, iterable_dir);
132}
133
134pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, iterable_dir: fs.IterableDir) !void {
135 var it = iterable_dir.iterate();
81136 while (try it.next()) |entry| {
82137 switch (entry.kind) {
83138 .File, .SymLink => {},
84139 else => continue,
85140 }
86141
87 try addCertsFromFile(cb, gpa, dir.dir, entry.name);
142 try addCertsFromFilePath(cb, gpa, iterable_dir.dir, entry.name);
88143 }
144}
89145
90 cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len);
146pub fn addCertsFromFilePathAbsolute(
147 cb: *Bundle,
148 gpa: Allocator,
149 abs_file_path: []const u8,
150) !void {
151 assert(fs.path.isAbsolute(abs_file_path));
152 var file = try fs.openFileAbsolute(abs_file_path, .{});
153 defer file.close();
154 return addCertsFromFile(cb, gpa, file);
91155}
92156
93pub fn addCertsFromFile(
157pub fn addCertsFromFilePath(
94158 cb: *Bundle,
95159 gpa: Allocator,
96160 dir: fs.Dir,
......@@ -98,7 +162,10 @@ pub fn addCertsFromFile(
98162) !void {
99163 var file = try dir.openFile(sub_file_path, .{});
100164 defer file.close();
165 return addCertsFromFile(cb, gpa, file);
166}
101167
168pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file: fs.File) !void {
102169 const size = try file.getEndPos();
103170
104171 // We borrow `bytes` as a temporary buffer for the base64-encoded data.
......@@ -152,6 +219,7 @@ pub fn addCertsFromFile(
152219
153220const builtin = @import("builtin");
154221const std = @import("../../std.zig");
222const assert = std.debug.assert;
155223const fs = std.fs;
156224const mem = std.mem;
157225const crypto = std.crypto;