| author | |
| committer | |
| log | d389dba04f66deb3dd1831ed96b6843a9eee1309 |
| tree | 5415ff8e6d75487f8c8c39fe356a1dcfc59ff1e4 |
| parent | 9856bea34e9d7eca6dfc2e883d46f24a53265dc6 |
| parent | e3505c0a5abcacdc17aeb4a12a58e4e12e0a7a59 |
| signature |
support v1/v2 SSL certificates and search more Linux directories for certificates2 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, |
| 3 | 3 | ||
| 4 | pub const Bundle = @import("Certificate/Bundle.zig"); | 4 | pub const Bundle = @import("Certificate/Bundle.zig"); |
| 5 | 5 | ||
| 6 | pub const Version = enum { v1, v2, v3 }; | ||
| 7 | |||
| 6 | pub const Algorithm = enum { | 8 | pub 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, | ||
| 133 | 136 | ||
| 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 as | 312 | // "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 | // Extensions | 376 | // 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; |
| 375 | 384 | ||
| ... | @@ -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 | } |
| 420 | 430 | ||
| ... | @@ -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 | } |
| 590 | 600 | ||
| 591 | pub fn checkVersion(bytes: []const u8, version: der.Element) !void { | 601 | pub fn parseVersion(bytes: []const u8, version_elem: der.Element) !Version { |
| 592 | if (@bitCast(u8, version.identifier) != 0xa0 or | 602 | 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 | } |
| 598 | 620 | ||
| 599 | fn verifyRsa( | 621 | fn 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 | } |
| 69 | 69 | ||
| 70 | pub fn rescanLinux(cb: *Bundle, gpa: Allocator) !void { | 70 | pub 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(); | ||
| 76 | 87 | ||
| 77 | cb.bytes.clearRetainingCapacity(); | 88 | cb.bytes.clearRetainingCapacity(); |
| 78 | cb.map.clearRetainingCapacity(); | 89 | cb.map.clearRetainingCapacity(); |
| 79 | 90 | ||
| 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 | |||
| 112 | pub 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 | |||
| 123 | pub 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 | |||
| 134 | pub 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 | } |
| 86 | 141 | ||
| 87 | try addCertsFromFile(cb, gpa, dir.dir, entry.name); | 142 | try addCertsFromFilePath(cb, gpa, iterable_dir.dir, entry.name); |
| 88 | } | 143 | } |
| 144 | } | ||
| 89 | 145 | ||
| 90 | cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len); | 146 | pub 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 | } |
| 92 | 156 | ||
| 93 | pub fn addCertsFromFile( | 157 | pub 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 | } | ||
| 101 | 167 | ||
| 168 | pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file: fs.File) !void { | ||
| 102 | const size = try file.getEndPos(); | 169 | const size = try file.getEndPos(); |
| 103 | 170 | ||
| 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( |
| 152 | 219 | ||
| 153 | const builtin = @import("builtin"); | 220 | const builtin = @import("builtin"); |
| 154 | const std = @import("../../std.zig"); | 221 | const std = @import("../../std.zig"); |
| 222 | const assert = std.debug.assert; | ||
| 155 | const fs = std.fs; | 223 | const fs = std.fs; |
| 156 | const mem = std.mem; | 224 | const mem = std.mem; |
| 157 | const crypto = std.crypto; | 225 | const crypto = std.crypto; |