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,...@@ -3,6 +3,8 @@ index: u32,
33
4pub const Bundle = @import("Certificate/Bundle.zig");4pub const Bundle = @import("Certificate/Bundle.zig");
55
6pub const Version = enum { v1, v2, v3 };
7
6pub const Algorithm = enum {8pub const Algorithm = enum {
7 sha1WithRSAEncryption,9 sha1WithRSAEncryption,
8 sha224WithRSAEncryption,10 sha224WithRSAEncryption,
...@@ -130,6 +132,7 @@ pub const Parsed = struct {...@@ -130,6 +132,7 @@ pub const Parsed = struct {
130 message_slice: Slice,132 message_slice: Slice,
131 subject_alt_name_slice: Slice,133 subject_alt_name_slice: Slice,
132 validity: Validity,134 validity: Validity,
135 version: Version,
133136
134 pub const PubKeyAlgo = union(AlgorithmCategory) {137 pub const PubKeyAlgo = union(AlgorithmCategory) {
135 rsaEncryption: void,138 rsaEncryption: void,
...@@ -299,9 +302,12 @@ pub fn parse(cert: Certificate) !Parsed {...@@ -299,9 +302,12 @@ pub fn parse(cert: Certificate) !Parsed {
299 const cert_bytes = cert.buffer;302 const cert_bytes = cert.buffer;
300 const certificate = try der.Element.parse(cert_bytes, cert.index);303 const certificate = try der.Element.parse(cert_bytes, cert.index);
301 const tbs_certificate = try der.Element.parse(cert_bytes, certificate.slice.start);304 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);305 const version_elem = try der.Element.parse(cert_bytes, tbs_certificate.slice.start);
303 try checkVersion(cert_bytes, version);306 const version = try parseVersion(cert_bytes, version_elem);
304 const serial_number = try der.Element.parse(cert_bytes, version.slice.end);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;
305 // RFC 5280, section 4.1.2.3:311 // RFC 5280, section 4.1.2.3:
306 // "This field MUST contain the same algorithm identifier as312 // "This field MUST contain the same algorithm identifier as
307 // the signatureAlgorithm field in the sequence Certificate."313 // the signatureAlgorithm field in the sequence Certificate."
...@@ -370,6 +376,9 @@ pub fn parse(cert: Certificate) !Parsed {...@@ -370,6 +376,9 @@ pub fn parse(cert: Certificate) !Parsed {
370 // Extensions376 // Extensions
371 var subject_alt_name_slice = der.Element.Slice.empty;377 var subject_alt_name_slice = der.Element.Slice.empty;
372 ext: {378 ext: {
379 if (version == .v1)
380 break :ext;
381
373 if (pub_key_info.slice.end >= tbs_certificate.slice.end)382 if (pub_key_info.slice.end >= tbs_certificate.slice.end)
374 break :ext;383 break :ext;
375384
...@@ -415,6 +424,7 @@ pub fn parse(cert: Certificate) !Parsed {...@@ -415,6 +424,7 @@ pub fn parse(cert: Certificate) !Parsed {
415 .not_after = not_after_utc,424 .not_after = not_after_utc,
416 },425 },
417 .subject_alt_name_slice = subject_alt_name_slice,426 .subject_alt_name_slice = subject_alt_name_slice,
427 .version = version,
418 };428 };
419}429}
420430
...@@ -588,12 +598,24 @@ fn parseEnum(comptime E: type, bytes: []const u8, element: der.Element) !E {...@@ -588,12 +598,24 @@ fn parseEnum(comptime E: type, bytes: []const u8, element: der.Element) !E {
588 return E.map.get(oid_bytes) orelse return error.CertificateHasUnrecognizedObjectId;598 return E.map.get(oid_bytes) orelse return error.CertificateHasUnrecognizedObjectId;
589}599}
590600
591pub fn checkVersion(bytes: []const u8, version: der.Element) !void {601pub fn parseVersion(bytes: []const u8, version_elem: der.Element) !Version {
592 if (@bitCast(u8, version.identifier) != 0xa0 or602 if (@bitCast(u8, version_elem.identifier) != 0xa0)
593 !mem.eql(u8, bytes[version.slice.start..version.slice.end], "\x02\x01\x02"))603 return .v1;
594 {604
595 return error.UnsupportedCertificateVersion;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;
596 }616 }
617
618 return error.UnsupportedCertificateVersion;
597}619}
598620
599fn verifyRsa(621fn verifyRsa(
lib/std/crypto/Certificate/Bundle.zig+76-8
...@@ -68,29 +68,93 @@ pub fn rescan(cb: *Bundle, gpa: Allocator) !void {...@@ -68,29 +68,93 @@ pub fn rescan(cb: *Bundle, gpa: Allocator) !void {
68}68}
6969
70pub fn rescanLinux(cb: *Bundle, gpa: Allocator) !void {70pub fn rescanLinux(cb: *Bundle, gpa: Allocator) !void {
71 var dir = fs.openIterableDirAbsolute("/etc/ssl/certs", .{}) catch |err| switch (err) {71 // Possible certificate files; stop after finding one.
72 error.FileNotFound => return,72 const cert_file_paths = [_][]const u8{
73 else => |e| return e,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
74 };86 };
75 defer dir.close();
7687
77 cb.bytes.clearRetainingCapacity();88 cb.bytes.clearRetainingCapacity();
78 cb.map.clearRetainingCapacity();89 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();
81 while (try it.next()) |entry| {136 while (try it.next()) |entry| {
82 switch (entry.kind) {137 switch (entry.kind) {
83 .File, .SymLink => {},138 .File, .SymLink => {},
84 else => continue,139 else => continue,
85 }140 }
86141
87 try addCertsFromFile(cb, gpa, dir.dir, entry.name);142 try addCertsFromFilePath(cb, gpa, iterable_dir.dir, entry.name);
88 }143 }
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);
91}155}
92156
93pub fn addCertsFromFile(157pub fn addCertsFromFilePath(
94 cb: *Bundle,158 cb: *Bundle,
95 gpa: Allocator,159 gpa: Allocator,
96 dir: fs.Dir,160 dir: fs.Dir,
...@@ -98,7 +162,10 @@ pub fn addCertsFromFile(...@@ -98,7 +162,10 @@ pub fn addCertsFromFile(
98) !void {162) !void {
99 var file = try dir.openFile(sub_file_path, .{});163 var file = try dir.openFile(sub_file_path, .{});
100 defer file.close();164 defer file.close();
165 return addCertsFromFile(cb, gpa, file);
166}
101167
168pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file: fs.File) !void {
102 const size = try file.getEndPos();169 const size = try file.getEndPos();
103170
104 // We borrow `bytes` as a temporary buffer for the base64-encoded data.171 // We borrow `bytes` as a temporary buffer for the base64-encoded data.
...@@ -152,6 +219,7 @@ pub fn addCertsFromFile(...@@ -152,6 +219,7 @@ pub fn addCertsFromFile(
152219
153const builtin = @import("builtin");220const builtin = @import("builtin");
154const std = @import("../../std.zig");221const std = @import("../../std.zig");
222const assert = std.debug.assert;
155const fs = std.fs;223const fs = std.fs;
156const mem = std.mem;224const mem = std.mem;
157const crypto = std.crypto;225const crypto = std.crypto;