authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-12-20 19:26:23-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-02 16:57:15-07:00
log244a97e8ada5349136ca642d89092dbaf6e52ae2
treefb6cadc03e1445f48d4681c7ddaee4941ee175c0
parent504070e8fc50de0c354f6322115e08fe99503578

std.crypto.tls: certificate signature validation


2 files changed, 327 insertions(+), 38 deletions(-)

lib/std/crypto/CertificateBundle.zig+303-10
...@@ -15,7 +15,7 @@ pub const Key = struct {...@@ -15,7 +15,7 @@ pub const Key = struct {
1515
16/// The returned bytes become invalid after calling any of the rescan functions16/// The returned bytes become invalid after calling any of the rescan functions
17/// or add functions.17/// or add functions.
18pub fn find(cb: CertificateBundle, subject_name: []const u8) ?[]const u8 {18pub fn find(cb: CertificateBundle, subject_name: []const u8) ?u32 {
19 const Adapter = struct {19 const Adapter = struct {
20 cb: CertificateBundle,20 cb: CertificateBundle,
2121
...@@ -29,8 +29,7 @@ pub fn find(cb: CertificateBundle, subject_name: []const u8) ?[]const u8 {...@@ -29,8 +29,7 @@ pub fn find(cb: CertificateBundle, subject_name: []const u8) ?[]const u8 {
29 return mem.eql(u8, a, b);29 return mem.eql(u8, a, b);
30 }30 }
31 };31 };
32 const index = cb.map.getAdapted(subject_name, Adapter{ .cb = cb }) orelse return null;32 return cb.map.getAdapted(subject_name, Adapter{ .cb = cb });
33 return cb.bytes.items[index..];
34}33}
3534
36pub fn deinit(cb: *CertificateBundle, gpa: Allocator) void {35pub fn deinit(cb: *CertificateBundle, gpa: Allocator) void {
...@@ -105,7 +104,7 @@ pub fn addCertsFromFile(...@@ -105,7 +104,7 @@ pub fn addCertsFromFile(
105 const decoded_start = @intCast(u32, cb.bytes.items.len);104 const decoded_start = @intCast(u32, cb.bytes.items.len);
106 const dest_buf = cb.bytes.allocatedSlice()[decoded_start..];105 const dest_buf = cb.bytes.allocatedSlice()[decoded_start..];
107 cb.bytes.items.len += try base64.decode(dest_buf, encoded_cert);106 cb.bytes.items.len += try base64.decode(dest_buf, encoded_cert);
108 const k = try key(cb, decoded_start);107 const k = try cb.key(decoded_start);
109 const gop = try cb.map.getOrPutContext(gpa, k, .{ .cb = cb });108 const gop = try cb.map.getOrPutContext(gpa, k, .{ .cb = cb });
110 if (gop.found_existing) {109 if (gop.found_existing) {
111 cb.bytes.items.len = decoded_start;110 cb.bytes.items.len = decoded_start;
...@@ -115,16 +114,12 @@ pub fn addCertsFromFile(...@@ -115,16 +114,12 @@ pub fn addCertsFromFile(
115 }114 }
116}115}
117116
118pub fn key(cb: *CertificateBundle, bytes_index: u32) !Key {117pub fn key(cb: CertificateBundle, bytes_index: u32) !Key {
119 const bytes = cb.bytes.items;118 const bytes = cb.bytes.items;
120 const certificate = try Der.parseElement(bytes, bytes_index);119 const certificate = try Der.parseElement(bytes, bytes_index);
121 const tbs_certificate = try Der.parseElement(bytes, certificate.start);120 const tbs_certificate = try Der.parseElement(bytes, certificate.start);
122 const version = try Der.parseElement(bytes, tbs_certificate.start);121 const version = try Der.parseElement(bytes, tbs_certificate.start);
123 if (@bitCast(u8, version.identifier) != 0xa0 or122 try checkVersion(bytes, version);
124 !mem.eql(u8, bytes[version.start..version.end], "\x02\x01\x02"))
125 {
126 return error.UnsupportedCertificateVersion;
127 }
128123
129 const serial_number = try Der.parseElement(bytes, version.end);124 const serial_number = try Der.parseElement(bytes, version.end);
130125
...@@ -144,10 +139,173 @@ pub fn key(cb: *CertificateBundle, bytes_index: u32) !Key {...@@ -144,10 +139,173 @@ pub fn key(cb: *CertificateBundle, bytes_index: u32) !Key {
144 };139 };
145}140}
146141
142pub const Certificate = struct {
143 buffer: []const u8,
144 index: u32,
145
146 pub fn verify(subject: Certificate, issuer: Certificate) !void {
147 const subject_certificate = try Der.parseElement(subject.buffer, subject.index);
148 const subject_tbs_certificate = try Der.parseElement(subject.buffer, subject_certificate.start);
149 const subject_version = try Der.parseElement(subject.buffer, subject_tbs_certificate.start);
150 try checkVersion(subject.buffer, subject_version);
151 const subject_serial_number = try Der.parseElement(subject.buffer, subject_version.end);
152 // RFC 5280, section 4.1.2.3:
153 // "This field MUST contain the same algorithm identifier as
154 // the signatureAlgorithm field in the sequence Certificate."
155 const subject_signature = try Der.parseElement(subject.buffer, subject_serial_number.end);
156 const subject_issuer = try Der.parseElement(subject.buffer, subject_signature.end);
157 const subject_validity = try Der.parseElement(subject.buffer, subject_issuer.end);
158 //const subject_name = try Der.parseElement(subject.buffer, subject_validity.end);
159
160 const subject_sig_algo = try Der.parseElement(subject.buffer, subject_tbs_certificate.end);
161 const subject_algo_elem = try Der.parseElement(subject.buffer, subject_sig_algo.start);
162 const subject_algo = try Der.parseObjectId(subject.buffer, subject_algo_elem);
163 const subject_sig_elem = try Der.parseElement(subject.buffer, subject_sig_algo.end);
164 const subject_sig = try parseBitString(subject, subject_sig_elem);
165
166 const issuer_certificate = try Der.parseElement(issuer.buffer, issuer.index);
167 const issuer_tbs_certificate = try Der.parseElement(issuer.buffer, issuer_certificate.start);
168 const issuer_version = try Der.parseElement(issuer.buffer, issuer_tbs_certificate.start);
169 try checkVersion(issuer.buffer, issuer_version);
170 const issuer_serial_number = try Der.parseElement(issuer.buffer, issuer_version.end);
171 // RFC 5280, section 4.1.2.3:
172 // "This field MUST contain the same algorithm identifier as
173 // the signatureAlgorithm field in the sequence Certificate."
174 const issuer_signature = try Der.parseElement(issuer.buffer, issuer_serial_number.end);
175 const issuer_issuer = try Der.parseElement(issuer.buffer, issuer_signature.end);
176 const issuer_validity = try Der.parseElement(issuer.buffer, issuer_issuer.end);
177 const issuer_name = try Der.parseElement(issuer.buffer, issuer_validity.end);
178 const issuer_pub_key_info = try Der.parseElement(issuer.buffer, issuer_name.end);
179 const issuer_pub_key_signature_algorithm = try Der.parseElement(issuer.buffer, issuer_pub_key_info.start);
180 const issuer_pub_key_algo_elem = try Der.parseElement(issuer.buffer, issuer_pub_key_signature_algorithm.start);
181 const issuer_pub_key_algo = try Der.parseObjectId(issuer.buffer, issuer_pub_key_algo_elem);
182 const issuer_pub_key_elem = try Der.parseElement(issuer.buffer, issuer_pub_key_signature_algorithm.end);
183 const issuer_pub_key = try parseBitString(issuer, issuer_pub_key_elem);
184
185 // Check that the subject's issuer name matches the issuer's subject
186 // name.
187 if (!mem.eql(u8, subject.contents(subject_issuer), issuer.contents(issuer_name))) {
188 return error.CertificateIssuerMismatch;
189 }
190
191 // TODO check the time validity for the subject
192 _ = subject_validity;
193 // TODO check the time validity for the issuer
194
195 const message = subject.buffer[subject_certificate.start..subject_tbs_certificate.end];
196 //std.debug.print("issuer algo: {any} subject algo: {any}\n", .{ issuer_pub_key_algo, subject_algo });
197 switch (subject_algo) {
198 // zig fmt: off
199 .sha1WithRSAEncryption => return verifyRsa(crypto.hash.Sha1, message, subject_sig, issuer_pub_key_algo, issuer_pub_key),
200 .sha224WithRSAEncryption => return verifyRsa(crypto.hash.sha2.Sha224, message, subject_sig, issuer_pub_key_algo, issuer_pub_key),
201 .sha256WithRSAEncryption => return verifyRsa(crypto.hash.sha2.Sha256, message, subject_sig, issuer_pub_key_algo, issuer_pub_key),
202 .sha384WithRSAEncryption => return verifyRsa(crypto.hash.sha2.Sha384, message, subject_sig, issuer_pub_key_algo, issuer_pub_key),
203 .sha512WithRSAEncryption => return verifyRsa(crypto.hash.sha2.Sha512, message, subject_sig, issuer_pub_key_algo, issuer_pub_key),
204 // zig fmt: on
205 else => {
206 std.debug.print("unhandled algorithm: {any}\n", .{subject_algo});
207 return error.UnsupportedCertificateSignatureAlgorithm;
208 },
209 }
210 }
211
212 pub fn contents(cert: Certificate, elem: Der.Element) []const u8 {
213 return cert.buffer[elem.start..elem.end];
214 }
215
216 pub fn parseBitString(cert: Certificate, elem: Der.Element) ![]const u8 {
217 if (elem.identifier.tag != .bitstring) return error.CertificateFieldHasWrongDataType;
218 if (cert.buffer[elem.start] != 0) return error.CertificateHasInvalidBitString;
219 return cert.buffer[elem.start + 1 .. elem.end];
220 }
221
222 fn verifyRsa(comptime Hash: type, message: []const u8, sig: []const u8, pub_key_algo: Der.Oid, pub_key: []const u8) !void {
223 if (pub_key_algo != .rsaEncryption) return error.CertificateSignatureAlgorithmMismatch;
224 const pub_key_seq = try Der.parseElement(pub_key, 0);
225 if (pub_key_seq.identifier.tag != .sequence) return error.CertificateFieldHasWrongDataType;
226 const modulus_elem = try Der.parseElement(pub_key, pub_key_seq.start);
227 if (modulus_elem.identifier.tag != .integer) return error.CertificateFieldHasWrongDataType;
228 const exponent_elem = try Der.parseElement(pub_key, modulus_elem.end);
229 if (exponent_elem.identifier.tag != .integer) return error.CertificateFieldHasWrongDataType;
230 // Skip over meaningless zeroes in the modulus.
231 const modulus_raw = pub_key[modulus_elem.start..modulus_elem.end];
232 const modulus_offset = for (modulus_raw) |byte, i| {
233 if (byte != 0) break i;
234 } else modulus_raw.len;
235 const modulus = modulus_raw[modulus_offset..];
236 const exponent = pub_key[exponent_elem.start..exponent_elem.end];
237 if (exponent.len > modulus.len) return error.CertificatePublicKeyInvalid;
238 if (sig.len != modulus.len) return error.CertificateSignatureInvalidLength;
239
240 const hash_der = switch (Hash) {
241 crypto.hash.Sha1 => [_]u8{
242 0x30, 0x21, 0x30, 0x09, 0x06, 0x05, 0x2b, 0x0e,
243 0x03, 0x02, 0x1a, 0x05, 0x00, 0x04, 0x14,
244 },
245 crypto.hash.sha2.Sha224 => [_]u8{
246 0x30, 0x2d, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
247 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x04, 0x05,
248 0x00, 0x04, 0x1c,
249 },
250 crypto.hash.sha2.Sha256 => [_]u8{
251 0x30, 0x31, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
252 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x01, 0x05,
253 0x00, 0x04, 0x20,
254 },
255 crypto.hash.sha2.Sha384 => [_]u8{
256 0x30, 0x41, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
257 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x02, 0x05,
258 0x00, 0x04, 0x30,
259 },
260 crypto.hash.sha2.Sha512 => [_]u8{
261 0x30, 0x51, 0x30, 0x0d, 0x06, 0x09, 0x60, 0x86,
262 0x48, 0x01, 0x65, 0x03, 0x04, 0x02, 0x03, 0x05,
263 0x00, 0x04, 0x40,
264 },
265 else => @compileError("unreachable"),
266 };
267
268 var msg_hashed: [Hash.digest_length]u8 = undefined;
269 Hash.hash(message, &msg_hashed, .{});
270
271 switch (modulus.len) {
272 inline 128, 256, 512 => |modulus_len| {
273 const ps_len = modulus_len - (hash_der.len + msg_hashed.len) - 3;
274 const em: [modulus_len]u8 =
275 [2]u8{ 0, 1 } ++
276 ([1]u8{0xff} ** ps_len) ++
277 [1]u8{0} ++
278 hash_der ++
279 msg_hashed;
280
281 const public_key = try rsa.PublicKey.fromBytes(exponent, modulus, rsa.poop);
282 const em_dec = try rsa.encrypt(modulus_len, sig[0..modulus_len].*, public_key, rsa.poop);
283
284 if (!mem.eql(u8, &em, &em_dec)) {
285 try std.testing.expectEqualSlices(u8, &em, &em_dec);
286 return error.CertificateSignatureInvalid;
287 }
288 },
289 else => {
290 return error.CertificateSignatureUnsupportedBitCount;
291 },
292 }
293 }
294};
295
296fn checkVersion(bytes: []const u8, version: Der.Element) !void {
297 if (@bitCast(u8, version.identifier) != 0xa0 or
298 !mem.eql(u8, bytes[version.start..version.end], "\x02\x01\x02"))
299 {
300 return error.UnsupportedCertificateVersion;
301 }
302}
303
147const builtin = @import("builtin");304const builtin = @import("builtin");
148const std = @import("../std.zig");305const std = @import("../std.zig");
149const fs = std.fs;306const fs = std.fs;
150const mem = std.mem;307const mem = std.mem;
308const crypto = std.crypto;
151const Allocator = std.mem.Allocator;309const Allocator = std.mem.Allocator;
152const Der = std.crypto.Der;310const Der = std.crypto.Der;
153const CertificateBundle = @This();311const CertificateBundle = @This();
...@@ -177,3 +335,138 @@ test {...@@ -177,3 +335,138 @@ test {
177335
178 try bundle.rescan(std.testing.allocator);336 try bundle.rescan(std.testing.allocator);
179}337}
338
339/// TODO: replace this with Frank's upcoming RSA implementation. the verify
340/// function won't have the possibility of failure - it will either identify a
341/// valid signature or an invalid signature.
342/// This code is borrowed from https://github.com/shiguredo/tls13-zig
343/// which is licensed under the Apache License Version 2.0, January 2004
344/// http://www.apache.org/licenses/
345/// The code has been modified.
346const rsa = struct {
347 const BigInt = std.math.big.int.Managed;
348
349 const PublicKey = struct {
350 n: BigInt,
351 e: BigInt,
352
353 pub fn deinit(self: *PublicKey) void {
354 self.n.deinit();
355 self.e.deinit();
356 }
357
358 pub fn fromBytes(pub_bytes: []const u8, modulus_bytes: []const u8, allocator: std.mem.Allocator) !PublicKey {
359 var _n = try BigInt.init(allocator);
360 errdefer _n.deinit();
361 try setBytes(&_n, modulus_bytes, allocator);
362
363 var _e = try BigInt.init(allocator);
364 errdefer _e.deinit();
365 try setBytes(&_e, pub_bytes, allocator);
366
367 return .{
368 .n = _n,
369 .e = _e,
370 };
371 }
372 };
373
374 fn encrypt(comptime modulus_len: usize, msg: [modulus_len]u8, public_key: PublicKey, allocator: std.mem.Allocator) ![modulus_len]u8 {
375 var m = try BigInt.init(allocator);
376 defer m.deinit();
377
378 try setBytes(&m, &msg, allocator);
379
380 if (m.order(public_key.n) != .lt) {
381 return error.MessageTooLong;
382 }
383
384 var e = try BigInt.init(allocator);
385 defer e.deinit();
386
387 try pow_montgomery(&e, &m, &public_key.e, &public_key.n, allocator);
388
389 var res: [modulus_len]u8 = undefined;
390
391 try toBytes(&res, &e, allocator);
392
393 return res;
394 }
395
396 fn setBytes(r: *BigInt, bytes: []const u8, allcator: std.mem.Allocator) !void {
397 try r.set(0);
398 var tmp = try BigInt.init(allcator);
399 defer tmp.deinit();
400 for (bytes) |b| {
401 try r.shiftLeft(r, 8);
402 try tmp.set(b);
403 try r.add(r, &tmp);
404 }
405 }
406
407 fn pow_montgomery(r: *BigInt, a: *const BigInt, x: *const BigInt, n: *const BigInt, allocator: std.mem.Allocator) !void {
408 var bin_raw: [512]u8 = undefined;
409 try toBytes(&bin_raw, x, allocator);
410
411 var i: usize = 0;
412 while (bin_raw[i] == 0x00) : (i += 1) {}
413 const bin = bin_raw[i..];
414
415 try r.set(1);
416 var r1 = try BigInt.init(allocator);
417 defer r1.deinit();
418 try BigInt.copy(&r1, a.toConst());
419 i = 0;
420 while (i < bin.len * 8) : (i += 1) {
421 if (((bin[i / 8] >> @intCast(u3, (7 - (i % 8)))) & 0x1) == 0) {
422 try BigInt.mul(&r1, r, &r1);
423 try mod(&r1, &r1, n, allocator);
424 try BigInt.sqr(r, r);
425 try mod(r, r, n, allocator);
426 } else {
427 try BigInt.mul(r, r, &r1);
428 try mod(r, r, n, allocator);
429 try BigInt.sqr(&r1, &r1);
430 try mod(&r1, &r1, n, allocator);
431 }
432 }
433 }
434
435 fn toBytes(out: []u8, a: *const BigInt, allocator: std.mem.Allocator) !void {
436 const Error = error{
437 BufferTooSmall,
438 };
439
440 var mask = try BigInt.initSet(allocator, 0xFF);
441 defer mask.deinit();
442 var tmp = try BigInt.init(allocator);
443 defer tmp.deinit();
444
445 var a_copy = try BigInt.init(allocator);
446 defer a_copy.deinit();
447 try a_copy.copy(a.toConst());
448
449 // Encoding into big-endian bytes
450 var i: usize = 0;
451 while (i < out.len) : (i += 1) {
452 try tmp.bitAnd(&a_copy, &mask);
453 const b = try tmp.to(u8);
454 out[out.len - i - 1] = b;
455 try a_copy.shiftRight(&a_copy, 8);
456 }
457
458 if (!a_copy.eqZero()) {
459 return Error.BufferTooSmall;
460 }
461 }
462
463 fn mod(rem: *BigInt, a: *const BigInt, n: *const BigInt, allocator: std.mem.Allocator) !void {
464 var q = try BigInt.init(allocator);
465 defer q.deinit();
466
467 try BigInt.divFloor(&q, rem, a, n);
468 }
469
470 // TODO: flush the toilet
471 const poop = std.heap.page_allocator;
472};
lib/std/crypto/tls/Client.zig+24-28
...@@ -53,15 +53,12 @@ pub fn init(stream: net.Stream, ca_bundle: crypto.CertificateBundle, host: []con...@@ -53,15 +53,12 @@ pub fn init(stream: net.Stream, ca_bundle: crypto.CertificateBundle, host: []con
53 0x02, // byte length of supported versions53 0x02, // byte length of supported versions
54 0x03, 0x04, // TLS 1.354 0x03, 0x04, // TLS 1.3
55 }) ++ tls.extension(.signature_algorithms, enum_array(tls.SignatureScheme, &.{55 }) ++ tls.extension(.signature_algorithms, enum_array(tls.SignatureScheme, &.{
56 .rsa_pkcs1_sha256,
57 .rsa_pkcs1_sha384,
58 .rsa_pkcs1_sha512,
59 .ecdsa_secp256r1_sha256,56 .ecdsa_secp256r1_sha256,
60 .ecdsa_secp384r1_sha384,57 .ecdsa_secp384r1_sha384,
61 .ecdsa_secp521r1_sha512,58 .ecdsa_secp521r1_sha512,
62 .rsa_pss_rsae_sha256,59 .rsa_pkcs1_sha256,
63 .rsa_pss_rsae_sha384,60 .rsa_pkcs1_sha384,
64 .rsa_pss_rsae_sha512,61 .rsa_pkcs1_sha512,
65 .ed25519,62 .ed25519,
66 })) ++ tls.extension(.supported_groups, enum_array(tls.NamedGroup, &.{63 })) ++ tls.extension(.supported_groups, enum_array(tls.NamedGroup, &.{
67 .secp256r1,64 .secp256r1,
...@@ -420,33 +417,32 @@ pub fn init(stream: net.Stream, ca_bundle: crypto.CertificateBundle, host: []con...@@ -420,33 +417,32 @@ pub fn init(stream: net.Stream, ca_bundle: crypto.CertificateBundle, host: []con
420 // "This field MUST contain the same algorithm identifier as417 // "This field MUST contain the same algorithm identifier as
421 // the signatureAlgorithm field in the sequence Certificate."418 // the signatureAlgorithm field in the sequence Certificate."
422 const signature = try Der.parseElement(handshake, serial_number.end);419 const signature = try Der.parseElement(handshake, serial_number.end);
423 const issuer = try Der.parseElement(handshake, signature.end);420 const issuer_elem = try Der.parseElement(handshake, signature.end);
424 const validity = try Der.parseElement(handshake, issuer.end);421
425 const subject = try Der.parseElement(handshake, validity.end);422 const issuer_bytes = handshake[issuer_elem.start..issuer_elem.end];
426 const subject_pub_key = try Der.parseElement(handshake, subject.end);423 if (ca_bundle.find(issuer_bytes)) |ca_cert_i| {
427 const extensions = try Der.parseElement(handshake, subject_pub_key.end);424 const Certificate = crypto.CertificateBundle.Certificate;
428 _ = extensions;425 const subject: Certificate = .{
429426 .buffer = handshake,
430 const signature_algorithm = try Der.parseElement(handshake, tbs_certificate.end);427 .index = hs_i,
431 const signature_value = try Der.parseElement(handshake, signature_algorithm.end);428 };
432 _ = signature_value;429 const issuer: Certificate = .{
433430 .buffer = ca_bundle.bytes.items,
434 const algorithm_elem = try Der.parseElement(handshake, signature_algorithm.start);431 .index = ca_cert_i,
435 const algorithm = try Der.parseObjectId(handshake, algorithm_elem);432 };
436 std.debug.print("cert has this signature algorithm: {any}\n", .{algorithm});433 if (subject.verify(issuer)) |_| {
437 //const parameters = try Der.parseElement(signature_algorithm.contents, &sa_i);434 std.debug.print("found a root CA cert matching issuer. verification success!\n", .{});
435 } else |err| {
436 std.debug.print("found a root CA cert matching issuer. verification failure: {s}\n", .{
437 @errorName(err),
438 });
439 }
440 }
438441
439 hs_i = end_cert;442 hs_i = end_cert;
440 const total_ext_size = mem.readIntBig(u16, handshake[hs_i..][0..2]);443 const total_ext_size = mem.readIntBig(u16, handshake[hs_i..][0..2]);
441 hs_i += 2;444 hs_i += 2;
442 hs_i += total_ext_size;445 hs_i += total_ext_size;
443
444 const issuer_bytes = handshake[issuer.start..issuer.end];
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,
449 });
450 }446 }
451 },447 },
452 @enumToInt(HandshakeType.certificate_verify) => {448 @enumToInt(HandshakeType.certificate_verify) => {