From 9a0e1704aee1db4a7ca29706e11dd5ec6739c094 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 15 Jan 2023 14:59:49 -0700 Subject: [PATCH 1/2] std.crypto.Certificate: support v1 closes #14304 --- lib/std/crypto/Certificate.zig | 38 +++++++++++++++++++++++++++------- 1 file changed, 30 insertions(+), 8 deletions(-) diff --git a/lib/std/crypto/Certificate.zig b/lib/std/crypto/Certificate.zig index fe211c6146..db984385bf 100644 --- a/lib/std/crypto/Certificate.zig +++ b/lib/std/crypto/Certificate.zig @@ -3,6 +3,8 @@ index: u32, pub const Bundle = @import("Certificate/Bundle.zig"); +pub const Version = enum { v1, v2, v3 }; + pub const Algorithm = enum { sha1WithRSAEncryption, sha224WithRSAEncryption, @@ -130,6 +132,7 @@ pub const Parsed = struct { message_slice: Slice, subject_alt_name_slice: Slice, validity: Validity, + version: Version, pub const PubKeyAlgo = union(AlgorithmCategory) { rsaEncryption: void, @@ -299,9 +302,12 @@ pub fn parse(cert: Certificate) !Parsed { const cert_bytes = cert.buffer; const certificate = try der.Element.parse(cert_bytes, cert.index); const tbs_certificate = try der.Element.parse(cert_bytes, certificate.slice.start); - const version = try der.Element.parse(cert_bytes, tbs_certificate.slice.start); - try checkVersion(cert_bytes, version); - const serial_number = try der.Element.parse(cert_bytes, version.slice.end); + const version_elem = try der.Element.parse(cert_bytes, tbs_certificate.slice.start); + const version = try parseVersion(cert_bytes, version_elem); + const serial_number = if (@bitCast(u8, version_elem.identifier) == 0xa0) + try der.Element.parse(cert_bytes, version_elem.slice.end) + else + version_elem; // RFC 5280, section 4.1.2.3: // "This field MUST contain the same algorithm identifier as // the signatureAlgorithm field in the sequence Certificate." @@ -370,6 +376,9 @@ pub fn parse(cert: Certificate) !Parsed { // Extensions var subject_alt_name_slice = der.Element.Slice.empty; ext: { + if (version == .v1) + break :ext; + if (pub_key_info.slice.end >= tbs_certificate.slice.end) break :ext; @@ -415,6 +424,7 @@ pub fn parse(cert: Certificate) !Parsed { .not_after = not_after_utc, }, .subject_alt_name_slice = subject_alt_name_slice, + .version = version, }; } @@ -588,12 +598,24 @@ fn parseEnum(comptime E: type, bytes: []const u8, element: der.Element) !E { return E.map.get(oid_bytes) orelse return error.CertificateHasUnrecognizedObjectId; } -pub fn checkVersion(bytes: []const u8, version: der.Element) !void { - if (@bitCast(u8, version.identifier) != 0xa0 or - !mem.eql(u8, bytes[version.slice.start..version.slice.end], "\x02\x01\x02")) - { - return error.UnsupportedCertificateVersion; +pub fn parseVersion(bytes: []const u8, version_elem: der.Element) !Version { + if (@bitCast(u8, version_elem.identifier) != 0xa0) + return .v1; + + if (version_elem.slice.end - version_elem.slice.start != 3) + return error.CertificateFieldHasInvalidLength; + + const encoded_version = bytes[version_elem.slice.start..version_elem.slice.end]; + + if (mem.eql(u8, encoded_version, "\x02\x01\x02")) { + return .v3; + } else if (mem.eql(u8, encoded_version, "\x02\x01\x01")) { + return .v2; + } else if (mem.eql(u8, encoded_version, "\x02\x01\x00")) { + return .v1; } + + return error.UnsupportedCertificateVersion; } fn verifyRsa( From e3505c0a5abcacdc17aeb4a12a58e4e12e0a7a59 Mon Sep 17 00:00:00 2001 From: Andrew Kelley Date: Sun, 15 Jan 2023 15:01:42 -0700 Subject: [PATCH 2/2] std.crypto.Certificate.Bundle: add more Linux directories Thanks to the Go project for finding all these paths. --- lib/std/crypto/Certificate/Bundle.zig | 86 ++++++++++++++++++++++++--- 1 file changed, 77 insertions(+), 9 deletions(-) diff --git a/lib/std/crypto/Certificate/Bundle.zig b/lib/std/crypto/Certificate/Bundle.zig index a1684fda73..16b0329a2d 100644 --- a/lib/std/crypto/Certificate/Bundle.zig +++ b/lib/std/crypto/Certificate/Bundle.zig @@ -68,29 +68,93 @@ pub fn rescan(cb: *Bundle, gpa: Allocator) !void { } pub fn rescanLinux(cb: *Bundle, gpa: Allocator) !void { - var dir = fs.openIterableDirAbsolute("/etc/ssl/certs", .{}) catch |err| switch (err) { - error.FileNotFound => return, - else => |e| return e, + // Possible certificate files; stop after finding one. + const cert_file_paths = [_][]const u8{ + "/etc/ssl/certs/ca-certificates.crt", // Debian/Ubuntu/Gentoo etc. + "/etc/pki/tls/certs/ca-bundle.crt", // Fedora/RHEL 6 + "/etc/ssl/ca-bundle.pem", // OpenSUSE + "/etc/pki/tls/cacert.pem", // OpenELEC + "/etc/pki/ca-trust/extracted/pem/tls-ca-bundle.pem", // CentOS/RHEL 7 + "/etc/ssl/cert.pem", // Alpine Linux + }; + + // Possible directories with certificate files; all will be read. + const cert_dir_paths = [_][]const u8{ + "/etc/ssl/certs", // SLES10/SLES11 + "/etc/pki/tls/certs", // Fedora/RHEL + "/system/etc/security/cacerts", // Android }; - defer dir.close(); cb.bytes.clearRetainingCapacity(); cb.map.clearRetainingCapacity(); - var it = dir.iterate(); + scan: { + for (cert_file_paths) |cert_file_path| { + if (addCertsFromFilePathAbsolute(cb, gpa, cert_file_path)) |_| { + break :scan; + } else |err| switch (err) { + error.FileNotFound => continue, + else => |e| return e, + } + } + + for (cert_dir_paths) |cert_dir_path| { + addCertsFromDirPathAbsolute(cb, gpa, cert_dir_path) catch |err| switch (err) { + error.FileNotFound => continue, + else => |e| return e, + }; + } + } + + cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len); +} + +pub fn addCertsFromDirPath( + cb: *Bundle, + gpa: Allocator, + dir: fs.Dir, + sub_dir_path: []const u8, +) !void { + var iterable_dir = try dir.openIterableDir(sub_dir_path, .{}); + defer iterable_dir.close(); + return addCertsFromDir(cb, gpa, iterable_dir); +} + +pub fn addCertsFromDirPathAbsolute( + cb: *Bundle, + gpa: Allocator, + abs_dir_path: []const u8, +) !void { + assert(fs.path.isAbsolute(abs_dir_path)); + var iterable_dir = try fs.openIterableDirAbsolute(abs_dir_path, .{}); + defer iterable_dir.close(); + return addCertsFromDir(cb, gpa, iterable_dir); +} + +pub fn addCertsFromDir(cb: *Bundle, gpa: Allocator, iterable_dir: fs.IterableDir) !void { + var it = iterable_dir.iterate(); while (try it.next()) |entry| { switch (entry.kind) { .File, .SymLink => {}, else => continue, } - try addCertsFromFile(cb, gpa, dir.dir, entry.name); + try addCertsFromFilePath(cb, gpa, iterable_dir.dir, entry.name); } - - cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len); } -pub fn addCertsFromFile( +pub fn addCertsFromFilePathAbsolute( + cb: *Bundle, + gpa: Allocator, + abs_file_path: []const u8, +) !void { + assert(fs.path.isAbsolute(abs_file_path)); + var file = try fs.openFileAbsolute(abs_file_path, .{}); + defer file.close(); + return addCertsFromFile(cb, gpa, file); +} + +pub fn addCertsFromFilePath( cb: *Bundle, gpa: Allocator, dir: fs.Dir, @@ -98,7 +162,10 @@ pub fn addCertsFromFile( ) !void { var file = try dir.openFile(sub_file_path, .{}); defer file.close(); + return addCertsFromFile(cb, gpa, file); +} +pub fn addCertsFromFile(cb: *Bundle, gpa: Allocator, file: fs.File) !void { const size = try file.getEndPos(); // We borrow `bytes` as a temporary buffer for the base64-encoded data. @@ -152,6 +219,7 @@ pub fn addCertsFromFile( const builtin = @import("builtin"); const std = @import("../../std.zig"); +const assert = std.debug.assert; const fs = std.fs; const mem = std.mem; const crypto = std.crypto;