mirror of
https://github.com/ziglang/zig.git
synced 2026-01-05 04:53:17 +00:00
std.crypto.tls: handle the certificate_verify message
This commit is contained in:
parent
29475b4518
commit
16f936b420
@ -9,6 +9,10 @@ pub const Algorithm = enum {
|
||||
sha256WithRSAEncryption,
|
||||
sha384WithRSAEncryption,
|
||||
sha512WithRSAEncryption,
|
||||
ecdsa_with_SHA224,
|
||||
ecdsa_with_SHA256,
|
||||
ecdsa_with_SHA384,
|
||||
ecdsa_with_SHA512,
|
||||
|
||||
pub const map = std.ComptimeStringMap(Algorithm, .{
|
||||
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 }, .sha1WithRSAEncryption },
|
||||
@ -16,15 +20,19 @@ pub const Algorithm = enum {
|
||||
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0C }, .sha384WithRSAEncryption },
|
||||
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0D }, .sha512WithRSAEncryption },
|
||||
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x0E }, .sha224WithRSAEncryption },
|
||||
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x01 }, .ecdsa_with_SHA224 },
|
||||
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x02 }, .ecdsa_with_SHA256 },
|
||||
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x03 }, .ecdsa_with_SHA384 },
|
||||
.{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x04 }, .ecdsa_with_SHA512 },
|
||||
});
|
||||
|
||||
pub fn Hash(comptime algorithm: Algorithm) type {
|
||||
return switch (algorithm) {
|
||||
.sha1WithRSAEncryption => crypto.hash.Sha1,
|
||||
.sha224WithRSAEncryption => crypto.hash.sha2.Sha224,
|
||||
.sha256WithRSAEncryption => crypto.hash.sha2.Sha256,
|
||||
.sha384WithRSAEncryption => crypto.hash.sha2.Sha384,
|
||||
.sha512WithRSAEncryption => crypto.hash.sha2.Sha512,
|
||||
.ecdsa_with_SHA224, .sha224WithRSAEncryption => crypto.hash.sha2.Sha224,
|
||||
.ecdsa_with_SHA256, .sha256WithRSAEncryption => crypto.hash.sha2.Sha256,
|
||||
.ecdsa_with_SHA384, .sha384WithRSAEncryption => crypto.hash.sha2.Sha384,
|
||||
.ecdsa_with_SHA512, .sha512WithRSAEncryption => crypto.hash.sha2.Sha512,
|
||||
};
|
||||
}
|
||||
};
|
||||
@ -125,6 +133,13 @@ pub const Parsed = struct {
|
||||
parsed_issuer.pub_key_algo,
|
||||
parsed_issuer.pubKey(),
|
||||
),
|
||||
.ecdsa_with_SHA224,
|
||||
.ecdsa_with_SHA256,
|
||||
.ecdsa_with_SHA384,
|
||||
.ecdsa_with_SHA512,
|
||||
=> {
|
||||
return error.CertificateSignatureAlgorithmUnsupported;
|
||||
},
|
||||
}
|
||||
}
|
||||
};
|
||||
@ -205,8 +220,11 @@ pub fn parseBitString(cert: Certificate, elem: der.Element) !der.Element.Slice {
|
||||
pub fn parseAlgorithm(bytes: []const u8, element: der.Element) !Algorithm {
|
||||
if (element.identifier.tag != .object_identifier)
|
||||
return error.CertificateFieldHasWrongDataType;
|
||||
return Algorithm.map.get(bytes[element.slice.start..element.slice.end]) orelse
|
||||
const oid_bytes = bytes[element.slice.start..element.slice.end];
|
||||
return Algorithm.map.get(oid_bytes) orelse {
|
||||
//std.debug.print("oid bytes: {}\n", .{std.fmt.fmtSliceHexLower(oid_bytes)});
|
||||
return error.CertificateHasUnrecognizedAlgorithm;
|
||||
};
|
||||
}
|
||||
|
||||
pub fn parseAlgorithmCategory(bytes: []const u8, element: der.Element) !AlgorithmCategory {
|
||||
|
||||
@ -308,8 +308,23 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8)
|
||||
var prev_cert: Certificate.Parsed = undefined;
|
||||
// Set to true once a trust chain has been established from the first
|
||||
// certificate to a root CA.
|
||||
var cert_verification_done = false;
|
||||
const HandshakeState = enum {
|
||||
/// In this state we expect only an encrypted_extensions message.
|
||||
encrypted_extensions,
|
||||
/// In this state we expect certificate messages.
|
||||
certificate,
|
||||
/// In this state we expect certificate or certificate_verify messages.
|
||||
/// certificate messages are ignored since the trust chain is already
|
||||
/// established.
|
||||
trust_chain_established,
|
||||
/// In this state, we expect only the finished message.
|
||||
finished,
|
||||
};
|
||||
var handshake_state: HandshakeState = .encrypted_extensions;
|
||||
var cleartext_bufs: [2][8000]u8 = undefined;
|
||||
var main_cert_pub_key_algo: Certificate.AlgorithmCategory = undefined;
|
||||
var main_cert_pub_key_buf: [128]u8 = undefined;
|
||||
var main_cert_pub_key_len: u8 = undefined;
|
||||
|
||||
while (true) {
|
||||
const end_hdr = i + 5;
|
||||
@ -376,6 +391,8 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8)
|
||||
const handshake = cleartext[ct_i..next_handshake_i];
|
||||
switch (handshake_type) {
|
||||
@enumToInt(HandshakeType.encrypted_extensions) => {
|
||||
if (handshake_state != .encrypted_extensions) return error.TlsUnexpectedMessage;
|
||||
handshake_state = .certificate;
|
||||
switch (handshake_cipher) {
|
||||
inline else => |*p| p.transcript_hash.update(wrapped_handshake),
|
||||
}
|
||||
@ -403,7 +420,11 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8)
|
||||
switch (handshake_cipher) {
|
||||
inline else => |*p| p.transcript_hash.update(wrapped_handshake),
|
||||
}
|
||||
if (cert_verification_done) break :cert;
|
||||
switch (handshake_state) {
|
||||
.certificate => {},
|
||||
.trust_chain_established => break :cert,
|
||||
else => return error.TlsUnexpectedMessage,
|
||||
}
|
||||
var hs_i: u32 = 0;
|
||||
const cert_req_ctx_len = handshake[hs_i];
|
||||
hs_i += 1;
|
||||
@ -421,38 +442,41 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8)
|
||||
.index = hs_i,
|
||||
};
|
||||
const subject = try subject_cert.parse();
|
||||
if (cert_index > 0) {
|
||||
if (prev_cert.verify(subject)) |_| {
|
||||
std.debug.print("previous certificate verified\n", .{});
|
||||
} else |err| {
|
||||
if (cert_index == 0) {
|
||||
// Verify the host on the first certificate.
|
||||
if (!hostMatchesCommonName(host, subject.commonName())) {
|
||||
return error.TlsCertificateHostMismatch;
|
||||
}
|
||||
|
||||
// Keep track of the public key for
|
||||
// the certificate_verify message
|
||||
// later.
|
||||
main_cert_pub_key_algo = subject.pub_key_algo;
|
||||
const pub_key = subject.pubKey();
|
||||
if (pub_key.len > main_cert_pub_key_buf.len)
|
||||
return error.CertificatePublicKeyInvalid;
|
||||
@memcpy(&main_cert_pub_key_buf, pub_key.ptr, pub_key.len);
|
||||
main_cert_pub_key_len = @intCast(@TypeOf(main_cert_pub_key_len), pub_key.len);
|
||||
} else {
|
||||
prev_cert.verify(subject) catch |err| {
|
||||
std.debug.print("unable to validate previous cert: {s}\n", .{
|
||||
@errorName(err),
|
||||
});
|
||||
}
|
||||
} else {
|
||||
// Verify the host on the first certificate.
|
||||
const common_name = subject.commonName();
|
||||
if (mem.eql(u8, common_name, host)) {
|
||||
std.debug.print("exact host match\n", .{});
|
||||
} else if (mem.startsWith(u8, common_name, "*.") and
|
||||
(mem.endsWith(u8, host, common_name[1..]) or
|
||||
mem.eql(u8, common_name[2..], host)))
|
||||
{
|
||||
std.debug.print("wildcard host match\n", .{});
|
||||
} else {
|
||||
std.debug.print("host does not match\n", .{});
|
||||
return error.TlsCertificateInvalidHost;
|
||||
}
|
||||
return err;
|
||||
};
|
||||
}
|
||||
|
||||
if (ca_bundle.verify(subject)) |_| {
|
||||
std.debug.print("found a root CA cert matching issuer. verification success!\n", .{});
|
||||
cert_verification_done = true;
|
||||
handshake_state = .trust_chain_established;
|
||||
break :cert;
|
||||
} else |err| {
|
||||
std.debug.print("unable to validate cert against system root CAs: {s}\n", .{
|
||||
@errorName(err),
|
||||
});
|
||||
} else |err| switch (err) {
|
||||
error.IssuerNotFound => {},
|
||||
else => |e| {
|
||||
std.debug.print("unable to validate cert against system root CAs: {s}\n", .{
|
||||
@errorName(e),
|
||||
});
|
||||
return e;
|
||||
},
|
||||
}
|
||||
|
||||
prev_cert = subject;
|
||||
@ -465,12 +489,46 @@ pub fn init(stream: net.Stream, ca_bundle: Certificate.Bundle, host: []const u8)
|
||||
}
|
||||
},
|
||||
@enumToInt(HandshakeType.certificate_verify) => {
|
||||
switch (handshake_cipher) {
|
||||
inline else => |*p| p.transcript_hash.update(wrapped_handshake),
|
||||
switch (handshake_state) {
|
||||
.trust_chain_established => handshake_state = .finished,
|
||||
.certificate => return error.TlsCertificateNotVerified,
|
||||
else => return error.TlsUnexpectedMessage,
|
||||
}
|
||||
|
||||
const algorithm = @intToEnum(tls.SignatureScheme, mem.readIntBig(u16, handshake[0..2]));
|
||||
const sig_len = mem.readIntBig(u16, handshake[2..4]);
|
||||
if (4 + sig_len > handshake.len) return error.TlsBadLength;
|
||||
const encoded_sig = handshake[4..][0..sig_len];
|
||||
const max_digest_len = 64;
|
||||
var verify_buffer =
|
||||
([1]u8{0x20} ** 64) ++
|
||||
"TLS 1.3, server CertificateVerify\x00".* ++
|
||||
([1]u8{undefined} ** max_digest_len);
|
||||
|
||||
const verify_bytes = switch (handshake_cipher) {
|
||||
inline else => |*p| v: {
|
||||
const transcript_digest = p.transcript_hash.peek();
|
||||
verify_buffer[verify_buffer.len - max_digest_len ..][0..transcript_digest.len].* = transcript_digest;
|
||||
p.transcript_hash.update(wrapped_handshake);
|
||||
break :v verify_buffer[0 .. verify_buffer.len - max_digest_len + transcript_digest.len];
|
||||
},
|
||||
};
|
||||
const main_cert_pub_key = main_cert_pub_key_buf[0..main_cert_pub_key_len];
|
||||
|
||||
switch (algorithm) {
|
||||
.ecdsa_secp256r1_sha256 => {
|
||||
if (main_cert_pub_key_algo != .X9_62_id_ecPublicKey)
|
||||
return error.TlsBadSignatureAlgorithm;
|
||||
const P256 = std.crypto.sign.ecdsa.EcdsaP256Sha256;
|
||||
const sig = try P256.Signature.fromDer(encoded_sig);
|
||||
const key = try P256.PublicKey.fromSec1(main_cert_pub_key);
|
||||
try sig.verify(verify_bytes, key);
|
||||
},
|
||||
else => return error.TlsBadSignatureAlgorithm,
|
||||
}
|
||||
std.debug.print("ignoring certificate_verify\n", .{});
|
||||
},
|
||||
@enumToInt(HandshakeType.finished) => {
|
||||
if (handshake_state != .finished) return error.TlsUnexpectedMessage;
|
||||
// This message is to trick buggy proxies into behaving correctly.
|
||||
const client_change_cipher_spec_msg = [_]u8{
|
||||
@enumToInt(ContentType.change_cipher_spec),
|
||||
@ -762,6 +820,26 @@ fn finishRead(c: *Client, frag: []const u8, in: usize, out: usize) usize {
|
||||
return out;
|
||||
}
|
||||
|
||||
fn hostMatchesCommonName(host: []const u8, common_name: []const u8) bool {
|
||||
if (mem.eql(u8, common_name, host)) {
|
||||
return true; // exact match
|
||||
}
|
||||
|
||||
if (mem.startsWith(u8, common_name, "*.")) {
|
||||
// wildcard certificate, matches any subdomain
|
||||
if (mem.endsWith(u8, host, common_name[1..])) {
|
||||
// The host has a subdomain, but the important part matches.
|
||||
return true;
|
||||
}
|
||||
if (mem.eql(u8, common_name[2..], host)) {
|
||||
// The host has no subdomain and matches exactly.
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
const builtin = @import("builtin");
|
||||
const native_endian = builtin.cpu.arch.endian();
|
||||
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user