diff --git a/Sources/X509/CSR/CertificateSigningRequest.swift b/Sources/X509/CSR/CertificateSigningRequest.swift index f0f2cab..50a9a4f 100644 --- a/Sources/X509/CSR/CertificateSigningRequest.swift +++ b/Sources/X509/CSR/CertificateSigningRequest.swift @@ -145,6 +145,35 @@ public struct CertificateSigningRequest { self.signatureBytes = try DER.Serializer.serialized(element: ASN1BitString(self.signature))[...] } + /// Construct a CSR for a specific private key. + /// + /// This API can be used to construct a certificate signing request that can be passed to a certificate + /// authority. It will correctly generate a signature over the request. + /// + /// A default signature algorithm to use for the signature of this CSR is automatically chosen based on + /// the type of the private key. + /// + /// - Parameters: + /// - version: The CSR version. + /// - subject: The ``DistinguishedName`` of the subject of this CSR + /// - privateKey: The private key associated with this CSR. + /// - attributes: The attributes associated with this CSR + @inlinable + public init( + version: Version, + subject: DistinguishedName, + privateKey: Certificate.PrivateKey, + attributes: Attributes + ) throws { + try self.init( + version: version, + subject: subject, + privateKey: privateKey, + attributes: attributes, + signatureAlgorithm: privateKey.defaultSignatureAlgorithm + ) + } + @inlinable internal init( info: CertificationRequestInfo, diff --git a/Sources/X509/Certificate.swift b/Sources/X509/Certificate.swift index ef4bd45..84a531c 100644 --- a/Sources/X509/Certificate.swift +++ b/Sources/X509/Certificate.swift @@ -209,6 +209,54 @@ public struct Certificate { self.signatureBytes = try DER.Serializer.serialized(element: ASN1BitString(self.signature))[...] } + /// Construct a certificate from constituent parts, signed by an issuer key. + /// + /// This API can be used to construct a ``Certificate`` directly, without an intermediary + /// Certificate Signing Request. The ``signature-swift.property`` for this certificate will be produced + /// automatically, using `issuerPrivateKey`. + /// + /// A default signature algorithm to use for the signature of this certificate is automatically chosen based + /// on the type of the issuer's private key. + /// + /// This API can be used to construct a self-signed key by passing the private key for `publicKey` as the + /// `issuerPrivateKey` argument. + /// + /// - Parameters: + /// - version: The X.509 specification version for this certificate. + /// - serialNumber: The serial number of this certificate. + /// - publicKey: The public key associated with this certificate. + /// - notValidBefore: The date before which this certificate is not valid. + /// - notValidAfter: The date after which this certificate is not valid. + /// - issuer: The ``DistinguishedName`` of the issuer of this certificate. + /// - subject: The ``DistinguishedName`` of the subject of this certificate. + /// - extensions: The extensions on this certificate. + /// - issuerPrivateKey: The private key to use to sign this certificate. + @inlinable + public init( + version: Version, + serialNumber: SerialNumber, + publicKey: PublicKey, + notValidBefore: Date, + notValidAfter: Date, + issuer: DistinguishedName, + subject: DistinguishedName, + extensions: Extensions, + issuerPrivateKey: PrivateKey + ) throws { + try self.init( + version: version, + serialNumber: serialNumber, + publicKey: publicKey, + notValidBefore: notValidBefore, + notValidAfter: notValidAfter, + issuer: issuer, + subject: subject, + signatureAlgorithm: issuerPrivateKey.defaultSignatureAlgorithm, + extensions: extensions, + issuerPrivateKey: issuerPrivateKey + ) + } + @inlinable init( tbsCertificate: TBSCertificate, diff --git a/Sources/X509/CertificatePrivateKey.swift b/Sources/X509/CertificatePrivateKey.swift index 1dfb7c2..4123b09 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) } } @@ -145,51 +136,37 @@ extension Certificate { } @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)" - ) - } + var defaultSignatureAlgorithm: SignatureAlgorithm { + switch backing { + case .p256: + return .ecdsaWithSHA256 + case .p384: + return .ecdsaWithSHA384 + case .p521: + return .ecdsaWithSHA512 case .rsa: - if !algorithm.isRSA { - throw CertificateError.unsupportedSignatureAlgorithm( - reason: "Cannot use \(algorithm) with RSA key \(self)" - ) - } + return .sha256WithRSAEncryption #if canImport(Darwin) case .secureEnclaveP256: - if !algorithm.isECDSA { - throw CertificateError.unsupportedSignatureAlgorithm( - reason: "Cannot use \(algorithm) with ECDSA key \(self)" - ) - } + return .ecdsaWithSHA256 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)" - ) + return .sha256WithRSAEncryption + case .ECDSA(let keySize): + switch keySize { + case .P256: + return .ecdsaWithSHA256 + case .P384: + return .ecdsaWithSHA384 + case .P521: + return .ecdsaWithSHA512 } } #endif case .ed25519: - if algorithm != .ed25519 { - throw CertificateError.unsupportedSignatureAlgorithm( - reason: "Cannot use \(algorithm) with Ed25519 key \(self)" - ) - } + return .ed25519 } - } } } 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/CryptographicMessageSyntax/CMSOperations.swift b/Sources/X509/CryptographicMessageSyntax/CMSOperations.swift index 34af492..e9c59d3 100644 --- a/Sources/X509/CryptographicMessageSyntax/CMSOperations.swift +++ b/Sources/X509/CryptographicMessageSyntax/CMSOperations.swift @@ -51,6 +51,27 @@ public enum CMS { return try self.serializeSignedData(signedData) } + @_spi(CMS) + @inlinable + public static func sign( + _ bytes: Bytes, + additionalIntermediateCertificates: [Certificate] = [], + certificate: Certificate, + privateKey: Certificate.PrivateKey, + signingTime: Date? = nil, + detached: Bool = true + ) throws -> [UInt8] { + return try self.sign( + bytes, + signatureAlgorithm: privateKey.defaultSignatureAlgorithm, + additionalIntermediateCertificates: additionalIntermediateCertificates, + certificate: certificate, + privateKey: privateKey, + signingTime: signingTime, + detached: detached + ) + } + @inlinable static func signWithSigningTime( _ bytes: Bytes, 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..a91c902 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,38 @@ 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 +242,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..2a39019 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..48c0582 100644 --- a/Sources/X509/SignatureAlgorithm.swift +++ b/Sources/X509/SignatureAlgorithm.swift @@ -112,6 +112,8 @@ extension Certificate.SignatureAlgorithm: CustomStringConvertible { return "SignatureAlgorithm.sha384WithRSAEncryption" case .sha512WithRSAEncryption: return "SignatureAlgorithm.sha512WithRSAEncryption" + case .ed25519: + return "SignatureAlgorithm.ed25519" default: return "SignatureAlgorithm(\(self._algorithmIdentifier))" } @@ -134,7 +136,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..02bca95 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( @@ -1166,6 +1245,55 @@ final class CMSTests: XCTestCase { CMSSignerIdentifier.subjectKeyIdentifier(.init(keyIdentifier: [10, 20, 30, 40])) ) } + + func testDefaultRSASignatureAlgorithm() throws { + let privateKey = try Certificate.PrivateKey(_RSA.Signing.PrivateKey(keySize: .bits2048)) + let signerInfo = try self.signAndExtractSignerInfo(privateKey: privateKey) + XCTAssertEqual(signerInfo?.signatureAlgorithm.description, "sha256WithRSAEncryption") + } + + func testDefaultP256SignatureAlgorithm() throws { + let privateKey = Certificate.PrivateKey(P256.Signing.PrivateKey()) + let signerInfo = try self.signAndExtractSignerInfo(privateKey: privateKey) + XCTAssertEqual(signerInfo?.signatureAlgorithm.description, "ecdsaWithSHA256") + } + + func testDefaultP384SignatureAlgorithm() throws { + let privateKey = Certificate.PrivateKey(P384.Signing.PrivateKey()) + let signerInfo = try self.signAndExtractSignerInfo(privateKey: privateKey) + XCTAssertEqual(signerInfo?.signatureAlgorithm.description, "ecdsaWithSHA384") + } + + func testDefaultP521SignatureAlgorithm() throws { + let privateKey = Certificate.PrivateKey(P521.Signing.PrivateKey()) + let signerInfo = try self.signAndExtractSignerInfo(privateKey: privateKey) + XCTAssertEqual(signerInfo?.signatureAlgorithm.description, "ecdsaWithSHA512") + } + + func testDefaultEd25519SignatureAlgorithm() throws { + let privateKey = Certificate.PrivateKey(Curve25519.Signing.PrivateKey()) + let signerInfo = try self.signAndExtractSignerInfo(privateKey: privateKey) + XCTAssertEqual(signerInfo?.signatureAlgorithm.description, "ed25519") + } + + private func signAndExtractSignerInfo(privateKey: Certificate.PrivateKey) throws -> CMSSignerInfo? { + let name = try DistinguishedName { CommonName("test") } + let certificate = try Certificate( + version: .v3, + serialNumber: .init(bytes: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]), + publicKey: privateKey.publicKey, + notValidBefore: Date(), + notValidAfter: Date() + 3600, + issuer: name, + subject: name, + extensions: Certificate.Extensions {}, + issuerPrivateKey: privateKey + ) + let data: [UInt8] = [0, 1, 2, 3, 4, 5, 6, 7, 8, 9] + let signatureBytes = try CMS.sign(data, certificate: certificate, privateKey: privateKey) + let contentInfo = try CMSContentInfo(derEncoded: signatureBytes) + return try contentInfo.signedData?.signerInfos.first + } } extension DERSerializable { diff --git a/Tests/X509Tests/CSRTests.swift b/Tests/X509Tests/CSRTests.swift index 4b170d0..2fd5abe 100644 --- a/Tests/X509Tests/CSRTests.swift +++ b/Tests/X509Tests/CSRTests.swift @@ -524,6 +524,47 @@ final class CSRTests: XCTestCase { CertificateSigningRequest.Attributes(attributes.prefix(2)) ) } + + func testDefaultRSASignatureAlgorithm() throws { + let privateKey = try Certificate.PrivateKey(_RSA.Signing.PrivateKey(keySize: .bits2048)) + let csr = try self.generateCertificateSigningRequest(privateKey: privateKey) + XCTAssertEqual(csr.signatureAlgorithm.description, "SignatureAlgorithm.sha256WithRSAEncryption") + } + + func testDefaultP256SignatureAlgorithm() throws { + let privateKey = Certificate.PrivateKey(P256.Signing.PrivateKey()) + let csr = try self.generateCertificateSigningRequest(privateKey: privateKey) + XCTAssertEqual(csr.signatureAlgorithm.description, "SignatureAlgorithm.ecdsaWithSHA256") + } + + func testDefaultP384SignatureAlgorithm() throws { + let privateKey = Certificate.PrivateKey(P384.Signing.PrivateKey()) + let csr = try self.generateCertificateSigningRequest(privateKey: privateKey) + XCTAssertEqual(csr.signatureAlgorithm.description, "SignatureAlgorithm.ecdsaWithSHA384") + } + + func testDefaultP521SignatureAlgorithm() throws { + let privateKey = Certificate.PrivateKey(P521.Signing.PrivateKey()) + let csr = try self.generateCertificateSigningRequest(privateKey: privateKey) + XCTAssertEqual(csr.signatureAlgorithm.description, "SignatureAlgorithm.ecdsaWithSHA512") + } + + func testDefaultEd25519SignatureAlgorithm() throws { + let privateKey = Certificate.PrivateKey(Curve25519.Signing.PrivateKey()) + let csr = try self.generateCertificateSigningRequest(privateKey: privateKey) + XCTAssertEqual(csr.signatureAlgorithm.description, "SignatureAlgorithm.ed25519") + } + + private func generateCertificateSigningRequest( + privateKey: Certificate.PrivateKey + ) throws -> CertificateSigningRequest { + try CertificateSigningRequest( + version: .v1, + subject: DistinguishedName { CommonName("test") }, + privateKey: privateKey, + attributes: CertificateSigningRequest.Attributes() + ) + } } extension RandomAccessCollection { diff --git a/Tests/X509Tests/CertificateTests.swift b/Tests/X509Tests/CertificateTests.swift index aac4ba8..190f0aa 100644 --- a/Tests/X509Tests/CertificateTests.swift +++ b/Tests/X509Tests/CertificateTests.swift @@ -729,4 +729,49 @@ final class CertificateTests: XCTestCase { let reEncoded = try parsedCert.serializeAsPEM().pemString XCTAssertEqual(cert, reEncoded) } + + func testDefaultRSASignatureAlgorithm() throws { + let privateKey = try Certificate.PrivateKey(_RSA.Signing.PrivateKey(keySize: .bits2048)) + let certificate = try self.issueSelfSignedCertificate(privateKey: privateKey) + XCTAssertEqual(certificate.signatureAlgorithm.description, "SignatureAlgorithm.sha256WithRSAEncryption") + } + + func testDefaultP256SignatureAlgorithm() throws { + let privateKey = Certificate.PrivateKey(P256.Signing.PrivateKey()) + let certificate = try self.issueSelfSignedCertificate(privateKey: privateKey) + XCTAssertEqual(certificate.signatureAlgorithm.description, "SignatureAlgorithm.ecdsaWithSHA256") + } + + func testDefaultP384SignatureAlgorithm() throws { + let privateKey = Certificate.PrivateKey(P384.Signing.PrivateKey()) + let certificate = try self.issueSelfSignedCertificate(privateKey: privateKey) + XCTAssertEqual(certificate.signatureAlgorithm.description, "SignatureAlgorithm.ecdsaWithSHA384") + } + + func testDefaultP521SignatureAlgorithm() throws { + let privateKey = Certificate.PrivateKey(P521.Signing.PrivateKey()) + let certificate = try self.issueSelfSignedCertificate(privateKey: privateKey) + XCTAssertEqual(certificate.signatureAlgorithm.description, "SignatureAlgorithm.ecdsaWithSHA512") + } + + func testDefaultEd25519SignatureAlgorithm() throws { + let privateKey = Certificate.PrivateKey(Curve25519.Signing.PrivateKey()) + let certificate = try self.issueSelfSignedCertificate(privateKey: privateKey) + XCTAssertEqual(certificate.signatureAlgorithm.description, "SignatureAlgorithm.ed25519") + } + + private func issueSelfSignedCertificate(privateKey: Certificate.PrivateKey) throws -> Certificate { + let name = try DistinguishedName { CommonName("test") } + return try Certificate( + version: .v3, + serialNumber: .init(bytes: [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12]), + publicKey: privateKey.publicKey, + notValidBefore: Date(), + notValidAfter: Date() + 3600, + issuer: name, + subject: name, + extensions: Certificate.Extensions {}, + issuerPrivateKey: privateKey + ) + } }