Commit bbc074252c

Andrew Kelley <andrew@ziglang.org>
2022-12-20 05:22:51
introduce std.crypto.CertificateBundle
for reading root certificate authority bundles from standard installation locations on the file system. So far only Linux logic is added.
1 parent 3237000
lib/std/crypto/tls/Client.zig
@@ -1,5 +1,6 @@
 const std = @import("../../std.zig");
 const tls = std.crypto.tls;
+const Der = std.crypto.Der;
 const Client = @This();
 const net = std.net;
 const mem = std.mem;
@@ -28,7 +29,7 @@ partially_read_len: u15,
 eof: bool,
 
 /// `host` is only borrowed during this function call.
-pub fn init(stream: net.Stream, host: []const u8) !Client {
+pub fn init(stream: net.Stream, ca_bundle: crypto.CertificateBundle, host: []const u8) !Client {
     const host_len = @intCast(u16, host.len);
 
     var random_buffer: [128]u8 = undefined;
@@ -392,7 +393,7 @@ pub fn init(stream: net.Stream, host: []const u8) !Client {
                                     switch (cipher_params) {
                                         inline else => |*p| p.transcript_hash.update(wrapped_handshake),
                                     }
-                                    var hs_i: usize = 0;
+                                    var hs_i: u32 = 0;
                                     const cert_req_ctx_len = handshake[hs_i];
                                     hs_i += 1;
                                     if (cert_req_ctx_len != 0) return error.TlsIllegalParameter;
@@ -404,75 +405,47 @@ pub fn init(stream: net.Stream, host: []const u8) !Client {
                                         hs_i += 3;
                                         const end_cert = hs_i + cert_size;
 
-                                        const certificate = try tls.Der.parseElement(handshake, &hs_i);
-                                        {
-                                            var cert_i: usize = 0;
-                                            const tbs_certificate = try tls.Der.parseElement(certificate.contents, &cert_i);
-                                            {
-                                                var tbs_i: usize = 0;
-                                                const version = try tls.Der.parseElement(tbs_certificate.contents, &tbs_i);
-                                                const serial_number = try tls.Der.parseElement(tbs_certificate.contents, &tbs_i);
-                                                const signature = try tls.Der.parseElement(tbs_certificate.contents, &tbs_i);
-                                                const issuer = try tls.Der.parseElement(tbs_certificate.contents, &tbs_i);
-                                                const validity = try tls.Der.parseElement(tbs_certificate.contents, &tbs_i);
-                                                const subject = try tls.Der.parseElement(tbs_certificate.contents, &tbs_i);
-                                                const subject_pub_key = try tls.Der.parseElement(tbs_certificate.contents, &tbs_i);
-                                                const extensions = try tls.Der.parseElement(tbs_certificate.contents, &tbs_i);
-
-                                                // RFC 5280, section 4.1.2.3:
-                                                // "This field MUST contain the same algorithm identifier as
-                                                // the signatureAlgorithm field in the sequence Certificate."
-                                                _ = signature;
-
-                                                _ = issuer;
-                                                _ = validity;
-
-                                                std.debug.print("version: {any} '{}'\n", .{
-                                                    version.identifier, std.fmt.fmtSliceHexLower(version.contents),
-                                                });
-
-                                                std.debug.print("serial_number: {any} {}\n", .{
-                                                    serial_number.identifier,
-                                                    std.fmt.fmtSliceHexLower(serial_number.contents),
-                                                });
+                                        const certificate = try Der.parseElement(handshake, hs_i);
+                                        const tbs_certificate = try Der.parseElement(handshake, certificate.start);
 
-                                                std.debug.print("subject: {any} {}\n", .{
-                                                    subject.identifier,
-                                                    std.fmt.fmtSliceHexLower(subject.contents),
-                                                });
-
-                                                std.debug.print("subject pub key: {any} {}\n", .{
-                                                    subject_pub_key.identifier,
-                                                    std.fmt.fmtSliceHexLower(subject_pub_key.contents),
-                                                });
-
-                                                std.debug.print("extensions: {any} {}\n", .{
-                                                    extensions.identifier,
-                                                    std.fmt.fmtSliceHexLower(extensions.contents),
-                                                });
-                                            }
-                                            const signature_algorithm = try tls.Der.parseElement(certificate.contents, &cert_i);
-                                            const signature_value = try tls.Der.parseElement(certificate.contents, &cert_i);
-
-                                            {
-                                                var sa_i: usize = 0;
-                                                const algorithm = try tls.Der.parseObjectId(signature_algorithm.contents, &sa_i);
-                                                std.debug.print("cert has this signature algorithm: {any}\n", .{algorithm});
-                                                //const parameters = try tls.Der.parseElement(signature_algorithm.contents, &sa_i);
-                                            }
-
-                                            std.debug.print("signature_value: {any} {d} bytes\n", .{
-                                                signature_value.identifier, signature_value.contents.len,
-                                            });
+                                        const version = try Der.parseElement(handshake, tbs_certificate.start);
+                                        if (@bitCast(u8, version.identifier) != 0xa0 or
+                                            !mem.eql(u8, handshake[version.start..version.end], "\x02\x01\x02"))
+                                        {
+                                            return error.UnsupportedCertificateVersion;
                                         }
 
+                                        const serial_number = try Der.parseElement(handshake, version.end);
+                                        // RFC 5280, section 4.1.2.3:
+                                        // "This field MUST contain the same algorithm identifier as
+                                        // the signatureAlgorithm field in the sequence Certificate."
+                                        const signature = try Der.parseElement(handshake, serial_number.end);
+                                        const issuer = try Der.parseElement(handshake, signature.end);
+                                        const validity = try Der.parseElement(handshake, issuer.end);
+                                        const subject = try Der.parseElement(handshake, validity.end);
+                                        const subject_pub_key = try Der.parseElement(handshake, subject.end);
+                                        const extensions = try Der.parseElement(handshake, subject_pub_key.end);
+                                        _ = extensions;
+
+                                        const signature_algorithm = try Der.parseElement(handshake, tbs_certificate.end);
+                                        const signature_value = try Der.parseElement(handshake, signature_algorithm.end);
+                                        _ = signature_value;
+
+                                        const algorithm_elem = try Der.parseElement(handshake, signature_algorithm.start);
+                                        const algorithm = try Der.parseObjectId(handshake, algorithm_elem);
+                                        std.debug.print("cert has this signature algorithm: {any}\n", .{algorithm});
+                                        //const parameters = try Der.parseElement(signature_algorithm.contents, &sa_i);
+
                                         hs_i = end_cert;
                                         const total_ext_size = mem.readIntBig(u16, handshake[hs_i..][0..2]);
                                         hs_i += 2;
                                         hs_i += total_ext_size;
 
-                                        std.debug.print("received certificate of size {d} bytes with {d} bytes of extensions\n", .{
-                                            cert_size, total_ext_size,
+                                        const issuer_bytes = handshake[issuer.start..issuer.end];
+                                        const ca_cert = ca_bundle.find(issuer_bytes);
+
+                                        std.debug.print("received certificate of size {d} bytes with {d} bytes of extensions. ca_found={any}\n", .{
+                                            cert_size, total_ext_size, ca_cert != null,
                                         });
                                     }
                                 },
lib/std/crypto/CertificateBundle.zig
@@ -0,0 +1,173 @@
+//! A set of certificates. Typically pre-installed on every operating system,
+//! these are "Certificate Authorities" used to validate SSL certificates.
+//! This data structure stores certificates in DER-encoded form, all of them
+//! concatenated together in the `bytes` array. The `map` field contains an
+//! index from the DER-encoded subject name to the index within `bytes`.
+
+map: std.HashMapUnmanaged(Key, u32, MapContext, std.hash_map.default_max_load_percentage) = .{},
+bytes: std.ArrayListUnmanaged(u8) = .{},
+
+pub const Key = struct {
+    subject_start: u32,
+    subject_end: u32,
+};
+
+/// The returned bytes become invalid after calling any of the rescan functions
+/// or add functions.
+pub fn find(cb: CertificateBundle, subject_name: []const u8) ?[]const u8 {
+    const Adapter = struct {
+        cb: CertificateBundle,
+
+        pub fn hash(ctx: @This(), k: []const u8) u64 {
+            _ = ctx;
+            return std.hash_map.hashString(k);
+        }
+
+        pub fn eql(ctx: @This(), a: []const u8, b_key: Key) bool {
+            const b = ctx.cb.bytes.items[b_key.subject_start..b_key.subject_end];
+            return mem.eql(u8, a, b);
+        }
+    };
+    const index = cb.map.getAdapted(subject_name, Adapter{ .cb = cb }) orelse return null;
+    return cb.bytes.items[index..];
+}
+
+pub fn deinit(cb: *CertificateBundle, gpa: Allocator) void {
+    cb.map.deinit(gpa);
+    cb.bytes.deinit(gpa);
+    cb.* = undefined;
+}
+
+/// Empties the set of certificates and then scans the host operating system
+/// file system standard locations for certificates.
+pub fn rescan(cb: *CertificateBundle, gpa: Allocator) !void {
+    switch (builtin.os.tag) {
+        .linux => return rescanLinux(cb, gpa),
+        else => @compileError("it is unknown where the root CA certificates live on this OS"),
+    }
+}
+
+pub fn rescanLinux(cb: *CertificateBundle, gpa: Allocator) !void {
+    var dir = fs.openIterableDirAbsolute("/etc/ssl/certs", .{}) catch |err| switch (err) {
+        error.FileNotFound => return,
+        else => |e| return e,
+    };
+    defer dir.close();
+
+    cb.bytes.clearRetainingCapacity();
+    cb.map.clearRetainingCapacity();
+
+    var it = dir.iterate();
+    while (try it.next()) |entry| {
+        switch (entry.kind) {
+            .File, .SymLink => {},
+            else => continue,
+        }
+
+        try addCertsFromFile(cb, gpa, dir.dir, entry.name);
+    }
+
+    cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len);
+}
+
+pub fn addCertsFromFile(
+    cb: *CertificateBundle,
+    gpa: Allocator,
+    dir: fs.Dir,
+    sub_file_path: []const u8,
+) !void {
+    var file = try dir.openFile(sub_file_path, .{});
+    defer file.close();
+
+    const size = try file.getEndPos();
+
+    // We borrow `bytes` as a temporary buffer for the base64-encoded data.
+    // This is possible by computing the decoded length and reserving the space
+    // for the decoded bytes first.
+    const decoded_size_upper_bound = size / 4 * 3;
+    try cb.bytes.ensureUnusedCapacity(gpa, decoded_size_upper_bound + size);
+    const end_reserved = cb.bytes.items.len + decoded_size_upper_bound;
+    const buffer = cb.bytes.allocatedSlice()[end_reserved..];
+    const end_index = try file.readAll(buffer);
+    const encoded_bytes = buffer[0..end_index];
+
+    const begin_marker = "-----BEGIN CERTIFICATE-----";
+    const end_marker = "-----END CERTIFICATE-----";
+
+    var start_index: usize = 0;
+    while (mem.indexOfPos(u8, encoded_bytes, start_index, begin_marker)) |begin_marker_start| {
+        const cert_start = begin_marker_start + begin_marker.len;
+        const cert_end = mem.indexOfPos(u8, encoded_bytes, cert_start, end_marker) orelse
+            return error.MissingEndCertificateMarker;
+        start_index = cert_end + end_marker.len;
+        const encoded_cert = mem.trim(u8, encoded_bytes[cert_start..cert_end], " \t\r\n");
+        const decoded_start = @intCast(u32, cb.bytes.items.len);
+        const dest_buf = cb.bytes.allocatedSlice()[decoded_start..];
+        cb.bytes.items.len += try base64.decode(dest_buf, encoded_cert);
+        const k = try key(cb, decoded_start);
+        try cb.map.putContext(gpa, k, decoded_start, .{ .cb = cb });
+    }
+}
+
+pub fn key(cb: *CertificateBundle, bytes_index: u32) !Key {
+    const bytes = cb.bytes.items;
+    const certificate = try Der.parseElement(bytes, bytes_index);
+    const tbs_certificate = try Der.parseElement(bytes, certificate.start);
+    const version = try Der.parseElement(bytes, tbs_certificate.start);
+    if (@bitCast(u8, version.identifier) != 0xa0 or
+        !mem.eql(u8, bytes[version.start..version.end], "\x02\x01\x02"))
+    {
+        return error.UnsupportedCertificateVersion;
+    }
+
+    const serial_number = try Der.parseElement(bytes, version.end);
+
+    // RFC 5280, section 4.1.2.3:
+    // "This field MUST contain the same algorithm identifier as
+    // the signatureAlgorithm field in the sequence Certificate."
+    const signature = try Der.parseElement(bytes, serial_number.end);
+    const issuer = try Der.parseElement(bytes, signature.end);
+    const validity = try Der.parseElement(bytes, issuer.end);
+    const subject = try Der.parseElement(bytes, validity.end);
+    //const subject_pub_key = try Der.parseElement(bytes, subject.end);
+    //const extensions = try Der.parseElement(bytes, subject_pub_key.end);
+
+    return .{
+        .subject_start = subject.start,
+        .subject_end = subject.end,
+    };
+}
+
+const builtin = @import("builtin");
+const std = @import("../std.zig");
+const fs = std.fs;
+const mem = std.mem;
+const Allocator = std.mem.Allocator;
+const Der = std.crypto.Der;
+const CertificateBundle = @This();
+
+const base64 = std.base64.standard.decoderWithIgnore(" \t\r\n");
+
+const MapContext = struct {
+    cb: *const CertificateBundle,
+
+    pub fn hash(ctx: MapContext, k: Key) u64 {
+        return std.hash_map.hashString(ctx.cb.bytes.items[k.subject_start..k.subject_end]);
+    }
+
+    pub fn eql(ctx: MapContext, a: Key, b: Key) bool {
+        const bytes = ctx.cb.bytes.items;
+        return mem.eql(
+            u8,
+            bytes[a.subject_start..a.subject_end],
+            bytes[b.subject_start..b.subject_end],
+        );
+    }
+};
+
+test {
+    var bundle: CertificateBundle = .{};
+    defer bundle.deinit(std.testing.allocator);
+
+    try bundle.rescan(std.testing.allocator);
+}
lib/std/crypto/Der.zig
@@ -0,0 +1,153 @@
+pub const Class = enum(u2) {
+    universal,
+    application,
+    context_specific,
+    private,
+};
+
+pub const PC = enum(u1) {
+    primitive,
+    constructed,
+};
+
+pub const Identifier = packed struct(u8) {
+    tag: Tag,
+    pc: PC,
+    class: Class,
+};
+
+pub const Tag = enum(u5) {
+    boolean = 1,
+    integer = 2,
+    bitstring = 3,
+    null = 5,
+    object_identifier = 6,
+    sequence = 16,
+    sequence_of = 17,
+    _,
+};
+
+pub const Oid = enum {
+    rsadsi,
+    pkcs,
+    rsaEncryption,
+    md2WithRSAEncryption,
+    md5WithRSAEncryption,
+    sha1WithRSAEncryption,
+    sha256WithRSAEncryption,
+    sha384WithRSAEncryption,
+    sha512WithRSAEncryption,
+    sha224WithRSAEncryption,
+    pbeWithMD2AndDES_CBC,
+    pbeWithMD5AndDES_CBC,
+    pkcs9_emailAddress,
+    md2,
+    md5,
+    rc4,
+    ecdsa_with_Recommended,
+    ecdsa_with_Specified,
+    ecdsa_with_SHA224,
+    ecdsa_with_SHA256,
+    ecdsa_with_SHA384,
+    ecdsa_with_SHA512,
+    X500,
+    X509,
+    commonName,
+    serialNumber,
+    countryName,
+    localityName,
+    stateOrProvinceName,
+    organizationName,
+    organizationalUnitName,
+    organizationIdentifier,
+
+    pub const map = std.ComptimeStringMap(Oid, .{
+        .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D }, .rsadsi },
+        .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01 }, .pkcs },
+        .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }, .rsaEncryption },
+        .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x02 }, .md2WithRSAEncryption },
+        .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x04 }, .md5WithRSAEncryption },
+        .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 }, .sha1WithRSAEncryption },
+        .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B }, .sha256WithRSAEncryption },
+        .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C }, .sha384WithRSAEncryption },
+        .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0D }, .sha512WithRSAEncryption },
+        .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0E }, .sha224WithRSAEncryption },
+        .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x05, 0x01 }, .pbeWithMD2AndDES_CBC },
+        .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x05, 0x03 }, .pbeWithMD5AndDES_CBC },
+        .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x01 }, .pkcs9_emailAddress },
+        .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x02 }, .md2 },
+        .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05 }, .md5 },
+        .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x03, 0x04 }, .rc4 },
+        .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x02 }, .ecdsa_with_Recommended },
+        .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03 }, .ecdsa_with_Specified },
+        .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x01 }, .ecdsa_with_SHA224 },
+        .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02 }, .ecdsa_with_SHA256 },
+        .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x03 }, .ecdsa_with_SHA384 },
+        .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x04 }, .ecdsa_with_SHA512 },
+        .{ &[_]u8{0x55}, .X500 },
+        .{ &[_]u8{ 0x55, 0x04 }, .X509 },
+        .{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName },
+        .{ &[_]u8{ 0x55, 0x04, 0x05 }, .serialNumber },
+        .{ &[_]u8{ 0x55, 0x04, 0x06 }, .countryName },
+        .{ &[_]u8{ 0x55, 0x04, 0x07 }, .localityName },
+        .{ &[_]u8{ 0x55, 0x04, 0x08 }, .stateOrProvinceName },
+        .{ &[_]u8{ 0x55, 0x04, 0x0A }, .organizationName },
+        .{ &[_]u8{ 0x55, 0x04, 0x0B }, .organizationalUnitName },
+        .{ &[_]u8{ 0x55, 0x04, 0x61 }, .organizationIdentifier },
+    });
+};
+
+pub const Element = struct {
+    identifier: Identifier,
+    start: u32,
+    end: u32,
+};
+
+pub const ParseElementError = error{CertificateHasFieldWithInvalidLength};
+
+pub fn parseElement(bytes: []const u8, index: u32) ParseElementError!Element {
+    var i = index;
+    const identifier = @bitCast(Identifier, bytes[i]);
+    i += 1;
+    const size_byte = bytes[i];
+    i += 1;
+    if ((size_byte >> 7) == 0) {
+        return .{
+            .identifier = identifier,
+            .start = i,
+            .end = i + size_byte,
+        };
+    }
+
+    const len_size = @truncate(u7, size_byte);
+    if (len_size > @sizeOf(u32)) {
+        return error.CertificateHasFieldWithInvalidLength;
+    }
+
+    const end_i = i + len_size;
+    var long_form_size: u32 = 0;
+    while (i < end_i) : (i += 1) {
+        long_form_size = (long_form_size << 8) | bytes[i];
+    }
+
+    return .{
+        .identifier = identifier,
+        .start = i,
+        .end = i + long_form_size,
+    };
+}
+
+pub const ParseObjectIdError = error{
+    CertificateHasUnrecognizedObjectId,
+    CertificateFieldHasWrongDataType,
+} || ParseElementError;
+
+pub fn parseObjectId(bytes: []const u8, element: Element) ParseObjectIdError!Oid {
+    if (element.identifier.tag != .object_identifier)
+        return error.CertificateFieldHasWrongDataType;
+    return Oid.map.get(bytes[element.start..element.end]) orelse
+        return error.CertificateHasUnrecognizedObjectId;
+}
+
+const std = @import("../std.zig");
+const Der = @This();
lib/std/crypto/tls.zig
@@ -349,112 +349,3 @@ pub inline fn int3(x: u24) [3]u8 {
         @truncate(u8, x),
     };
 }
