From d747208a9c9fea18b7710f10c35977e6000e65d0 Mon Sep 17 00:00:00 2001 From: Nicolas Bachschmidt Date: Wed, 15 Jan 2025 18:36:49 +0100 Subject: [PATCH 1/2] Add support for Ed25519 to CMS Contrary to RSA and ECDSA that take a SHA digest of the message as their input, Ed25519 uses the original message when computing the signature. For this reason, no digest algorithm was previously defined for the Ed25519 signature algorithm. However CMS requires a digest algorithm to be present in the SignerInfo. Therefore creating a CMS signature using a Ed25519 private key would previously fail. Per RFC 8419 section 2.3, "When signing with Ed25519, the message digest algorithm MUST be SHA-512". AlgorithmIdentifier.init(digestAlgorithmFor:) has been updated accordingly. Signature operations have been updated to delegate the validation of the signature algorithm and the computation of the message digest (when relevant) to the underlying key. --- Sources/X509/CertificatePrivateKey.swift | 71 +----- Sources/X509/CertificatePublicKey.swift | 52 +--- Sources/X509/Digests.swift | 236 ------------------- Sources/X509/SecKeyWrapper.swift | 38 ++- Sources/X509/Signature.swift | 288 +++++++++++++++++++++++ Sources/X509/SignatureAlgorithm.swift | 2 +- Tests/X509Tests/CMSTests.swift | 79 +++++++ 7 files changed, 404 insertions(+), 362 deletions(-) diff --git a/Sources/X509/CertificatePrivateKey.swift b/Sources/X509/CertificatePrivateKey.swift index 1dfb7c2..de8ffb2 100644 --- a/Sources/X509/CertificatePrivateKey.swift +++ b/Sources/X509/CertificatePrivateKey.swift @@ -91,32 +91,23 @@ extension Certificate { bytes: Bytes, signatureAlgorithm: SignatureAlgorithm ) throws -> Signature { - try self.validateAlgorithmForKey(algorithm: signatureAlgorithm) - switch self.backing { case .p256(let p256): - let digestAlgorithm = try AlgorithmIdentifier(digestAlgorithmFor: signatureAlgorithm) - return try p256.signature(for: bytes, digestAlgorithm: digestAlgorithm) + return try p256.signature(for: bytes, signatureAlgorithm: signatureAlgorithm) case .p384(let p384): - let digestAlgorithm = try AlgorithmIdentifier(digestAlgorithmFor: signatureAlgorithm) - return try p384.signature(for: bytes, digestAlgorithm: digestAlgorithm) + return try p384.signature(for: bytes, signatureAlgorithm: signatureAlgorithm) case .p521(let p521): - let digestAlgorithm = try AlgorithmIdentifier(digestAlgorithmFor: signatureAlgorithm) - return try p521.signature(for: bytes, digestAlgorithm: digestAlgorithm) + return try p521.signature(for: bytes, signatureAlgorithm: signatureAlgorithm) 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) + return try rsa.signature(for: bytes, signatureAlgorithm: signatureAlgorithm) #if canImport(Darwin) case .secureEnclaveP256(let secureEnclaveP256): - let digestAlgorithm = try AlgorithmIdentifier(digestAlgorithmFor: signatureAlgorithm) - return try secureEnclaveP256.signature(for: bytes, digestAlgorithm: digestAlgorithm) + return try secureEnclaveP256.signature(for: bytes, signatureAlgorithm: signatureAlgorithm) case .secKey(let secKeyWrapper): - let digestAlgorithm = try AlgorithmIdentifier(digestAlgorithmFor: signatureAlgorithm) - return try secKeyWrapper.signature(for: bytes, digestAlgorithm: digestAlgorithm) + return try secKeyWrapper.signature(for: bytes, signatureAlgorithm: signatureAlgorithm) #endif case .ed25519(let ed25519): - return try ed25519.signature(for: bytes) + return try ed25519.signature(for: bytes, signatureAlgorithm: signatureAlgorithm) } } @@ -143,54 +134,6 @@ extension Certificate { return PublicKey(ed25519.publicKey) } } - - @inlinable - func validateAlgorithmForKey(algorithm: SignatureAlgorithm) throws { - switch self.backing { - case .p256, .p384, .p521: - if !algorithm.isECDSA { - throw CertificateError.unsupportedSignatureAlgorithm( - reason: "Cannot use \(algorithm) with ECDSA key \(self)" - ) - } - case .rsa: - if !algorithm.isRSA { - throw CertificateError.unsupportedSignatureAlgorithm( - reason: "Cannot use \(algorithm) with RSA key \(self)" - ) - } - #if canImport(Darwin) - case .secureEnclaveP256: - if !algorithm.isECDSA { - throw CertificateError.unsupportedSignatureAlgorithm( - reason: "Cannot use \(algorithm) with ECDSA key \(self)" - ) - } - case .secKey(let key): - switch key.type { - case .ECDSA: - if !algorithm.isECDSA { - throw CertificateError.unsupportedSignatureAlgorithm( - reason: "Cannot use \(algorithm) with ECDSA key \(self)" - ) - } - case .RSA: - if !algorithm.isRSA { - throw CertificateError.unsupportedSignatureAlgorithm( - reason: "Cannot use \(algorithm) with RSA key \(self)" - ) - } - } - #endif - case .ed25519: - if algorithm != .ed25519 { - throw CertificateError.unsupportedSignatureAlgorithm( - reason: "Cannot use \(algorithm) with Ed25519 key \(self)" - ) - } - } - - } } } diff --git a/Sources/X509/CertificatePublicKey.swift b/Sources/X509/CertificatePublicKey.swift index c81e57e..0dba412 100644 --- a/Sources/X509/CertificatePublicKey.swift +++ b/Sources/X509/CertificatePublicKey.swift @@ -137,32 +137,17 @@ extension Certificate.PublicKey { for bytes: Bytes, signatureAlgorithm: Certificate.SignatureAlgorithm ) -> Bool { - var digest: Digest? - - if let digestAlgorithm = try? AlgorithmIdentifier(digestAlgorithmFor: signatureAlgorithm) { - digest = try? Digest.computeDigest(for: bytes, using: digestAlgorithm) - } - - switch (self.backing, digest) { - case (.p256(let p256), .some(let digest)): - return p256.isValidSignature(signature, for: digest) - case (.p384(let p384), .some(let digest)): - return p384.isValidSignature(signature, for: digest) - case (.p521(let p521), .some(let digest)): - return p521.isValidSignature(signature, for: digest) - 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 { - let padding = try _RSA.Signing.Padding(forSignatureAlgorithm: signatureAlgorithm) - return rsa.isValidSignature(signature, for: digest, padding: padding) - } catch { - return false - } - case (.ed25519(let ed25519), .none): - return ed25519.isValidSignature(signature, for: bytes) - default: - return false + switch self.backing { + case .p256(let p256): + return p256.isValidSignature(signature, for: bytes, signatureAlgorithm: signatureAlgorithm) + case .p384(let p384): + return p384.isValidSignature(signature, for: bytes, signatureAlgorithm: signatureAlgorithm) + case .p521(let p521): + return p521.isValidSignature(signature, for: bytes, signatureAlgorithm: signatureAlgorithm) + case .rsa(let rsa): + return rsa.isValidSignature(signature, for: bytes, signatureAlgorithm: signatureAlgorithm) + case .ed25519(let ed25519): + return ed25519.isValidSignature(signature, for: bytes, signatureAlgorithm: signatureAlgorithm) } } } @@ -277,21 +262,6 @@ extension Certificate.PublicKey { } } -extension _RSA.Signing.Padding { - @inlinable - init(forSignatureAlgorithm signatureAlgorithm: Certificate.SignatureAlgorithm) throws { - switch signatureAlgorithm { - case .sha1WithRSAEncryption, .sha256WithRSAEncryption, .sha384WithRSAEncryption, .sha512WithRSAEncryption: - self = .insecurePKCS1v1_5 - default: - // Either this is RSA PSS, or we hit a bug. Either way, unsupported. - throw CertificateError.unsupportedSignatureAlgorithm( - reason: "Unable to determine RSA padding mode for \(signatureAlgorithm)" - ) - } - } -} - extension P256.Signing.PublicKey { /// Create a P256 Public Key from a given ``Certificate/PublicKey-swift.struct``. /// diff --git a/Sources/X509/Digests.swift b/Sources/X509/Digests.swift index 40fea28..11c0f41 100644 --- a/Sources/X509/Digests.swift +++ b/Sources/X509/Digests.swift @@ -14,7 +14,6 @@ import Foundation import Crypto -import _CryptoExtras @usableFromInline enum Digest { @@ -58,238 +57,3 @@ extension Digest: Sequence { } } } - -// MARK: Public key operations - -extension P256.Signing.PublicKey { - @inlinable - func isValidSignature(_ signature: Certificate.Signature, for digest: Digest) -> Bool { - guard case .ecdsa(let rawInnerSignature) = signature.backing, - let innerSignature = P256.Signing.ECDSASignature(rawInnerSignature) - else { - // Signature mismatch - return false - } - - switch digest { - case .insecureSHA1(let sha1): - return self.isValidSignature(innerSignature, for: sha1) - case .sha256(let sha256): - return self.isValidSignature(innerSignature, for: sha256) - case .sha384(let sha384): - return self.isValidSignature(innerSignature, for: sha384) - case .sha512(let sha512): - return self.isValidSignature(innerSignature, for: sha512) - } - } -} - -extension P384.Signing.PublicKey { - @inlinable - func isValidSignature(_ signature: Certificate.Signature, for digest: Digest) -> Bool { - guard case .ecdsa(let rawInnerSignature) = signature.backing, - let innerSignature = P384.Signing.ECDSASignature(rawInnerSignature) - else { - // Signature mismatch - return false - } - - switch digest { - case .insecureSHA1(let sha1): - return self.isValidSignature(innerSignature, for: sha1) - case .sha256(let sha256): - return self.isValidSignature(innerSignature, for: sha256) - case .sha384(let sha384): - return self.isValidSignature(innerSignature, for: sha384) - case .sha512(let sha512): - return self.isValidSignature(innerSignature, for: sha512) - } - } -} - -extension P521.Signing.PublicKey { - @inlinable - func isValidSignature(_ signature: Certificate.Signature, for digest: Digest) -> Bool { - guard case .ecdsa(let rawInnerSignature) = signature.backing, - let innerSignature = P521.Signing.ECDSASignature(rawInnerSignature) - else { - // Signature mismatch - return false - } - - switch digest { - case .insecureSHA1(let sha1): - return self.isValidSignature(innerSignature, for: sha1) - case .sha256(let sha256): - return self.isValidSignature(innerSignature, for: sha256) - case .sha384(let sha384): - return self.isValidSignature(innerSignature, for: sha384) - case .sha512(let sha512): - return self.isValidSignature(innerSignature, for: sha512) - } - } -} - -extension _RSA.Signing.PublicKey { - @inlinable - func isValidSignature(_ signature: Certificate.Signature, for digest: Digest, padding: _RSA.Signing.Padding) -> Bool - { - guard case .rsa(let innerSignature) = signature.backing else { - // Signature mismatch - return false - } - - switch digest { - case .insecureSHA1(let sha1): - return self.isValidSignature(innerSignature, for: sha1, padding: padding) - case .sha256(let sha256): - return self.isValidSignature(innerSignature, for: sha256, padding: padding) - case .sha384(let sha384): - return self.isValidSignature(innerSignature, for: sha384, padding: padding) - case .sha512(let sha512): - return self.isValidSignature(innerSignature, for: sha512, padding: padding) - } - } -} - -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 { - @inlinable - func signature( - for bytes: Bytes, - digestAlgorithm: AlgorithmIdentifier - ) throws -> Certificate.Signature { - let signature: P256.Signing.ECDSASignature - - switch try Digest.computeDigest(for: bytes, using: digestAlgorithm) { - case .insecureSHA1(let sha1): - signature = try self.signature(for: sha1) - case .sha256(let sha256): - signature = try self.signature(for: sha256) - case .sha384(let sha384): - signature = try self.signature(for: sha384) - case .sha512(let sha512): - signature = try self.signature(for: sha512) - } - - return Certificate.Signature(backing: .ecdsa(.init(signature))) - } -} - -#if canImport(Darwin) -extension SecureEnclave.P256.Signing.PrivateKey { - @inlinable - func signature( - for bytes: Bytes, - digestAlgorithm: AlgorithmIdentifier - ) throws -> Certificate.Signature { - let signature: P256.Signing.ECDSASignature - - switch try Digest.computeDigest(for: bytes, using: digestAlgorithm) { - case .insecureSHA1(let sha1): - signature = try self.signature(for: sha1) - case .sha256(let sha256): - signature = try self.signature(for: sha256) - case .sha384(let sha384): - signature = try self.signature(for: sha384) - case .sha512(let sha512): - signature = try self.signature(for: sha512) - } - - return Certificate.Signature(backing: .ecdsa(.init(signature))) - } -} -#endif - -extension P384.Signing.PrivateKey { - @inlinable - func signature( - for bytes: Bytes, - digestAlgorithm: AlgorithmIdentifier - ) throws -> Certificate.Signature { - let signature: P384.Signing.ECDSASignature - - switch try Digest.computeDigest(for: bytes, using: digestAlgorithm) { - case .insecureSHA1(let sha1): - signature = try self.signature(for: sha1) - case .sha256(let sha256): - signature = try self.signature(for: sha256) - case .sha384(let sha384): - signature = try self.signature(for: sha384) - case .sha512(let sha512): - signature = try self.signature(for: sha512) - } - - return Certificate.Signature(backing: .ecdsa(.init(signature))) - } -} - -extension P521.Signing.PrivateKey { - @inlinable - func signature( - for bytes: Bytes, - digestAlgorithm: AlgorithmIdentifier - ) throws -> Certificate.Signature { - let signature: P521.Signing.ECDSASignature - - switch try Digest.computeDigest(for: bytes, using: digestAlgorithm) { - case .insecureSHA1(let sha1): - signature = try self.signature(for: sha1) - case .sha256(let sha256): - signature = try self.signature(for: sha256) - case .sha384(let sha384): - signature = try self.signature(for: sha384) - case .sha512(let sha512): - signature = try self.signature(for: sha512) - } - - return Certificate.Signature(backing: .ecdsa(.init(signature))) - } -} - -extension _RSA.Signing.PrivateKey { - @inlinable - func signature( - for bytes: Bytes, - digestAlgorithm: AlgorithmIdentifier, - padding: _RSA.Signing.Padding - ) throws -> Certificate.Signature { - let signature: _RSA.Signing.RSASignature - - switch try Digest.computeDigest(for: bytes, using: digestAlgorithm) { - case .insecureSHA1(let sha1): - signature = try self.signature(for: sha1, padding: padding) - case .sha256(let sha256): - signature = try self.signature(for: sha256, padding: padding) - case .sha384(let sha384): - signature = try self.signature(for: sha384, padding: padding) - case .sha512(let sha512): - signature = try self.signature(for: sha512, padding: padding) - } - - 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/SecKeyWrapper.swift b/Sources/X509/SecKeyWrapper.swift index 4e99eb2..5801e5f 100644 --- a/Sources/X509/SecKeyWrapper.swift +++ b/Sources/X509/SecKeyWrapper.swift @@ -173,11 +173,11 @@ extension Certificate.PrivateKey { static func signatureData( key: SecKey, type: KeyType, - digestAlgorithm: AlgorithmIdentifier, + signatureAlgorithm: Certificate.SignatureAlgorithm, bytes: Bytes ) throws -> Data { - let signatureAlgorithm = try Self.signatureAlgorithm(digestAlgorithm: digestAlgorithm, type: type) + let signatureAlgorithm = try Self.keyAlgorithm(signatureAlgorithm: signatureAlgorithm, type: type) var error: Unmanaged? guard @@ -200,37 +200,35 @@ extension Certificate.PrivateKey { return signatureData } - static func signatureAlgorithm(digestAlgorithm: AlgorithmIdentifier, type: KeyType) throws -> SecKeyAlgorithm { + static func keyAlgorithm(signatureAlgorithm: Certificate.SignatureAlgorithm, type: KeyType) throws -> SecKeyAlgorithm { let algorithm: SecKeyAlgorithm switch type { case .RSA: - switch digestAlgorithm { - case .sha1, .sha1UsingNil: + switch signatureAlgorithm { + case .sha1WithRSAEncryption: algorithm = .rsaSignatureMessagePKCS1v15SHA1 - case .sha256, .sha256UsingNil: + case .sha256WithRSAEncryption: algorithm = .rsaSignatureMessagePKCS1v15SHA256 - case .sha384, .sha384UsingNil: + case .sha384WithRSAEncryption: algorithm = .rsaSignatureMessagePKCS1v15SHA384 - case .sha512, .sha512UsingNil: + case .sha512WithRSAEncryption: algorithm = .rsaSignatureMessagePKCS1v15SHA512 default: - throw CertificateError.unsupportedPrivateKey( - reason: "unsupported SecKey RSA digest algorithm: \(digestAlgorithm)" + throw CertificateError.unsupportedSignatureAlgorithm( + reason: "Cannot use \(signatureAlgorithm) with RSA key" ) } case .ECDSA: - switch digestAlgorithm { - case .sha1, .sha1UsingNil: - algorithm = .ecdsaSignatureMessageX962SHA1 - case .sha256, .sha256UsingNil: + switch signatureAlgorithm { + case .ecdsaWithSHA256: algorithm = .ecdsaSignatureMessageX962SHA256 - case .sha384, .sha384UsingNil: + case .ecdsaWithSHA384: algorithm = .ecdsaSignatureMessageX962SHA384 - case .sha512, .sha512UsingNil: + case .ecdsaWithSHA512: algorithm = .ecdsaSignatureMessageX962SHA512 default: - throw CertificateError.unsupportedPrivateKey( - reason: "unsupported SecKey ECDSA digest algorithm: \(digestAlgorithm)" + throw CertificateError.unsupportedSignatureAlgorithm( + reason: "Cannot use \(signatureAlgorithm) with ECDSA key" ) } } @@ -241,13 +239,13 @@ extension Certificate.PrivateKey { @usableFromInline func signature( for bytes: Bytes, - digestAlgorithm: AlgorithmIdentifier + signatureAlgorithm: Certificate.SignatureAlgorithm ) throws -> Certificate.Signature { let signatureData = try Self.signatureData( key: self.privateKey, type: self.type, - digestAlgorithm: digestAlgorithm, + signatureAlgorithm: signatureAlgorithm, bytes: bytes ) diff --git a/Sources/X509/Signature.swift b/Sources/X509/Signature.swift index 6963bdb..a0e9edd 100644 --- a/Sources/X509/Signature.swift +++ b/Sources/X509/Signature.swift @@ -13,6 +13,7 @@ //===----------------------------------------------------------------------===// import SwiftASN1 +import Crypto import _CryptoExtras import Foundation @@ -149,3 +150,290 @@ extension ASN1OctetString { } } } + +// MARK: Public key operations + +extension P256.Signing.PublicKey { + @inlinable + internal func isValidSignature( + _ signature: Certificate.Signature, + for bytes: Bytes, + signatureAlgorithm: Certificate.SignatureAlgorithm + ) -> Bool { + guard case .ecdsa(let rawInnerSignature) = signature.backing, + let innerSignature = P256.Signing.ECDSASignature(rawInnerSignature) + else { + // Signature mismatch + return false + } + + switch signatureAlgorithm { + case .ecdsaWithSHA256: + return self.isValidSignature(innerSignature, for: SHA256.hash(data: bytes)) + case .ecdsaWithSHA384: + return self.isValidSignature(innerSignature, for: SHA384.hash(data: bytes)) + case .ecdsaWithSHA512: + return self.isValidSignature(innerSignature, for: SHA512.hash(data: bytes)) + default: + return false + } + } +} + +extension P384.Signing.PublicKey { + @inlinable + internal func isValidSignature( + _ signature: Certificate.Signature, + for bytes: Bytes, + signatureAlgorithm: Certificate.SignatureAlgorithm + ) -> Bool { + guard case .ecdsa(let rawInnerSignature) = signature.backing, + let innerSignature = P384.Signing.ECDSASignature(rawInnerSignature) + else { + // Signature mismatch + return false + } + + switch signatureAlgorithm { + case .ecdsaWithSHA256: + return self.isValidSignature(innerSignature, for: SHA256.hash(data: bytes)) + case .ecdsaWithSHA384: + return self.isValidSignature(innerSignature, for: SHA384.hash(data: bytes)) + case .ecdsaWithSHA512: + return self.isValidSignature(innerSignature, for: SHA512.hash(data: bytes)) + default: + return false + } + } +} + +extension P521.Signing.PublicKey { + @inlinable + internal func isValidSignature( + _ signature: Certificate.Signature, + for bytes: Bytes, + signatureAlgorithm: Certificate.SignatureAlgorithm + ) -> Bool { + guard case .ecdsa(let rawInnerSignature) = signature.backing, + let innerSignature = P521.Signing.ECDSASignature(rawInnerSignature) + else { + // Signature mismatch + return false + } + + switch signatureAlgorithm { + case .ecdsaWithSHA256: + return self.isValidSignature(innerSignature, for: SHA256.hash(data: bytes)) + case .ecdsaWithSHA384: + return self.isValidSignature(innerSignature, for: SHA384.hash(data: bytes)) + case .ecdsaWithSHA512: + return self.isValidSignature(innerSignature, for: SHA512.hash(data: bytes)) + default: + return false + } + } +} + +extension _RSA.Signing.PublicKey { + @inlinable + internal func isValidSignature( + _ signature: Certificate.Signature, + for bytes: Bytes, + signatureAlgorithm: Certificate.SignatureAlgorithm + ) -> Bool { + guard case .rsa(let innerSignature) = signature.backing else { + // Signature mismatch + return false + } + // For now we don't support RSA PSS, as it's not deployed in the WebPKI. + let padding = _RSA.Signing.Padding.insecurePKCS1v1_5 + + switch signatureAlgorithm { + case .sha1WithRSAEncryption: + return self.isValidSignature(innerSignature, for: Insecure.SHA1.hash(data: bytes), padding: padding) + case .sha256WithRSAEncryption: + return self.isValidSignature(innerSignature, for: SHA256.hash(data: bytes), padding: padding) + case .sha384WithRSAEncryption: + return self.isValidSignature(innerSignature, for: SHA384.hash(data: bytes), padding: padding) + case .sha512WithRSAEncryption: + return self.isValidSignature(innerSignature, for: SHA512.hash(data: bytes), padding: padding) + default: + return false + } + } +} + +extension Curve25519.Signing.PublicKey { + @inlinable + internal func isValidSignature( + _ signature: Certificate.Signature, + for bytes: Bytes, + signatureAlgorithm: Certificate.SignatureAlgorithm + ) -> Bool { + guard case .ed25519(let rawInnerSignature) = signature.backing else { + // Signature mismatch + return false + } + + switch signatureAlgorithm { + case .ed25519: + return self.isValidSignature(rawInnerSignature, for: bytes) + default: + return false + } + } +} + +// MARK: Private key operations + +extension P256.Signing.PrivateKey { + @inlinable + func signature( + for bytes: Bytes, + signatureAlgorithm: Certificate.SignatureAlgorithm + ) throws -> Certificate.Signature { + let signature: P256.Signing.ECDSASignature + + switch signatureAlgorithm { + case .ecdsaWithSHA256: + signature = try self.signature(for: SHA256.hash(data: bytes)) + case .ecdsaWithSHA384: + signature = try self.signature(for: SHA384.hash(data: bytes)) + case .ecdsaWithSHA512: + signature = try self.signature(for: SHA512.hash(data: bytes)) + default: + throw CertificateError.unsupportedSignatureAlgorithm( + reason: "Cannot use \(signatureAlgorithm) with ECDSA key" + ) + } + + return Certificate.Signature(backing: .ecdsa(.init(signature))) + } +} + +#if canImport(Darwin) +extension SecureEnclave.P256.Signing.PrivateKey { + @inlinable + func signature( + for bytes: Bytes, + signatureAlgorithm: Certificate.SignatureAlgorithm + ) throws -> Certificate.Signature { + let signature: P256.Signing.ECDSASignature + + switch signatureAlgorithm { + case .ecdsaWithSHA256: + signature = try self.signature(for: SHA256.hash(data: bytes)) + case .ecdsaWithSHA384: + signature = try self.signature(for: SHA384.hash(data: bytes)) + case .ecdsaWithSHA512: + signature = try self.signature(for: SHA512.hash(data: bytes)) + default: + throw CertificateError.unsupportedSignatureAlgorithm( + reason: "Cannot use \(signatureAlgorithm) with ECDSA key" + ) + } + + return Certificate.Signature(backing: .ecdsa(.init(signature))) + } +} +#endif + +extension P384.Signing.PrivateKey { + @inlinable + func signature( + for bytes: Bytes, + signatureAlgorithm: Certificate.SignatureAlgorithm + ) throws -> Certificate.Signature { + let signature: P384.Signing.ECDSASignature + + switch signatureAlgorithm { + case .ecdsaWithSHA256: + signature = try self.signature(for: SHA256.hash(data: bytes)) + case .ecdsaWithSHA384: + signature = try self.signature(for: SHA384.hash(data: bytes)) + case .ecdsaWithSHA512: + signature = try self.signature(for: SHA512.hash(data: bytes)) + default: + throw CertificateError.unsupportedSignatureAlgorithm( + reason: "Cannot use \(signatureAlgorithm) with ECDSA key" + ) + } + + return Certificate.Signature(backing: .ecdsa(.init(signature))) + } +} + +extension P521.Signing.PrivateKey { + @inlinable + func signature( + for bytes: Bytes, + signatureAlgorithm: Certificate.SignatureAlgorithm + ) throws -> Certificate.Signature { + let signature: P521.Signing.ECDSASignature + + switch signatureAlgorithm { + case .ecdsaWithSHA256: + signature = try self.signature(for: SHA256.hash(data: bytes)) + case .ecdsaWithSHA384: + signature = try self.signature(for: SHA384.hash(data: bytes)) + case .ecdsaWithSHA512: + signature = try self.signature(for: SHA512.hash(data: bytes)) + default: + throw CertificateError.unsupportedSignatureAlgorithm( + reason: "Cannot use \(signatureAlgorithm) with ECDSA key" + ) + } + + return Certificate.Signature(backing: .ecdsa(.init(signature))) + } +} + +extension _RSA.Signing.PrivateKey { + @inlinable + func signature( + for bytes: Bytes, + signatureAlgorithm: Certificate.SignatureAlgorithm + ) throws -> Certificate.Signature { + let signature: _RSA.Signing.RSASignature + // For now we don't support RSA PSS, as it's not deployed in the WebPKI. + let padding = _RSA.Signing.Padding.insecurePKCS1v1_5 + + switch signatureAlgorithm { + case .sha1WithRSAEncryption: + signature = try self.signature(for: Insecure.SHA1.hash(data: bytes), padding: padding) + case .sha256WithRSAEncryption: + signature = try self.signature(for: SHA256.hash(data: bytes), padding: padding) + case .sha384WithRSAEncryption: + signature = try self.signature(for: SHA384.hash(data: bytes), padding: padding) + case .sha512WithRSAEncryption: + signature = try self.signature(for: SHA512.hash(data: bytes), padding: padding) + default: + throw CertificateError.unsupportedSignatureAlgorithm( + reason: "Cannot use \(signatureAlgorithm) with RSA key" + ) + } + + return Certificate.Signature(backing: .rsa(signature)) + } +} + +extension Curve25519.Signing.PrivateKey { + @inlinable + func signature( + for bytes: Bytes, + signatureAlgorithm: Certificate.SignatureAlgorithm + ) throws -> Certificate.Signature { + let signature: Data + + switch signatureAlgorithm { + case .ed25519: + signature = try self.signature(for: bytes) + default: + throw CertificateError.unsupportedSignatureAlgorithm( + reason: "Cannot use \(signatureAlgorithm) with Ed25519 key" + ) + } + + return Certificate.Signature(backing: .ed25519(.init(signature))) + } +} diff --git a/Sources/X509/SignatureAlgorithm.swift b/Sources/X509/SignatureAlgorithm.swift index 8becfc2..b4e4e42 100644 --- a/Sources/X509/SignatureAlgorithm.swift +++ b/Sources/X509/SignatureAlgorithm.swift @@ -134,7 +134,7 @@ extension AlgorithmIdentifier { self = .sha256UsingNil case .ecdsaWithSHA384, .sha384WithRSAEncryption: self = .sha384UsingNil - case .ecdsaWithSHA512, .sha512WithRSAEncryption: + case .ecdsaWithSHA512, .sha512WithRSAEncryption, .ed25519: self = .sha512UsingNil case .sha1WithRSAEncryption: self = .sha1 diff --git a/Tests/X509Tests/CMSTests.swift b/Tests/X509Tests/CMSTests.swift index a0c8f31..a3a2a5f 100644 --- a/Tests/X509Tests/CMSTests.swift +++ b/Tests/X509Tests/CMSTests.swift @@ -15,6 +15,7 @@ import Foundation import XCTest import Crypto +import _CryptoExtras import SwiftASN1 @testable @_spi(CMS) import X509 @@ -128,6 +129,48 @@ final class CMSTests: XCTestCase { issuerPrivateKey: intermediateKey ) + static let rsaCertKey = try! Certificate.PrivateKey(_RSA.Signing.PrivateKey(keySize: .bits2048)) + static let rsaCertName = try! DistinguishedName { + CommonName("CMS RSA") + } + static let rsaCert = try! Certificate( + version: .v3, + serialNumber: .init(), + publicKey: rsaCertKey.publicKey, + notValidBefore: Date(), + notValidAfter: Date().advanced(by: 60 * 60 * 24 * 360), + issuer: rsaCertName, + subject: rsaCertName, + signatureAlgorithm: .sha1WithRSAEncryption, + extensions: try! Certificate.Extensions { + Critical( + BasicConstraints.isCertificateAuthority(maxPathLength: nil) + ) + }, + issuerPrivateKey: rsaCertKey + ) + + static let ed25519CertKey = Certificate.PrivateKey(Curve25519.Signing.PrivateKey()) + static let ed25519CertName = try! DistinguishedName { + CommonName("CMS ED25519") + } + static let ed25519Cert = try! Certificate( + version: .v3, + serialNumber: .init(), + publicKey: ed25519CertKey.publicKey, + notValidBefore: Date(), + notValidAfter: Date().advanced(by: 60 * 60 * 24 * 360), + issuer: ed25519CertName, + subject: ed25519CertName, + signatureAlgorithm: .ed25519, + extensions: try! Certificate.Extensions { + Critical( + BasicConstraints.isCertificateAuthority(maxPathLength: nil) + ) + }, + issuerPrivateKey: ed25519CertKey + ) + @PolicyBuilder static var defaultPolicies: some VerifierPolicy { RFC5280Policy(validationTime: Date()) } @@ -1033,6 +1076,42 @@ final class CMSTests: XCTestCase { XCTAssertValidSignature(isValidSignature) } + func testSigningWithRSA() async throws { + let data: [UInt8] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + let signature = try CMS.sign( + data, + signatureAlgorithm: .sha256WithRSAEncryption, + certificate: Self.rsaCert, + privateKey: Self.rsaCertKey + ) + let isValidSignature = await CMS.isValidSignature( + dataBytes: data, + signatureBytes: signature, + trustRoots: CertificateStore([Self.rsaCert]) + ) { + Self.defaultPolicies + } + XCTAssertValidSignature(isValidSignature) + } + + func testSigningWithEd25519() async throws { + let data: [UInt8] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + let signature = try CMS.sign( + data, + signatureAlgorithm: .ed25519, + certificate: Self.ed25519Cert, + privateKey: Self.ed25519CertKey + ) + let isValidSignature = await CMS.isValidSignature( + dataBytes: data, + signatureBytes: signature, + trustRoots: CertificateStore([Self.ed25519Cert]) + ) { + Self.defaultPolicies + } + XCTAssertValidSignature(isValidSignature) + } + func testSigningWithSigningTimeSignedAttr() async throws { let data: [UInt8] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] let signature = try CMS.sign( From 9293cd40533adf5337de2d0a6e63db93f26eed68 Mon Sep 17 00:00:00 2001 From: Nicolas Bachschmidt Date: Tue, 21 Jan 2025 10:16:29 +0100 Subject: [PATCH 2/2] Fix formatting --- Sources/X509/SecKeyWrapper.swift | 5 ++++- Sources/X509/Signature.swift | 6 +++--- 2 files changed, 7 insertions(+), 4 deletions(-) diff --git a/Sources/X509/SecKeyWrapper.swift b/Sources/X509/SecKeyWrapper.swift index 5801e5f..a91c902 100644 --- a/Sources/X509/SecKeyWrapper.swift +++ b/Sources/X509/SecKeyWrapper.swift @@ -200,7 +200,10 @@ extension Certificate.PrivateKey { return signatureData } - static func keyAlgorithm(signatureAlgorithm: Certificate.SignatureAlgorithm, type: KeyType) throws -> SecKeyAlgorithm { + static func keyAlgorithm( + signatureAlgorithm: Certificate.SignatureAlgorithm, + type: KeyType + ) throws -> SecKeyAlgorithm { let algorithm: SecKeyAlgorithm switch type { case .RSA: diff --git a/Sources/X509/Signature.swift b/Sources/X509/Signature.swift index a0e9edd..2a39019 100644 --- a/Sources/X509/Signature.swift +++ b/Sources/X509/Signature.swift @@ -161,7 +161,7 @@ extension P256.Signing.PublicKey { signatureAlgorithm: Certificate.SignatureAlgorithm ) -> Bool { guard case .ecdsa(let rawInnerSignature) = signature.backing, - let innerSignature = P256.Signing.ECDSASignature(rawInnerSignature) + let innerSignature = P256.Signing.ECDSASignature(rawInnerSignature) else { // Signature mismatch return false @@ -188,7 +188,7 @@ extension P384.Signing.PublicKey { signatureAlgorithm: Certificate.SignatureAlgorithm ) -> Bool { guard case .ecdsa(let rawInnerSignature) = signature.backing, - let innerSignature = P384.Signing.ECDSASignature(rawInnerSignature) + let innerSignature = P384.Signing.ECDSASignature(rawInnerSignature) else { // Signature mismatch return false @@ -215,7 +215,7 @@ extension P521.Signing.PublicKey { signatureAlgorithm: Certificate.SignatureAlgorithm ) -> Bool { guard case .ecdsa(let rawInnerSignature) = signature.backing, - let innerSignature = P521.Signing.ECDSASignature(rawInnerSignature) + let innerSignature = P521.Signing.ECDSASignature(rawInnerSignature) else { // Signature mismatch return false