Files
browser/src/http/async/tls.zig/handshake_common.zig
2024-11-18 17:39:37 +01:00

449 lines
18 KiB
Zig

const std = @import("std");
const assert = std.debug.assert;
const mem = std.mem;
const crypto = std.crypto;
const Certificate = crypto.Certificate;
const Transcript = @import("transcript.zig").Transcript;
const PrivateKey = @import("PrivateKey.zig");
const record = @import("record.zig");
const rsa = @import("rsa/rsa.zig");
const proto = @import("protocol.zig");
const X25519 = crypto.dh.X25519;
const EcdsaP256Sha256 = crypto.sign.ecdsa.EcdsaP256Sha256;
const EcdsaP384Sha384 = crypto.sign.ecdsa.EcdsaP384Sha384;
const Kyber768 = crypto.kem.kyber_d00.Kyber768;
pub const supported_signature_algorithms = &[_]proto.SignatureScheme{
.ecdsa_secp256r1_sha256,
.ecdsa_secp384r1_sha384,
.rsa_pss_rsae_sha256,
.rsa_pss_rsae_sha384,
.rsa_pss_rsae_sha512,
.ed25519,
.rsa_pkcs1_sha1,
.rsa_pkcs1_sha256,
.rsa_pkcs1_sha384,
};
pub const CertKeyPair = struct {
/// A chain of one or more certificates, leaf first.
///
/// Each X.509 certificate contains the public key of a key pair, extra
/// information (the name of the holder, the name of an issuer of the
/// certificate, validity time spans) and a signature generated using the
/// private key of the issuer of the certificate.
///
/// All certificates from the bundle are sent to the other side when creating
/// Certificate tls message.
///
/// Leaf certificate and private key are used to create signature for
/// CertifyVerify tls message.
bundle: Certificate.Bundle,
/// Private key corresponding to the public key in leaf certificate from the
/// bundle.
key: PrivateKey,
pub fn load(
allocator: std.mem.Allocator,
dir: std.fs.Dir,
cert_path: []const u8,
key_path: []const u8,
) !CertKeyPair {
var bundle: Certificate.Bundle = .{};
try bundle.addCertsFromFilePath(allocator, dir, cert_path);
const key_file = try dir.openFile(key_path, .{});
defer key_file.close();
const key = try PrivateKey.fromFile(allocator, key_file);
return .{ .bundle = bundle, .key = key };
}
pub fn deinit(c: *CertKeyPair, allocator: std.mem.Allocator) void {
c.bundle.deinit(allocator);
}
};
pub const CertBundle = struct {
// A chain of one or more certificates.
//
// They are used to verify that certificate chain sent by the other side
// forms valid trust chain.
bundle: Certificate.Bundle = .{},
pub fn fromFile(allocator: std.mem.Allocator, dir: std.fs.Dir, path: []const u8) !CertBundle {
var bundle: Certificate.Bundle = .{};
try bundle.addCertsFromFilePath(allocator, dir, path);
return .{ .bundle = bundle };
}
pub fn fromSystem(allocator: std.mem.Allocator) !CertBundle {
var bundle: Certificate.Bundle = .{};
try bundle.rescan(allocator);
return .{ .bundle = bundle };
}
pub fn deinit(cb: *CertBundle, allocator: std.mem.Allocator) void {
cb.bundle.deinit(allocator);
}
};
pub const CertificateBuilder = struct {
bundle: Certificate.Bundle,
key: PrivateKey,
transcript: *Transcript,
tls_version: proto.Version = .tls_1_3,
side: proto.Side = .client,
pub fn makeCertificate(h: CertificateBuilder, buf: []u8) ![]const u8 {
var w = record.Writer{ .buf = buf };
const certs = h.bundle.bytes.items;
const certs_count = h.bundle.map.size;
// Differences between tls 1.3 and 1.2
// TLS 1.3 has request context in header and extensions for each certificate.
// Here we use empty length for each field.
// TLS 1.2 don't have these two fields.
const request_context, const extensions = if (h.tls_version == .tls_1_3)
.{ &[_]u8{0}, &[_]u8{ 0, 0 } }
else
.{ &[_]u8{}, &[_]u8{} };
const certs_len = certs.len + (3 + extensions.len) * certs_count;
// Write handshake header
try w.writeHandshakeHeader(.certificate, certs_len + request_context.len + 3);
try w.write(request_context);
try w.writeInt(@as(u24, @intCast(certs_len)));
// Write each certificate
var index: u32 = 0;
while (index < certs.len) {
const e = try Certificate.der.Element.parse(certs, index);
const cert = certs[index..e.slice.end];
try w.writeInt(@as(u24, @intCast(cert.len))); // certificate length
try w.write(cert); // certificate
try w.write(extensions); // certificate extensions
index = e.slice.end;
}
return w.getWritten();
}
pub fn makeCertificateVerify(h: CertificateBuilder, buf: []u8) ![]const u8 {
var w = record.Writer{ .buf = buf };
const signature, const signature_scheme = try h.createSignature();
try w.writeHandshakeHeader(.certificate_verify, signature.len + 4);
try w.writeEnum(signature_scheme);
try w.writeInt(@as(u16, @intCast(signature.len)));
try w.write(signature);
return w.getWritten();
}
/// Creates signature for client certificate signature message.
/// Returns signature bytes and signature scheme.
inline fn createSignature(h: CertificateBuilder) !struct { []const u8, proto.SignatureScheme } {
switch (h.key.signature_scheme) {
inline .ecdsa_secp256r1_sha256,
.ecdsa_secp384r1_sha384,
=> |comptime_scheme| {
const Ecdsa = SchemeEcdsa(comptime_scheme);
const key = h.key.key.ecdsa;
const key_len = Ecdsa.SecretKey.encoded_length;
if (key.len < key_len) return error.InvalidEncoding;
const secret_key = try Ecdsa.SecretKey.fromBytes(key[0..key_len].*);
const key_pair = try Ecdsa.KeyPair.fromSecretKey(secret_key);
var signer = try key_pair.signer(null);
h.setSignatureVerifyBytes(&signer);
const signature = try signer.finalize();
var buf: [Ecdsa.Signature.der_encoded_length_max]u8 = undefined;
return .{ signature.toDer(&buf), comptime_scheme };
},
inline .rsa_pss_rsae_sha256,
.rsa_pss_rsae_sha384,
.rsa_pss_rsae_sha512,
=> |comptime_scheme| {
const Hash = SchemeHash(comptime_scheme);
var signer = try h.key.key.rsa.signerOaep(Hash, null);
h.setSignatureVerifyBytes(&signer);
var buf: [512]u8 = undefined;
const signature = try signer.finalize(&buf);
return .{ signature.bytes, comptime_scheme };
},
else => return error.TlsUnknownSignatureScheme,
}
}
fn setSignatureVerifyBytes(h: CertificateBuilder, signer: anytype) void {
if (h.tls_version == .tls_1_2) {
// tls 1.2 signature uses current transcript hash value.
// ref: https://datatracker.ietf.org/doc/html/rfc5246.html#section-7.4.8
const Hash = @TypeOf(signer.h);
signer.h = h.transcript.hash(Hash);
} else {
// tls 1.3 signature is computed over concatenation of 64 spaces,
// context, separator and content.
// ref: https://datatracker.ietf.org/doc/html/rfc8446#section-4.4.3
if (h.side == .server) {
signer.update(h.transcript.serverCertificateVerify());
} else {
signer.update(h.transcript.clientCertificateVerify());
}
}
}
fn SchemeEcdsa(comptime scheme: proto.SignatureScheme) type {
return switch (scheme) {
.ecdsa_secp256r1_sha256 => EcdsaP256Sha256,
.ecdsa_secp384r1_sha384 => EcdsaP384Sha384,
else => unreachable,
};
}
};
pub const CertificateParser = struct {
pub_key_algo: Certificate.Parsed.PubKeyAlgo = undefined,
pub_key_buf: [600]u8 = undefined,
pub_key: []const u8 = undefined,
signature_scheme: proto.SignatureScheme = @enumFromInt(0),
signature_buf: [1024]u8 = undefined,
signature: []const u8 = undefined,
root_ca: Certificate.Bundle,
host: []const u8,
skip_verify: bool = false,
now_sec: i64 = 0,
pub fn parseCertificate(h: *CertificateParser, d: *record.Decoder, tls_version: proto.Version) !void {
if (h.now_sec == 0) {
h.now_sec = std.time.timestamp();
}
if (tls_version == .tls_1_3) {
const request_context = try d.decode(u8);
if (request_context != 0) return error.TlsIllegalParameter;
}
var trust_chain_established = false;
var last_cert: ?Certificate.Parsed = null;
const certs_len = try d.decode(u24);
const start_idx = d.idx;
while (d.idx - start_idx < certs_len) {
const cert_len = try d.decode(u24);
// std.debug.print("=> {} {} {} {}\n", .{ certs_len, d.idx, cert_len, d.payload.len });
const cert = try d.slice(cert_len);
if (tls_version == .tls_1_3) {
// certificate extensions present in tls 1.3
try d.skip(try d.decode(u16));
}
if (trust_chain_established)
continue;
const subject = try (Certificate{ .buffer = cert, .index = 0 }).parse();
if (last_cert) |pc| {
if (pc.verify(subject, h.now_sec)) {
last_cert = subject;
} else |err| switch (err) {
error.CertificateIssuerMismatch => {
// skip certificate which is not part of the chain
continue;
},
else => return err,
}
} else { // first certificate
if (!h.skip_verify and h.host.len > 0) {
try subject.verifyHostName(h.host);
}
h.pub_key = dupe(&h.pub_key_buf, subject.pubKey());
h.pub_key_algo = subject.pub_key_algo;
last_cert = subject;
}
if (!h.skip_verify) {
if (h.root_ca.verify(last_cert.?, h.now_sec)) |_| {
trust_chain_established = true;
} else |err| switch (err) {
error.CertificateIssuerNotFound => {},
else => return err,
}
}
}
if (!h.skip_verify and !trust_chain_established) {
return error.CertificateIssuerNotFound;
}
}
pub fn parseCertificateVerify(h: *CertificateParser, d: *record.Decoder) !void {
h.signature_scheme = try d.decode(proto.SignatureScheme);
h.signature = dupe(&h.signature_buf, try d.slice(try d.decode(u16)));
}
pub fn verifySignature(h: *CertificateParser, verify_bytes: []const u8) !void {
switch (h.signature_scheme) {
inline .ecdsa_secp256r1_sha256,
.ecdsa_secp384r1_sha384,
=> |comptime_scheme| {
if (h.pub_key_algo != .X9_62_id_ecPublicKey) return error.TlsBadSignatureScheme;
const cert_named_curve = h.pub_key_algo.X9_62_id_ecPublicKey;
switch (cert_named_curve) {
inline .secp384r1, .X9_62_prime256v1 => |comptime_cert_named_curve| {
const Ecdsa = SchemeEcdsaCert(comptime_scheme, comptime_cert_named_curve);
const key = try Ecdsa.PublicKey.fromSec1(h.pub_key);
const sig = try Ecdsa.Signature.fromDer(h.signature);
try sig.verify(verify_bytes, key);
},
else => return error.TlsUnknownSignatureScheme,
}
},
.ed25519 => {
if (h.pub_key_algo != .curveEd25519) return error.TlsBadSignatureScheme;
const Eddsa = crypto.sign.Ed25519;
if (h.signature.len != Eddsa.Signature.encoded_length) return error.InvalidEncoding;
const sig = Eddsa.Signature.fromBytes(h.signature[0..Eddsa.Signature.encoded_length].*);
if (h.pub_key.len != Eddsa.PublicKey.encoded_length) return error.InvalidEncoding;
const key = try Eddsa.PublicKey.fromBytes(h.pub_key[0..Eddsa.PublicKey.encoded_length].*);
try sig.verify(verify_bytes, key);
},
inline .rsa_pss_rsae_sha256,
.rsa_pss_rsae_sha384,
.rsa_pss_rsae_sha512,
=> |comptime_scheme| {
if (h.pub_key_algo != .rsaEncryption) return error.TlsBadSignatureScheme;
const Hash = SchemeHash(comptime_scheme);
const pk = try rsa.PublicKey.fromDer(h.pub_key);
const sig = rsa.Pss(Hash).Signature{ .bytes = h.signature };
try sig.verify(verify_bytes, pk, null);
},
inline .rsa_pkcs1_sha1,
.rsa_pkcs1_sha256,
.rsa_pkcs1_sha384,
.rsa_pkcs1_sha512,
=> |comptime_scheme| {
if (h.pub_key_algo != .rsaEncryption) return error.TlsBadSignatureScheme;
const Hash = SchemeHash(comptime_scheme);
const pk = try rsa.PublicKey.fromDer(h.pub_key);
const sig = rsa.PKCS1v1_5(Hash).Signature{ .bytes = h.signature };
try sig.verify(verify_bytes, pk);
},
else => return error.TlsUnknownSignatureScheme,
}
}
fn SchemeEcdsaCert(comptime scheme: proto.SignatureScheme, comptime cert_named_curve: Certificate.NamedCurve) type {
const Sha256 = crypto.hash.sha2.Sha256;
const Sha384 = crypto.hash.sha2.Sha384;
const Ecdsa = crypto.sign.ecdsa.Ecdsa;
return switch (scheme) {
.ecdsa_secp256r1_sha256 => Ecdsa(cert_named_curve.Curve(), Sha256),
.ecdsa_secp384r1_sha384 => Ecdsa(cert_named_curve.Curve(), Sha384),
else => @compileError("bad scheme"),
};
}
};
fn SchemeHash(comptime scheme: proto.SignatureScheme) type {
const Sha256 = crypto.hash.sha2.Sha256;
const Sha384 = crypto.hash.sha2.Sha384;
const Sha512 = crypto.hash.sha2.Sha512;
return switch (scheme) {
.rsa_pkcs1_sha1 => crypto.hash.Sha1,
.rsa_pss_rsae_sha256, .rsa_pkcs1_sha256 => Sha256,
.rsa_pss_rsae_sha384, .rsa_pkcs1_sha384 => Sha384,
.rsa_pss_rsae_sha512, .rsa_pkcs1_sha512 => Sha512,
else => @compileError("bad scheme"),
};
}
pub fn dupe(buf: []u8, data: []const u8) []u8 {
const n = @min(data.len, buf.len);
@memcpy(buf[0..n], data[0..n]);
return buf[0..n];
}
pub const DhKeyPair = struct {
x25519_kp: X25519.KeyPair = undefined,
secp256r1_kp: EcdsaP256Sha256.KeyPair = undefined,
secp384r1_kp: EcdsaP384Sha384.KeyPair = undefined,
kyber768_kp: Kyber768.KeyPair = undefined,
pub const seed_len = 32 + 32 + 48 + 64;
pub fn init(seed: [seed_len]u8, named_groups: []const proto.NamedGroup) !DhKeyPair {
var kp: DhKeyPair = .{};
for (named_groups) |ng|
switch (ng) {
.x25519 => kp.x25519_kp = try X25519.KeyPair.create(seed[0..][0..X25519.seed_length].*),
.secp256r1 => kp.secp256r1_kp = try EcdsaP256Sha256.KeyPair.create(seed[32..][0..EcdsaP256Sha256.KeyPair.seed_length].*),
.secp384r1 => kp.secp384r1_kp = try EcdsaP384Sha384.KeyPair.create(seed[32 + 32 ..][0..EcdsaP384Sha384.KeyPair.seed_length].*),
.x25519_kyber768d00 => kp.kyber768_kp = try Kyber768.KeyPair.create(seed[32 + 32 + 48 ..][0..Kyber768.seed_length].*),
else => return error.TlsIllegalParameter,
};
return kp;
}
pub inline fn sharedKey(self: DhKeyPair, named_group: proto.NamedGroup, server_pub_key: []const u8) ![]const u8 {
return switch (named_group) {
.x25519 => brk: {
if (server_pub_key.len != X25519.public_length)
return error.TlsIllegalParameter;
break :brk &(try X25519.scalarmult(
self.x25519_kp.secret_key,
server_pub_key[0..X25519.public_length].*,
));
},
.secp256r1 => brk: {
const pk = try EcdsaP256Sha256.PublicKey.fromSec1(server_pub_key);
const mul = try pk.p.mulPublic(self.secp256r1_kp.secret_key.bytes, .big);
break :brk &mul.affineCoordinates().x.toBytes(.big);
},
.secp384r1 => brk: {
const pk = try EcdsaP384Sha384.PublicKey.fromSec1(server_pub_key);
const mul = try pk.p.mulPublic(self.secp384r1_kp.secret_key.bytes, .big);
break :brk &mul.affineCoordinates().x.toBytes(.big);
},
.x25519_kyber768d00 => brk: {
const xksl = crypto.dh.X25519.public_length;
const hksl = xksl + Kyber768.ciphertext_length;
if (server_pub_key.len != hksl)
return error.TlsIllegalParameter;
break :brk &((crypto.dh.X25519.scalarmult(
self.x25519_kp.secret_key,
server_pub_key[0..xksl].*,
) catch return error.TlsDecryptFailure) ++ (self.kyber768_kp.secret_key.decaps(
server_pub_key[xksl..hksl],
) catch return error.TlsDecryptFailure));
},
else => return error.TlsIllegalParameter,
};
}
// Returns 32, 65, 97 or 1216 bytes
pub inline fn publicKey(self: DhKeyPair, named_group: proto.NamedGroup) ![]const u8 {
return switch (named_group) {
.x25519 => &self.x25519_kp.public_key,
.secp256r1 => &self.secp256r1_kp.public_key.toUncompressedSec1(),
.secp384r1 => &self.secp384r1_kp.public_key.toUncompressedSec1(),
.x25519_kyber768d00 => &self.x25519_kp.public_key ++ self.kyber768_kp.public_key.toBytes(),
else => return error.TlsIllegalParameter,
};
}
};
const testing = std.testing;
const testu = @import("testu.zig");
test "DhKeyPair.x25519" {
var seed: [DhKeyPair.seed_len]u8 = undefined;
testu.fill(&seed);
const server_pub_key = &testu.hexToBytes("3303486548531f08d91e675caf666c2dc924ac16f47a861a7f4d05919d143637");
const expected = &testu.hexToBytes(
\\ F1 67 FB 4A 49 B2 91 77 08 29 45 A1 F7 08 5A 21
\\ AF FE 9E 78 C2 03 9B 81 92 40 72 73 74 7A 46 1E
);
const kp = try DhKeyPair.init(seed, &.{.x25519});
try testing.expectEqualSlices(u8, expected, try kp.sharedKey(.x25519, server_pub_key));
}