-
-pub const Der = struct {
-    pub const Class = enum(u2) {
-        universal,
-        application,
-        context_specific,
-        private,
-    };
-
-    pub const PC = enum(u1) {
-        primitive,
-        constructed,
-    };
-
-    pub const Identifier = packed struct(u8) {
-        tag: Tag,
-        pc: PC,
-        class: Class,
-    };
-
-    pub const Tag = enum(u5) {
-        boolean = 1,
-        integer = 2,
-        bitstring = 3,
-        null = 5,
-        object_identifier = 6,
-        sequence = 16,
-        _,
-    };
-
-    pub const Oid = enum {
-        commonName,
-        countryName,
-        localityName,
-        stateOrProvinceName,
-        organizationName,
-        organizationalUnitName,
-        sha256WithRSAEncryption,
-        sha384WithRSAEncryption,
-        sha512WithRSAEncryption,
-        sha224WithRSAEncryption,
-
-        pub const map = std.ComptimeStringMap(Oid, .{
-            .{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName },
-            .{ &[_]u8{ 0x55, 0x04, 0x06 }, .countryName },
-            .{ &[_]u8{ 0x55, 0x04, 0x07 }, .localityName },
-            .{ &[_]u8{ 0x55, 0x04, 0x08 }, .stateOrProvinceName },
-            .{ &[_]u8{ 0x55, 0x04, 0x0A }, .organizationName },
-            .{ &[_]u8{ 0x55, 0x04, 0x0B }, .organizationalUnitName },
-            .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B }, .sha256WithRSAEncryption },
-            .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C }, .sha384WithRSAEncryption },
-            .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0D }, .sha512WithRSAEncryption },
-            .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0E }, .sha224WithRSAEncryption },
-        });
-    };
-
-    pub const Element = struct {
-        identifier: Identifier,
-        contents: []const u8,
-    };
-
-    pub const ParseElementError = error{CertificateHasFieldWithInvalidLength};
-
-    pub fn parseElement(bytes: []const u8, index: *usize) ParseElementError!Der.Element {
-        var i = index.*;
-        const identifier = @bitCast(Identifier, bytes[i]);
-        i += 1;
-        const size_byte = bytes[i];
-        i += 1;
-        if ((size_byte >> 7) == 0) {
-            const contents = bytes[i..][0..size_byte];
-            index.* = i + contents.len;
-            return .{
-                .identifier = identifier,
-                .contents = contents,
-            };
-        }
-
-        const len_size = @truncate(u7, size_byte);
-        if (len_size > @sizeOf(usize)) {
-            return error.CertificateHasFieldWithInvalidLength;
-        }
-
-        const end = i + len_size;
-        var long_form_size: usize = 0;
-        while (i < end) : (i += 1) {
-            long_form_size = (long_form_size << 8) | bytes[i];
-        }
-
-        const contents = bytes[i..][0..long_form_size];
-        index.* = i + contents.len;
-
-        return .{
-            .identifier = identifier,
-            .contents = contents,
-        };
-    }
-
-    pub const ParseObjectIdError = error{
-        CertificateHasUnrecognizedObjectId,
-        CertificateFieldHasWrongDataType,
-    } || ParseElementError;
-
-    pub fn parseObjectId(bytes: []const u8, index: *usize) ParseObjectIdError!Oid {
-        const oid_element = try parseElement(bytes, index);
-        if (oid_element.identifier.tag != .object_identifier) return error.CertificateFieldHasWrongDataType;
-        return Oid.map.get(oid_element.contents) orelse return error.CertificateHasUnrecognizedObjectId;
-    }
-};
lib/std/http/Client.zig
@@ -7,6 +7,7 @@ const Client = @This();
 allocator: std.mem.Allocator,
 headers: std.ArrayListUnmanaged(u8) = .{},
 active_requests: usize = 0,
+ca_bundle: std.crypto.CertificateBundle = .{},
 
 pub const Request = struct {
     client: *Client,
@@ -102,7 +103,7 @@ pub fn request(client: *Client, options: Request.Options) !Request {
     switch (options.protocol) {
         .http => {},
         .https => {
-            req.tls_client = try std.crypto.tls.Client.init(req.stream, options.host);
+            req.tls_client = try std.crypto.tls.Client.init(req.stream, client.ca_bundle, options.host);
         },
     }
 
lib/std/crypto.zig
@@ -177,6 +177,8 @@ const std = @import("std.zig");
 pub const errors = @import("crypto/errors.zig");
 
 pub const tls = @import("crypto/tls.zig");
+pub const Der = @import("crypto/Der.zig");
+pub const CertificateBundle = @import("crypto/CertificateBundle.zig");
 
 test {
     _ = aead.aegis.Aegis128L;
@@ -266,6 +268,9 @@ test {
     _ = utils;
     _ = random;
     _ = errors;
+    _ = tls;
+    _ = Der;
+    _ = CertificateBundle;
 }
 
 test "CSPRNG" {