authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-12-19 21:22:51-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-02 16:57:15-07:00
logbbc074252cde0f45576b3910bec5a0f9e867c7f2
treea5d047c3ad4d2e553867f09db6702e793c0a51eb
parent3237000d957617120e32e54498cac9afa23cbcd4

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.

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");
177pub const errors = @import("crypto/errors.zig");177pub const errors = @import("crypto/errors.zig");
178178
179pub const tls = @import("crypto/tls.zig");179pub const tls = @import("crypto/tls.zig");
180pub const Der = @import("crypto/Der.zig");
181pub const CertificateBundle = @import("crypto/CertificateBundle.zig");
180182
181test {183test {
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}
270275
271test "CSPRNG" {276test "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
7map: std.HashMapUnmanaged(Key, u32, MapContext, std.hash_map.default_max_load_percentage) = .{},
8bytes: std.ArrayListUnmanaged(u8) = .{},
9
10pub 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.
17pub 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
35pub 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.
43pub 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
50pub 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
73pub 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
112pub 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
141const builtin = @import("builtin");
142const std = @import("../std.zig");
143const fs = std.fs;
144const mem = std.mem;
145const Allocator = std.mem.Allocator;
146const Der = std.crypto.Der;
147const CertificateBundle = @This();
148
149const base64 = std.base64.standard.decoderWithIgnore(" \t\r\n");
150
151const 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
168test {
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 @@
1pub const Class = enum(u2) {
2 universal,
3 application,
4 context_specific,
5 private,
6};
7
8pub const PC = enum(u1) {
9 primitive,
10 constructed,
11};
12
13pub const Identifier = packed struct(u8) {
14 tag: Tag,
15 pc: PC,
16 class: Class,
17};
18
19pub 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
30pub 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
100pub const Element = struct {
101 identifier: Identifier,
102 start: u32,
103 end: u32,
104};
105
106pub const ParseElementError = error{CertificateHasFieldWithInvalidLength};
107
108pub 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
140pub const ParseObjectIdError = error{
141 CertificateHasUnrecognizedObjectId,
142 CertificateFieldHasWrongDataType,
143} || ParseElementError;
144
145pub 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
152const std = @import("../std.zig");
153const 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
353pub 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 @@
1const std = @import("../../std.zig");1const std = @import("../../std.zig");
2const tls = std.crypto.tls;2const tls = std.crypto.tls;
3const Der = std.crypto.Der;
3const Client = @This();4const Client = @This();
4const net = std.net;5const net = std.net;
5const mem = std.mem;6const mem = std.mem;
...@@ -28,7 +29,7 @@ partially_read_len: u15,...@@ -28,7 +29,7 @@ partially_read_len: u15,
28eof: bool,29eof: bool,
2930
30/// `host` is only borrowed during this function call.31/// `host` is only borrowed during this function call.
31pub fn init(stream: net.Stream, host: []const u8) !Client {32pub 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);
3334
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;
406407
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 });
438410
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 {
443415 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 }
468417
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;
473443
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();
7allocator: std.mem.Allocator,7allocator: std.mem.Allocator,
8headers: std.ArrayListUnmanaged(u8) = .{},8headers: std.ArrayListUnmanaged(u8) = .{},
9active_requests: usize = 0,9active_requests: usize = 0,
10ca_bundle: std.crypto.CertificateBundle = .{},
1011
11pub const Request = struct {12pub 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 }
108109