From 5e791e8e0786ea050f0f09da5de4657f3f9caad1 Mon Sep 17 00:00:00 2001 From: Jacob Young Date: Fri, 2 Feb 2024 19:14:27 +0100 Subject: [PATCH] tls: support ed25519 signatures Which were claimed to be supported during the handshake but were not actually implemented. --- lib/std/crypto/25519/ed25519.zig | 8 +++---- lib/std/crypto/Certificate.zig | 40 +++++++++++++++++++++++++++++++- lib/std/crypto/tls/Client.zig | 22 ++++++++++++++---- 3 files changed, 60 insertions(+), 10 deletions(-) diff --git a/lib/std/crypto/25519/ed25519.zig b/lib/std/crypto/25519/ed25519.zig index c00ce4387e..65f031ada7 100644 --- a/lib/std/crypto/25519/ed25519.zig +++ b/lib/std/crypto/25519/ed25519.zig @@ -199,8 +199,8 @@ pub const Ed25519 = struct { /// Return the raw signature (r, s) in little-endian format. pub fn toBytes(self: Signature) [encoded_length]u8 { var bytes: [encoded_length]u8 = undefined; - bytes[0 .. encoded_length / 2].* = self.r; - bytes[encoded_length / 2 ..].* = self.s; + bytes[0..Curve.encoded_length].* = self.r; + bytes[Curve.encoded_length..].* = self.s; return bytes; } @@ -208,8 +208,8 @@ pub const Ed25519 = struct { /// EdDSA always assumes little-endian. pub fn fromBytes(bytes: [encoded_length]u8) Signature { return Signature{ - .r = bytes[0 .. encoded_length / 2].*, - .s = bytes[encoded_length / 2 ..].*, + .r = bytes[0..Curve.encoded_length].*, + .s = bytes[Curve.encoded_length..].*, }; } diff --git a/lib/std/crypto/Certificate.zig b/lib/std/crypto/Certificate.zig index 8318269d87..c3ac3e22aa 100644 --- a/lib/std/crypto/Certificate.zig +++ b/lib/std/crypto/Certificate.zig @@ -17,6 +17,7 @@ pub const Algorithm = enum { ecdsa_with_SHA512, md2WithRSAEncryption, md5WithRSAEncryption, + curveEd25519, pub const map = std.ComptimeStringMap(Algorithm, .{ .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x05 }, .sha1WithRSAEncryption }, @@ -30,6 +31,7 @@ pub const Algorithm = enum { .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x04, 0x03, 0x04 }, .ecdsa_with_SHA512 }, .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x02 }, .md2WithRSAEncryption }, .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x04 }, .md5WithRSAEncryption }, + .{ &[_]u8{ 0x2B, 0x65, 0x70 }, .curveEd25519 }, }); pub fn Hash(comptime algorithm: Algorithm) type { @@ -38,7 +40,7 @@ pub const Algorithm = enum { .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, + .ecdsa_with_SHA512, .sha512WithRSAEncryption, .curveEd25519 => crypto.hash.sha2.Sha512, .md2WithRSAEncryption => @compileError("unimplemented"), .md5WithRSAEncryption => crypto.hash.Md5, }; @@ -48,10 +50,12 @@ pub const Algorithm = enum { pub const AlgorithmCategory = enum { rsaEncryption, X9_62_id_ecPublicKey, + curveEd25519, pub const map = std.ComptimeStringMap(AlgorithmCategory, .{ .{ &[_]u8{ 0x2A, 0x86, 0x48, 0x86, 0xF7, 0x0D, 0x01, 0x01, 0x01 }, .rsaEncryption }, .{ &[_]u8{ 0x2A, 0x86, 0x48, 0xCE, 0x3D, 0x02, 0x01 }, .X9_62_id_ecPublicKey }, + .{ &[_]u8{ 0x2B, 0x65, 0x70 }, .curveEd25519 }, }); }; @@ -182,6 +186,7 @@ pub const Parsed = struct { pub const PubKeyAlgo = union(AlgorithmCategory) { rsaEncryption: void, X9_62_id_ecPublicKey: NamedCurve, + curveEd25519: void, }; pub const Validity = struct { @@ -287,6 +292,13 @@ pub const Parsed = struct { .md2WithRSAEncryption, .md5WithRSAEncryption => { return error.CertificateSignatureAlgorithmUnsupported; }, + + .curveEd25519 => return verifyEd25519( + parsed_subject.message(), + parsed_subject.signature(), + parsed_issuer.pub_key_algo, + parsed_issuer.pubKey(), + ), } } @@ -415,6 +427,9 @@ pub fn parse(cert: Certificate) ParseError!Parsed { const named_curve = try parseNamedCurve(cert_bytes, params_elem); pub_key_algo = .{ .X9_62_id_ecPublicKey = named_curve }; }, + .curveEd25519 => { + pub_key_algo = .{ .curveEd25519 = {} }; + }, } const pub_key_elem = try der.Element.parse(cert_bytes, pub_key_signature_algorithm.slice.end); const pub_key = try parseBitString(cert, pub_key_elem); @@ -818,6 +833,29 @@ fn verify_ecdsa( } } +fn verifyEd25519( + message: []const u8, + encoded_sig: []const u8, + pub_key_algo: Parsed.PubKeyAlgo, + encoded_pub_key: []const u8, +) !void { + if (pub_key_algo != .curveEd25519) return error.CertificateSignatureAlgorithmMismatch; + const Ed25519 = crypto.sign.Ed25519; + if (encoded_sig.len != Ed25519.Signature.encoded_length) return error.CertificateSignatureInvalid; + const sig = Ed25519.Signature.fromBytes(encoded_sig[0..Ed25519.Signature.encoded_length].*); + if (encoded_pub_key.len != Ed25519.PublicKey.encoded_length) return error.CertificateSignatureInvalid; + const pub_key = Ed25519.PublicKey.fromBytes(encoded_pub_key[0..Ed25519.PublicKey.encoded_length].*) catch |err| switch (err) { + error.NonCanonical => return error.CertificateSignatureInvalid, + }; + sig.verify(message, pub_key) catch |err| switch (err) { + error.IdentityElement => return error.CertificateSignatureInvalid, + error.NonCanonical => return error.CertificateSignatureInvalid, + error.SignatureVerificationFailed => return error.CertificateSignatureInvalid, + error.InvalidEncoding => return error.CertificateSignatureInvalid, + error.WeakPublicKey => return error.CertificateSignatureInvalid, + }; +} + const std = @import("../std.zig"); const crypto = std.crypto; const mem = std.mem; diff --git a/lib/std/crypto/tls/Client.zig b/lib/std/crypto/tls/Client.zig index abae597ae7..c17899e941 100644 --- a/lib/std/crypto/tls/Client.zig +++ b/lib/std/crypto/tls/Client.zig @@ -132,6 +132,7 @@ pub fn InitError(comptime Stream: type) type { InvalidSignature, NotSquare, NonCanonical, + WeakPublicKey, }; } @@ -166,13 +167,9 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In }) ++ tls.extension(.signature_algorithms, enum_array(tls.SignatureScheme, &.{ .ecdsa_secp256r1_sha256, .ecdsa_secp384r1_sha384, - .ecdsa_secp521r1_sha512, .rsa_pss_rsae_sha256, .rsa_pss_rsae_sha384, .rsa_pss_rsae_sha512, - .rsa_pkcs1_sha256, - .rsa_pkcs1_sha384, - .rsa_pkcs1_sha512, .ed25519, })) ++ tls.extension(.supported_groups, enum_array(tls.NamedGroup, &.{ .x25519_kyber768d00, @@ -618,6 +615,15 @@ pub fn init(stream: anytype, ca_bundle: Certificate.Bundle, host: []const u8) In }, } }, + inline .ed25519 => |comptime_scheme| { + if (main_cert_pub_key_algo != .curveEd25519) return error.TlsBadSignatureScheme; + const Eddsa = SchemeEddsa(comptime_scheme); + if (encoded_sig.len != Eddsa.Signature.encoded_length) return error.InvalidEncoding; + const sig = Eddsa.Signature.fromBytes(encoded_sig[0..Eddsa.Signature.encoded_length].*); + if (main_cert_pub_key.len != Eddsa.PublicKey.encoded_length) return error.InvalidEncoding; + const key = try Eddsa.PublicKey.fromBytes(main_cert_pub_key[0..Eddsa.PublicKey.encoded_length].*); + try sig.verify(verify_bytes, key); + }, else => { return error.TlsBadSignatureScheme; }, @@ -1297,7 +1303,6 @@ fn SchemeEcdsa(comptime scheme: tls.SignatureScheme) type { return switch (scheme) { .ecdsa_secp256r1_sha256 => crypto.sign.ecdsa.EcdsaP256Sha256, .ecdsa_secp384r1_sha384 => crypto.sign.ecdsa.EcdsaP384Sha384, - .ecdsa_secp521r1_sha512 => crypto.sign.ecdsa.EcdsaP512Sha512, else => @compileError("bad scheme"), }; } @@ -1311,6 +1316,13 @@ fn SchemeHash(comptime scheme: tls.SignatureScheme) type { }; } +fn SchemeEddsa(comptime scheme: tls.SignatureScheme) type { + return switch (scheme) { + .ed25519 => crypto.sign.Ed25519, + else => @compileError("bad scheme"), + }; +} + /// Abstraction for sending multiple byte buffers to a slice of iovecs. const VecPut = struct { iovecs: []const std.os.iovec,