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 {...@@ -9,6 +9,10 @@ pub const Algorithm = enum {
9 sha256WithRSAEncryption,9 sha256WithRSAEncryption,
10 sha384WithRSAEncryption,10 sha384WithRSAEncryption,
11 sha512WithRSAEncryption,11 sha512WithRSAEncryption,
12 ecdsa_with_SHA224,
13 ecdsa_with_SHA256,
14 ecdsa_with_SHA384,
15 ecdsa_with_SHA512,
1216
13 pub const map = std.ComptimeStringMap(Algorithm, .{17 pub const map = std.ComptimeStringMap(Algorithm, .{
14 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 }, .sha1WithRSAEncryption },18 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 }, .sha1WithRSAEncryption },
...@@ -16,15 +20,19 @@ pub const Algorithm = enum {...@@ -16,15 +20,19 @@ pub const Algorithm = enum {
16 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C }, .sha384WithRSAEncryption },20 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C }, .sha384WithRSAEncryption },
17 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0D }, .sha512WithRSAEncryption },21 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0D }, .sha512WithRSAEncryption },
18 .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0E }, .sha224WithRSAEncryption },22 .{ &[_]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 },
19 });27 });
2028
21 pub fn Hash(comptime algorithm: Algorithm) type {29 pub fn Hash(comptime algorithm: Algorithm) type {
22 return switch (algorithm) {30 return switch (algorithm) {
23 .sha1WithRSAEncryption => crypto.hash.Sha1,31 .sha1WithRSAEncryption => crypto.hash.Sha1,
24 .sha224WithRSAEncryption => crypto.hash.sha2.Sha224,32 .ecdsa_with_SHA224, .sha224WithRSAEncryption => crypto.hash.sha2.Sha224,
25 .sha256WithRSAEncryption => crypto.hash.sha2.Sha256,33 .ecdsa_with_SHA256, .sha256WithRSAEncryption => crypto.hash.sha2.Sha256,
26 .sha384WithRSAEncryption => crypto.hash.sha2.Sha384,34 .ecdsa_with_SHA384, .sha384WithRSAEncryption => crypto.hash.sha2.Sha384,
27 .sha512WithRSAEncryption => crypto.hash.sha2.Sha512,35 .ecdsa_with_SHA512, .sha512WithRSAEncryption => crypto.hash.sha2.Sha512,
28 };36 };
29 }37 }
30};38};
...@@ -125,6 +133,13 @@ pub const Parsed = struct {...@@ -125,6 +133,13 @@ pub const Parsed = struct {
125 parsed_issuer.pub_key_algo,133 parsed_issuer.pub_key_algo,
126 parsed_issuer.pubKey(),134 parsed_issuer.pubKey(),
127 ),135 ),
136 .ecdsa_with_SHA224,
137 .ecdsa_with_SHA256,
138 .ecdsa_with_SHA384,
139 .ecdsa_with_SHA512,
140 => {
141 return error.CertificateSignatureAlgorithmUnsupported;
142 },
128 }143 }
129 }144 }
130};145};
...@@ -205,8 +220,11 @@ pub fn parseBitString(cert: Certificate, elem: der.Element) !der.Element.Slice {...@@ -205,8 +220,11 @@ pub fn parseBitString(cert: Certificate, elem: der.Element) !der.Element.Slice {
205pub fn parseAlgorithm(bytes: []const u8, element: der.Element) !Algorithm {220pub fn parseAlgorithm(bytes: []const u8, element: der.Element) !Algorithm {
206 if (element.identifier.tag != .object_identifier)221 if (element.identifier.tag != .object_identifier)
207 return error.CertificateFieldHasWrongDataType;222 return error.CertificateFieldHasWrongDataType;
208 return Algorithm.map.get(bytes[element.slice.start..element.slice.end]) orelse223 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)});
209 return error.CertificateHasUnrecognizedAlgorithm;226 return error.CertificateHasUnrecognizedAlgorithm;
227 };
210}228}
211229
212pub fn parseAlgorithmCategory(bytes: []const u8, element: der.Element) !AlgorithmCategory {230pub 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)...@@ -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 first309 // 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;
313328
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 }
447468
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 }
457481
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}
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
765const builtin = @import("builtin");843const builtin = @import("builtin");
766const native_endian = builtin.cpu.arch.endian();844const native_endian = builtin.cpu.arch.endian();
767845