authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-12-21 17:12:34-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-02 16:57:15-07:00
log29475b45185f90c2437d160567e67a4b141f5845
tree5899f130a8fddd9131eb8734fa7f440da1deb6c6
parent4f9f4575bdf35fa69f09910d3d0ae349f9071c18

std.crypto.tls: validate previous certificate


8 files changed, 824 insertions(+), 767 deletions(-)

lib/std/crypto.zig+4-4
...@@ -177,8 +177,8 @@ const std = @import("std.zig");...@@ -177,8 +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");180pub const der = @import("crypto/der.zig");
181pub const CertificateBundle = @import("crypto/CertificateBundle.zig");181pub const Certificate = @import("crypto/Certificate.zig");
182182
183test {183test {
184 _ = aead.aegis.Aegis128L;184 _ = aead.aegis.Aegis128L;
...@@ -269,8 +269,8 @@ test {...@@ -269,8 +269,8 @@ test {
269 _ = random;269 _ = random;
270 _ = errors;270 _ = errors;
271 _ = tls;271 _ = tls;
272 _ = Der;272 _ = der;
273 _ = CertificateBundle;273 _ = Certificate;
274}274}
275275
276test "CSPRNG" {276test "CSPRNG" {
lib/std/crypto/Certificate.zig created+446
...@@ -0,0 +1,446 @@
1buffer: []const u8,
2index: u32,
3
4pub const Bundle = @import("Certificate/Bundle.zig");
5
6pub const Algorithm = enum {
7 sha1WithRSAEncryption,
8 sha224WithRSAEncryption,
9 sha256WithRSAEncryption,
10 sha384WithRSAEncryption,
11 sha512WithRSAEncryption,
12
13 pub const map = std.ComptimeStringMap(Algorithm, .{
14 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 }, .sha1WithRSAEncryption },
15 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B }, .sha256WithRSAEncryption },
16 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C }, .sha384WithRSAEncryption },
17 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0D }, .sha512WithRSAEncryption },
18 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0E }, .sha224WithRSAEncryption },
19 });
20
21 pub fn Hash(comptime algorithm: Algorithm) type {
22 return switch (algorithm) {
23 .sha1WithRSAEncryption => crypto.hash.Sha1,
24 .sha224WithRSAEncryption => crypto.hash.sha2.Sha224,
25 .sha256WithRSAEncryption => crypto.hash.sha2.Sha256,
26 .sha384WithRSAEncryption => crypto.hash.sha2.Sha384,
27 .sha512WithRSAEncryption => crypto.hash.sha2.Sha512,
28 };
29 }
30};
31
32pub const AlgorithmCategory = enum {
33 rsaEncryption,
34 X9_62_id_ecPublicKey,
35
36 pub const map = std.ComptimeStringMap(AlgorithmCategory, .{
37 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }, .rsaEncryption },
38 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01 }, .X9_62_id_ecPublicKey },
39 });
40};
41
42pub const Attribute = enum {
43 commonName,
44 serialNumber,
45 countryName,
46 localityName,
47 stateOrProvinceName,
48 organizationName,
49 organizationalUnitName,
50 organizationIdentifier,
51
52 pub const map = std.ComptimeStringMap(Attribute, .{
53 .{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName },
54 .{ &[_]u8{ 0x55, 0x04, 0x05 }, .serialNumber },
55 .{ &[_]u8{ 0x55, 0x04, 0x06 }, .countryName },
56 .{ &[_]u8{ 0x55, 0x04, 0x07 }, .localityName },
57 .{ &[_]u8{ 0x55, 0x04, 0x08 }, .stateOrProvinceName },
58 .{ &[_]u8{ 0x55, 0x04, 0x0A }, .organizationName },
59 .{ &[_]u8{ 0x55, 0x04, 0x0B }, .organizationalUnitName },
60 .{ &[_]u8{ 0x55, 0x04, 0x61 }, .organizationIdentifier },
61 });
62};
63
64pub const Parsed = struct {
65 certificate: Certificate,
66 issuer_slice: Slice,
67 subject_slice: Slice,
68 common_name_slice: Slice,
69 signature_slice: Slice,
70 signature_algorithm: Algorithm,
71 pub_key_algo: AlgorithmCategory,
72 pub_key_slice: Slice,
73 message_slice: Slice,
74
75 pub const Slice = der.Element.Slice;
76
77 pub fn slice(p: Parsed, s: Slice) []const u8 {
78 return p.certificate.buffer[s.start..s.end];
79 }
80
81 pub fn issuer(p: Parsed) []const u8 {
82 return p.slice(p.issuer_slice);
83 }
84
85 pub fn subject(p: Parsed) []const u8 {
86 return p.slice(p.subject_slice);
87 }
88
89 pub fn commonName(p: Parsed) []const u8 {
90 return p.slice(p.common_name_slice);
91 }
92
93 pub fn signature(p: Parsed) []const u8 {
94 return p.slice(p.signature_slice);
95 }
96
97 pub fn pubKey(p: Parsed) []const u8 {
98 return p.slice(p.pub_key_slice);
99 }
100
101 pub fn message(p: Parsed) []const u8 {
102 return p.slice(p.message_slice);
103 }
104
105 pub fn verify(parsed_subject: Parsed, parsed_issuer: Parsed) !void {
106 // Check that the subject's issuer name matches the issuer's
107 // subject name.
108 if (!mem.eql(u8, parsed_subject.issuer(), parsed_issuer.subject())) {
109 return error.CertificateIssuerMismatch;
110 }
111
112 // TODO check the time validity for the subject
113 // TODO check the time validity for the issuer
114
115 switch (parsed_subject.signature_algorithm) {
116 inline .sha1WithRSAEncryption,
117 .sha224WithRSAEncryption,
118 .sha256WithRSAEncryption,
119 .sha384WithRSAEncryption,
120 .sha512WithRSAEncryption,
121 => |algorithm| return verifyRsa(
122 algorithm.Hash(),
123 parsed_subject.message(),
124 parsed_subject.signature(),
125 parsed_issuer.pub_key_algo,
126 parsed_issuer.pubKey(),
127 ),
128 }
129 }
130};
131
132pub fn parse(cert: Certificate) !Parsed {
133 const cert_bytes = cert.buffer;
134 const certificate = try der.parseElement(cert_bytes, cert.index);
135 const tbs_certificate = try der.parseElement(cert_bytes, certificate.slice.start);
136 const version = try der.parseElement(cert_bytes, tbs_certificate.slice.start);
137 try checkVersion(cert_bytes, version);
138 const serial_number = try der.parseElement(cert_bytes, version.slice.end);
139 // RFC 5280, section 4.1.2.3:
140 // "This field MUST contain the same algorithm identifier as
141 // the signatureAlgorithm field in the sequence Certificate."
142 const tbs_signature = try der.parseElement(cert_bytes, serial_number.slice.end);
143 const issuer = try der.parseElement(cert_bytes, tbs_signature.slice.end);
144 const validity = try der.parseElement(cert_bytes, issuer.slice.end);
145 const subject = try der.parseElement(cert_bytes, validity.slice.end);
146
147 const pub_key_info = try der.parseElement(cert_bytes, subject.slice.end);
148 const pub_key_signature_algorithm = try der.parseElement(cert_bytes, pub_key_info.slice.start);
149 const pub_key_algo_elem = try der.parseElement(cert_bytes, pub_key_signature_algorithm.slice.start);
150 const pub_key_algo = try parseAlgorithmCategory(cert_bytes, pub_key_algo_elem);
151 const pub_key_elem = try der.parseElement(cert_bytes, pub_key_signature_algorithm.slice.end);
152 const pub_key = try parseBitString(cert, pub_key_elem);
153
154 const rdn = try der.parseElement(cert_bytes, subject.slice.start);
155 const atav = try der.parseElement(cert_bytes, rdn.slice.start);
156
157 var common_name = der.Element.Slice.empty;
158 var atav_i = atav.slice.start;
159 while (atav_i < atav.slice.end) {
160 const ty_elem = try der.parseElement(cert_bytes, atav_i);
161 const ty = try parseAttribute(cert_bytes, ty_elem);
162 const val = try der.parseElement(cert_bytes, ty_elem.slice.end);
163 switch (ty) {
164 .commonName => common_name = val.slice,
165 else => {},
166 }
167 atav_i = val.slice.end;
168 }
169
170 const sig_algo = try der.parseElement(cert_bytes, tbs_certificate.slice.end);
171 const algo_elem = try der.parseElement(cert_bytes, sig_algo.slice.start);
172 const signature_algorithm = try parseAlgorithm(cert_bytes, algo_elem);
173 const sig_elem = try der.parseElement(cert_bytes, sig_algo.slice.end);
174 const signature = try parseBitString(cert, sig_elem);
175
176 return .{
177 .certificate = cert,
178 .common_name_slice = common_name,
179 .issuer_slice = issuer.slice,
180 .subject_slice = subject.slice,
181 .signature_slice = signature,
182 .signature_algorithm = signature_algorithm,
183 .message_slice = .{ .start = certificate.slice.start, .end = tbs_certificate.slice.end },
184 .pub_key_algo = pub_key_algo,
185 .pub_key_slice = pub_key,
186 };
187}
188
189pub fn verify(subject: Certificate, issuer: Certificate) !void {
190 const parsed_subject = try subject.parse();
191 const parsed_issuer = try issuer.parse();
192 return parsed_subject.verify(parsed_issuer);
193}
194
195pub fn contents(cert: Certificate, elem: der.Element) []const u8 {
196 return cert.buffer[elem.start..elem.end];
197}
198
199pub fn parseBitString(cert: Certificate, elem: der.Element) !der.Element.Slice {
200 if (elem.identifier.tag != .bitstring) return error.CertificateFieldHasWrongDataType;
201 if (cert.buffer[elem.slice.start] != 0) return error.CertificateHasInvalidBitString;
202 return .{ .start = elem.slice.start + 1, .end = elem.slice.end };
203}
204
205pub fn parseAlgorithm(bytes: []const u8, element: der.Element) !Algorithm {
206 if (element.identifier.tag != .object_identifier)
207 return error.CertificateFieldHasWrongDataType;
208 return Algorithm.map.get(bytes[element.slice.start..element.slice.end]) orelse
209 return error.CertificateHasUnrecognizedAlgorithm;
210}
211
212pub fn parseAlgorithmCategory(bytes: []const u8, element: der.Element) !AlgorithmCategory {
213 if (element.identifier.tag != .object_identifier)
214 return error.CertificateFieldHasWrongDataType;
215 return AlgorithmCategory.map.get(bytes[element.slice.start..element.slice.end]) orelse
216 return error.CertificateHasUnrecognizedAlgorithmCategory;
217}
218
219pub fn parseAttribute(bytes: []const u8, element: der.Element) !Attribute {
220 if (element.identifier.tag != .object_identifier)
221 return error.CertificateFieldHasWrongDataType;
222 return Attribute.map.get(bytes[element.slice.start..element.slice.end]) orelse
223 return error.CertificateHasUnrecognizedAlgorithm;
224}
225
226fn verifyRsa(comptime Hash: type, message: []const u8, sig: []const u8, pub_key_algo: AlgorithmCategory, pub_key: []const u8) !void {
227 if (pub_key_algo != .rsaEncryption) return error.CertificateSignatureAlgorithmMismatch;
228 const pub_key_seq = try der.parseElement(pub_key, 0);
229 if (pub_key_seq.identifier.tag != .sequence) return error.CertificateFieldHasWrongDataType;
230 const modulus_elem = try der.parseElement(pub_key, pub_key_seq.slice.start);
231 if (modulus_elem.identifier.tag != .integer) return error.CertificateFieldHasWrongDataType;
232 const exponent_elem = try der.parseElement(pub_key, modulus_elem.slice.end);
233 if (exponent_elem.identifier.tag != .integer) return error.CertificateFieldHasWrongDataType;
234 // Skip over meaningless zeroes in the modulus.
235 const modulus_raw = pub_key[modulus_elem.slice.start..modulus_elem.slice.end];
236 const modulus_offset = for (modulus_raw) |byte, i| {
237 if (byte != 0) break i;
238 } else modulus_raw.len;
239 const modulus = modulus_raw[modulus_offset..];
240 const exponent = pub_key[exponent_elem.slice.start..exponent_elem.slice.end];
241 if (exponent.len > modulus.len) return error.CertificatePublicKeyInvalid;
242 if (sig.len != modulus.len) return error.CertificateSignatureInvalidLength;
243
244 const hash_der = switch (Hash) {
245 crypto.hash.Sha1 => [_]u8{
246 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e,
247 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14,
248 },
249 crypto.hash.sha2.Sha224 => [_]u8{
250 0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
251 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05,
252 0x00, 0x04, 0x1c,
253 },
254 crypto.hash.sha2.Sha256 => [_]u8{
255 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
256 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
257 0x00, 0x04, 0x20,
258 },
259 crypto.hash.sha2.Sha384 => [_]u8{
260 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
261 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05,
262 0x00, 0x04, 0x30,
263 },
264 crypto.hash.sha2.Sha512 => [_]u8{
265 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
266 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
267 0x00, 0x04, 0x40,
268 },
269 else => @compileError("unreachable"),
270 };
271
272 var msg_hashed: [Hash.digest_length]u8 = undefined;
273 Hash.hash(message, &msg_hashed, .{});
274
275 switch (modulus.len) {
276 inline 128, 256, 512 => |modulus_len| {
277 const ps_len = modulus_len - (hash_der.len + msg_hashed.len) - 3;
278 const em: [modulus_len]u8 =
279 [2]u8{ 0, 1 } ++
280 ([1]u8{0xff} ** ps_len) ++
281 [1]u8{0} ++
282 hash_der ++
283 msg_hashed;
284
285 const public_key = try rsa.PublicKey.fromBytes(exponent, modulus, rsa.poop);
286 const em_dec = try rsa.encrypt(modulus_len, sig[0..modulus_len].*, public_key, rsa.poop);
287
288 if (!mem.eql(u8, &em, &em_dec)) {
289 try std.testing.expectEqualSlices(u8, &em, &em_dec);
290 return error.CertificateSignatureInvalid;
291 }
292 },
293 else => {
294 return error.CertificateSignatureUnsupportedBitCount;
295 },
296 }
297}
298
299pub fn checkVersion(bytes: []const u8, version: der.Element) !void {
300 if (@bitCast(u8, version.identifier) != 0xa0 or
301 !mem.eql(u8, bytes[version.slice.start..version.slice.end], "\x02\x01\x02"))
302 {
303 return error.UnsupportedCertificateVersion;
304 }
305}
306
307const std = @import("../std.zig");
308const crypto = std.crypto;
309const mem = std.mem;
310const der = std.crypto.der;
311const Certificate = @This();
312
313/// TODO: replace this with Frank's upcoming RSA implementation. the verify
314/// function won't have the possibility of failure - it will either identify a
315/// valid signature or an invalid signature.
316/// This code is borrowed from https://github.com/shiguredo/tls13-zig
317/// which is licensed under the Apache License Version 2.0, January 2004
318/// http://www.apache.org/licenses/
319/// The code has been modified.
320const rsa = struct {
321 const BigInt = std.math.big.int.Managed;
322
323 const PublicKey = struct {
324 n: BigInt,
325 e: BigInt,
326
327 pub fn deinit(self: *PublicKey) void {
328 self.n.deinit();
329 self.e.deinit();
330 }
331
332 pub fn fromBytes(pub_bytes: []const u8, modulus_bytes: []const u8, allocator: std.mem.Allocator) !PublicKey {
333 var _n = try BigInt.init(allocator);
334 errdefer _n.deinit();
335 try setBytes(&_n, modulus_bytes, allocator);
336
337 var _e = try BigInt.init(allocator);
338 errdefer _e.deinit();
339 try setBytes(&_e, pub_bytes, allocator);
340
341 return .{
342 .n = _n,
343 .e = _e,
344 };
345 }
346 };
347
348 fn encrypt(comptime modulus_len: usize, msg: [modulus_len]u8, public_key: PublicKey, allocator: std.mem.Allocator) ![modulus_len]u8 {
349 var m = try BigInt.init(allocator);
350 defer m.deinit();
351
352 try setBytes(&m, &msg, allocator);
353
354 if (m.order(public_key.n) != .lt) {
355 return error.MessageTooLong;
356 }
357
358 var e = try BigInt.init(allocator);
359 defer e.deinit();
360
361 try pow_montgomery(&e, &m, &public_key.e, &public_key.n, allocator);
362
363 var res: [modulus_len]u8 = undefined;
364
365 try toBytes(&res, &e, allocator);
366
367 return res;
368 }
369
370 fn setBytes(r: *BigInt, bytes: []const u8, allcator: std.mem.Allocator) !void {
371 try r.set(0);
372 var tmp = try BigInt.init(allcator);
373 defer tmp.deinit();
374 for (bytes) |b| {
375 try r.shiftLeft(r, 8);
376 try tmp.set(b);
377 try r.add(r, &tmp);
378 }
379 }
380
381 fn pow_montgomery(r: *BigInt, a: *const BigInt, x: *const BigInt, n: *const BigInt, allocator: std.mem.Allocator) !void {
382 var bin_raw: [512]u8 = undefined;
383 try toBytes(&bin_raw, x, allocator);
384
385 var i: usize = 0;
386 while (bin_raw[i] == 0x00) : (i += 1) {}
387 const bin = bin_raw[i..];
388
389 try r.set(1);
390 var r1 = try BigInt.init(allocator);
391 defer r1.deinit();
392 try BigInt.copy(&r1, a.toConst());
393 i = 0;
394 while (i < bin.len * 8) : (i += 1) {
395 if (((bin[i / 8] >> @intCast(u3, (7 - (i % 8)))) & 0x1) == 0) {
396 try BigInt.mul(&r1, r, &r1);
397 try mod(&r1, &r1, n, allocator);
398 try BigInt.sqr(r, r);
399 try mod(r, r, n, allocator);
400 } else {
401 try BigInt.mul(r, r, &r1);
402 try mod(r, r, n, allocator);
403 try BigInt.sqr(&r1, &r1);
404 try mod(&r1, &r1, n, allocator);
405 }
406 }
407 }
408
409 fn toBytes(out: []u8, a: *const BigInt, allocator: std.mem.Allocator) !void {
410 const Error = error{
411 BufferTooSmall,
412 };
413
414 var mask = try BigInt.initSet(allocator, 0xFF);
415 defer mask.deinit();
416 var tmp = try BigInt.init(allocator);
417 defer tmp.deinit();
418
419 var a_copy = try BigInt.init(allocator);
420 defer a_copy.deinit();
421 try a_copy.copy(a.toConst());
422
423 // Encoding into big-endian bytes
424 var i: usize = 0;
425 while (i < out.len) : (i += 1) {
426 try tmp.bitAnd(&a_copy, &mask);
427 const b = try tmp.to(u8);
428 out[out.len - i - 1] = b;
429 try a_copy.shiftRight(&a_copy, 8);
430 }
431
432 if (!a_copy.eqZero()) {
433 return Error.BufferTooSmall;
434 }
435 }
436
437 fn mod(rem: *BigInt, a: *const BigInt, n: *const BigInt, allocator: std.mem.Allocator) !void {
438 var q = try BigInt.init(allocator);
439 defer q.deinit();
440
441 try BigInt.divFloor(&q, rem, a, n);
442 }
443
444 // TODO: flush the toilet
445 const poop = std.heap.page_allocator;
446};
lib/std/crypto/Certificate/Bundle.zig created+174
...@@ -0,0 +1,174 @@
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 of the containing
6//! certificate within `bytes`.
7
8/// The key is the contents slice of the subject.
9map: std.HashMapUnmanaged(der.Element.Slice, u32, MapContext, std.hash_map.default_max_load_percentage) = .{},
10bytes: std.ArrayListUnmanaged(u8) = .{},
11
12pub fn verify(cb: Bundle, subject: Certificate.Parsed) !void {
13 const bytes_index = cb.find(subject.issuer()) orelse return error.IssuerNotFound;
14 const issuer_cert: Certificate = .{
15 .buffer = cb.bytes.items,
16 .index = bytes_index,
17 };
18 const issuer = try issuer_cert.parse();
19 try subject.verify(issuer);
20}
21
22/// The returned bytes become invalid after calling any of the rescan functions
23/// or add functions.
24pub fn find(cb: Bundle, subject_name: []const u8) ?u32 {
25 const Adapter = struct {
26 cb: Bundle,
27
28 pub fn hash(ctx: @This(), k: []const u8) u64 {
29 _ = ctx;
30 return std.hash_map.hashString(k);
31 }
32
33 pub fn eql(ctx: @This(), a: []const u8, b_key: der.Element.Slice) bool {
34 const b = ctx.cb.bytes.items[b_key.start..b_key.end];
35 return mem.eql(u8, a, b);
36 }
37 };
38 return cb.map.getAdapted(subject_name, Adapter{ .cb = cb });
39}
40
41pub fn deinit(cb: *Bundle, gpa: Allocator) void {
42 cb.map.deinit(gpa);
43 cb.bytes.deinit(gpa);
44 cb.* = undefined;
45}
46
47/// Empties the set of certificates and then scans the host operating system
48/// file system standard locations for certificates.
49pub fn rescan(cb: *Bundle, gpa: Allocator) !void {
50 switch (builtin.os.tag) {
51 .linux => return rescanLinux(cb, gpa),
52 else => @compileError("it is unknown where the root CA certificates live on this OS"),
53 }
54}
55
56pub fn rescanLinux(cb: *Bundle, gpa: Allocator) !void {
57 var dir = fs.openIterableDirAbsolute("/etc/ssl/certs", .{}) catch |err| switch (err) {
58 error.FileNotFound => return,
59 else => |e| return e,
60 };
61 defer dir.close();
62
63 cb.bytes.clearRetainingCapacity();
64 cb.map.clearRetainingCapacity();
65
66 var it = dir.iterate();
67 while (try it.next()) |entry| {
68 switch (entry.kind) {
69 .File, .SymLink => {},
70 else => continue,
71 }
72
73 try addCertsFromFile(cb, gpa, dir.dir, entry.name);
74 }
75
76 cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len);
77}
78
79pub fn addCertsFromFile(
80 cb: *Bundle,
81 gpa: Allocator,
82 dir: fs.Dir,
83 sub_file_path: []const u8,
84) !void {
85 var file = try dir.openFile(sub_file_path, .{});
86 defer file.close();
87
88 const size = try file.getEndPos();
89
90 // We borrow `bytes` as a temporary buffer for the base64-encoded data.
91 // This is possible by computing the decoded length and reserving the space
92 // for the decoded bytes first.
93 const decoded_size_upper_bound = size / 4 * 3;
94 try cb.bytes.ensureUnusedCapacity(gpa, decoded_size_upper_bound + size);
95 const end_reserved = cb.bytes.items.len + decoded_size_upper_bound;
96 const buffer = cb.bytes.allocatedSlice()[end_reserved..];
97 const end_index = try file.readAll(buffer);
98 const encoded_bytes = buffer[0..end_index];
99
100 const begin_marker = "-----BEGIN CERTIFICATE-----";
101 const end_marker = "-----END CERTIFICATE-----";
102
103 var start_index: usize = 0;
104 while (mem.indexOfPos(u8, encoded_bytes, start_index, begin_marker)) |begin_marker_start| {
105 const cert_start = begin_marker_start + begin_marker.len;
106 const cert_end = mem.indexOfPos(u8, encoded_bytes, cert_start, end_marker) orelse
107 return error.MissingEndCertificateMarker;
108 start_index = cert_end + end_marker.len;
109 const encoded_cert = mem.trim(u8, encoded_bytes[cert_start..cert_end], " \t\r\n");
110 const decoded_start = @intCast(u32, cb.bytes.items.len);
111 const dest_buf = cb.bytes.allocatedSlice()[decoded_start..];
112 cb.bytes.items.len += try base64.decode(dest_buf, encoded_cert);
113 const k = try cb.key(decoded_start);
114 const gop = try cb.map.getOrPutContext(gpa, k, .{ .cb = cb });
115 if (gop.found_existing) {
116 cb.bytes.items.len = decoded_start;
117 } else {
118 gop.value_ptr.* = decoded_start;
119 }
120 }
121}
122
123pub fn key(cb: Bundle, bytes_index: u32) !der.Element.Slice {
124 const bytes = cb.bytes.items;
125 const certificate = try der.parseElement(bytes, bytes_index);
126 const tbs_certificate = try der.parseElement(bytes, certificate.slice.start);
127 const version = try der.parseElement(bytes, tbs_certificate.slice.start);
128 try Certificate.checkVersion(bytes, version);
129 const serial_number = try der.parseElement(bytes, version.slice.end);
130 const signature = try der.parseElement(bytes, serial_number.slice.end);
131 const issuer = try der.parseElement(bytes, signature.slice.end);
132 const validity = try der.parseElement(bytes, issuer.slice.end);
133 const subject = try der.parseElement(bytes, validity.slice.end);
134
135 return subject.slice;
136}
137
138const builtin = @import("builtin");
139const std = @import("../../std.zig");
140const fs = std.fs;
141const mem = std.mem;
142const crypto = std.crypto;
143const Allocator = std.mem.Allocator;
144const der = std.crypto.der;
145const Certificate = std.crypto.Certificate;
146const Bundle = @This();
147
148const base64 = std.base64.standard.decoderWithIgnore(" \t\r\n");
149
150const MapContext = struct {
151 cb: *const Bundle,
152
153 pub fn hash(ctx: MapContext, k: der.Element.Slice) u64 {
154 return std.hash_map.hashString(ctx.cb.bytes.items[k.start..k.end]);
155 }
156
157 pub fn eql(ctx: MapContext, a: der.Element.Slice, b: der.Element.Slice) bool {
158 const bytes = ctx.cb.bytes.items;
159 return mem.eql(
160 u8,
161 bytes[a.start..a.end],
162 bytes[b.start..b.end],
163 );
164 }
165};
166
167test "scan for OS-provided certificates" {
168 if (builtin.os.tag == .wasi) return error.SkipZigTest;
169
170 var bundle: Bundle = .{};
171 defer bundle.deinit(std.testing.allocator);
172
173 try bundle.rescan(std.testing.allocator);
174}
lib/std/crypto/CertificateBundle.zig deleted-593
...@@ -1,593 +0,0 @@
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 of the containing
6//! certificate within `bytes`.
7
8map: std.HashMapUnmanaged(Key, u32, MapContext, std.hash_map.default_max_load_percentage) = .{},
9bytes: std.ArrayListUnmanaged(u8) = .{},
10
11pub const Key = struct {
12 subject_start: u32,
13 subject_end: u32,
14};
15
16pub fn verify(cb: CertificateBundle, subject: Certificate.Parsed) !void {
17 const bytes_index = cb.find(subject.issuer) orelse return error.IssuerNotFound;
18 const issuer_cert: Certificate = .{
19 .buffer = cb.bytes.items,
20 .index = bytes_index,
21 };
22 const issuer = try issuer_cert.parse();
23 try subject.verify(issuer);
24}
25
26/// The returned bytes become invalid after calling any of the rescan functions
27/// or add functions.
28pub fn find(cb: CertificateBundle, subject_name: []const u8) ?u32 {
29 const Adapter = struct {
30 cb: CertificateBundle,
31
32 pub fn hash(ctx: @This(), k: []const u8) u64 {
33 _ = ctx;
34 return std.hash_map.hashString(k);
35 }
36
37 pub fn eql(ctx: @This(), a: []const u8, b_key: Key) bool {
38 const b = ctx.cb.bytes.items[b_key.subject_start..b_key.subject_end];
39 return mem.eql(u8, a, b);
40 }
41 };
42 return cb.map.getAdapted(subject_name, Adapter{ .cb = cb });
43}
44
45pub fn deinit(cb: *CertificateBundle, gpa: Allocator) void {
46 cb.map.deinit(gpa);
47 cb.bytes.deinit(gpa);
48 cb.* = undefined;
49}
50
51/// Empties the set of certificates and then scans the host operating system
52/// file system standard locations for certificates.
53pub fn rescan(cb: *CertificateBundle, gpa: Allocator) !void {
54 switch (builtin.os.tag) {
55 .linux => return rescanLinux(cb, gpa),
56 else => @compileError("it is unknown where the root CA certificates live on this OS"),
57 }
58}
59
60pub fn rescanLinux(cb: *CertificateBundle, gpa: Allocator) !void {
61 var dir = fs.openIterableDirAbsolute("/etc/ssl/certs", .{}) catch |err| switch (err) {
62 error.FileNotFound => return,
63 else => |e| return e,
64 };
65 defer dir.close();
66
67 cb.bytes.clearRetainingCapacity();
68 cb.map.clearRetainingCapacity();
69
70 var it = dir.iterate();
71 while (try it.next()) |entry| {
72 switch (entry.kind) {
73 .File, .SymLink => {},
74 else => continue,
75 }
76
77 try addCertsFromFile(cb, gpa, dir.dir, entry.name);
78 }
79
80 cb.bytes.shrinkAndFree(gpa, cb.bytes.items.len);
81}
82
83pub fn addCertsFromFile(
84 cb: *CertificateBundle,
85 gpa: Allocator,
86 dir: fs.Dir,
87 sub_file_path: []const u8,
88) !void {
89 var file = try dir.openFile(sub_file_path, .{});
90 defer file.close();
91
92 const size = try file.getEndPos();
93
94 // We borrow `bytes` as a temporary buffer for the base64-encoded data.
95 // This is possible by computing the decoded length and reserving the space
96 // for the decoded bytes first.
97 const decoded_size_upper_bound = size / 4 * 3;
98 try cb.bytes.ensureUnusedCapacity(gpa, decoded_size_upper_bound + size);
99 const end_reserved = cb.bytes.items.len + decoded_size_upper_bound;
100 const buffer = cb.bytes.allocatedSlice()[end_reserved..];
101 const end_index = try file.readAll(buffer);
102 const encoded_bytes = buffer[0..end_index];
103
104 const begin_marker = "-----BEGIN CERTIFICATE-----";
105 const end_marker = "-----END CERTIFICATE-----";
106
107 var start_index: usize = 0;
108 while (mem.indexOfPos(u8, encoded_bytes, start_index, begin_marker)) |begin_marker_start| {
109 const cert_start = begin_marker_start + begin_marker.len;
110 const cert_end = mem.indexOfPos(u8, encoded_bytes, cert_start, end_marker) orelse
111 return error.MissingEndCertificateMarker;
112 start_index = cert_end + end_marker.len;
113 const encoded_cert = mem.trim(u8, encoded_bytes[cert_start..cert_end], " \t\r\n");
114 const decoded_start = @intCast(u32, cb.bytes.items.len);
115 const dest_buf = cb.bytes.allocatedSlice()[decoded_start..];
116 cb.bytes.items.len += try base64.decode(dest_buf, encoded_cert);
117 const k = try cb.key(decoded_start);
118 const gop = try cb.map.getOrPutContext(gpa, k, .{ .cb = cb });
119 if (gop.found_existing) {
120 cb.bytes.items.len = decoded_start;
121 } else {
122 gop.value_ptr.* = decoded_start;
123 }
124 }
125}
126
127pub fn key(cb: CertificateBundle, bytes_index: u32) !Key {
128 const bytes = cb.bytes.items;
129 const certificate = try Der.parseElement(bytes, bytes_index);
130 const tbs_certificate = try Der.parseElement(bytes, certificate.start);
131 const version = try Der.parseElement(bytes, tbs_certificate.start);
132 try checkVersion(bytes, version);
133 const serial_number = try Der.parseElement(bytes, version.end);
134 const signature = try Der.parseElement(bytes, serial_number.end);
135 const issuer = try Der.parseElement(bytes, signature.end);
136 const validity = try Der.parseElement(bytes, issuer.end);
137 const subject = try Der.parseElement(bytes, validity.end);
138
139 return .{
140 .subject_start = subject.start,
141 .subject_end = subject.end,
142 };
143}
144
145pub const Certificate = struct {
146 buffer: []const u8,
147 index: u32,
148
149 pub const Algorithm = enum {
150 sha1WithRSAEncryption,
151 sha224WithRSAEncryption,
152 sha256WithRSAEncryption,
153 sha384WithRSAEncryption,
154 sha512WithRSAEncryption,
155
156 pub const map = std.ComptimeStringMap(Algorithm, .{
157 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 }, .sha1WithRSAEncryption },
158 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0B }, .sha256WithRSAEncryption },
159 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C }, .sha384WithRSAEncryption },
160 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0D }, .sha512WithRSAEncryption },
161 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0E }, .sha224WithRSAEncryption },
162 });
163
164 pub fn Hash(comptime algorithm: Algorithm) type {
165 return switch (algorithm) {
166 .sha1WithRSAEncryption => crypto.hash.Sha1,
167 .sha224WithRSAEncryption => crypto.hash.sha2.Sha224,
168 .sha256WithRSAEncryption => crypto.hash.sha2.Sha256,
169 .sha384WithRSAEncryption => crypto.hash.sha2.Sha384,
170 .sha512WithRSAEncryption => crypto.hash.sha2.Sha512,
171 };
172 }
173 };
174
175 pub const AlgorithmCategory = enum {
176 rsaEncryption,
177 X9_62_id_ecPublicKey,
178
179 pub const map = std.ComptimeStringMap(AlgorithmCategory, .{
180 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }, .rsaEncryption },
181 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01 }, .X9_62_id_ecPublicKey },
182 });
183 };
184
185 pub const Attribute = enum {
186 commonName,
187 serialNumber,
188 countryName,
189 localityName,
190 stateOrProvinceName,
191 organizationName,
192 organizationalUnitName,
193 organizationIdentifier,
194
195 pub const map = std.ComptimeStringMap(Attribute, .{
196 .{ &[_]u8{ 0x55, 0x04, 0x03 }, .commonName },
197 .{ &[_]u8{ 0x55, 0x04, 0x05 }, .serialNumber },
198 .{ &[_]u8{ 0x55, 0x04, 0x06 }, .countryName },
199 .{ &[_]u8{ 0x55, 0x04, 0x07 }, .localityName },
200 .{ &[_]u8{ 0x55, 0x04, 0x08 }, .stateOrProvinceName },
201 .{ &[_]u8{ 0x55, 0x04, 0x0A }, .organizationName },
202 .{ &[_]u8{ 0x55, 0x04, 0x0B }, .organizationalUnitName },
203 .{ &[_]u8{ 0x55, 0x04, 0x61 }, .organizationIdentifier },
204 });
205 };
206
207 pub const Parsed = struct {
208 certificate: Certificate,
209 issuer: []const u8,
210 subject: []const u8,
211 common_name: []const u8,
212 signature: []const u8,
213 signature_algorithm: Algorithm,
214 message: []const u8,
215 pub_key_algo: AlgorithmCategory,
216 pub_key: []const u8,
217
218 pub fn verify(subject: Parsed, issuer: Parsed) !void {
219 // Check that the subject's issuer name matches the issuer's
220 // subject name.
221 if (!mem.eql(u8, subject.issuer, issuer.subject)) {
222 return error.CertificateIssuerMismatch;
223 }
224
225 // TODO check the time validity for the subject
226 // TODO check the time validity for the issuer
227
228 switch (subject.signature_algorithm) {
229 inline .sha1WithRSAEncryption,
230 .sha224WithRSAEncryption,
231 .sha256WithRSAEncryption,
232 .sha384WithRSAEncryption,
233 .sha512WithRSAEncryption,
234 => |algorithm| return verifyRsa(
235 algorithm.Hash(),
236 subject.message,
237 subject.signature,
238 issuer.pub_key_algo,
239 issuer.pub_key,
240 ),
241 }
242 }
243 };
244
245 pub fn parse(cert: Certificate) !Parsed {
246 const cert_bytes = cert.buffer;
247 const certificate = try Der.parseElement(cert_bytes, cert.index);
248 const tbs_certificate = try Der.parseElement(cert_bytes, certificate.start);
249 const version = try Der.parseElement(cert_bytes, tbs_certificate.start);
250 try checkVersion(cert_bytes, version);
251 const serial_number = try Der.parseElement(cert_bytes, version.end);
252 // RFC 5280, section 4.1.2.3:
253 // "This field MUST contain the same algorithm identifier as
254 // the signatureAlgorithm field in the sequence Certificate."
255 const tbs_signature = try Der.parseElement(cert_bytes, serial_number.end);
256 const issuer = try Der.parseElement(cert_bytes, tbs_signature.end);
257 const validity = try Der.parseElement(cert_bytes, issuer.end);
258 const subject = try Der.parseElement(cert_bytes, validity.end);
259
260 const pub_key_info = try Der.parseElement(cert_bytes, subject.end);
261 const pub_key_signature_algorithm = try Der.parseElement(cert_bytes, pub_key_info.start);
262 const pub_key_algo_elem = try Der.parseElement(cert_bytes, pub_key_signature_algorithm.start);
263 const pub_key_algo = try parseAlgorithmCategory(cert_bytes, pub_key_algo_elem);
264 const pub_key_elem = try Der.parseElement(cert_bytes, pub_key_signature_algorithm.end);
265 const pub_key = try parseBitString(cert, pub_key_elem);
266
267 const rdn = try Der.parseElement(cert_bytes, subject.start);
268 const atav = try Der.parseElement(cert_bytes, rdn.start);
269
270 var common_name: []const u8 = &.{};
271 var atav_i = atav.start;
272 while (atav_i < atav.end) {
273 const ty_elem = try Der.parseElement(cert_bytes, atav_i);
274 const ty = try parseAttribute(cert_bytes, ty_elem);
275 const val = try Der.parseElement(cert_bytes, ty_elem.end);
276 switch (ty) {
277 .commonName => common_name = cert.contents(val),
278 else => {},
279 }
280 atav_i = val.end;
281 }
282
283 const sig_algo = try Der.parseElement(cert_bytes, tbs_certificate.end);
284 const algo_elem = try Der.parseElement(cert_bytes, sig_algo.start);
285 const signature_algorithm = try parseAlgorithm(cert_bytes, algo_elem);
286 const sig_elem = try Der.parseElement(cert_bytes, sig_algo.end);
287 const signature = try parseBitString(cert, sig_elem);
288
289 return .{
290 .certificate = cert,
291 .common_name = common_name,
292 .issuer = cert.contents(issuer),
293 .subject = cert.contents(subject),
294 .signature = signature,
295 .signature_algorithm = signature_algorithm,
296 .message = cert_bytes[certificate.start..tbs_certificate.end],
297 .pub_key_algo = pub_key_algo,
298 .pub_key = pub_key,
299 };
300 }
301
302 pub fn verify(subject: Certificate, issuer: Certificate) !void {
303 const parsed_subject = try subject.parse();
304 const parsed_issuer = try issuer.parse();
305 return parsed_subject.verify(parsed_issuer);
306 }
307
308 pub fn contents(cert: Certificate, elem: Der.Element) []const u8 {
309 return cert.buffer[elem.start..elem.end];
310 }
311
312 pub fn parseBitString(cert: Certificate, elem: Der.Element) ![]const u8 {
313 if (elem.identifier.tag != .bitstring) return error.CertificateFieldHasWrongDataType;
314 if (cert.buffer[elem.start] != 0) return error.CertificateHasInvalidBitString;
315 return cert.buffer[elem.start + 1 .. elem.end];
316 }
317
318 pub fn parseAlgorithm(bytes: []const u8, element: Der.Element) !Algorithm {
319 if (element.identifier.tag != .object_identifier)
320 return error.CertificateFieldHasWrongDataType;
321 return Algorithm.map.get(bytes[element.start..element.end]) orelse
322 return error.CertificateHasUnrecognizedAlgorithm;
323 }
324
325 pub fn parseAlgorithmCategory(bytes: []const u8, element: Der.Element) !AlgorithmCategory {
326 if (element.identifier.tag != .object_identifier)
327 return error.CertificateFieldHasWrongDataType;
328 return AlgorithmCategory.map.get(bytes[element.start..element.end]) orelse {
329 std.debug.print("unrecognized algorithm category: {}\n", .{std.fmt.fmtSliceHexLower(bytes[element.start..element.end])});
330 return error.CertificateHasUnrecognizedAlgorithmCategory;
331 };
332 }
333
334 pub fn parseAttribute(bytes: []const u8, element: Der.Element) !Attribute {
335 if (element.identifier.tag != .object_identifier)
336 return error.CertificateFieldHasWrongDataType;
337 return Attribute.map.get(bytes[element.start..element.end]) orelse
338 return error.CertificateHasUnrecognizedAlgorithm;
339 }
340
341 fn verifyRsa(comptime Hash: type, message: []const u8, sig: []const u8, pub_key_algo: AlgorithmCategory, pub_key: []const u8) !void {
342 if (pub_key_algo != .rsaEncryption) return error.CertificateSignatureAlgorithmMismatch;
343 const pub_key_seq = try Der.parseElement(pub_key, 0);
344 if (pub_key_seq.identifier.tag != .sequence) return error.CertificateFieldHasWrongDataType;
345 const modulus_elem = try Der.parseElement(pub_key, pub_key_seq.start);
346 if (modulus_elem.identifier.tag != .integer) return error.CertificateFieldHasWrongDataType;
347 const exponent_elem = try Der.parseElement(pub_key, modulus_elem.end);
348 if (exponent_elem.identifier.tag != .integer) return error.CertificateFieldHasWrongDataType;
349 // Skip over meaningless zeroes in the modulus.
350 const modulus_raw = pub_key[modulus_elem.start..modulus_elem.end];
351 const modulus_offset = for (modulus_raw) |byte, i| {
352 if (byte != 0) break i;
353 } else modulus_raw.len;
354 const modulus = modulus_raw[modulus_offset..];
355 const exponent = pub_key[exponent_elem.start..exponent_elem.end];
356 if (exponent.len > modulus.len) return error.CertificatePublicKeyInvalid;
357 if (sig.len != modulus.len) return error.CertificateSignatureInvalidLength;
358
359 const hash_der = switch (Hash) {
360 crypto.hash.Sha1 => [_]u8{
361 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e,
362 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14,
363 },
364 crypto.hash.sha2.Sha224 => [_]u8{
365 0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
366 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05,
367 0x00, 0x04, 0x1c,
368 },
369 crypto.hash.sha2.Sha256 => [_]u8{
370 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
371 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
372 0x00, 0x04, 0x20,
373 },
374 crypto.hash.sha2.Sha384 => [_]u8{
375 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
376 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05,
377 0x00, 0x04, 0x30,
378 },
379 crypto.hash.sha2.Sha512 => [_]u8{
380 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
381 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
382 0x00, 0x04, 0x40,
383 },
384 else => @compileError("unreachable"),
385 };
386
387 var msg_hashed: [Hash.digest_length]u8 = undefined;
388 Hash.hash(message, &msg_hashed, .{});
389
390 switch (modulus.len) {
391 inline 128, 256, 512 => |modulus_len| {
392 const ps_len = modulus_len - (hash_der.len + msg_hashed.len) - 3;
393 const em: [modulus_len]u8 =
394 [2]u8{ 0, 1 } ++
395 ([1]u8{0xff} ** ps_len) ++
396 [1]u8{0} ++
397 hash_der ++
398 msg_hashed;
399
400 const public_key = try rsa.PublicKey.fromBytes(exponent, modulus, rsa.poop);
401 const em_dec = try rsa.encrypt(modulus_len, sig[0..modulus_len].*, public_key, rsa.poop);
402
403 if (!mem.eql(u8, &em, &em_dec)) {
404 try std.testing.expectEqualSlices(u8, &em, &em_dec);
405 return error.CertificateSignatureInvalid;
406 }
407 },
408 else => {
409 return error.CertificateSignatureUnsupportedBitCount;
410 },
411 }
412 }
413};
414
415fn checkVersion(bytes: []const u8, version: Der.Element) !void {
416 if (@bitCast(u8, version.identifier) != 0xa0 or
417 !mem.eql(u8, bytes[version.start..version.end], "\x02\x01\x02"))
418 {
419 return error.UnsupportedCertificateVersion;
420 }
421}
422
423const builtin = @import("builtin");
424const std = @import("../std.zig");
425const fs = std.fs;
426const mem = std.mem;
427const crypto = std.crypto;
428const Allocator = std.mem.Allocator;
429const Der = std.crypto.Der;
430const CertificateBundle = @This();
431
432const base64 = std.base64.standard.decoderWithIgnore(" \t\r\n");
433
434const MapContext = struct {
435 cb: *const CertificateBundle,
436
437 pub fn hash(ctx: MapContext, k: Key) u64 {
438 return std.hash_map.hashString(ctx.cb.bytes.items[k.subject_start..k.subject_end]);
439 }
440
441 pub fn eql(ctx: MapContext, a: Key, b: Key) bool {
442 const bytes = ctx.cb.bytes.items;
443 return mem.eql(
444 u8,
445 bytes[a.subject_start..a.subject_end],
446 bytes[b.subject_start..b.subject_end],
447 );
448 }
449};
450
451test "scan for OS-provided certificates" {
452 if (builtin.os.tag == .wasi) return error.SkipZigTest;
453
454 var bundle: CertificateBundle = .{};
455 defer bundle.deinit(std.testing.allocator);
456
457 try bundle.rescan(std.testing.allocator);
458}
459
460/// TODO: replace this with Frank's upcoming RSA implementation. the verify
461/// function won't have the possibility of failure - it will either identify a
462/// valid signature or an invalid signature.
463/// This code is borrowed from https://github.com/shiguredo/tls13-zig
464/// which is licensed under the Apache License Version 2.0, January 2004
465/// http://www.apache.org/licenses/
466/// The code has been modified.
467const rsa = struct {
468 const BigInt = std.math.big.int.Managed;
469
470 const PublicKey = struct {
471 n: BigInt,
472 e: BigInt,
473
474 pub fn deinit(self: *PublicKey) void {
475 self.n.deinit();
476 self.e.deinit();
477 }
478
479 pub fn fromBytes(pub_bytes: []const u8, modulus_bytes: []const u8, allocator: std.mem.Allocator) !PublicKey {
480 var _n = try BigInt.init(allocator);
481 errdefer _n.deinit();
482 try setBytes(&_n, modulus_bytes, allocator);
483
484 var _e = try BigInt.init(allocator);
485 errdefer _e.deinit();
486 try setBytes(&_e, pub_bytes, allocator);
487
488 return .{
489 .n = _n,
490 .e = _e,
491 };
492 }
493 };
494
495 fn encrypt(comptime modulus_len: usize, msg: [modulus_len]u8, public_key: PublicKey, allocator: std.mem.Allocator) ![modulus_len]u8 {
496 var m = try BigInt.init(allocator);
497 defer m.deinit();
498
499 try setBytes(&m, &msg, allocator);
500
501 if (m.order(public_key.n) != .lt) {
502 return error.MessageTooLong;
503 }
504
505 var e = try BigInt.init(allocator);
506 defer e.deinit();
507
508 try pow_montgomery(&e, &m, &public_key.e, &public_key.n, allocator);
509
510 var res: [modulus_len]u8 = undefined;
511
512 try toBytes(&res, &e, allocator);
513
514 return res;
515 }
516
517 fn setBytes(r: *BigInt, bytes: []const u8, allcator: std.mem.Allocator) !void {
518 try r.set(0);
519 var tmp = try BigInt.init(allcator);
520 defer tmp.deinit();
521 for (bytes) |b| {
522 try r.shiftLeft(r, 8);
523 try tmp.set(b);
524 try r.add(r, &tmp);
525 }
526 }
527
528 fn pow_montgomery(r: *BigInt, a: *const BigInt, x: *const BigInt, n: *const BigInt, allocator: std.mem.Allocator) !void {
529 var bin_raw: [512]u8 = undefined;
530 try toBytes(&bin_raw, x, allocator);
531
532 var i: usize = 0;
533 while (bin_raw[i] == 0x00) : (i += 1) {}
534 const bin = bin_raw[i..];
535
536 try r.set(1);
537 var r1 = try BigInt.init(allocator);
538 defer r1.deinit();
539 try BigInt.copy(&r1, a.toConst());
540 i = 0;
541 while (i < bin.len * 8) : (i += 1) {
542 if (((bin[i / 8] >> @intCast(u3, (7 - (i % 8)))) & 0x1) == 0) {
543 try BigInt.mul(&r1, r, &r1);
544 try mod(&r1, &r1, n, allocator);
545 try BigInt.sqr(r, r);
546 try mod(r, r, n, allocator);
547 } else {
548 try BigInt.mul(r, r, &r1);
549 try mod(r, r, n, allocator);
550 try BigInt.sqr(&r1, &r1);
551 try mod(&r1, &r1, n, allocator);
552 }
553 }
554 }
555
556 fn toBytes(out: []u8, a: *const BigInt, allocator: std.mem.Allocator) !void {
557 const Error = error{
558 BufferTooSmall,
559 };
560
561 var mask = try BigInt.initSet(allocator, 0xFF);
562 defer mask.deinit();
563 var tmp = try BigInt.init(allocator);
564 defer tmp.deinit();
565
566 var a_copy = try BigInt.init(allocator);
567 defer a_copy.deinit();
568 try a_copy.copy(a.toConst());
569
570 // Encoding into big-endian bytes
571 var i: usize = 0;
572 while (i < out.len) : (i += 1) {
573 try tmp.bitAnd(&a_copy, &mask);
574 const b = try tmp.to(u8);
575 out[out.len - i - 1] = b;
576 try a_copy.shiftRight(&a_copy, 8);
577 }
578
579 if (!a_copy.eqZero()) {
580 return Error.BufferTooSmall;
581 }
582 }
583
584 fn mod(rem: *BigInt, a: *const BigInt, n: *const BigInt, allocator: std.mem.Allocator) !void {
585 var q = try BigInt.init(allocator);
586 defer q.deinit();
587
588 try BigInt.divFloor(&q, rem, a, n);
589 }
590
591 // TODO: flush the toilet
592 const poop = std.heap.page_allocator;
593};
lib/std/crypto/Der.zig deleted-153
...@@ -1,153 +0,0 @@
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/der.zig created+163
...@@ -0,0 +1,163 @@
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 slice: Slice,
103
104 pub const Slice = struct {
105 start: u32,
106 end: u32,
107
108 pub const empty: Slice = .{ .start = 0, .end = 0 };
109 };
110};
111
112pub const ParseElementError = error{CertificateHasFieldWithInvalidLength};
113
114pub fn parseElement(bytes: []const u8, index: u32) ParseElementError!Element {
115 var i = index;
116 const identifier = @bitCast(Identifier, bytes[i]);
117 i += 1;
118 const size_byte = bytes[i];
119 i += 1;
120 if ((size_byte >> 7) == 0) {
121 return .{
122 .identifier = identifier,
123 .slice = .{
124 .start = i,
125 .end = i + size_byte,
126 },
127 };
128 }
129
130 const len_size = @truncate(u7, size_byte);
131 if (len_size > @sizeOf(u32)) {
132 return error.CertificateHasFieldWithInvalidLength;
133 }
134
135 const end_i = i + len_size;
136 var long_form_size: u32 = 0;
137 while (i < end_i) : (i += 1) {
138 long_form_size = (long_form_size << 8) | bytes[i];
139 }
140
141 return .{
142 .identifier = identifier,
143 .slice = .{
144 .start = i,
145 .end = i + long_form_size,
146 },
147 };
148}
149
150pub const ParseObjectIdError = error{
151 CertificateHasUnrecognizedObjectId,
152 CertificateFieldHasWrongDataType,
153} || ParseElementError;
154
155pub fn parseObjectId(bytes: []const u8, element: Element) ParseObjectIdError!Oid {
156 if (element.identifier.tag != .object_identifier)
157 return error.CertificateFieldHasWrongDataType;
158 return Oid.map.get(bytes[element.slice.start..element.slice.end]) orelse
159 return error.CertificateHasUnrecognizedObjectId;
160}
161
162const std = @import("../std.zig");
163const der = @This();
lib/std/crypto/tls/Client.zig+36-16
...@@ -1,6 +1,5 @@...@@ -1,6 +1,5 @@
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;
4const Client = @This();3const Client = @This();
5const net = std.net;4const net = std.net;
6const mem = std.mem;5const mem = std.mem;
...@@ -18,7 +17,7 @@ const int2 = tls.int2;...@@ -18,7 +17,7 @@ const int2 = tls.int2;
18const int3 = tls.int3;17const int3 = tls.int3;
19const array = tls.array;18const array = tls.array;
20const enum_array = tls.enum_array;19const enum_array = tls.enum_array;
21const Certificate = crypto.CertificateBundle.Certificate;20const Certificate = crypto.Certificate;
2221
23application_cipher: ApplicationCipher,22application_cipher: ApplicationCipher,
24read_seq: u64,23read_seq: u64,
...@@ -30,7 +29,7 @@ partially_read_len: u15,...@@ -30,7 +29,7 @@ partially_read_len: u15,
30eof: bool,29eof: bool,
3130
32/// `host` is only borrowed during this function call.31/// `host` is only borrowed during this function call.
33pub fn init(stream: net.Stream, ca_bundle: crypto.CertificateBundle, host: []const u8) !Client {32pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8) !Client {
34 const host_len = @intCast(u16, host.len);33 const host_len = @intCast(u16, host.len);
3534
36 var random_buffer: [128]u8 = undefined;35 var random_buffer: [128]u8 = undefined;
...@@ -298,9 +297,19 @@ pub fn init(stream: net.Stream, ca_bundle: crypto.CertificateBundle, host: []con...@@ -298,9 +297,19 @@ pub fn init(stream: net.Stream, ca_bundle: crypto.CertificateBundle, host: []con
298 break :i end;297 break :i end;
299 };298 };
300299
300 // This is used for two purposes:
301 // * Detect whether a certificate is the first one presented, in which case
302 // we need to verify the host name.
303 // * Flip back and forth between the two cleartext buffers in order to keep
304 // the previous certificate in memory so that it can be verified by the
305 // next one.
306 var cert_index: usize = 0;
301 var read_seq: u64 = 0;307 var read_seq: u64 = 0;
302 var validated_cert = false;308 var prev_cert: Certificate.Parsed = undefined;
303 var is_subsequent_cert = false;309 // Set to true once a trust chain has been established from the first
310 // certificate to a root CA.
311 var cert_verification_done = false;
312 var cleartext_bufs: [2][8000]u8 = undefined;
304313
305 while (true) {314 while (true) {
306 const end_hdr = i + 5;315 const end_hdr = i + 5;
...@@ -328,7 +337,8 @@ pub fn init(stream: net.Stream, ca_bundle: crypto.CertificateBundle, host: []con...@@ -328,7 +337,8 @@ pub fn init(stream: net.Stream, ca_bundle: crypto.CertificateBundle, host: []con
328 if (handshake_buf[i] != 0x01) return error.TlsUnexpectedMessage;337 if (handshake_buf[i] != 0x01) return error.TlsUnexpectedMessage;
329 },338 },
330 .application_data => {339 .application_data => {
331 var cleartext_buf: [8000]u8 = undefined;340 const cleartext_buf = &cleartext_bufs[cert_index % 2];
341
332 const cleartext = switch (handshake_cipher) {342 const cleartext = switch (handshake_cipher) {
333 inline else => |*p| c: {343 inline else => |*p| c: {
334 const P = @TypeOf(p.*);344 const P = @TypeOf(p.*);
...@@ -393,7 +403,7 @@ pub fn init(stream: net.Stream, ca_bundle: crypto.CertificateBundle, host: []con...@@ -393,7 +403,7 @@ pub fn init(stream: net.Stream, ca_bundle: crypto.CertificateBundle, host: []con
393 switch (handshake_cipher) {403 switch (handshake_cipher) {
394 inline else => |*p| p.transcript_hash.update(wrapped_handshake),404 inline else => |*p| p.transcript_hash.update(wrapped_handshake),
395 }405 }
396 if (validated_cert) break :cert;406 if (cert_verification_done) break :cert;
397 var hs_i: u32 = 0;407 var hs_i: u32 = 0;
398 const cert_req_ctx_len = handshake[hs_i];408 const cert_req_ctx_len = handshake[hs_i];
399 hs_i += 1;409 hs_i += 1;
...@@ -411,12 +421,22 @@ pub fn init(stream: net.Stream, ca_bundle: crypto.CertificateBundle, host: []con...@@ -411,12 +421,22 @@ pub fn init(stream: net.Stream, ca_bundle: crypto.CertificateBundle, host: []con
411 .index = hs_i,421 .index = hs_i,
412 };422 };
413 const subject = try subject_cert.parse();423 const subject = try subject_cert.parse();
414 if (!is_subsequent_cert) {424 if (cert_index > 0) {
415 is_subsequent_cert = true;425 if (prev_cert.verify(subject)) |_| {
416 if (mem.eql(u8, subject.common_name, host)) {426 std.debug.print("previous certificate verified\n", .{});
427 } else |err| {
428 std.debug.print("unable to validate previous cert: {s}\n", .{
429 @errorName(err),
430 });
431 }
432 } else {
433 // Verify the host on the first certificate.
434 const common_name = subject.commonName();
435 if (mem.eql(u8, common_name, host)) {
417 std.debug.print("exact host match\n", .{});436 std.debug.print("exact host match\n", .{});
418 } else if (mem.startsWith(u8, subject.common_name, "*.") and437 } else if (mem.startsWith(u8, common_name, "*.") and
419 mem.eql(u8, subject.common_name[2..], host))438 (mem.endsWith(u8, host, common_name[1..]) or
439 mem.eql(u8, common_name[2..], host)))
420 {440 {
421 std.debug.print("wildcard host match\n", .{});441 std.debug.print("wildcard host match\n", .{});
422 } else {442 } else {
...@@ -427,17 +447,17 @@ pub fn init(stream: net.Stream, ca_bundle: crypto.CertificateBundle, host: []con...@@ -427,17 +447,17 @@ pub fn init(stream: net.Stream, ca_bundle: crypto.CertificateBundle, host: []con
427447
428 if (ca_bundle.verify(subject)) |_| {448 if (ca_bundle.verify(subject)) |_| {
429 std.debug.print("found a root CA cert matching issuer. verification success!\n", .{});449 std.debug.print("found a root CA cert matching issuer. verification success!\n", .{});
430 validated_cert = true;450 cert_verification_done = true;
431 break :cert;451 break :cert;
432 } else |err| {452 } else |err| {
433 std.debug.print("unable to validate cert against system root CAs: {s}\n", .{453 std.debug.print("unable to validate cert against system root CAs: {s}\n", .{
434 @errorName(err),454 @errorName(err),
435 });455 });
436 // TODO handle a certificate
437 // signing chain that ends in a
438 // root-validated one.
439 }456 }
440457
458 prev_cert = subject;
459 cert_index += 1;
460
441 hs_i = end_cert;461 hs_i = end_cert;
442 const total_ext_size = mem.readIntBig(u16, handshake[hs_i..][0..2]);462 const total_ext_size = mem.readIntBig(u16, handshake[hs_i..][0..2]);
443 hs_i += 2;463 hs_i += 2;
lib/std/http/Client.zig+1-1
...@@ -7,7 +7,7 @@ const Client = @This();...@@ -7,7 +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 = .{},10ca_bundle: std.crypto.Certificate.Bundle = .{},
1111
12pub const Request = struct {12pub const Request = struct {
13 client: *Client,13 client: *Client,