| ... | @@ -308,8 +308,23 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8) | ... | @@ -308,8 +308,23 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8) |
| 308 | var prev_cert: Certificate.Parsed = undefined; | 308 | var prev_cert: Certificate.Parsed = undefined; |
| 309 | // Set to true once a trust chain has been established from the first | 309 | // Set to true once a trust chain has been established from the first |
| 310 | // certificate to a root CA. | 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 | var cleartext_bufs: [2][8000]u8 = undefined; | 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 | while (true) { | 329 | while (true) { |
| 315 | const end_hdr = i + 5; | 330 | const end_hdr = i + 5; |
| ... | @@ -376,6 +391,8 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8) | ... | @@ -376,6 +391,8 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8) |
| 376 | const handshake = cleartext[ct_i..next_handshake_i]; | 391 | const handshake = cleartext[ct_i..next_handshake_i]; |
| 377 | switch (handshake_type) { | 392 | switch (handshake_type) { |
| 378 | @enumToInt(HandshakeType.encrypted_extensions) => { | 393 | @enumToInt(HandshakeType.encrypted_extensions) => { |
| | 394 | if (handshake_state != .encrypted_extensions) return error.TlsUnexpectedMessage; |
| | 395 | handshake_state = .certificate; |
| 379 | switch (handshake_cipher) { | 396 | switch (handshake_cipher) { |
| 380 | inline else => |*p| p.transcript_hash.update(wrapped_handshake), | 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,7 +420,11 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8) |
| 403 | switch (handshake_cipher) { | 420 | switch (handshake_cipher) { |
| 404 | inline else => |*p| p.transcript_hash.update(wrapped_handshake), | 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 | var hs_i: u32 = 0; | 428 | var hs_i: u32 = 0; |
| 408 | const cert_req_ctx_len = handshake[hs_i]; | 429 | const cert_req_ctx_len = handshake[hs_i]; |
| 409 | hs_i += 1; | 430 | hs_i += 1; |
| ... | @@ -421,38 +442,41 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8) | ... | @@ -421,38 +442,41 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8) |
| 421 | .index = hs_i, | 442 | .index = hs_i, |
| 422 | }; | 443 | }; |
| 423 | const subject = try subject_cert.parse(); | 444 | const subject = try subject_cert.parse(); |
| 424 | if (cert_index > 0) { | 445 | if (cert_index == 0) { |
| 425 | if (prev_cert.verify(subject)) |_| { | 446 | // Verify the host on the first certificate. |
| 426 | std.debug.print("previous certificate verified\n", .{}); | 447 | if (!hostMatchesCommonName(host, subject.commonName())) { |
| 427 | } else |err| { | 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 | std.debug.print("unable to validate previous cert: {s}\n", .{ | 462 | std.debug.print("unable to validate previous cert: {s}\n", .{ |
| 429 | @errorName(err), | 463 | @errorName(err), |
| 430 | }); | 464 | }); |
| 431 | } | 465 | return err; |
| 432 | } else { | 466 | }; |
| 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 | } | | |
| 446 | } | 467 | } |
| 447 | | 468 | |
| 448 | if (ca_bundle.verify(subject)) |_| { | 469 | if (ca_bundle.verify(subject)) |_| { |
| 449 | std.debug.print("found a root CA cert matching issuer. verification success!\n", .{}); | 470 | handshake_state = .trust_chain_established; |
| 450 | cert_verification_done = true; | | |
| 451 | break :cert; | 471 | break :cert; |
| 452 | } else |err| { | 472 | } else |err| switch (err) { |
| 453 | std.debug.print("unable to validate cert against system root CAs: {s}\n", .{ | 473 | error.IssuerNotFound => {}, |
| 454 | @errorName(err), | 474 | else => |e| { |
| 455 | }); | 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 | prev_cert = subject; | 482 | prev_cert = subject; |
| ... | @@ -465,12 +489,46 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8) | ... | @@ -465,12 +489,46 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8) |
| 465 | } | 489 | } |
| 466 | }, | 490 | }, |
| 467 | @enumToInt(HandshakeType.certificate_verify) => { | 491 | @enumToInt(HandshakeType.certificate_verify) => { |
| 468 | switch (handshake_cipher) { | 492 | switch (handshake_state) { |
| 469 | inline else => |*p| p.transcript_hash.update(wrapped_handshake), | 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 | @enumToInt(HandshakeType.finished) => { | 530 | @enumToInt(HandshakeType.finished) => { |
| | 531 | if (handshake_state != .finished) return error.TlsUnexpectedMessage; |
| 474 | // This message is to trick buggy proxies into behaving correctly. | 532 | // This message is to trick buggy proxies into behaving correctly. |
| 475 | const client_change_cipher_spec_msg = [_]u8{ | 533 | const client_change_cipher_spec_msg = [_]u8{ |
| 476 | @enumToInt(ContentType.change_cipher_spec), | 534 | @enumToInt(ContentType.change_cipher_spec), |
| ... | @@ -762,6 +820,26 @@ fn finishRead(c: *Client, frag: []const u8, in: usize, out: usize) usize { | ... | @@ -762,6 +820,26 @@ fn finishRead(c: *Client, frag: []const u8, in: usize, out: usize) usize { |
| 762 | return out; | 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 | const builtin = @import("builtin"); | 843 | const builtin = @import("builtin"); |
| 766 | const native_endian = builtin.cpu.arch.endian(); | 844 | const native_endian = builtin.cpu.arch.endian(); |
| 767 | | 845 | |