From 9ee1fe61d5c6217c1dc9f241c7db080193dea20d Mon Sep 17 00:00:00 2001 From: Cory Benfield Date: Tue, 29 Oct 2024 20:51:23 +0000 Subject: [PATCH] Add support for Ed25519 certificates Motivation While Ed25519 is not available in the WebPKI, there are a number of contexts where it's an appropriate signature algorithm for X.509. In those contexts, we should allow the signature algorithm. Modifications Add support for Ed25519 keys and signatures. Add unit tests Result Ed25519 support in the X509 certs --- Sources/X509/CertificatePrivateKey.swift | 35 ++++- Sources/X509/CertificatePublicKey.swift | 60 ++++++-- Sources/X509/Curve25519+DER.swift | 44 ++++++ Sources/X509/Digests.swift | 22 +++ Sources/X509/PKCS8PrivateKey.swift | 4 +- Sources/X509/Signature.swift | 18 +++ Sources/X509/SignatureAlgorithm.swift | 3 + .../X509BaseTypes/AlgorithmIdentifier.swift | 8 ++ Tests/X509Tests/CertificateTests.swift | 42 ++++++ Tests/X509Tests/SignatureTests.swift | 130 ++++++++++++++++++ 10 files changed, 352 insertions(+), 14 deletions(-) create mode 100644 Sources/X509/Curve25519+DER.swift diff --git a/Sources/X509/CertificatePrivateKey.swift b/Sources/X509/CertificatePrivateKey.swift index 928565b7..1dfb7c25 100644 --- a/Sources/X509/CertificatePrivateKey.swift +++ b/Sources/X509/CertificatePrivateKey.swift @@ -63,6 +63,13 @@ extension Certificate { self.backing = .rsa(rsa) } + /// Construct a private key wrapping an Ed25519 private key. + /// - Parameter ed25519: The Ed25519 private key to wrap. + @inlinable + public init(_ ed25519: Curve25519.Signing.PrivateKey) { + self.backing = .ed25519(ed25519) + } + #if canImport(Darwin) /// Construct a private key wrapping a SecureEnclave.P256 private key. /// - Parameter secureEnclaveP256: The SecureEnclave.P256 private key to wrap. @@ -85,24 +92,31 @@ extension Certificate { signatureAlgorithm: SignatureAlgorithm ) throws -> Signature { try self.validateAlgorithmForKey(algorithm: signatureAlgorithm) - let digestAlgorithm = try AlgorithmIdentifier(digestAlgorithmFor: signatureAlgorithm) switch self.backing { case .p256(let p256): + let digestAlgorithm = try AlgorithmIdentifier(digestAlgorithmFor: signatureAlgorithm) return try p256.signature(for: bytes, digestAlgorithm: digestAlgorithm) case .p384(let p384): + let digestAlgorithm = try AlgorithmIdentifier(digestAlgorithmFor: signatureAlgorithm) return try p384.signature(for: bytes, digestAlgorithm: digestAlgorithm) case .p521(let p521): + let digestAlgorithm = try AlgorithmIdentifier(digestAlgorithmFor: signatureAlgorithm) return try p521.signature(for: bytes, digestAlgorithm: digestAlgorithm) case .rsa(let rsa): + let digestAlgorithm = try AlgorithmIdentifier(digestAlgorithmFor: signatureAlgorithm) let padding = try _RSA.Signing.Padding(forSignatureAlgorithm: signatureAlgorithm) return try rsa.signature(for: bytes, digestAlgorithm: digestAlgorithm, padding: padding) #if canImport(Darwin) case .secureEnclaveP256(let secureEnclaveP256): + let digestAlgorithm = try AlgorithmIdentifier(digestAlgorithmFor: signatureAlgorithm) return try secureEnclaveP256.signature(for: bytes, digestAlgorithm: digestAlgorithm) case .secKey(let secKeyWrapper): + let digestAlgorithm = try AlgorithmIdentifier(digestAlgorithmFor: signatureAlgorithm) return try secKeyWrapper.signature(for: bytes, digestAlgorithm: digestAlgorithm) #endif + case .ed25519(let ed25519): + return try ed25519.signature(for: bytes) } } @@ -125,6 +139,8 @@ extension Certificate { case .secKey(let secKeyWrapper): return secKeyWrapper.publicKey #endif + case .ed25519(let ed25519): + return PublicKey(ed25519.publicKey) } } @@ -166,6 +182,12 @@ extension Certificate { } } #endif + case .ed25519: + if algorithm != .ed25519 { + throw CertificateError.unsupportedSignatureAlgorithm( + reason: "Cannot use \(algorithm) with Ed25519 key \(self)" + ) + } } } @@ -193,6 +215,8 @@ extension Certificate.PrivateKey: CustomStringConvertible { case .secKey: return "SecKey" #endif + case .ed25519: + return "Ed25519.PrivateKey" } } } @@ -208,6 +232,7 @@ extension Certificate.PrivateKey { case secureEnclaveP256(SecureEnclave.P256.Signing.PrivateKey) case secKey(SecKeyWrapper) #endif + case ed25519(Crypto.Curve25519.Signing.PrivateKey) @inlinable static func == (lhs: BackingPrivateKey, rhs: BackingPrivateKey) -> Bool { @@ -226,6 +251,8 @@ extension Certificate.PrivateKey { case (.secKey(let l), .secKey(let r)): return l.publicKey.backing == r.publicKey.backing #endif + case (.ed25519(let l), .ed25519(let r)): + return l.rawRepresentation == r.rawRepresentation default: return false } @@ -255,6 +282,9 @@ extension Certificate.PrivateKey { hasher.combine(secKeyWrapper.privateKey.hashValue) hasher.combine(secKeyWrapper.publicKey.hashValue) #endif + case .ed25519(let digest): + hasher.combine(6) + hasher.combine(digest.rawRepresentation) } } } @@ -300,6 +330,8 @@ extension Certificate.PrivateKey { case .rsaKey: self = try .init(_CryptoExtras._RSA.Signing.PrivateKey(derRepresentation: pkcs8.privateKey.bytes)) + case .ed25519: + self = try .init(Curve25519.Signing.PrivateKey(pkcs8Key: pkcs8)) default: throw CertificateError.unsupportedPrivateKey(reason: "unknown algorithm \(pkcs8.algorithm)") } @@ -342,6 +374,7 @@ extension Certificate.PrivateKey { ) case .secKey(let key): return try key.pemDocument() #endif + case .ed25519(let key): return key.pemRepresentation } } } diff --git a/Sources/X509/CertificatePublicKey.swift b/Sources/X509/CertificatePublicKey.swift index 9d9c4dd6..c81e57e2 100644 --- a/Sources/X509/CertificatePublicKey.swift +++ b/Sources/X509/CertificatePublicKey.swift @@ -46,6 +46,9 @@ extension Certificate { _ = try RSAPKCS1PublicKey(derEncoded: spki.key.bytes) let key = try _RSA.Signing.PublicKey(derRepresentation: spki.key.bytes) self.backing = .rsa(key) + case .ed25519: + let key = try Curve25519.Signing.PublicKey(rawRepresentation: spki.key.bytes) + self.backing = .ed25519(key) default: throw CertificateError.unsupportedPublicKeyAlgorithm(reason: "\(spki.algorithmIdentifier)") } @@ -83,6 +86,13 @@ extension Certificate { public init(_ rsa: _RSA.Signing.PublicKey) { self.backing = .rsa(rsa) } + + /// Construct a public key wrapping an Ed25519 public key. + /// - Parameter ed25519: The Ed25519 public key to wrap. + @inlinable + public init(_ ed25519: Curve25519.Signing.PublicKey) { + self.backing = .ed25519(ed25519) + } } } @@ -127,22 +137,20 @@ extension Certificate.PublicKey { for bytes: Bytes, signatureAlgorithm: Certificate.SignatureAlgorithm ) -> Bool { - let digest: Digest - do { - let digestAlgorithm = try AlgorithmIdentifier(digestAlgorithmFor: signatureAlgorithm) - digest = try Digest.computeDigest(for: bytes, using: digestAlgorithm) - } catch { - return false + var digest: Digest? + + if let digestAlgorithm = try? AlgorithmIdentifier(digestAlgorithmFor: signatureAlgorithm) { + digest = try? Digest.computeDigest(for: bytes, using: digestAlgorithm) } - switch self.backing { - case .p256(let p256): + switch (self.backing, digest) { + case (.p256(let p256), .some(let digest)): return p256.isValidSignature(signature, for: digest) - case .p384(let p384): + case (.p384(let p384), .some(let digest)): return p384.isValidSignature(signature, for: digest) - case .p521(let p521): + case (.p521(let p521), .some(let digest)): return p521.isValidSignature(signature, for: digest) - case .rsa(let rsa): + case (.rsa(let rsa), .some(let digest)): // For now we don't support RSA PSS, as it's not deployed in the WebPKI. // We could, if there are sufficient user needs. do { @@ -151,6 +159,10 @@ extension Certificate.PublicKey { } catch { return false } + case (.ed25519(let ed25519), .none): + return ed25519.isValidSignature(signature, for: bytes) + default: + return false } } } @@ -170,6 +182,8 @@ extension Certificate.PublicKey: CustomStringConvertible { return "P521.PublicKey" case .rsa(let publicKey): return "RSA\(publicKey.keySizeInBits).PublicKey" + case .ed25519: + return "Ed25519.PublicKey" } } } @@ -181,6 +195,7 @@ extension Certificate.PublicKey { case p384(Crypto.P384.Signing.PublicKey) case p521(Crypto.P521.Signing.PublicKey) case rsa(_CryptoExtras._RSA.Signing.PublicKey) + case ed25519(Curve25519.Signing.PublicKey) @inlinable static func == (lhs: BackingPublicKey, rhs: BackingPublicKey) -> Bool { @@ -193,6 +208,8 @@ extension Certificate.PublicKey { return l.rawRepresentation == r.rawRepresentation case (.rsa(let l), .rsa(let r)): return l.derRepresentation == r.derRepresentation + case (.ed25519(let l), .ed25519(let r)): + return l.rawRepresentation == r.rawRepresentation default: return false } @@ -213,6 +230,9 @@ extension Certificate.PublicKey { case .rsa(let digest): hasher.combine(3) hasher.combine(digest.derRepresentation) + case .ed25519(let digest): + hasher.combine(4) + hasher.combine(digest.rawRepresentation) } } } @@ -237,6 +257,9 @@ extension SubjectPublicKeyInfo { case .rsa(let rsa): algorithmIdentifier = .rsaKey key = .init(bytes: ArraySlice(rsa.pkcs1DERRepresentation)) + case .ed25519(let ed25519): + algorithmIdentifier = .ed25519 + key = .init(bytes: ArraySlice(ed25519.rawRepresentation)) } self.algorithmIdentifier = algorithmIdentifier @@ -329,6 +352,21 @@ extension _RSA.Signing.PublicKey { } } +extension Curve25519.Signing.PublicKey { + /// Create a Curve25519 Public Key from a given ``Certificate/PublicKey-swift.struct``. + /// + /// Fails if the key is not a Curve25519 key. + /// + /// - parameters: + /// - key: The key to unwrap. + public init?(_ key: Certificate.PublicKey) { + guard case .ed25519(let inner) = key.backing else { + return nil + } + self = inner + } +} + extension Certificate.PublicKey: PEMParseable, PEMSerializable { @inlinable public static var defaultPEMDiscriminator: String { diff --git a/Sources/X509/Curve25519+DER.swift b/Sources/X509/Curve25519+DER.swift new file mode 100644 index 00000000..431f299d --- /dev/null +++ b/Sources/X509/Curve25519+DER.swift @@ -0,0 +1,44 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftCertificates open source project +// +// Copyright (c) 2024 Apple Inc. and the SwiftCertificates project authors +// Licensed under Apache License v2.0 +// +// See LICENSE.txt for license information +// See CONTRIBUTORS.txt for the list of SwiftCertificates project authors +// +// SPDX-License-Identifier: Apache-2.0 +// +//===----------------------------------------------------------------------===// + +import Foundation +import SwiftASN1 +import Crypto + +extension Curve25519.Signing.PrivateKey { + @inlinable + init(pkcs8Key: PKCS8PrivateKey) throws { + // Annoyingly, the PKCS8 key has the raw bytes wrapped inside an octet string. + let rawRepresentation = try ASN1OctetString(derEncoded: pkcs8Key.privateKey.bytes) + self = try .init(rawRepresentation: rawRepresentation.bytes) + } + + @inlinable + var derRepresentation: [UInt8] { + // The DER representation we want is a PKCS8 private key. Somewhat annoyingly + // for us, we have to wrap the key bytes in an extra layer of ASN1OctetString + // which we encode separately. + let pkcs8Key = PKCS8PrivateKey( + algorithm: .ed25519, privateKey: ASN1OctetString(contentBytes: ArraySlice(self.rawRepresentation)) + ) + var serializer = DER.Serializer() + try! serializer.serialize(pkcs8Key) + return serializer.serializedBytes + } + + @inlinable + var pemRepresentation: PEMDocument { + return PEMDocument(type: "PRIVATE KEY", derBytes: self.derRepresentation) + } +} diff --git a/Sources/X509/Digests.swift b/Sources/X509/Digests.swift index d347cd2c..9c840648 100644 --- a/Sources/X509/Digests.swift +++ b/Sources/X509/Digests.swift @@ -136,6 +136,18 @@ extension _RSA.Signing.PublicKey { } } +extension Curve25519.Signing.PublicKey { + @inlinable + func isValidSignature(_ signature: Certificate.Signature, for bytes: Bytes) -> Bool { + guard case .ed25519(let rawInnerSignature) = signature.backing else { + // Signature mismatch + return false + } + + return self.isValidSignature(rawInnerSignature, for: bytes) + } +} + // MARK: Private key operations extension P256.Signing.PrivateKey { @@ -255,3 +267,13 @@ extension _RSA.Signing.PrivateKey { return Certificate.Signature(backing: .rsa(signature)) } } + +extension Curve25519.Signing.PrivateKey { + @inlinable + func signature( + for bytes: Bytes + ) throws -> Certificate.Signature { + let signature: Data = try self.signature(for: bytes) + return Certificate.Signature(backing: .ed25519(.init(signature))) + } +} diff --git a/Sources/X509/PKCS8PrivateKey.swift b/Sources/X509/PKCS8PrivateKey.swift index adc27965..003457e0 100644 --- a/Sources/X509/PKCS8PrivateKey.swift +++ b/Sources/X509/PKCS8PrivateKey.swift @@ -73,12 +73,12 @@ struct PKCS8PrivateKey: DERImplicitlyTaggable { // We ignore the attributes _ = try DER.optionalExplicitlyTagged(&nodes, tagNumber: 0, tagClass: .contextSpecific) { _ in } - return try .init(algorithm: algorithm, privateKey: privateKeyBytes) + return .init(algorithm: algorithm, privateKey: privateKeyBytes) } } @inlinable - init(algorithm: AlgorithmIdentifier, privateKey: ASN1OctetString) throws { + init(algorithm: AlgorithmIdentifier, privateKey: ASN1OctetString) { self.privateKey = privateKey self.algorithm = algorithm } diff --git a/Sources/X509/Signature.swift b/Sources/X509/Signature.swift index c9cb580b..7ea82b75 100644 --- a/Sources/X509/Signature.swift +++ b/Sources/X509/Signature.swift @@ -48,6 +48,12 @@ extension Certificate { case .sha1WithRSAEncryption, .sha256WithRSAEncryption, .sha384WithRSAEncryption, .sha512WithRSAEncryption: let signature = _RSA.Signing.RSASignature(rawRepresentation: signatureBytes.bytes) self.backing = .rsa(signature) + case .ed25519: + guard signatureBytes.paddingBits == 0 else { + throw CertificateError.invalidSignatureForCertificate(reason: "No padding bits are allowed on Ed25519 signatures") + } + let signature = Data(signatureBytes.bytes) + self.backing = .ed25519(signature) default: throw CertificateError.unsupportedSignatureAlgorithm(reason: "\(signatureAlgorithm)") } @@ -66,6 +72,8 @@ extension Certificate.Signature: CustomStringConvertible { return "ECDSA" case .rsa: return "RSA" + case .ed25519: + return "Ed25519" } } } @@ -75,6 +83,7 @@ extension Certificate.Signature { enum BackingSignature: Hashable, Sendable { case ecdsa(ECDSASignature) case rsa(_CryptoExtras._RSA.Signing.RSASignature) + case ed25519(Data) @inlinable static func == (lhs: BackingSignature, rhs: BackingSignature) -> Bool { @@ -83,6 +92,8 @@ extension Certificate.Signature { return l == r case (.rsa(let l), .rsa(let r)): return l.rawRepresentation == r.rawRepresentation + case (.ed25519(let l), .ed25519(let r)): + return l == r default: return false } @@ -97,6 +108,9 @@ extension Certificate.Signature { case .rsa(let digest): hasher.combine(1) hasher.combine(digest.rawRepresentation) + case .ed25519(let sig): + hasher.combine(2) + hasher.combine(sig) } } } @@ -112,6 +126,8 @@ extension ASN1BitString { self = ASN1BitString(bytes: serializer.serializedBytes[...]) case .rsa(let sig): self = ASN1BitString(bytes: ArraySlice(sig.rawRepresentation)) + case .ed25519(let sig): + self = ASN1BitString(bytes: ArraySlice(sig)) } } } @@ -126,6 +142,8 @@ extension ASN1OctetString { self = ASN1OctetString(contentBytes: serializer.serializedBytes[...]) case .rsa(let sig): self = ASN1OctetString(contentBytes: ArraySlice(sig.rawRepresentation)) + case .ed25519(let sig): + self = ASN1OctetString(contentBytes: ArraySlice(sig)) } } } diff --git a/Sources/X509/SignatureAlgorithm.swift b/Sources/X509/SignatureAlgorithm.swift index 29c0f3aa..8becfc24 100644 --- a/Sources/X509/SignatureAlgorithm.swift +++ b/Sources/X509/SignatureAlgorithm.swift @@ -65,6 +65,9 @@ extension Certificate { /// This value represents an RSA signature with PKCS1v1.5 padding and SHA512 as the hash function. public static let sha512WithRSAEncryption = Self(algorithmIdentifier: .sha512WithRSAEncryption) + /// This value represents an EdDSA signature using Curve25519. + public static let ed25519 = Self(algorithmIdentifier: .ed25519) + /// Whether this algorithm represents an ECDSA signature. @inlinable var isECDSA: Bool { diff --git a/Sources/X509/X509BaseTypes/AlgorithmIdentifier.swift b/Sources/X509/X509BaseTypes/AlgorithmIdentifier.swift index 665fd20c..1db97914 100644 --- a/Sources/X509/X509BaseTypes/AlgorithmIdentifier.swift +++ b/Sources/X509/X509BaseTypes/AlgorithmIdentifier.swift @@ -207,6 +207,12 @@ extension AlgorithmIdentifier { algorithm: .AlgorithmIdentifier.sha512, parameters: try! ASN1Any(erasing: ASN1Null()) ) + + @usableFromInline + static let ed25519 = AlgorithmIdentifier( + algorithm: .AlgorithmIdentifier.ed25519, + parameters: nil + ) } extension AlgorithmIdentifier: CustomStringConvertible { @@ -264,6 +270,8 @@ extension ASN1ObjectIdentifier.AlgorithmIdentifier { static let sha384: ASN1ObjectIdentifier = [2, 16, 840, 1, 101, 3, 4, 2, 2] static let sha512: ASN1ObjectIdentifier = [2, 16, 840, 1, 101, 3, 4, 2, 3] + + static let ed25519: ASN1ObjectIdentifier = [1, 3, 101, 112] } extension AlgorithmIdentifier { diff --git a/Tests/X509Tests/CertificateTests.swift b/Tests/X509Tests/CertificateTests.swift index 9a7512b4..15b80725 100644 --- a/Tests/X509Tests/CertificateTests.swift +++ b/Tests/X509Tests/CertificateTests.swift @@ -681,4 +681,46 @@ final class CertificateTests: XCTestCase { ] ) } + + func testRFC8410Ed25519PublicKey() throws { + let pemKey = """ + -----BEGIN PUBLIC KEY----- + MCowBQYDK2VwAyEAGb9ECWmEzf6FQbrBZ9w7lshQhqowtrbLDFw4rXAxZuE= + -----END PUBLIC KEY----- + """ + let resultingKey = try Certificate.PublicKey(pemEncoded: pemKey) + let unwrappedKey = Curve25519.Signing.PublicKey(resultingKey) + let reWrappedKey = Certificate.PublicKey(unwrappedKey!) + XCTAssertEqual(reWrappedKey, resultingKey) + XCTAssertEqual(try reWrappedKey.serializeAsPEM().pemString, pemKey) + } + + func testRFC8410Ed25519PrivateKey() throws { + let pemKey = """ + -----BEGIN PRIVATE KEY----- + MC4CAQAwBQYDK2VwBCIEINTuctv5E1hK1bbY8fdp+K06/nwoy/HU++CXqI9EdVhC + -----END PRIVATE KEY----- + """ + let resultingKey = try Certificate.PrivateKey(pemEncoded: pemKey) + XCTAssertEqual(try resultingKey.serializeAsPEM().pemString, pemKey) + } + + func testExampleEd25519SelfIssuedSelfSignedCert() throws { + let cert = """ + -----BEGIN CERTIFICATE----- + MIIBCDCBuwIUGW78zw0OL0GptJi++a91dBa7DsQwBQYDK2VwMCcxCzAJBgNVBAYT + AkRFMRgwFgYDVQQDDA93d3cuZXhhbXBsZS5jb20wHhcNMTkwMzMxMTc1MTIyWhcN + MjEwMjI4MTc1MTIyWjAnMQswCQYDVQQGEwJERTEYMBYGA1UEAwwPd3d3LmV4YW1w + bGUuY29tMCowBQYDK2VwAyEAK87g0b8CC1eA5mvKXt9uezZwJYWEyg74Y0xTZEkq + CcwwBQYDK2VwA0EAIIu/aa3Qtr3IE5to/nvWVY9y3ciwG5DnA70X3ALUhFs+U5aL + tfY8sNT1Ng72ht+UBwByuze20UsL9qMsmknQCA== + -----END CERTIFICATE----- + """ + let parsedCert = try Certificate(pemEncoded: cert) + XCTAssertTrue(parsedCert.publicKey.isValidSignature(parsedCert.signature, for: parsedCert)) + XCTAssertNotNil(Curve25519.Signing.PublicKey(parsedCert.publicKey)) + + let reEncoded = try parsedCert.serializeAsPEM().pemString + XCTAssertEqual(cert, reEncoded) + } } diff --git a/Tests/X509Tests/SignatureTests.swift b/Tests/X509Tests/SignatureTests.swift index e810a88a..ff7a8c48 100644 --- a/Tests/X509Tests/SignatureTests.swift +++ b/Tests/X509Tests/SignatureTests.swift @@ -27,6 +27,7 @@ final class SignatureTests: XCTestCase { static let p384Key = P384.Signing.PrivateKey() static let p521Key = P521.Signing.PrivateKey() static let rsaKey = try! _RSA.Signing.PrivateKey(keySize: .bits2048) + static let ed25519Key = Curve25519.Signing.PrivateKey() #if canImport(Darwin) static let secureEnclaveP256 = try? SecureEnclave.P256.Signing.PrivateKey() static let secKeyRSA = try? generateSecKey(keyType: kSecAttrKeyTypeRSA, keySize: 2048, useSEP: false) @@ -228,6 +229,14 @@ final class SignatureTests: XCTestCase { ) } + func testHashFunctionMismatch_p256_ed25519() throws { + try self.hashFunctionMismatchTest( + privateKey: .init(Self.p256Key), + signatureAlgorithm: .ed25519, + validCombination: false + ) + } + func testHashFunctionMismatch_p384_ecdsaWithSHA256() throws { try self.hashFunctionMismatchTest( privateKey: .init(Self.p384Key), @@ -284,6 +293,14 @@ final class SignatureTests: XCTestCase { ) } + func testHashFunctionMismatch_p384_ed25519() throws { + try self.hashFunctionMismatchTest( + privateKey: .init(Self.p384Key), + signatureAlgorithm: .ed25519, + validCombination: false + ) + } + func testHashFunctionMismatch_p521_ecdsaWithSHA256() throws { try self.hashFunctionMismatchTest( privateKey: .init(Self.p521Key), @@ -340,6 +357,14 @@ final class SignatureTests: XCTestCase { ) } + func testHashFunctionMismatch_p521_ed25519() throws { + try self.hashFunctionMismatchTest( + privateKey: .init(Self.p521Key), + signatureAlgorithm: .ed25519, + validCombination: false + ) + } + func testHashFunctionMismatch_rsa_ecdsaWithSHA256() throws { try self.hashFunctionMismatchTest( privateKey: .init(Self.rsaKey), @@ -364,6 +389,14 @@ final class SignatureTests: XCTestCase { ) } + func testHashFunctionMismatch_rsa_ed25519() throws { + try self.hashFunctionMismatchTest( + privateKey: .init(Self.rsaKey), + signatureAlgorithm: .ed25519, + validCombination: false + ) + } + func testHashFunctionMismatch_rsa_sha1WithRSAEncryption() throws { try self.hashFunctionMismatchTest( privateKey: .init(Self.rsaKey), @@ -591,6 +624,17 @@ final class SignatureTests: XCTestCase { ) } + func testHashFunctionMismatch_secKeyEC521_ed25519() throws { + guard let secKeyEC521 = Self.secKeyEC521 else { + throw XCTSkip("Key Error") + } + try self.hashFunctionMismatchTest( + privateKey: .init(secKeyEC521), + signatureAlgorithm: .ed25519, + validCombination: false + ) + } + func testHashFunctionMismatch_secKeyEnclaveEC256_ecdsaWithSHA256() throws { guard let secKeyEnclaveEC256 = Self.secKeyEnclaveEC256 else { throw XCTSkip("No SEP") @@ -668,6 +712,17 @@ final class SignatureTests: XCTestCase { ) } + func testHashFunctionMismatch_secKeyEnclaveEC384_ed25519() throws { + guard let secKeyEnclaveEC384 = Self.secKeyEnclaveEC384 else { + throw XCTSkip("No SEP") + } + try self.hashFunctionMismatchTest( + privateKey: .init(secKeyEnclaveEC384), + signatureAlgorithm: .ed25519, + validCombination: false + ) + } + func testHashFunctionMismatch_secureEnclaveP256_ecdsaWithSHA256() throws { guard let secureEnclaveP256 = Self.secureEnclaveP256 else { throw XCTSkip("No SEP") @@ -744,8 +799,83 @@ final class SignatureTests: XCTestCase { validCombination: false ) } + + func testHashFunctionMismatch_secureEnclaveP256_ed25519() throws { + guard let secureEnclaveP256 = Self.secureEnclaveP256 else { + throw XCTSkip("No SEP") + } + try self.hashFunctionMismatchTest( + privateKey: .init(secureEnclaveP256), + signatureAlgorithm: .ed25519, + validCombination: false + ) + } #endif + func testHashFunctionMismatch_ped25519_ecdsaWithSHA256() throws { + try self.hashFunctionMismatchTest( + privateKey: .init(Self.ed25519Key), + signatureAlgorithm: .ecdsaWithSHA256, + validCombination: false + ) + } + + func testHashFunctionMismatch_ed25519_ecdsaWithSHA384() throws { + try self.hashFunctionMismatchTest( + privateKey: .init(Self.ed25519Key), + signatureAlgorithm: .ecdsaWithSHA384, + validCombination: false + ) + } + + func testHashFunctionMismatch_ed25519_ecdsaWithSHA512() throws { + try self.hashFunctionMismatchTest( + privateKey: .init(Self.ed25519Key), + signatureAlgorithm: .ecdsaWithSHA512, + validCombination: false + ) + } + + func testHashFunctionMismatch_ed25519_sha1WithRSAEncryption() throws { + try self.hashFunctionMismatchTest( + privateKey: .init(Self.ed25519Key), + signatureAlgorithm: .sha1WithRSAEncryption, + validCombination: false + ) + } + + func testHashFunctionMismatch_ed25519_sha256WithRSAEncryption() throws { + try self.hashFunctionMismatchTest( + privateKey: .init(Self.ed25519Key), + signatureAlgorithm: .sha256WithRSAEncryption, + validCombination: false + ) + } + + func testHashFunctionMismatch_ed25519_sha384WithRSAEncryption() throws { + try self.hashFunctionMismatchTest( + privateKey: .init(Self.ed25519Key), + signatureAlgorithm: .sha384WithRSAEncryption, + validCombination: false + ) + } + + func testHashFunctionMismatch_ed25519_sha512WithRSAEncryption() throws { + try self.hashFunctionMismatchTest( + privateKey: .init(Self.ed25519Key), + signatureAlgorithm: .sha512WithRSAEncryption, + validCombination: false + ) + } + + func testHashFunctionMismatch_ed25519_ed25519() throws { + try self.hashFunctionMismatchTest( + privateKey: .init(Self.ed25519Key), + signatureAlgorithm: .ed25519, + validCombination: true + ) + } + func testECDSASignatureCorrectlyStripsLeadingZerosFromRawByteRepresentation() throws { // We're testing a round-trip logic here, ensuring that the ECDSA signature correctly round-trips. func testECDSASignatureRoundTrip(rawSignatureBytes: [UInt8]) throws {