diff --git a/lib/std/crypto.zig b/lib/std/crypto.zig index 2f7f729302..17c2e4afe9 100644 --- a/lib/std/crypto.zig +++ b/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" { diff --git a/lib/std/crypto/CertificateBundle.zig b/lib/std/crypto/CertificateBundle.zig new file mode 100644 index 0000000000..50f43ba404 --- /dev/null +++ b/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); +} diff --git a/lib/std/crypto/Der.zig b/lib/std/crypto/Der.zig new file mode 100644 index 0000000000..7b183d5c34 --- /dev/null +++ b/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(); diff --git a/lib/std/crypto/tls.zig b/lib/std/crypto/tls.zig index 33d6bbe906..09a22f9a23 100644 --- a/lib/std/crypto/tls.zig +++ b/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; - } -}; diff --git a/lib/std/crypto/tls/Client.zig b/lib/std/crypto/tls/Client.zig index c167d85134..45c96ed290 100644 --- a/lib/std/crypto/tls/Client.zig +++ b/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); + const certificate = try Der.parseElement(handshake, hs_i); + const tbs_certificate = try Der.parseElement(handshake, certificate.start); + + 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")) { - 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), - }); - - 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, - }); + 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, }); } }, diff --git a/lib/std/http/Client.zig b/lib/std/http/Client.zig index fcadf3669b..58686ed2e5 100644 --- a/lib/std/http/Client.zig +++ b/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); }, }