| ... | ... | @@ -308,8 +308,23 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8) |
| 308 | 308 | var prev_cert: Certificate.Parsed = undefined; |
| 309 | 309 | // Set to true once a trust chain has been established from the first |
| 310 | 310 | // 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; |
| 312 | 324 | 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; |
| 313 | 328 | |
| 314 | 329 | while (true) { |
| 315 | 330 | const end_hdr = i + 5; |
| ... | ... | @@ -376,6 +391,8 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8) |
| 376 | 391 | const handshake = cleartext[ct_i..next_handshake_i]; |
| 377 | 392 | switch (handshake_type) { |
| 378 | 393 | @enumToInt(HandshakeType.encrypted_extensions) => { |
| 394 | if (handshake_state != .encrypted_extensions) return error.TlsUnexpectedMessage; |
| 395 | handshake_state = .certificate; |
| 379 | 396 | switch (handshake_cipher) { |
| 380 | 397 | inline else => |*p| p.transcript_hash.update(wrapped_handshake), |
| 381 | 398 | } |
| ... | ... | @@ -403,7 +420,11 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8) |
| 403 | 420 | switch (handshake_cipher) { |
| 404 | 421 | inline else => |*p| p.transcript_hash.update(wrapped_handshake), |
| 405 | 422 | } |
| 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 | } |
| 407 | 428 | var hs_i: u32 = 0; |
| 408 | 429 | const cert_req_ctx_len = handshake[hs_i]; |
| 409 | 430 | hs_i += 1; |
| ... | ... | @@ -421,38 +442,41 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8) |
| 421 | 442 | .index = hs_i, |
| 422 | 443 | }; |
| 423 | 444 | 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| { |
| 428 | 462 | std.debug.print("unable to validate previous cert: {s}\n", .{ |
| 429 | 463 | @errorName(err), |
| 430 | 464 | }); |
| 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 | }; |
| 446 | 467 | } |
| 447 | 468 | |
| 448 | 469 | 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; |
| 451 | 471 | 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 | }, |
| 456 | 480 | } |
| 457 | 481 | |
| 458 | 482 | prev_cert = subject; |
| ... | ... | @@ -465,12 +489,46 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8) |
| 465 | 489 | } |
| 466 | 490 | }, |
| 467 | 491 | @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, |
| 470 | 528 | } |
| 471 | | std.debug.print("ignoring certificate_verify\n", .{}); |
| 472 | 529 | }, |
| 473 | 530 | @enumToInt(HandshakeType.finished) => { |
| 531 | if (handshake_state != .finished) return error.TlsUnexpectedMessage; |
| 474 | 532 | // This message is to trick buggy proxies into behaving correctly. |
| 475 | 533 | const client_change_cipher_spec_msg = [_]u8{ |
| 476 | 534 | @enumToInt(ContentType.change_cipher_spec), |
| ... | ... | @@ -762,6 +820,26 @@ fn finishRead(c: *Client, frag: []const u8, in: usize, out: usize) usize { |
| 762 | 820 | return out; |
| 763 | 821 | } |
| 764 | 822 | |
| 823 | fn 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 | |
| 765 | 843 | const builtin = @import("builtin"); |
| 766 | 844 | const native_endian = builtin.cpu.arch.endian(); |
| 767 | 845 | |