authorgravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2022-12-21 18:54:17-07:00
committergravatar for andrew@ziglang.orgAndrew Kelley <andrew@ziglang.org> 2023-01-02 16:57:15-07:00
log16f936b4202d352a6a8cf91a265fdd4bc64dde5d
treea4c198abd29cdaed8b410af2bda005fe0c12ea3b
parent29475b45185f90c2437d160567e67a4b141f5845

std.crypto.tls: handle the certificate_verify message


2 files changed, 131 insertions(+), 35 deletions(-)

lib/std/crypto/Certificate.zig+23-5
......@@ -9,6 +9,10 @@ pub const Algorithm = enum {
99 sha256WithRSAEncryption,
1010 sha384WithRSAEncryption,
1111 sha512WithRSAEncryption,
12 ecdsa_with_SHA224,
13 ecdsa_with_SHA256,
14 ecdsa_with_SHA384,
15 ecdsa_with_SHA512,
1216
1317 pub const map = std.ComptimeStringMap(Algorithm, .{
1418 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 }, .sha1WithRSAEncryption },
......@@ -16,15 +20,19 @@ pub const Algorithm = enum {
1620 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C }, .sha384WithRSAEncryption },
1721 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0D }, .sha512WithRSAEncryption },
1822 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0E }, .sha224WithRSAEncryption },
23 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x01 }, .ecdsa_with_SHA224 },
24 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02 }, .ecdsa_with_SHA256 },
25 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x03 }, .ecdsa_with_SHA384 },
26 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x04 }, .ecdsa_with_SHA512 },
1927 });
2028
2129 pub fn Hash(comptime algorithm: Algorithm) type {
2230 return switch (algorithm) {
2331 .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,
32 .ecdsa_with_SHA224, .sha224WithRSAEncryption => crypto.hash.sha2.Sha224,
33 .ecdsa_with_SHA256, .sha256WithRSAEncryption => crypto.hash.sha2.Sha256,
34 .ecdsa_with_SHA384, .sha384WithRSAEncryption => crypto.hash.sha2.Sha384,
35 .ecdsa_with_SHA512, .sha512WithRSAEncryption => crypto.hash.sha2.Sha512,
2836 };
2937 }
3038};
......@@ -125,6 +133,13 @@ pub const Parsed = struct {
125133 parsed_issuer.pub_key_algo,
126134 parsed_issuer.pubKey(),
127135 ),
136 .ecdsa_with_SHA224,
137 .ecdsa_with_SHA256,
138 .ecdsa_with_SHA384,
139 .ecdsa_with_SHA512,
140 => {
141 return error.CertificateSignatureAlgorithmUnsupported;
142 },
128143 }
129144 }
130145};
......@@ -205,8 +220,11 @@ pub fn parseBitString(cert: Certificate, elem: der.Element) !der.Element.Slice {
205220pub fn parseAlgorithm(bytes: []const u8, element: der.Element) !Algorithm {
206221 if (element.identifier.tag != .object_identifier)
207222 return error.CertificateFieldHasWrongDataType;
208 return Algorithm.map.get(bytes[element.slice.start..element.slice.end]) orelse
223 const oid_bytes = bytes[element.slice.start..element.slice.end];
224 return Algorithm.map.get(oid_bytes) orelse {
225 //std.debug.print("oid bytes: {}\n", .{std.fmt.fmtSliceHexLower(oid_bytes)});
209226 return error.CertificateHasUnrecognizedAlgorithm;
227 };
210228}
211229
212230pub fn parseAlgorithmCategory(bytes: []const u8, element: der.Element) !AlgorithmCategory {
lib/std/crypto/tls/Client.zig+108-30
......@@ -308,8 +308,23 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8)
308308 var prev_cert: Certificate.Parsed = undefined;
309309 // Set to true once a trust chain has been established from the first
310310 // certificate to a root CA.
311 var cert_verification_done = false;
311 const HandshakeState = enum {
312 /// In this state we expect only an encrypted_extensions message.
313 encrypted_extensions,
314 /// In this state we expect certificate messages.
315 certificate,
316 /// In this state we expect certificate or certificate_verify messages.
317 /// certificate messages are ignored since the trust chain is already
318 /// established.
319 trust_chain_established,
320 /// In this state, we expect only the finished message.
321 finished,
322 };
323 var handshake_state: HandshakeState = .encrypted_extensions;
312324 var cleartext_bufs: [2][8000]u8 = undefined;
325 var main_cert_pub_key_algo: Certificate.AlgorithmCategory = undefined;
326 var main_cert_pub_key_buf: [128]u8 = undefined;
327 var main_cert_pub_key_len: u8 = undefined;
313328
314329 while (true) {
315330 const end_hdr = i + 5;
......@@ -376,6 +391,8 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8)
376391 const handshake = cleartext[ct_i..next_handshake_i];
377392 switch (handshake_type) {
378393 @enumToInt(HandshakeType.encrypted_extensions) => {
394 if (handshake_state != .encrypted_extensions) return error.TlsUnexpectedMessage;
395 handshake_state = .certificate;
379396 switch (handshake_cipher) {
380397 inline else => |*p| p.transcript_hash.update(wrapped_handshake),
381398 }
......@@ -403,7 +420,11 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8)
403420 switch (handshake_cipher) {
404421 inline else => |*p| p.transcript_hash.update(wrapped_handshake),
405422 }
406 if (cert_verification_done) break :cert;
423 switch (handshake_state) {
424 .certificate => {},
425 .trust_chain_established => break :cert,
426 else => return error.TlsUnexpectedMessage,
427 }
407428 var hs_i: u32 = 0;
408429 const cert_req_ctx_len = handshake[hs_i];
409430 hs_i += 1;
......@@ -421,38 +442,41 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8)
421442 .index = hs_i,
422443 };
423444 const subject = try subject_cert.parse();
424 if (cert_index > 0) {
425 if (prev_cert.verify(subject)) |_| {
426 std.debug.print("previous certificate verified\n", .{});
427 } else |err| {
445 if (cert_index == 0) {
446 // Verify the host on the first certificate.
447 if (!hostMatchesCommonName(host, subject.commonName())) {
448 return error.TlsCertificateHostMismatch;
449 }
450
451 // Keep track of the public key for
452 // the certificate_verify message
453 // later.
454 main_cert_pub_key_algo = subject.pub_key_algo;
455 const pub_key = subject.pubKey();
456 if (pub_key.len > main_cert_pub_key_buf.len)
457 return error.CertificatePublicKeyInvalid;
458 @memcpy(&main_cert_pub_key_buf, pub_key.ptr, pub_key.len);
459 main_cert_pub_key_len = @intCast(@TypeOf(main_cert_pub_key_len), pub_key.len);
460 } else {
461 prev_cert.verify(subject) catch |err| {
428462 std.debug.print("unable to validate previous cert: {s}\n", .{
429463 @errorName(err),
430464 });
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)) {
436 std.debug.print("exact host match\n", .{});
437 } else if (mem.startsWith(u8, common_name, "*.") and
438 (mem.endsWith(u8, host, common_name[1..]) or
439 mem.eql(u8, common_name[2..], host)))
440 {
441 std.debug.print("wildcard host match\n", .{});
442 } else {
443 std.debug.print("host does not match\n", .{});
444 return error.TlsCertificateInvalidHost;
445 }
465 return err;
466 };
446467 }
447468
448469 if (ca_bundle.verify(subject)) |_| {
449 std.debug.print("found a root CA cert matching issuer. verification success!\n", .{});
450 cert_verification_done = true;
470 handshake_state = .trust_chain_established;
451471 break :cert;
452 } else |err| {
453 std.debug.print("unable to validate cert against system root CAs: {s}\n", .{
454 @errorName(err),
455 });
472 } else |err| switch (err) {
473 error.IssuerNotFound => {},
474 else => |e| {
475 std.debug.print("unable to validate cert against system root CAs: {s}\n", .{
476 @errorName(e),
477 });
478 return e;
479 },
456480 }
457481
458482 prev_cert = subject;
......@@ -465,12 +489,46 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8)
465489 }
466490 },
467491 @enumToInt(HandshakeType.certificate_verify) => {
468 switch (handshake_cipher) {
469 inline else => |*p| p.transcript_hash.update(wrapped_handshake),
492 switch (handshake_state) {
493 .trust_chain_established => handshake_state = .finished,
494 .certificate => return error.TlsCertificateNotVerified,
495 else => return error.TlsUnexpectedMessage,
496 }
497
498 const algorithm = @intToEnum(tls.SignatureScheme, mem.readIntBig(u16, handshake[0..2]));
499 const sig_len = mem.readIntBig(u16, handshake[2..4]);
500 if (4 + sig_len > handshake.len) return error.TlsBadLength;
501 const encoded_sig = handshake[4..][0..sig_len];
502 const max_digest_len = 64;
503 var verify_buffer =
504 ([1]u8{0x20} ** 64) ++
505 "TLS 1.3, server CertificateVerify\x00".* ++
506 ([1]u8{undefined} ** max_digest_len);
507
508 const verify_bytes = switch (handshake_cipher) {
509 inline else => |*p| v: {
510 const transcript_digest = p.transcript_hash.peek();
511 verify_buffer[verify_buffer.len - max_digest_len ..][0..transcript_digest.len].* = transcript_digest;
512 p.transcript_hash.update(wrapped_handshake);
513 break :v verify_buffer[0 .. verify_buffer.len - max_digest_len + transcript_digest.len];
514 },
515 };
516 const main_cert_pub_key = main_cert_pub_key_buf[0..main_cert_pub_key_len];
517
518 switch (algorithm) {
519 .ecdsa_secp256r1_sha256 => {
520 if (main_cert_pub_key_algo != .X9_62_id_ecPublicKey)
521 return error.TlsBadSignatureAlgorithm;
522 const P256 = std.crypto.sign.ecdsa.EcdsaP256Sha256;
523 const sig = try P256.Signature.fromDer(encoded_sig);
524 const key = try P256.PublicKey.fromSec1(main_cert_pub_key);
525 try sig.verify(verify_bytes, key);
526 },
527 else => return error.TlsBadSignatureAlgorithm,
470528 }
471 std.debug.print("ignoring certificate_verify\n", .{});
472529 },
473530 @enumToInt(HandshakeType.finished) => {
531 if (handshake_state != .finished) return error.TlsUnexpectedMessage;
474532 // This message is to trick buggy proxies into behaving correctly.
475533 const client_change_cipher_spec_msg = [_]u8{
476534 @enumToInt(ContentType.change_cipher_spec),
......@@ -762,6 +820,26 @@ fn finishRead(c: *Client, frag: []const u8, in: usize, out: usize) usize {
762820 return out;
763821}
764822
823fn hostMatchesCommonName(host: []const u8, common_name: []const u8) bool {
824 if (mem.eql(u8, common_name, host)) {
825 return true; // exact match
826 }
827
828 if (mem.startsWith(u8, common_name, "*.")) {
829 // wildcard certificate, matches any subdomain
830 if (mem.endsWith(u8, host, common_name[1..])) {
831 // The host has a subdomain, but the important part matches.
832 return true;
833 }
834 if (mem.eql(u8, common_name[2..], host)) {
835 // The host has no subdomain and matches exactly.
836 return true;
837 }
838 }
839
840 return false;
841}
842
765843const builtin = @import("builtin");
766844const native_endian = builtin.cpu.arch.endian();
767845