| author | |
| committer | |
| log | bbc074252cde0f45576b3910bec5a0f9e867c7f2 |
| tree | a5d047c3ad4d2e553867f09db6702e793c0a51eb |
| parent | 3237000d957617120e32e54498cac9afa23cbcd4 |
for reading root certificate authority bundles from standard
installation locations on the file system. So far only Linux logic is
added.6 files changed, 369 insertions(+), 173 deletions(-)
lib/std/crypto.zig+5| ... | @@ -177,6 +177,8 @@ const std = @import("std.zig"); | ... | @@ -177,6 +177,8 @@ const std = @import("std.zig"); |
| 177 | pub const errors = @import("crypto/errors.zig"); | 177 | pub const errors = @import("crypto/errors.zig"); |
| 178 | 178 | ||
| 179 | pub const tls = @import("crypto/tls.zig"); | 179 | pub const tls = @import("crypto/tls.zig"); |
| 180 | pub const Der = @import("crypto/Der.zig"); | ||
| 181 | pub const CertificateBundle = @import("crypto/CertificateBundle.zig"); | ||
| 180 | 182 | ||
| 181 | test { | 183 | test { |
| 182 | _ = aead.aegis.Aegis128L; | 184 | _ = aead.aegis.Aegis128L; |
| ... | @@ -266,6 +268,9 @@ test { | ... | @@ -266,6 +268,9 @@ test { |
| 266 | _ = utils; | 268 | _ = utils; |
| 267 | _ = random; | 269 | _ = random; |
| 268 | _ = errors; | 270 | _ = errors; |
| 271 | _ = tls; | ||
| 272 | _ = Der; | ||
| 273 | _ = CertificateBundle; | ||
| 269 | } | 274 | } |
| 270 | 275 | ||
| 271 | test "CSPRNG" { | 276 | test "CSPRNG" { |
lib/std/crypto/CertificateBundle.zig created+173| ... | @@ -0,0 +1,173 @@ | ||
| 1 | //! A set of certificates. Typically pre-installed on every operating system, | ||
| 2 | //! these are "Certificate Authorities" used to validate SSL certificates. | ||
| 3 | //! This data structure stores certificates in DER-encoded form, all of them | ||
| 4 | //! concatenated together in the `bytes` array. The `map` field contains an | ||
| 5 | //! index from the DER-encoded subject name to the index within `bytes`. | ||
| 6 | |||
| 7 | map: std.HashMapUnmanaged(Key, u32, MapContext, std.hash_map.default_max_load_percentage) = .{}, | ||
| 8 | bytes: std.ArrayListUnmanaged(u8) = .{}, | ||
| 9 | |||
| 10 | pub const Key = struct { | ||
| 11 | subject_start: u32, | ||
| 12 | subject_end: u32, | ||
| 13 | }; | ||
| 14 | |||
| 15 | /// The returned bytes become invalid after calling any of the rescan functions | ||
| 16 | /// or add functions. | ||
| 17 | pub fn find(cb: CertificateBundle, subject_name: []const u8) ?[]const u8 { | ||
| 18 | const Adapter = struct { | ||
| 19 | cb: CertificateBundle, | ||
| 20 | |||
| 21 | pub fn hash(ctx: @This(), k: []const u8) u64 { | ||
| 22 | _ = ctx; | ||
| 23 | return std.hash_map.hashString(k); | ||
| 24 | } | ||
| 25 | |||
| 26 | pub fn eql(ctx: @This(), a: []const u8, b_key: Key) bool { | ||
| 27 | const b = ctx.cb.bytes.items[b_key.subject_start..b_key.subject_end]; | ||
| 28 | return mem.eql(u8, a, b); | ||
| 29 | } | ||
| 30 | }; | ||
| 31 | const index = cb.map.getAdapted(subject_name, Adapter{ .cb = cb }) orelse return null; | ||
| 32 | return cb.bytes.items[index..]; | ||
| 33 | } | ||
| 34 | |||
| 35 | pub fn deinit(cb: *CertificateBundle, gpa: Allocator) void { | ||
| 36 | cb.map.deinit(gpa); | ||
| 37 | cb.bytes.deinit(gpa); | ||
| 38 | cb.* = undefined; | ||
| 39 | } | ||
| 40 | |||
| 41 | /// Empties the set of certificates and then scans the host operating system | ||
| 42 | /// file system standard locations for certificates. | ||
| 43 | pub fn rescan(cb: *CertificateBundle, gpa: Allocator) !void { | ||
| 44 | switch (builtin.os.tag) { | ||
| 45 | .linux => return rescanLinux(cb, gpa), | ||
| 46 | else => @compileError("it is unknown where the root CA certificates live on this OS"), | ||
| 47 | } | ||
| 48 | } | ||
| 49 | |||
| 50 | pub fn rescanLinux(cb: *CertificateBundle, gpa: Allocator) !void { | ||
| 51 | var dir = fs.openIterableDirAbsolute("/etc/ssl/certs", .{}) catch |err| switch (err) { | ||
| 52 | error.FileNotFound => return, | ||
| 53 | else => |e| return e, | ||
| 54 | }; | ||
| 55 | defer dir.close(); | ||
| 56 | |||
| 57 | cb.bytes.clearRetainingCapacity(); | ||
| 58 | cb.map.clearRetainingCapacity(); | ||
| 59 | |||
| 60 | var it = dir.iterate(); | ||
| 61 | while (try it.next()) |entry| { | ||
| 62 | switch (entry.kind) { | ||
| 63 | .File, .SymLink => {}, | ||
| 64 | else => continue, | ||
| 65 | } | ||
| 66 | |||
| 67 | try addCertsFromFile(cb, gpa, dir.dir, entry.name); | ||
| 68 | } | ||
| 69 | |||
| 70 | cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len); | ||
| 71 | } | ||
| 72 | |||
| 73 | pub fn addCertsFromFile( | ||
| 74 | cb: *CertificateBundle, | ||
| 75 | gpa: Allocator, | ||
| 76 | dir: fs.Dir, | ||
| 77 | sub_file_path: []const u8, | ||
| 78 | ) !void { | ||
| 79 | var file = try dir.openFile(sub_file_path, .{}); | ||
| 80 | defer file.close(); | ||
| 81 | |||
| 82 | const size = try file.getEndPos(); | ||
| 83 | |||
| 84 | // We borrow `bytes` as a temporary buffer for the base64-encoded data. | ||
| 85 | // This is possible by computing the decoded length and reserving the space | ||
| 86 | // for the decoded bytes first. | ||
| 87 | const decoded_size_upper_bound = size / 4 * 3; | ||
| 88 | try cb.bytes.ensureUnusedCapacity(gpa, decoded_size_upper_bound + size); | ||
| 89 | const end_reserved = cb.bytes.items.len + decoded_size_upper_bound; | ||
| 90 | const buffer = cb.bytes.allocatedSlice()[end_reserved..]; | ||
| 91 | const end_index = try file.readAll(buffer); | ||
| 92 | const encoded_bytes = buffer[0..end_index]; | ||
| 93 | |||
| 94 | const begin_marker = "-----BEGIN CERTIFICATE-----"; | ||
| 95 | const end_marker = "-----END CERTIFICATE-----"; | ||
| 96 | |||
| 97 | var start_index: usize = 0; | ||
| 98 | while (mem.indexOfPos(u8, encoded_bytes, start_index, begin_marker)) |begin_marker_start| { | ||
| 99 | const cert_start = begin_marker_start + begin_marker.len; | ||
| 100 | const cert_end = mem.indexOfPos(u8, encoded_bytes, cert_start, end_marker) orelse | ||
| 101 | return error.MissingEndCertificateMarker; | ||
| 102 | start_index = cert_end + end_marker.len; | ||
| 103 | const encoded_cert = mem.trim(u8, encoded_bytes[cert_start..cert_end], " \t\r\n"); | ||
| 104 | const decoded_start = @intCast(u32, cb.bytes.items.len); | ||
| 105 | const dest_buf = cb.bytes.allocatedSlice()[decoded_start..]; | ||
| 106 | cb.bytes.items.len += try base64.decode(dest_buf, encoded_cert); | ||
| 107 | const k = try key(cb, decoded_start); | ||
| 108 | try cb.map.putContext(gpa, k, decoded_start, .{ .cb = cb }); | ||
| 109 | } | ||
| 110 | } | ||
| 111 | |||
| 112 | pub fn key(cb: *CertificateBundle, bytes_index: u32) !Key { | ||
| 113 | const bytes = cb.bytes.items; | ||
| 114 | const certificate = try Der.parseElement(bytes, bytes_index); | ||
| 115 | const tbs_certificate = try Der.parseElement(bytes, certificate.start); | ||
| 116 | const version = try Der.parseElement(bytes, tbs_certificate.start); | ||
| 117 | if (@bitCast(u8, version.identifier) != 0xa0 or | ||
| 118 | !mem.eql(u8, bytes[version.start..version.end], "\x02\x01\x02")) | ||
| 119 | { | ||
| 120 | return error.UnsupportedCertificateVersion; | ||
| 121 | } | ||
| 122 | |||
| 123 | const serial_number = try Der.parseElement(bytes, version.end); | ||
| 124 | |||
| 125 | // RFC 5280, section 4.1.2.3: | ||
| 126 | // "This field MUST contain the same algorithm identifier as | ||
| 127 | // the signatureAlgorithm field in the sequence Certificate." | ||
| 128 | const signature = try Der.parseElement(bytes, serial_number.end); | ||
| 129 | const issuer = try Der.parseElement(bytes, signature.end); | ||
| 130 | const validity = try Der.parseElement(bytes, issuer.end); | ||
| 131 | const subject = try Der.parseElement(bytes, validity.end); | ||
| 132 | //const subject_pub_key = try Der.parseElement(bytes, subject.end); | ||
| 133 | //const extensions = try Der.parseElement(bytes, subject_pub_key.end); | ||
| 134 | |||
| 135 | return .{ | ||
| 136 | .subject_start = subject.start, | ||
| 137 | .subject_end = subject.end, | ||
| 138 | }; | ||
| 139 | } | ||
| 140 | |||
| 141 | const builtin = @import("builtin"); | ||
| 142 | const std = @import("../std.zig"); | ||
| 143 | const fs = std.fs; | ||
| 144 | const mem = std.mem; | ||
| 145 | const Allocator = std.mem.Allocator; | ||
| 146 | const Der = std.crypto.Der; | ||
| 147 | const CertificateBundle = @This(); | ||
| 148 | |||
| 149 | const base64 = std.base64.standard.decoderWithIgnore(" \t\r\n"); | ||
| 150 | |||
| 151 | const MapContext = struct { | ||
| 152 | cb: *const CertificateBundle, | ||
| 153 | |||
| 154 | pub fn hash(ctx: MapContext, k: Key) u64 { | ||
| 155 | return std.hash_map.hashString(ctx.cb.bytes.items[k.subject_start..k.subject_end]); | ||
| 156 | } | ||
| 157 | |||
| 158 | pub fn eql(ctx: MapContext, a: Key, b: Key) bool { | ||
| 159 | const bytes = ctx.cb.bytes.items; | ||
| 160 | return mem.eql( | ||
| 161 | u8, | ||
| 162 | bytes[a.subject_start..a.subject_end], | ||
| 163 | bytes[b.subject_start..b.subject_end], | ||
| 164 | ); | ||
| 165 | } | ||
| 166 | }; | ||
| 167 | |||
| 168 | test { | ||
| 169 | var bundle: CertificateBundle = .{}; | ||
| 170 | defer bundle.deinit(std.testing.allocator); | ||
| 171 | |||
| 172 | try bundle.rescan(std.testing.allocator); | ||
| 173 | } | ||
lib/std/crypto/Der.zig created+153| ... | @@ -0,0 +1,153 @@ | ||
| 1 | pub const Class = enum(u2) { | ||
| 2 | universal, | ||
| 3 | application, | ||
| 4 | context_specific, | ||
| 5 | private, | ||
| 6 | }; | ||
| 7 | |||
| 8 | pub const PC = enum(u1) { | ||
| 9 | primitive, | ||
| 10 | constructed, | ||
| 11 | }; | ||
| 12 | |||
| 13 | pub const Identifier = packed struct(u8) { | ||
| 14 | tag: Tag, | ||
| 15 | pc: PC, | ||
| 16 | class: Class, | ||
| 17 | }; | ||
| 18 | |||
| 19 | pub const Tag = enum(u5) { | ||
| 20 | boolean = 1, | ||
| 21 | integer = 2, | ||
| 22 | bitstring = 3, | ||
| 23 | null = 5, | ||
| 24 | object_identifier = 6, | ||
| 25 | sequence = 16, | ||
| 26 | sequence_of = 17, | ||
| 27 | _, | ||
| 28 | }; | ||
| 29 | |||
| 30 | pub const Oid = enum { | ||
| 31 | rsadsi, | ||
| 32 | pkcs, | ||
| 33 | rsaEncryption, | ||
| 34 | md2WithRSAEncryption, | ||
| 35 | md5WithRSAEncryption, | ||
| 36 | sha1WithRSAEncryption, | ||
| 37 | sha256WithRSAEncryption, | ||
| 38 | sha384WithRSAEncryption, | ||
| 39 | sha512WithRSAEncryption, | ||
| 40 | sha224WithRSAEncryption, | ||
| 41 | pbeWithMD2AndDES_CBC, | ||
| 42 | pbeWithMD5AndDES_CBC, | ||
| 43 | pkcs9_emailAddress, | ||
| 44 | md2, | ||
| 45 | md5, | ||
| 46 | rc4, | ||
| 47 | ecdsa_with_Recommended, | ||
| 48 | ecdsa_with_Specified, | ||
| 49 | ecdsa_with_SHA224, | ||
| 50 | ecdsa_with_SHA256, | ||
| 51 | ecdsa_with_SHA384, | ||
| 52 | ecdsa_with_SHA512, | ||
| 53 | X500, | ||
| 54 | X509, | ||
| 55 | commonName, | ||
| 56 | serialNumber, | ||
| 57 | countryName, | ||
| 58 | localityName, | ||
| 59 | stateOrProvinceName, | ||
| 60 | organizationName, | ||
| 61 | organizationalUnitName, | ||
| 62 | organizationIdentifier, | ||
| 63 | |||
| 64 | pub const map = std.ComptimeStringMap(Oid, .{ | ||
| 65 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D }, .rsadsi }, | ||
| 66 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01 }, .pkcs }, | ||
| 67 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }, .rsaEncryption }, | ||
| 68 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x02 }, .md2WithRSAEncryption }, | ||
| 69 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x04 }, .md5WithRSAEncryption }, | ||
| 70 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 }, .sha1WithRSAEncryption }, | ||
| 71 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B }, .sha256WithRSAEncryption }, | ||
| 72 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C }, .sha384WithRSAEncryption }, | ||
| 73 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0D }, .sha512WithRSAEncryption }, | ||
| 74 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0E }, .sha224WithRSAEncryption }, | ||
| 75 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x05, 0x01 }, .pbeWithMD2AndDES_CBC }, | ||
| 76 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x05, 0x03 }, .pbeWithMD5AndDES_CBC }, | ||
| 77 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x09, 0x01 }, .pkcs9_emailAddress }, | ||
| 78 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x02 }, .md2 }, | ||
| 79 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x02, 0x05 }, .md5 }, | ||
| 80 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x03, 0x04 }, .rc4 }, | ||
| 81 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x02 }, .ecdsa_with_Recommended }, | ||
| 82 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03 }, .ecdsa_with_Specified }, | ||
| 83 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x01 }, .ecdsa_with_SHA224 }, | ||
| 84 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02 }, .ecdsa_with_SHA256 }, | ||
| 85 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x03 }, .ecdsa_with_SHA384 }, | ||
| 86 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x04 }, .ecdsa_with_SHA512 }, | ||
| 87 | .{ &[_]u8{0x55}, .X500 }, | ||
| 88 | .{ &[_]u8{ 0x55, 0x04 }, .X509 }, | ||
| 89 | .{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName }, | ||
| 90 | .{ &[_]u8{ 0x55, 0x04, 0x05 }, .serialNumber }, | ||
| 91 | .{ &[_]u8{ 0x55, 0x04, 0x06 }, .countryName }, | ||
| 92 | .{ &[_]u8{ 0x55, 0x04, 0x07 }, .localityName }, | ||
| 93 | .{ &[_]u8{ 0x55, 0x04, 0x08 }, .stateOrProvinceName }, | ||
| 94 | .{ &[_]u8{ 0x55, 0x04, 0x0A }, .organizationName }, | ||
| 95 | .{ &[_]u8{ 0x55, 0x04, 0x0B }, .organizationalUnitName }, | ||
| 96 | .{ &[_]u8{ 0x55, 0x04, 0x61 }, .organizationIdentifier }, | ||
| 97 | }); | ||
| 98 | }; | ||
| 99 | |||
| 100 | pub const Element = struct { | ||
| 101 | identifier: Identifier, | ||
| 102 | start: u32, | ||
| 103 | end: u32, | ||
| 104 | }; | ||
| 105 | |||
| 106 | pub const ParseElementError = error{CertificateHasFieldWithInvalidLength}; | ||
| 107 | |||
| 108 | pub fn parseElement(bytes: []const u8, index: u32) ParseElementError!Element { | ||
| 109 | var i = index; | ||
| 110 | const identifier = @bitCast(Identifier, bytes[i]); | ||
| 111 | i += 1; | ||
| 112 | const size_byte = bytes[i]; | ||
| 113 | i += 1; | ||
| 114 | if ((size_byte >> 7) == 0) { | ||
| 115 | return .{ | ||
| 116 | .identifier = identifier, | ||
| 117 | .start = i, | ||
| 118 | .end = i + size_byte, | ||
| 119 | }; | ||
| 120 | } | ||
| 121 | |||
| 122 | const len_size = @truncate(u7, size_byte); | ||
| 123 | if (len_size > @sizeOf(u32)) { | ||
| 124 | return error.CertificateHasFieldWithInvalidLength; | ||
| 125 | } | ||
| 126 | |||
| 127 | const end_i = i + len_size; | ||
| 128 | var long_form_size: u32 = 0; | ||
| 129 | while (i < end_i) : (i += 1) { | ||
| 130 | long_form_size = (long_form_size << 8) | bytes[i]; | ||
| 131 | } | ||
| 132 | |||
| 133 | return .{ | ||
| 134 | .identifier = identifier, | ||
| 135 | .start = i, | ||
| 136 | .end = i + long_form_size, | ||
| 137 | }; | ||
| 138 | } | ||
| 139 | |||
| 140 | pub const ParseObjectIdError = error{ | ||
| 141 | CertificateHasUnrecognizedObjectId, | ||
| 142 | CertificateFieldHasWrongDataType, | ||
| 143 | } || ParseElementError; | ||
| 144 | |||
| 145 | pub fn parseObjectId(bytes: []const u8, element: Element) ParseObjectIdError!Oid { | ||
| 146 | if (element.identifier.tag != .object_identifier) | ||
| 147 | return error.CertificateFieldHasWrongDataType; | ||
| 148 | return Oid.map.get(bytes[element.start..element.end]) orelse | ||
| 149 | return error.CertificateHasUnrecognizedObjectId; | ||
| 150 | } | ||
| 151 | |||
| 152 | const std = @import("../std.zig"); | ||
| 153 | const Der = @This(); | ||
lib/std/crypto/tls.zig-109| ... | @@ -349,112 +349,3 @@ pub inline fn int3(x: u24) [3]u8 { | ... | @@ -349,112 +349,3 @@ pub inline fn int3(x: u24) [3]u8 { |
| 349 | @truncate(u8, x), | 349 | @truncate(u8, x), |
| 350 | }; | 350 | }; |
| 351 | } | 351 | } |
| 352 | |||
| 353 | pub const Der = struct { | ||
| 354 | pub const Class = enum(u2) { | ||
| 355 | universal, | ||
| 356 | application, | ||
| 357 | context_specific, | ||
| 358 | private, | ||
| 359 | }; | ||
| 360 | |||
| 361 | pub const PC = enum(u1) { | ||
| 362 | primitive, | ||
| 363 | constructed, | ||
| 364 | }; | ||
| 365 | |||
| 366 | pub const Identifier = packed struct(u8) { | ||
| 367 | tag: Tag, | ||
| 368 | pc: PC, | ||
| 369 | class: Class, | ||
| 370 | }; | ||
| 371 | |||
| 372 | pub const Tag = enum(u5) { | ||
| 373 | boolean = 1, | ||
| 374 | integer = 2, | ||
| 375 | bitstring = 3, | ||
| 376 | null = 5, | ||
| 377 | object_identifier = 6, | ||
| 378 | sequence = 16, | ||
| 379 | _, | ||
| 380 | }; | ||
| 381 | |||
| 382 | pub const Oid = enum { | ||
| 383 | commonName, | ||
| 384 | countryName, | ||
| 385 | localityName, | ||
| 386 | stateOrProvinceName, | ||
| 387 | organizationName, | ||
| 388 | organizationalUnitName, | ||
| 389 | sha256WithRSAEncryption, | ||
| 390 | sha384WithRSAEncryption, | ||
| 391 | sha512WithRSAEncryption, | ||
| 392 | sha224WithRSAEncryption, | ||
| 393 | |||
| 394 | pub const map = std.ComptimeStringMap(Oid, .{ | ||
| 395 | .{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName }, | ||
| 396 | .{ &[_]u8{ 0x55, 0x04, 0x06 }, .countryName }, | ||
| 397 | .{ &[_]u8{ 0x55, 0x04, 0x07 }, .localityName }, | ||
| 398 | .{ &[_]u8{ 0x55, 0x04, 0x08 }, .stateOrProvinceName }, | ||
| 399 | .{ &[_]u8{ 0x55, 0x04, 0x0A }, .organizationName }, | ||
| 400 | .{ &[_]u8{ 0x55, 0x04, 0x0B }, .organizationalUnitName }, | ||
| 401 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B }, .sha256WithRSAEncryption }, | ||
| 402 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C }, .sha384WithRSAEncryption }, | ||
| 403 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0D }, .sha512WithRSAEncryption }, | ||
| 404 | .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0E }, .sha224WithRSAEncryption }, | ||
| 405 | }); | ||
| 406 | }; | ||
| 407 | |||
| 408 | pub const Element = struct { | ||
| 409 | identifier: Identifier, | ||
| 410 | contents: []const u8, | ||
| 411 | }; | ||
| 412 | |||
| 413 | pub const ParseElementError = error{CertificateHasFieldWithInvalidLength}; | ||
| 414 | |||
| 415 | pub fn parseElement(bytes: []const u8, index: *usize) ParseElementError!Der.Element { | ||
| 416 | var i = index.*; | ||
| 417 | const identifier = @bitCast(Identifier, bytes[i]); | ||
| 418 | i += 1; | ||
| 419 | const size_byte = bytes[i]; | ||
| 420 | i += 1; | ||
| 421 | if ((size_byte >> 7) == 0) { | ||
| 422 | const contents = bytes[i..][0..size_byte]; | ||
| 423 | index.* = i + contents.len; | ||
| 424 | return .{ | ||
| 425 | .identifier = identifier, | ||
| 426 | .contents = contents, | ||
| 427 | }; | ||
| 428 | } | ||
| 429 | |||
| 430 | const len_size = @truncate(u7, size_byte); | ||
| 431 | if (len_size > @sizeOf(usize)) { | ||
| 432 | return error.CertificateHasFieldWithInvalidLength; | ||
| 433 | } | ||
| 434 | |||
| 435 | const end = i + len_size; | ||
| 436 | var long_form_size: usize = 0; | ||
| 437 | while (i < end) : (i += 1) { | ||
| 438 | long_form_size = (long_form_size << 8) | bytes[i]; | ||
| 439 | } | ||
| 440 | |||
| 441 | const contents = bytes[i..][0..long_form_size]; | ||
| 442 | index.* = i + contents.len; | ||
| 443 | |||
| 444 | return .{ | ||
| 445 | .identifier = identifier, | ||
| 446 | .contents = contents, | ||
| 447 | }; | ||
| 448 | } | ||
| 449 | |||
| 450 | pub const ParseObjectIdError = error{ | ||
| 451 | CertificateHasUnrecognizedObjectId, | ||
| 452 | CertificateFieldHasWrongDataType, | ||
| 453 | } || ParseElementError; | ||
| 454 | |||
| 455 | pub fn parseObjectId(bytes: []const u8, index: *usize) ParseObjectIdError!Oid { | ||
| 456 | const oid_element = try parseElement(bytes, index); | ||
| 457 | if (oid_element.identifier.tag != .object_identifier) return error.CertificateFieldHasWrongDataType; | ||
| 458 | return Oid.map.get(oid_element.contents) orelse return error.CertificateHasUnrecognizedObjectId; | ||
| 459 | } | ||
| 460 | }; |
lib/std/crypto/tls/Client.zig+36-63| ... | @@ -1,5 +1,6 @@ | ... | @@ -1,5 +1,6 @@ |
| 1 | const std = @import("../../std.zig"); | 1 | const std = @import("../../std.zig"); |
| 2 | const tls = std.crypto.tls; | 2 | const tls = std.crypto.tls; |
| 3 | const Der = std.crypto.Der; | ||
| 3 | const Client = @This(); | 4 | const Client = @This(); |
| 4 | const net = std.net; | 5 | const net = std.net; |
| 5 | const mem = std.mem; | 6 | const mem = std.mem; |
| ... | @@ -28,7 +29,7 @@ partially_read_len: u15, | ... | @@ -28,7 +29,7 @@ partially_read_len: u15, |
| 28 | eof: bool, | 29 | eof: bool, |
| 29 | 30 | ||
| 30 | /// `host` is only borrowed during this function call. | 31 | /// `host` is only borrowed during this function call. |
| 31 | pub fn init(stream: net.Stream, host: []const u8) !Client { | 32 | pub fn init(stream: net.Stream, ca_bundle: crypto.CertificateBundle, host: []const u8) !Client { |
| 32 | const host_len = @intCast(u16, host.len); | 33 | const host_len = @intCast(u16, host.len); |
| 33 | 34 | ||
| 34 | var random_buffer: [128]u8 = undefined; | 35 | var random_buffer: [128]u8 = undefined; |
| ... | @@ -392,7 +393,7 @@ pub fn init(stream: net.Stream, host: []const u8) !Client { | ... | @@ -392,7 +393,7 @@ pub fn init(stream: net.Stream, host: []const u8) !Client { |
| 392 | switch (cipher_params) { | 393 | switch (cipher_params) { |
| 393 | inline else => |*p| p.transcript_hash.update(wrapped_handshake), | 394 | inline else => |*p| p.transcript_hash.update(wrapped_handshake), |
| 394 | } | 395 | } |
| 395 | var hs_i: usize = 0; | 396 | var hs_i: u32 = 0; |
| 396 | const cert_req_ctx_len = handshake[hs_i]; | 397 | const cert_req_ctx_len = handshake[hs_i]; |
| 397 | hs_i += 1; | 398 | hs_i += 1; |
| 398 | if (cert_req_ctx_len != 0) return error.TlsIllegalParameter; | 399 | if (cert_req_ctx_len != 0) return error.TlsIllegalParameter; |
| ... | @@ -404,75 +405,47 @@ pub fn init(stream: net.Stream, host: []const u8) !Client { | ... | @@ -404,75 +405,47 @@ pub fn init(stream: net.Stream, host: []const u8) !Client { |
| 404 | hs_i += 3; | 405 | hs_i += 3; |
| 405 | const end_cert = hs_i + cert_size; | 406 | const end_cert = hs_i + cert_size; |
| 406 | 407 | ||
| 407 | const certificate = try tls.Der.parseElement(handshake, &hs_i); | 408 | const certificate = try Der.parseElement(handshake, hs_i); |
| 408 | { | 409 | const tbs_certificate = try Der.parseElement(handshake, certificate.start); |
| 409 | var cert_i: usize = 0; | ||
| 410 | const tbs_certificate = try tls.Der.parseElement(certificate.contents, &cert_i); | ||
| 411 | { | ||
| 412 | var tbs_i: usize = 0; | ||
| 413 | const version = try tls.Der.parseElement(tbs_certificate.contents, &tbs_i); | ||
| 414 | const serial_number = try tls.Der.parseElement(tbs_certificate.contents, &tbs_i); | ||
| 415 | const signature = try tls.Der.parseElement(tbs_certificate.contents, &tbs_i); | ||
| 416 | const issuer = try tls.Der.parseElement(tbs_certificate.contents, &tbs_i); | ||
| 417 | const validity = try tls.Der.parseElement(tbs_certificate.contents, &tbs_i); | ||
| 418 | const subject = try tls.Der.parseElement(tbs_certificate.contents, &tbs_i); | ||
| 419 | const subject_pub_key = try tls.Der.parseElement(tbs_certificate.contents, &tbs_i); | ||
| 420 | const extensions = try tls.Der.parseElement(tbs_certificate.contents, &tbs_i); | ||
| 421 | |||
| 422 | // RFC 5280, section 4.1.2.3: | ||
| 423 | // "This field MUST contain the same algorithm identifier as | ||
| 424 | // the signatureAlgorithm field in the sequence Certificate." | ||
| 425 | _ = signature; | ||
| 426 | |||
| 427 | _ = issuer; | ||
| 428 | _ = validity; | ||
| 429 | |||
| 430 | std.debug.print("version: {any} '{}'\n", .{ | ||
| 431 | version.identifier, std.fmt.fmtSliceHexLower(version.contents), | ||
| 432 | }); | ||
| 433 | |||
| 434 | std.debug.print("serial_number: {any} {}\n", .{ | ||
| 435 | serial_number.identifier, | ||
| 436 | std.fmt.fmtSliceHexLower(serial_number.contents), | ||
| 437 | }); | ||
| 438 | 410 | ||
| 439 | std.debug.print("subject: {any} {}\n", .{ | 411 | const version = try Der.parseElement(handshake, tbs_certificate.start); |
| 440 | subject.identifier, | 412 | if (@bitCast(u8, version.identifier) != 0xa0 or |
| 441 | std.fmt.fmtSliceHexLower(subject.contents), | 413 | !mem.eql(u8, handshake[version.start..version.end], "\x02\x01\x02")) |
| 442 | }); | 414 | { |
| 443 | 415 | return error.UnsupportedCertificateVersion; | |
| 444 | std.debug.print("subject pub key: {any} {}\n", .{ | ||
| 445 | subject_pub_key.identifier, | ||
| 446 | std.fmt.fmtSliceHexLower(subject_pub_key.contents), | ||
| 447 | }); | ||
| 448 | |||
| 449 | std.debug.print("extensions: {any} {}\n", .{ | ||
| 450 | extensions.identifier, | ||
| 451 | std.fmt.fmtSliceHexLower(extensions.contents), | ||
| 452 | }); | ||
| 453 | } | ||
| 454 | const signature_algorithm = try tls.Der.parseElement(certificate.contents, &cert_i); | ||
| 455 | const signature_value = try tls.Der.parseElement(certificate.contents, &cert_i); | ||
| 456 | |||
| 457 | { | ||
| 458 | var sa_i: usize = 0; | ||
| 459 | const algorithm = try tls.Der.parseObjectId(signature_algorithm.contents, &sa_i); | ||
| 460 | std.debug.print("cert has this signature algorithm: {any}\n", .{algorithm}); | ||
| 461 | //const parameters = try tls.Der.parseElement(signature_algorithm.contents, &sa_i); | ||
| 462 | } | ||
| 463 | |||
| 464 | std.debug.print("signature_value: {any} {d} bytes\n", .{ | ||
| 465 | signature_value.identifier, signature_value.contents.len, | ||
| 466 | }); | ||
| 467 | } | 416 | } |
| 468 | 417 | ||
| 418 | const serial_number = try Der.parseElement(handshake, version.end); | ||
| 419 | // RFC 5280, section 4.1.2.3: | ||
| 420 | // "This field MUST contain the same algorithm identifier as | ||
| 421 | // the signatureAlgorithm field in the sequence Certificate." | ||
| 422 | const signature = try Der.parseElement(handshake, serial_number.end); | ||
| 423 | const issuer = try Der.parseElement(handshake, signature.end); | ||
| 424 | const validity = try Der.parseElement(handshake, issuer.end); | ||
| 425 | const subject = try Der.parseElement(handshake, validity.end); | ||
| 426 | const subject_pub_key = try Der.parseElement(handshake, subject.end); | ||
| 427 | const extensions = try Der.parseElement(handshake, subject_pub_key.end); | ||
| 428 | _ = extensions; | ||
| 429 | |||
| 430 | const signature_algorithm = try Der.parseElement(handshake, tbs_certificate.end); | ||
| 431 | const signature_value = try Der.parseElement(handshake, signature_algorithm.end); | ||
| 432 | _ = signature_value; | ||
| 433 | |||
| 434 | const algorithm_elem = try Der.parseElement(handshake, signature_algorithm.start); | ||
| 435 | const algorithm = try Der.parseObjectId(handshake, algorithm_elem); | ||
| 436 | std.debug.print("cert has this signature algorithm: {any}\n", .{algorithm}); | ||
| 437 | //const parameters = try Der.parseElement(signature_algorithm.contents, &sa_i); | ||
| 438 | |||
| 469 | hs_i = end_cert; | 439 | hs_i = end_cert; |
| 470 | const total_ext_size = mem.readIntBig(u16, handshake[hs_i..][0..2]); | 440 | const total_ext_size = mem.readIntBig(u16, handshake[hs_i..][0..2]); |
| 471 | hs_i += 2; | 441 | hs_i += 2; |
| 472 | hs_i += total_ext_size; | 442 | hs_i += total_ext_size; |
| 473 | 443 | ||
| 474 | std.debug.print("received certificate of size {d} bytes with {d} bytes of extensions\n", .{ | 444 | const issuer_bytes = handshake[issuer.start..issuer.end]; |
| 475 | cert_size, total_ext_size, | 445 | const ca_cert = ca_bundle.find(issuer_bytes); |
| 446 | |||
| 447 | std.debug.print("received certificate of size {d} bytes with {d} bytes of extensions. ca_found={any}\n", .{ | ||
| 448 | cert_size, total_ext_size, ca_cert != null, | ||
| 476 | }); | 449 | }); |
| 477 | } | 450 | } |
| 478 | }, | 451 | }, |
lib/std/http/Client.zig+2-1| ... | @@ -7,6 +7,7 @@ const Client = @This(); | ... | @@ -7,6 +7,7 @@ const Client = @This(); |
| 7 | allocator: std.mem.Allocator, | 7 | allocator: std.mem.Allocator, |
| 8 | headers: std.ArrayListUnmanaged(u8) = .{}, | 8 | headers: std.ArrayListUnmanaged(u8) = .{}, |
| 9 | active_requests: usize = 0, | 9 | active_requests: usize = 0, |
| 10 | ca_bundle: std.crypto.CertificateBundle = .{}, | ||
| 10 | 11 | ||
| 11 | pub const Request = struct { | 12 | pub const Request = struct { |
| 12 | client: *Client, | 13 | client: *Client, |
| ... | @@ -102,7 +103,7 @@ pub fn request(client: *Client, options: Request.Options) !Request { | ... | @@ -102,7 +103,7 @@ pub fn request(client: *Client, options: Request.Options) !Request { |
| 102 | switch (options.protocol) { | 103 | switch (options.protocol) { |
| 103 | .http => {}, | 104 | .http => {}, |
| 104 | .https => { | 105 | .https => { |
| 105 | req.tls_client = try std.crypto.tls.Client.init(req.stream, options.host); | 106 | req.tls_client = try std.crypto.tls.Client.init(req.stream, client.ca_bundle, options.host); |
| 106 | }, | 107 | }, |
| 107 | } | 108 | } |
| 108 | 109 |