From 7ada70d0bca795c8c6af269fa816e5b6f8ec40a7 Mon Sep 17 00:00:00 2001 From: Justin Oroz Date: Wed, 8 Oct 2025 16:27:43 -0700 Subject: [PATCH 01/29] Certificate.Signer --- Sources/X509/Certificate.swift | 54 ++++++++++++++++++++++++ Sources/X509/CertificatePrivateKey.swift | 3 ++ Sources/X509/CertificateSigner.swift | 22 ++++++++++ 3 files changed, 79 insertions(+) create mode 100644 Sources/X509/CertificateSigner.swift diff --git a/Sources/X509/Certificate.swift b/Sources/X509/Certificate.swift index 4ea02e2..0ca183b 100644 --- a/Sources/X509/Certificate.swift +++ b/Sources/X509/Certificate.swift @@ -214,6 +214,60 @@ public struct Certificate { self.signatureBytes = try DER.Serializer.serialized(element: ASN1BitString(self.signature))[...] } + /// Construct a certificate from constituent parts, signed by an custom signer. + /// + /// 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 `issuerSigner`. + /// + /// - 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. + /// - signatureAlgorithm: The signature algorithm that will be used to produce `signature`. Must be compatible with the private key type. + /// - extensions: The extensions on this certificate. + /// - issuerSigner: The signer to use to sign this certificate. + @inlinable + public init( + version: Version, + serialNumber: SerialNumber, + publicKey: PublicKey, + notValidBefore: Date, + notValidAfter: Date, + issuer: DistinguishedName, + subject: DistinguishedName, + signatureAlgorithm: SignatureAlgorithm, + extensions: Extensions, + issuerSigner: any Signer + ) throws { + self.tbsCertificate = TBSCertificate( + version: version, + serialNumber: serialNumber, + signature: signatureAlgorithm, + issuer: issuer, + validity: try Validity( + notBefore: .makeTime(from: notValidBefore), + notAfter: .makeTime(from: notValidAfter) + ), + subject: subject, + publicKey: publicKey, + extensions: extensions + ) + self.signatureAlgorithm = signatureAlgorithm + + let tbsCertificateBytes = try DER.Serializer.serialized(element: self.tbsCertificate)[...] + self.signature = try issuerSigner.sign(bytes: tbsCertificateBytes, signatureAlgorithm: signatureAlgorithm) + self.tbsCertificateBytes = tbsCertificateBytes + self.signatureAlgorithmBytes = try DER.Serializer.serialized( + element: AlgorithmIdentifier(self.signatureAlgorithm) + )[...] + 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 diff --git a/Sources/X509/CertificatePrivateKey.swift b/Sources/X509/CertificatePrivateKey.swift index 33b46d6..65fb4f9 100644 --- a/Sources/X509/CertificatePrivateKey.swift +++ b/Sources/X509/CertificatePrivateKey.swift @@ -182,6 +182,9 @@ extension Certificate { } } +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) +extension Certificate.PrivateKey: Certificate.Signer {} + @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) extension Certificate.PrivateKey: Hashable {} diff --git a/Sources/X509/CertificateSigner.swift b/Sources/X509/CertificateSigner.swift new file mode 100644 index 0000000..94f6862 --- /dev/null +++ b/Sources/X509/CertificateSigner.swift @@ -0,0 +1,22 @@ +import Foundation + +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) +extension Certificate { + + public protocol Signer: Sendable { + + /// Use the signer to sign the provided bytes with a given signature algorithm. + /// + /// - Parameters: + /// - bytes: The data to create the signature for. + /// - signatureAlgorithm: The signature algorithm to use. + /// - Returns: The signature. + @inlinable + func sign( + bytes: Bytes, + signatureAlgorithm: SignatureAlgorithm + ) throws -> Signature + + } + +} From fc55fb153818ddc2ec568a8ef4eeeabdfa556746 Mon Sep 17 00:00:00 2001 From: Justin Oroz Date: Wed, 8 Oct 2025 19:06:14 -0700 Subject: [PATCH 02/29] AsyncSigner --- Sources/X509/Certificate.swift | 54 ++++++++++++++++++++++++++++ Sources/X509/CertificateSigner.swift | 25 +++++++++++-- 2 files changed, 77 insertions(+), 2 deletions(-) diff --git a/Sources/X509/Certificate.swift b/Sources/X509/Certificate.swift index 0ca183b..38b8682 100644 --- a/Sources/X509/Certificate.swift +++ b/Sources/X509/Certificate.swift @@ -268,6 +268,60 @@ public struct Certificate { self.signatureBytes = try DER.Serializer.serialized(element: ASN1BitString(self.signature))[...] } + /// Construct a certificate from constituent parts, signed by an custom signer. + /// + /// 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 `issuerSigner`. + /// + /// - 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. + /// - signatureAlgorithm: The signature algorithm that will be used to produce `signature`. Must be compatible with the private key type. + /// - extensions: The extensions on this certificate. + /// - issuerSigner: The signer to use to sign this certificate. + @inlinable + public init( + version: Version, + serialNumber: SerialNumber, + publicKey: PublicKey, + notValidBefore: Date, + notValidAfter: Date, + issuer: DistinguishedName, + subject: DistinguishedName, + signatureAlgorithm: SignatureAlgorithm, + extensions: Extensions, + issuerSigner: some AsyncSigner + ) async throws { + self.tbsCertificate = TBSCertificate( + version: version, + serialNumber: serialNumber, + signature: signatureAlgorithm, + issuer: issuer, + validity: try Validity( + notBefore: .makeTime(from: notValidBefore), + notAfter: .makeTime(from: notValidAfter) + ), + subject: subject, + publicKey: publicKey, + extensions: extensions + ) + self.signatureAlgorithm = signatureAlgorithm + + let tbsCertificateBytes = try DER.Serializer.serialized(element: self.tbsCertificate)[...] + self.signature = try await issuerSigner.sign(bytes: tbsCertificateBytes, signatureAlgorithm: signatureAlgorithm) + self.tbsCertificateBytes = tbsCertificateBytes + self.signatureAlgorithmBytes = try DER.Serializer.serialized( + element: AlgorithmIdentifier(self.signatureAlgorithm) + )[...] + 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 diff --git a/Sources/X509/CertificateSigner.swift b/Sources/X509/CertificateSigner.swift index 94f6862..8ff822a 100644 --- a/Sources/X509/CertificateSigner.swift +++ b/Sources/X509/CertificateSigner.swift @@ -12,11 +12,32 @@ extension Certificate { /// - signatureAlgorithm: The signature algorithm to use. /// - Returns: The signature. @inlinable - func sign( - bytes: Bytes, + func sign( + bytes: some DataProtocol, signatureAlgorithm: SignatureAlgorithm ) throws -> Signature } } + +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) +extension Certificate { + + public protocol AsyncSigner: Sendable { + + /// Use the signer to sign the provided bytes with a given signature algorithm. + /// + /// - Parameters: + /// - bytes: The data to create the signature for. + /// - signatureAlgorithm: The signature algorithm to use. + /// - Returns: The signature. + @inlinable + func sign( + bytes: some DataProtocol, + signatureAlgorithm: SignatureAlgorithm + ) async throws -> Signature + + } + +} From 418294da68d6801de713f7183ff494dd3d9cdafd Mon Sep 17 00:00:00 2001 From: Justin Oroz Date: Wed, 8 Oct 2025 19:06:25 -0700 Subject: [PATCH 03/29] use some instead of any --- Sources/X509/Certificate.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/X509/Certificate.swift b/Sources/X509/Certificate.swift index 38b8682..ccb9283 100644 --- a/Sources/X509/Certificate.swift +++ b/Sources/X509/Certificate.swift @@ -242,7 +242,7 @@ public struct Certificate { subject: DistinguishedName, signatureAlgorithm: SignatureAlgorithm, extensions: Extensions, - issuerSigner: any Signer + issuerSigner: some Signer ) throws { self.tbsCertificate = TBSCertificate( version: version, From 32ccb78717ac52539b826cc72a4e9652cb363c3c Mon Sep 17 00:00:00 2001 From: Justin Oroz Date: Wed, 8 Oct 2025 19:42:05 -0700 Subject: [PATCH 04/29] public signature init --- Sources/X509/Signature.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/X509/Signature.swift b/Sources/X509/Signature.swift index 1787597..4a8caa2 100644 --- a/Sources/X509/Signature.swift +++ b/Sources/X509/Signature.swift @@ -46,7 +46,7 @@ extension Certificate { } @inlinable - internal init(signatureAlgorithm: SignatureAlgorithm, signatureBytes: ASN1BitString) throws { + public init(signatureAlgorithm: SignatureAlgorithm, signatureBytes: ASN1BitString) throws { switch signatureAlgorithm { case .ecdsaWithSHA256, .ecdsaWithSHA384, .ecdsaWithSHA512: let signature = try ECDSASignature(derEncoded: signatureBytes.bytes) From e8c95ad095546f89231839595a7b715abee2dbaf Mon Sep 17 00:00:00 2001 From: Justin Oroz Date: Thu, 9 Oct 2025 01:30:02 -0700 Subject: [PATCH 05/29] Add CSR initializers --- .../X509/CSR/CertificateSigningRequest.swift | 76 +++++++++++++++++++ 1 file changed, 76 insertions(+) diff --git a/Sources/X509/CSR/CertificateSigningRequest.swift b/Sources/X509/CSR/CertificateSigningRequest.swift index 1825a80..8170917 100644 --- a/Sources/X509/CSR/CertificateSigningRequest.swift +++ b/Sources/X509/CSR/CertificateSigningRequest.swift @@ -150,6 +150,82 @@ public struct CertificateSigningRequest { self.signatureBytes = try DER.Serializer.serialized(element: ASN1BitString(self.signature))[...] } + /// Construct a CSR for a signer. + /// + /// 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. + /// + /// - Parameters: + /// - version: The CSR version. + /// - subject: The ``DistinguishedName`` of the subject of this CSR + /// - publicKey: The public key associated with this CSR. + /// - signer: The signer associated with this CSR. + /// - attributes: The attributes associated with this CSR + /// - signatureAlgorithm: The signature algorithm to use for the signature on this CSR. + @inlinable + public init( + version: Version, + subject: DistinguishedName, + publicKey: Certificate.PublicKey, + signer: some Certificate.Signer, + attributes: Attributes, + signatureAlgorithm: Certificate.SignatureAlgorithm + ) throws { + self.info = CertificationRequestInfo( + version: version, + subject: subject, + publicKey: publicKey, + attributes: attributes + ) + self.signatureAlgorithm = signatureAlgorithm + + let infoBytes = try DER.Serializer.serialized(element: self.info) + self.signature = try signer.sign(bytes: infoBytes, signatureAlgorithm: signatureAlgorithm) + self.infoBytes = infoBytes[...] + self.signatureAlgorithmBytes = try DER.Serializer.serialized( + element: AlgorithmIdentifier(self.signatureAlgorithm) + )[...] + self.signatureBytes = try DER.Serializer.serialized(element: ASN1BitString(self.signature))[...] + } + + /// Construct a CSR for a signer. + /// + /// 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. + /// + /// - Parameters: + /// - version: The CSR version. + /// - subject: The ``DistinguishedName`` of the subject of this CSR + /// - publicKey: The public key associated with this CSR. + /// - signer: The signer associated with this CSR. + /// - attributes: The attributes associated with this CSR + /// - signatureAlgorithm: The signature algorithm to use for the signature on this CSR. + @inlinable + public init( + version: Version, + subject: DistinguishedName, + publicKey: Certificate.PublicKey, + signer: some Certificate.AsyncSigner, + attributes: Attributes, + signatureAlgorithm: Certificate.SignatureAlgorithm + ) async throws { + self.info = CertificationRequestInfo( + version: version, + subject: subject, + publicKey: publicKey, + attributes: attributes + ) + self.signatureAlgorithm = signatureAlgorithm + + let infoBytes = try DER.Serializer.serialized(element: self.info) + self.signature = try await signer.sign(bytes: infoBytes, signatureAlgorithm: signatureAlgorithm) + self.infoBytes = infoBytes[...] + self.signatureAlgorithmBytes = try DER.Serializer.serialized( + element: AlgorithmIdentifier(self.signatureAlgorithm) + )[...] + 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 From 50547446be0e5ae30abd8fe4e81fde584d6f8755 Mon Sep 17 00:00:00 2001 From: Justin Oroz Date: Thu, 9 Oct 2025 01:34:17 -0700 Subject: [PATCH 06/29] =?UTF-8?q?Signer=20=E2=86=92=20SignatureProvider?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- Sources/X509/CSR/CertificateSigningRequest.swift | 16 ++++++++-------- Sources/X509/Certificate.swift | 12 ++++++------ Sources/X509/CertificatePrivateKey.swift | 2 +- Sources/X509/CertificateSigner.swift | 4 ++-- 4 files changed, 17 insertions(+), 17 deletions(-) diff --git a/Sources/X509/CSR/CertificateSigningRequest.swift b/Sources/X509/CSR/CertificateSigningRequest.swift index 8170917..c850e82 100644 --- a/Sources/X509/CSR/CertificateSigningRequest.swift +++ b/Sources/X509/CSR/CertificateSigningRequest.swift @@ -150,7 +150,7 @@ public struct CertificateSigningRequest { self.signatureBytes = try DER.Serializer.serialized(element: ASN1BitString(self.signature))[...] } - /// Construct a CSR for a signer. + /// Construct a CSR using a signature provider. /// /// 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. @@ -159,7 +159,7 @@ public struct CertificateSigningRequest { /// - version: The CSR version. /// - subject: The ``DistinguishedName`` of the subject of this CSR /// - publicKey: The public key associated with this CSR. - /// - signer: The signer associated with this CSR. + /// - signatureProvider: The signature provider associated with this CSR. /// - attributes: The attributes associated with this CSR /// - signatureAlgorithm: The signature algorithm to use for the signature on this CSR. @inlinable @@ -167,7 +167,7 @@ public struct CertificateSigningRequest { version: Version, subject: DistinguishedName, publicKey: Certificate.PublicKey, - signer: some Certificate.Signer, + signatureProvider: some Certificate.SignatureProvider, attributes: Attributes, signatureAlgorithm: Certificate.SignatureAlgorithm ) throws { @@ -180,7 +180,7 @@ public struct CertificateSigningRequest { self.signatureAlgorithm = signatureAlgorithm let infoBytes = try DER.Serializer.serialized(element: self.info) - self.signature = try signer.sign(bytes: infoBytes, signatureAlgorithm: signatureAlgorithm) + self.signature = try signatureProvider.sign(bytes: infoBytes, signatureAlgorithm: signatureAlgorithm) self.infoBytes = infoBytes[...] self.signatureAlgorithmBytes = try DER.Serializer.serialized( element: AlgorithmIdentifier(self.signatureAlgorithm) @@ -188,7 +188,7 @@ public struct CertificateSigningRequest { self.signatureBytes = try DER.Serializer.serialized(element: ASN1BitString(self.signature))[...] } - /// Construct a CSR for a signer. + /// Construct a CSR using a signature provider. /// /// 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. @@ -197,7 +197,7 @@ public struct CertificateSigningRequest { /// - version: The CSR version. /// - subject: The ``DistinguishedName`` of the subject of this CSR /// - publicKey: The public key associated with this CSR. - /// - signer: The signer associated with this CSR. + /// - signatureProvider: The signature provider associated with this CSR. /// - attributes: The attributes associated with this CSR /// - signatureAlgorithm: The signature algorithm to use for the signature on this CSR. @inlinable @@ -205,7 +205,7 @@ public struct CertificateSigningRequest { version: Version, subject: DistinguishedName, publicKey: Certificate.PublicKey, - signer: some Certificate.AsyncSigner, + signatureProvider: some Certificate.AsyncSignatureProvider, attributes: Attributes, signatureAlgorithm: Certificate.SignatureAlgorithm ) async throws { @@ -218,7 +218,7 @@ public struct CertificateSigningRequest { self.signatureAlgorithm = signatureAlgorithm let infoBytes = try DER.Serializer.serialized(element: self.info) - self.signature = try await signer.sign(bytes: infoBytes, signatureAlgorithm: signatureAlgorithm) + self.signature = try await signatureProvider.sign(bytes: infoBytes, signatureAlgorithm: signatureAlgorithm) self.infoBytes = infoBytes[...] self.signatureAlgorithmBytes = try DER.Serializer.serialized( element: AlgorithmIdentifier(self.signatureAlgorithm) diff --git a/Sources/X509/Certificate.swift b/Sources/X509/Certificate.swift index ccb9283..9b83236 100644 --- a/Sources/X509/Certificate.swift +++ b/Sources/X509/Certificate.swift @@ -230,7 +230,7 @@ public struct Certificate { /// - subject: The ``DistinguishedName`` of the subject of this certificate. /// - signatureAlgorithm: The signature algorithm that will be used to produce `signature`. Must be compatible with the private key type. /// - extensions: The extensions on this certificate. - /// - issuerSigner: The signer to use to sign this certificate. + /// - issuerSignatureProvider: The signature provider to use to sign this certificate. @inlinable public init( version: Version, @@ -242,7 +242,7 @@ public struct Certificate { subject: DistinguishedName, signatureAlgorithm: SignatureAlgorithm, extensions: Extensions, - issuerSigner: some Signer + issuerSignatureProvider: some SignatureProvider ) throws { self.tbsCertificate = TBSCertificate( version: version, @@ -260,7 +260,7 @@ public struct Certificate { self.signatureAlgorithm = signatureAlgorithm let tbsCertificateBytes = try DER.Serializer.serialized(element: self.tbsCertificate)[...] - self.signature = try issuerSigner.sign(bytes: tbsCertificateBytes, signatureAlgorithm: signatureAlgorithm) + self.signature = try issuerSignatureProvider.sign(bytes: tbsCertificateBytes, signatureAlgorithm: signatureAlgorithm) self.tbsCertificateBytes = tbsCertificateBytes self.signatureAlgorithmBytes = try DER.Serializer.serialized( element: AlgorithmIdentifier(self.signatureAlgorithm) @@ -284,7 +284,7 @@ public struct Certificate { /// - subject: The ``DistinguishedName`` of the subject of this certificate. /// - signatureAlgorithm: The signature algorithm that will be used to produce `signature`. Must be compatible with the private key type. /// - extensions: The extensions on this certificate. - /// - issuerSigner: The signer to use to sign this certificate. + /// - issuerSignatureProvider: The signature provider to use to sign this certificate. @inlinable public init( version: Version, @@ -296,7 +296,7 @@ public struct Certificate { subject: DistinguishedName, signatureAlgorithm: SignatureAlgorithm, extensions: Extensions, - issuerSigner: some AsyncSigner + issuerSignatureProvider: some AsyncSignatureProvider ) async throws { self.tbsCertificate = TBSCertificate( version: version, @@ -314,7 +314,7 @@ public struct Certificate { self.signatureAlgorithm = signatureAlgorithm let tbsCertificateBytes = try DER.Serializer.serialized(element: self.tbsCertificate)[...] - self.signature = try await issuerSigner.sign(bytes: tbsCertificateBytes, signatureAlgorithm: signatureAlgorithm) + self.signature = try await issuerSignatureProvider.sign(bytes: tbsCertificateBytes, signatureAlgorithm: signatureAlgorithm) self.tbsCertificateBytes = tbsCertificateBytes self.signatureAlgorithmBytes = try DER.Serializer.serialized( element: AlgorithmIdentifier(self.signatureAlgorithm) diff --git a/Sources/X509/CertificatePrivateKey.swift b/Sources/X509/CertificatePrivateKey.swift index 65fb4f9..fcf9803 100644 --- a/Sources/X509/CertificatePrivateKey.swift +++ b/Sources/X509/CertificatePrivateKey.swift @@ -183,7 +183,7 @@ extension Certificate { } @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) -extension Certificate.PrivateKey: Certificate.Signer {} +extension Certificate.PrivateKey: Certificate.SignatureProvider {} @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) extension Certificate.PrivateKey: Hashable {} diff --git a/Sources/X509/CertificateSigner.swift b/Sources/X509/CertificateSigner.swift index 8ff822a..1ede803 100644 --- a/Sources/X509/CertificateSigner.swift +++ b/Sources/X509/CertificateSigner.swift @@ -3,7 +3,7 @@ import Foundation @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) extension Certificate { - public protocol Signer: Sendable { + public protocol SignatureProvider: Sendable { /// Use the signer to sign the provided bytes with a given signature algorithm. /// @@ -24,7 +24,7 @@ extension Certificate { @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) extension Certificate { - public protocol AsyncSigner: Sendable { + public protocol AsyncSignatureProvider: Sendable { /// Use the signer to sign the provided bytes with a given signature algorithm. /// From b344fd46008e50d5dc7ffe241bd0ac08fc324578 Mon Sep 17 00:00:00 2001 From: Justin Oroz Date: Thu, 9 Oct 2025 23:20:48 -0700 Subject: [PATCH 07/29] =?UTF-8?q?Certificate.SignatureProvider=20=E2=86=92?= =?UTF-8?q?=20Certificate.PrivateKeyProtocol?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../X509/CSR/CertificateSigningRequest.swift | 4 +-- Sources/X509/Certificate.swift | 26 ++++++++++++------- Sources/X509/CertificatePrivateKey.swift | 2 +- Sources/X509/CertificateSigner.swift | 16 +++++++++--- 4 files changed, 31 insertions(+), 17 deletions(-) diff --git a/Sources/X509/CSR/CertificateSigningRequest.swift b/Sources/X509/CSR/CertificateSigningRequest.swift index c850e82..4feb751 100644 --- a/Sources/X509/CSR/CertificateSigningRequest.swift +++ b/Sources/X509/CSR/CertificateSigningRequest.swift @@ -167,7 +167,7 @@ public struct CertificateSigningRequest { version: Version, subject: DistinguishedName, publicKey: Certificate.PublicKey, - signatureProvider: some Certificate.SignatureProvider, + signatureProvider: some Certificate.PrivateKeyProtocol, attributes: Attributes, signatureAlgorithm: Certificate.SignatureAlgorithm ) throws { @@ -205,7 +205,7 @@ public struct CertificateSigningRequest { version: Version, subject: DistinguishedName, publicKey: Certificate.PublicKey, - signatureProvider: some Certificate.AsyncSignatureProvider, + signatureProvider: some Certificate.AsyncPrivateKeyProtocol, attributes: Attributes, signatureAlgorithm: Certificate.SignatureAlgorithm ) async throws { diff --git a/Sources/X509/Certificate.swift b/Sources/X509/Certificate.swift index 9b83236..ff907d9 100644 --- a/Sources/X509/Certificate.swift +++ b/Sources/X509/Certificate.swift @@ -214,11 +214,14 @@ public struct Certificate { self.signatureBytes = try DER.Serializer.serialized(element: ASN1BitString(self.signature))[...] } - /// Construct a certificate from constituent parts, signed by an custom signer. + /// 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 `issuerSigner`. + /// automatically, using `issuerPrivateKey`. + /// + /// 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. @@ -230,7 +233,7 @@ public struct Certificate { /// - subject: The ``DistinguishedName`` of the subject of this certificate. /// - signatureAlgorithm: The signature algorithm that will be used to produce `signature`. Must be compatible with the private key type. /// - extensions: The extensions on this certificate. - /// - issuerSignatureProvider: The signature provider to use to sign this certificate. + /// - issuerPrivateKey: The private key to use to sign this certificate. @inlinable public init( version: Version, @@ -242,7 +245,7 @@ public struct Certificate { subject: DistinguishedName, signatureAlgorithm: SignatureAlgorithm, extensions: Extensions, - issuerSignatureProvider: some SignatureProvider + issuerPrivateKey: some PrivateKeyProtocol ) throws { self.tbsCertificate = TBSCertificate( version: version, @@ -260,7 +263,7 @@ public struct Certificate { self.signatureAlgorithm = signatureAlgorithm let tbsCertificateBytes = try DER.Serializer.serialized(element: self.tbsCertificate)[...] - self.signature = try issuerSignatureProvider.sign(bytes: tbsCertificateBytes, signatureAlgorithm: signatureAlgorithm) + self.signature = try issuerPrivateKey.sign(bytes: tbsCertificateBytes, signatureAlgorithm: signatureAlgorithm) self.tbsCertificateBytes = tbsCertificateBytes self.signatureAlgorithmBytes = try DER.Serializer.serialized( element: AlgorithmIdentifier(self.signatureAlgorithm) @@ -268,11 +271,14 @@ public struct Certificate { self.signatureBytes = try DER.Serializer.serialized(element: ASN1BitString(self.signature))[...] } - /// Construct a certificate from constituent parts, signed by an custom signer. + /// 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 `issuerSigner`. + /// automatically, using `issuerPrivateKey`. + /// + /// 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. @@ -284,7 +290,7 @@ public struct Certificate { /// - subject: The ``DistinguishedName`` of the subject of this certificate. /// - signatureAlgorithm: The signature algorithm that will be used to produce `signature`. Must be compatible with the private key type. /// - extensions: The extensions on this certificate. - /// - issuerSignatureProvider: The signature provider to use to sign this certificate. + /// - issuerPrivateKey: The private key to use to sign this certificate. @inlinable public init( version: Version, @@ -296,7 +302,7 @@ public struct Certificate { subject: DistinguishedName, signatureAlgorithm: SignatureAlgorithm, extensions: Extensions, - issuerSignatureProvider: some AsyncSignatureProvider + issuerPrivateKey: some AsyncPrivateKeyProtocol ) async throws { self.tbsCertificate = TBSCertificate( version: version, @@ -314,7 +320,7 @@ public struct Certificate { self.signatureAlgorithm = signatureAlgorithm let tbsCertificateBytes = try DER.Serializer.serialized(element: self.tbsCertificate)[...] - self.signature = try await issuerSignatureProvider.sign(bytes: tbsCertificateBytes, signatureAlgorithm: signatureAlgorithm) + self.signature = try await issuerPrivateKey.sign(bytes: tbsCertificateBytes, signatureAlgorithm: signatureAlgorithm) self.tbsCertificateBytes = tbsCertificateBytes self.signatureAlgorithmBytes = try DER.Serializer.serialized( element: AlgorithmIdentifier(self.signatureAlgorithm) diff --git a/Sources/X509/CertificatePrivateKey.swift b/Sources/X509/CertificatePrivateKey.swift index fcf9803..c64c8df 100644 --- a/Sources/X509/CertificatePrivateKey.swift +++ b/Sources/X509/CertificatePrivateKey.swift @@ -183,7 +183,7 @@ extension Certificate { } @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) -extension Certificate.PrivateKey: Certificate.SignatureProvider {} +extension Certificate.PrivateKey: Certificate.PrivateKeyProtocol {} @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) extension Certificate.PrivateKey: Hashable {} diff --git a/Sources/X509/CertificateSigner.swift b/Sources/X509/CertificateSigner.swift index 1ede803..beb5a34 100644 --- a/Sources/X509/CertificateSigner.swift +++ b/Sources/X509/CertificateSigner.swift @@ -3,9 +3,13 @@ import Foundation @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) extension Certificate { - public protocol SignatureProvider: Sendable { + public protocol PrivateKeyProtocol: Sendable { - /// Use the signer to sign the provided bytes with a given signature algorithm. + /// Obtain the ``Certificate/PublicKey-swift.struct`` corresponding to + /// this private key. + var publicKey: PublicKey { get } + + /// Use the private key to sign the provided bytes with a given signature algorithm. /// /// - Parameters: /// - bytes: The data to create the signature for. @@ -24,9 +28,13 @@ extension Certificate { @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) extension Certificate { - public protocol AsyncSignatureProvider: Sendable { + public protocol AsyncPrivateKeyProtocol: Sendable { + + /// Obtain the ``Certificate/PublicKey-swift.struct`` corresponding to + /// this private key. + var publicKey: PublicKey { get } - /// Use the signer to sign the provided bytes with a given signature algorithm. + /// Use the private key to sign the provided bytes with a given signature algorithm. /// /// - Parameters: /// - bytes: The data to create the signature for. From 2623976713ad11184ba711681e9b986858b358e4 Mon Sep 17 00:00:00 2001 From: Justin Oroz Date: Thu, 9 Oct 2025 23:23:32 -0700 Subject: [PATCH 08/29] Fix CSR inits --- .../X509/CSR/CertificateSigningRequest.swift | 24 ++++++++----------- 1 file changed, 10 insertions(+), 14 deletions(-) diff --git a/Sources/X509/CSR/CertificateSigningRequest.swift b/Sources/X509/CSR/CertificateSigningRequest.swift index 4feb751..bdea500 100644 --- a/Sources/X509/CSR/CertificateSigningRequest.swift +++ b/Sources/X509/CSR/CertificateSigningRequest.swift @@ -150,7 +150,7 @@ public struct CertificateSigningRequest { self.signatureBytes = try DER.Serializer.serialized(element: ASN1BitString(self.signature))[...] } - /// Construct a CSR using a signature provider. + /// 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. @@ -158,29 +158,27 @@ public struct CertificateSigningRequest { /// - Parameters: /// - version: The CSR version. /// - subject: The ``DistinguishedName`` of the subject of this CSR - /// - publicKey: The public key associated with this CSR. - /// - signatureProvider: The signature provider associated with this CSR. + /// - privateKey: The private key associated with this CSR. /// - attributes: The attributes associated with this CSR /// - signatureAlgorithm: The signature algorithm to use for the signature on this CSR. @inlinable public init( version: Version, subject: DistinguishedName, - publicKey: Certificate.PublicKey, - signatureProvider: some Certificate.PrivateKeyProtocol, + privateKey: some Certificate.PrivateKeyProtocol, attributes: Attributes, signatureAlgorithm: Certificate.SignatureAlgorithm ) throws { self.info = CertificationRequestInfo( version: version, subject: subject, - publicKey: publicKey, + publicKey: privateKey.publicKey, attributes: attributes ) self.signatureAlgorithm = signatureAlgorithm let infoBytes = try DER.Serializer.serialized(element: self.info) - self.signature = try signatureProvider.sign(bytes: infoBytes, signatureAlgorithm: signatureAlgorithm) + self.signature = try privateKey.sign(bytes: infoBytes, signatureAlgorithm: signatureAlgorithm) self.infoBytes = infoBytes[...] self.signatureAlgorithmBytes = try DER.Serializer.serialized( element: AlgorithmIdentifier(self.signatureAlgorithm) @@ -188,7 +186,7 @@ public struct CertificateSigningRequest { self.signatureBytes = try DER.Serializer.serialized(element: ASN1BitString(self.signature))[...] } - /// Construct a CSR using a signature provider. + /// 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. @@ -196,29 +194,27 @@ public struct CertificateSigningRequest { /// - Parameters: /// - version: The CSR version. /// - subject: The ``DistinguishedName`` of the subject of this CSR - /// - publicKey: The public key associated with this CSR. - /// - signatureProvider: The signature provider associated with this CSR. + /// - privateKey: The private key associated with this CSR. /// - attributes: The attributes associated with this CSR /// - signatureAlgorithm: The signature algorithm to use for the signature on this CSR. @inlinable public init( version: Version, subject: DistinguishedName, - publicKey: Certificate.PublicKey, - signatureProvider: some Certificate.AsyncPrivateKeyProtocol, + privateKey: some Certificate.AsyncPrivateKeyProtocol, attributes: Attributes, signatureAlgorithm: Certificate.SignatureAlgorithm ) async throws { self.info = CertificationRequestInfo( version: version, subject: subject, - publicKey: publicKey, + publicKey: privateKey.publicKey, attributes: attributes ) self.signatureAlgorithm = signatureAlgorithm let infoBytes = try DER.Serializer.serialized(element: self.info) - self.signature = try await signatureProvider.sign(bytes: infoBytes, signatureAlgorithm: signatureAlgorithm) + self.signature = try await privateKey.sign(bytes: infoBytes, signatureAlgorithm: signatureAlgorithm) self.infoBytes = infoBytes[...] self.signatureAlgorithmBytes = try DER.Serializer.serialized( element: AlgorithmIdentifier(self.signatureAlgorithm) From f4eea460e59a17ef94f7404836cf4186723f6f2e Mon Sep 17 00:00:00 2001 From: Justin Oroz Date: Mon, 13 Oct 2025 00:47:46 -0700 Subject: [PATCH 09/29] formatting --- Sources/X509/Certificate.swift | 5 ++++- 1 file changed, 4 insertions(+), 1 deletion(-) diff --git a/Sources/X509/Certificate.swift b/Sources/X509/Certificate.swift index ff907d9..eb5eeea 100644 --- a/Sources/X509/Certificate.swift +++ b/Sources/X509/Certificate.swift @@ -320,7 +320,10 @@ public struct Certificate { self.signatureAlgorithm = signatureAlgorithm let tbsCertificateBytes = try DER.Serializer.serialized(element: self.tbsCertificate)[...] - self.signature = try await issuerPrivateKey.sign(bytes: tbsCertificateBytes, signatureAlgorithm: signatureAlgorithm) + self.signature = try await issuerPrivateKey.sign( + bytes: tbsCertificateBytes, + signatureAlgorithm: signatureAlgorithm + ) self.tbsCertificateBytes = tbsCertificateBytes self.signatureAlgorithmBytes = try DER.Serializer.serialized( element: AlgorithmIdentifier(self.signatureAlgorithm) From d4db0e6925733f193e7ae75bdad3334b07a6ebe5 Mon Sep 17 00:00:00 2001 From: Justin Oroz Date: Mon, 13 Oct 2025 00:48:10 -0700 Subject: [PATCH 10/29] rename file to match protocol name --- ...ift => CertificatePrivateKeyProtocol.swift} | 18 ++++++++++++++++++ 1 file changed, 18 insertions(+) rename Sources/X509/{CertificateSigner.swift => CertificatePrivateKeyProtocol.swift} (72%) diff --git a/Sources/X509/CertificateSigner.swift b/Sources/X509/CertificatePrivateKeyProtocol.swift similarity index 72% rename from Sources/X509/CertificateSigner.swift rename to Sources/X509/CertificatePrivateKeyProtocol.swift index beb5a34..1e0109a 100644 --- a/Sources/X509/CertificateSigner.swift +++ b/Sources/X509/CertificatePrivateKeyProtocol.swift @@ -1,4 +1,22 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftCertificates open source project +// +// Copyright (c) 2022 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 +// +//===----------------------------------------------------------------------===// + +#if canImport(FoundationEssentials) +import FoundationEssentials +#else import Foundation +#endif @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) extension Certificate { From 3797b50b701f5f912a4e4c9296a04eb61b51754d Mon Sep 17 00:00:00 2001 From: Justin Oroz Date: Mon, 13 Oct 2025 00:53:52 -0700 Subject: [PATCH 11/29] adopt Hashable --- Sources/X509/CertificatePrivateKeyProtocol.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Sources/X509/CertificatePrivateKeyProtocol.swift b/Sources/X509/CertificatePrivateKeyProtocol.swift index 1e0109a..374de65 100644 --- a/Sources/X509/CertificatePrivateKeyProtocol.swift +++ b/Sources/X509/CertificatePrivateKeyProtocol.swift @@ -21,7 +21,7 @@ import Foundation @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) extension Certificate { - public protocol PrivateKeyProtocol: Sendable { + public protocol PrivateKeyProtocol: Sendable, Hashable { /// Obtain the ``Certificate/PublicKey-swift.struct`` corresponding to /// this private key. @@ -46,7 +46,7 @@ extension Certificate { @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) extension Certificate { - public protocol AsyncPrivateKeyProtocol: Sendable { + public protocol AsyncPrivateKeyProtocol: Sendable, Hashable { /// Obtain the ``Certificate/PublicKey-swift.struct`` corresponding to /// this private key. From 7265cfc6ee4e55e13312aaa4ce3faaa4ed552082 Mon Sep 17 00:00:00 2001 From: Justin Oroz Date: Thu, 23 Oct 2025 22:54:16 -0700 Subject: [PATCH 12/29] Move protocols to root level Rename to CustomPrivateKey --- .../X509/CSR/CertificateSigningRequest.swift | 4 +- Sources/X509/Certificate.swift | 4 +- .../X509/CertificatePrivateKeyProtocol.swift | 76 +++++++++---------- 3 files changed, 38 insertions(+), 46 deletions(-) diff --git a/Sources/X509/CSR/CertificateSigningRequest.swift b/Sources/X509/CSR/CertificateSigningRequest.swift index bdea500..ca1ed90 100644 --- a/Sources/X509/CSR/CertificateSigningRequest.swift +++ b/Sources/X509/CSR/CertificateSigningRequest.swift @@ -165,7 +165,7 @@ public struct CertificateSigningRequest { public init( version: Version, subject: DistinguishedName, - privateKey: some Certificate.PrivateKeyProtocol, + privateKey: some CustomPrivateKey, attributes: Attributes, signatureAlgorithm: Certificate.SignatureAlgorithm ) throws { @@ -201,7 +201,7 @@ public struct CertificateSigningRequest { public init( version: Version, subject: DistinguishedName, - privateKey: some Certificate.AsyncPrivateKeyProtocol, + privateKey: some AsyncCustomPrivateKey, attributes: Attributes, signatureAlgorithm: Certificate.SignatureAlgorithm ) async throws { diff --git a/Sources/X509/Certificate.swift b/Sources/X509/Certificate.swift index eb5eeea..d90b5b6 100644 --- a/Sources/X509/Certificate.swift +++ b/Sources/X509/Certificate.swift @@ -245,7 +245,7 @@ public struct Certificate { subject: DistinguishedName, signatureAlgorithm: SignatureAlgorithm, extensions: Extensions, - issuerPrivateKey: some PrivateKeyProtocol + issuerPrivateKey: some CustomPrivateKey ) throws { self.tbsCertificate = TBSCertificate( version: version, @@ -302,7 +302,7 @@ public struct Certificate { subject: DistinguishedName, signatureAlgorithm: SignatureAlgorithm, extensions: Extensions, - issuerPrivateKey: some AsyncPrivateKeyProtocol + issuerPrivateKey: some AsyncCustomPrivateKey ) async throws { self.tbsCertificate = TBSCertificate( version: version, diff --git a/Sources/X509/CertificatePrivateKeyProtocol.swift b/Sources/X509/CertificatePrivateKeyProtocol.swift index 374de65..dfca774 100644 --- a/Sources/X509/CertificatePrivateKeyProtocol.swift +++ b/Sources/X509/CertificatePrivateKeyProtocol.swift @@ -19,51 +19,43 @@ import Foundation #endif @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) -extension Certificate { - - public protocol PrivateKeyProtocol: Sendable, Hashable { - - /// Obtain the ``Certificate/PublicKey-swift.struct`` corresponding to - /// this private key. - var publicKey: PublicKey { get } - - /// Use the private key to sign the provided bytes with a given signature algorithm. - /// - /// - Parameters: - /// - bytes: The data to create the signature for. - /// - signatureAlgorithm: The signature algorithm to use. - /// - Returns: The signature. - @inlinable - func sign( - bytes: some DataProtocol, - signatureAlgorithm: SignatureAlgorithm - ) throws -> Signature - - } +public protocol CustomPrivateKey: Sendable, Hashable { + + /// Obtain the ``Certificate/PublicKey-swift.struct`` corresponding to + /// this private key. + var publicKey: Certificate.PublicKey { get } + + /// Use the private key to sign the provided bytes with a given signature algorithm. + /// + /// - Parameters: + /// - bytes: The data to create the signature for. + /// - signatureAlgorithm: The signature algorithm to use. + /// - Returns: The signature. + @inlinable + func sign( + bytes: some DataProtocol, + signatureAlgorithm: Certificate.SignatureAlgorithm + ) throws -> Certificate.Signature } @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) -extension Certificate { - - public protocol AsyncPrivateKeyProtocol: Sendable, Hashable { - - /// Obtain the ``Certificate/PublicKey-swift.struct`` corresponding to - /// this private key. - var publicKey: PublicKey { get } - - /// Use the private key to sign the provided bytes with a given signature algorithm. - /// - /// - Parameters: - /// - bytes: The data to create the signature for. - /// - signatureAlgorithm: The signature algorithm to use. - /// - Returns: The signature. - @inlinable - func sign( - bytes: some DataProtocol, - signatureAlgorithm: SignatureAlgorithm - ) async throws -> Signature - - } +public protocol AsyncCustomPrivateKey: Sendable, Hashable { + + /// Obtain the ``Certificate/PublicKey-swift.struct`` corresponding to + /// this private key. + var publicKey: Certificate.PublicKey { get } + + /// Use the private key to sign the provided bytes with a given signature algorithm. + /// + /// - Parameters: + /// - bytes: The data to create the signature for. + /// - signatureAlgorithm: The signature algorithm to use. + /// - Returns: The signature. + @inlinable + func sign( + bytes: some DataProtocol, + signatureAlgorithm: Certificate.SignatureAlgorithm + ) async throws -> Certificate.Signature } From 5a1fec436fa17038959ea84a36a16496bf8441ff Mon Sep 17 00:00:00 2001 From: Justin Oroz Date: Thu, 23 Oct 2025 22:54:21 -0700 Subject: [PATCH 13/29] Drop PrivateKey protocol conformance --- Sources/X509/CertificatePrivateKey.swift | 3 --- 1 file changed, 3 deletions(-) diff --git a/Sources/X509/CertificatePrivateKey.swift b/Sources/X509/CertificatePrivateKey.swift index c64c8df..33b46d6 100644 --- a/Sources/X509/CertificatePrivateKey.swift +++ b/Sources/X509/CertificatePrivateKey.swift @@ -182,9 +182,6 @@ extension Certificate { } } -@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) -extension Certificate.PrivateKey: Certificate.PrivateKeyProtocol {} - @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) extension Certificate.PrivateKey: Hashable {} From 0807f83dc31fbf2721e161e79c365bbc14a5a867 Mon Sep 17 00:00:00 2001 From: Justin Oroz Date: Thu, 23 Oct 2025 22:54:24 -0700 Subject: [PATCH 14/29] Rename file to match --- ...CertificatePrivateKeyProtocol.swift => CustomPrivateKey.swift} | 0 1 file changed, 0 insertions(+), 0 deletions(-) rename Sources/X509/{CertificatePrivateKeyProtocol.swift => CustomPrivateKey.swift} (100%) diff --git a/Sources/X509/CertificatePrivateKeyProtocol.swift b/Sources/X509/CustomPrivateKey.swift similarity index 100% rename from Sources/X509/CertificatePrivateKeyProtocol.swift rename to Sources/X509/CustomPrivateKey.swift From 3dc6e3b7d3da388d4ca7ddb14ee8c4654c1e0a7e Mon Sep 17 00:00:00 2001 From: Justin Oroz Date: Thu, 23 Oct 2025 22:54:28 -0700 Subject: [PATCH 15/29] Merge CustomPrivateKey protocols --- .../X509/CSR/CertificateSigningRequest.swift | 7 ++++-- Sources/X509/Certificate.swift | 4 +-- Sources/X509/CustomPrivateKey.swift | 25 +++++++++++-------- 3 files changed, 21 insertions(+), 15 deletions(-) diff --git a/Sources/X509/CSR/CertificateSigningRequest.swift b/Sources/X509/CSR/CertificateSigningRequest.swift index ca1ed90..2d3ac7f 100644 --- a/Sources/X509/CSR/CertificateSigningRequest.swift +++ b/Sources/X509/CSR/CertificateSigningRequest.swift @@ -201,7 +201,7 @@ public struct CertificateSigningRequest { public init( version: Version, subject: DistinguishedName, - privateKey: some AsyncCustomPrivateKey, + privateKey: some CustomPrivateKey, attributes: Attributes, signatureAlgorithm: Certificate.SignatureAlgorithm ) async throws { @@ -214,7 +214,10 @@ public struct CertificateSigningRequest { self.signatureAlgorithm = signatureAlgorithm let infoBytes = try DER.Serializer.serialized(element: self.info) - self.signature = try await privateKey.sign(bytes: infoBytes, signatureAlgorithm: signatureAlgorithm) + self.signature = try await privateKey.signAsynchronously( + bytes: infoBytes, + signatureAlgorithm: signatureAlgorithm + ) self.infoBytes = infoBytes[...] self.signatureAlgorithmBytes = try DER.Serializer.serialized( element: AlgorithmIdentifier(self.signatureAlgorithm) diff --git a/Sources/X509/Certificate.swift b/Sources/X509/Certificate.swift index d90b5b6..29f07aa 100644 --- a/Sources/X509/Certificate.swift +++ b/Sources/X509/Certificate.swift @@ -302,7 +302,7 @@ public struct Certificate { subject: DistinguishedName, signatureAlgorithm: SignatureAlgorithm, extensions: Extensions, - issuerPrivateKey: some AsyncCustomPrivateKey + issuerPrivateKey: some CustomPrivateKey ) async throws { self.tbsCertificate = TBSCertificate( version: version, @@ -320,7 +320,7 @@ public struct Certificate { self.signatureAlgorithm = signatureAlgorithm let tbsCertificateBytes = try DER.Serializer.serialized(element: self.tbsCertificate)[...] - self.signature = try await issuerPrivateKey.sign( + self.signature = try await issuerPrivateKey.signAsynchronously( bytes: tbsCertificateBytes, signatureAlgorithm: signatureAlgorithm ) diff --git a/Sources/X509/CustomPrivateKey.swift b/Sources/X509/CustomPrivateKey.swift index dfca774..5f126dd 100644 --- a/Sources/X509/CustomPrivateKey.swift +++ b/Sources/X509/CustomPrivateKey.swift @@ -37,25 +37,28 @@ public protocol CustomPrivateKey: Sendable, Hashable { signatureAlgorithm: Certificate.SignatureAlgorithm ) throws -> Certificate.Signature -} - -@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) -public protocol AsyncCustomPrivateKey: Sendable, Hashable { - - /// Obtain the ``Certificate/PublicKey-swift.struct`` corresponding to - /// this private key. - var publicKey: Certificate.PublicKey { get } - - /// Use the private key to sign the provided bytes with a given signature algorithm. + /// Use the private key to sign the provided bytes asynchronously with a given signature algorithm. /// /// - Parameters: /// - bytes: The data to create the signature for. /// - signatureAlgorithm: The signature algorithm to use. /// - Returns: The signature. @inlinable - func sign( + func signAsynchronously( bytes: some DataProtocol, signatureAlgorithm: Certificate.SignatureAlgorithm ) async throws -> Certificate.Signature } + +@available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) +extension CustomPrivateKey { + + func signAsynchronously( + bytes: some DataProtocol, + signatureAlgorithm: Certificate.SignatureAlgorithm + ) async throws -> Certificate.Signature { + try sign(bytes: bytes, signatureAlgorithm: signatureAlgorithm) + } + +} From 939f1f953cf891f508ca7514a8eb01d6282524b7 Mon Sep 17 00:00:00 2001 From: Justin Oroz Date: Fri, 24 Oct 2025 10:21:04 -0700 Subject: [PATCH 16/29] CustomPrivateKey backs Certificate.PrivateKey --- .../X509/CSR/CertificateSigningRequest.swift | 38 +----------- Sources/X509/Certificate.swift | 59 +------------------ Sources/X509/CertificatePrivateKey.swift | 42 +++++++++++++ Sources/X509/CustomPrivateKey.swift | 3 + 4 files changed, 47 insertions(+), 95 deletions(-) diff --git a/Sources/X509/CSR/CertificateSigningRequest.swift b/Sources/X509/CSR/CertificateSigningRequest.swift index 2d3ac7f..084d12d 100644 --- a/Sources/X509/CSR/CertificateSigningRequest.swift +++ b/Sources/X509/CSR/CertificateSigningRequest.swift @@ -165,43 +165,7 @@ public struct CertificateSigningRequest { public init( version: Version, subject: DistinguishedName, - privateKey: some CustomPrivateKey, - attributes: Attributes, - signatureAlgorithm: Certificate.SignatureAlgorithm - ) throws { - self.info = CertificationRequestInfo( - version: version, - subject: subject, - publicKey: privateKey.publicKey, - attributes: attributes - ) - self.signatureAlgorithm = signatureAlgorithm - - let infoBytes = try DER.Serializer.serialized(element: self.info) - self.signature = try privateKey.sign(bytes: infoBytes, signatureAlgorithm: signatureAlgorithm) - self.infoBytes = infoBytes[...] - self.signatureAlgorithmBytes = try DER.Serializer.serialized( - element: AlgorithmIdentifier(self.signatureAlgorithm) - )[...] - 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. - /// - /// - 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 - /// - signatureAlgorithm: The signature algorithm to use for the signature on this CSR. - @inlinable - public init( - version: Version, - subject: DistinguishedName, - privateKey: some CustomPrivateKey, + privateKey: Certificate.PrivateKey, attributes: Attributes, signatureAlgorithm: Certificate.SignatureAlgorithm ) async throws { diff --git a/Sources/X509/Certificate.swift b/Sources/X509/Certificate.swift index 29f07aa..97a90f6 100644 --- a/Sources/X509/Certificate.swift +++ b/Sources/X509/Certificate.swift @@ -245,64 +245,7 @@ public struct Certificate { subject: DistinguishedName, signatureAlgorithm: SignatureAlgorithm, extensions: Extensions, - issuerPrivateKey: some CustomPrivateKey - ) throws { - self.tbsCertificate = TBSCertificate( - version: version, - serialNumber: serialNumber, - signature: signatureAlgorithm, - issuer: issuer, - validity: try Validity( - notBefore: .makeTime(from: notValidBefore), - notAfter: .makeTime(from: notValidAfter) - ), - subject: subject, - publicKey: publicKey, - extensions: extensions - ) - self.signatureAlgorithm = signatureAlgorithm - - let tbsCertificateBytes = try DER.Serializer.serialized(element: self.tbsCertificate)[...] - self.signature = try issuerPrivateKey.sign(bytes: tbsCertificateBytes, signatureAlgorithm: signatureAlgorithm) - self.tbsCertificateBytes = tbsCertificateBytes - self.signatureAlgorithmBytes = try DER.Serializer.serialized( - element: AlgorithmIdentifier(self.signatureAlgorithm) - )[...] - 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`. - /// - /// 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. - /// - signatureAlgorithm: The signature algorithm that will be used to produce `signature`. Must be compatible with the private key type. - /// - 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, - signatureAlgorithm: SignatureAlgorithm, - extensions: Extensions, - issuerPrivateKey: some CustomPrivateKey + issuerPrivateKey: PrivateKey ) async throws { self.tbsCertificate = TBSCertificate( version: version, diff --git a/Sources/X509/CertificatePrivateKey.swift b/Sources/X509/CertificatePrivateKey.swift index 33b46d6..23c6709 100644 --- a/Sources/X509/CertificatePrivateKey.swift +++ b/Sources/X509/CertificatePrivateKey.swift @@ -119,6 +119,27 @@ extension Certificate { #endif case .ed25519(let ed25519): return try ed25519.signature(for: bytes, signatureAlgorithm: signatureAlgorithm) + case .custom(let custom): + return try custom.sign(bytes: bytes, signatureAlgorithm: signatureAlgorithm) + } + } + + /// Use the private key to sign the provided bytes asynchronously with a given signature algorithm. + /// + /// - Parameters: + /// - bytes: The data to create the signature for. + /// - signatureAlgorithm: The signature algorithm to use. + /// - Returns: The signature. + @inlinable + public func signAsynchronously( + bytes: Bytes, + signatureAlgorithm: SignatureAlgorithm + ) async throws -> Signature { + switch self.backing { + case .custom(let custom): + return try await custom.signAsynchronously(bytes: bytes, signatureAlgorithm: signatureAlgorithm) + default: + return try self.sign(bytes: bytes, signatureAlgorithm: signatureAlgorithm) } } @@ -143,6 +164,8 @@ extension Certificate { #endif case .ed25519(let ed25519): return PublicKey(ed25519.publicKey) + case .custom(let custom): + return custom.publicKey } } @@ -177,6 +200,8 @@ extension Certificate { #endif case .ed25519: return .ed25519 + case .custom(let custom): + return custom.defaultSignatureAlgorithm } } } @@ -208,6 +233,11 @@ extension Certificate.PrivateKey: CustomStringConvertible { #endif case .ed25519: return "Ed25519.PrivateKey" + case .custom(let custom): + if let custom = custom as? CustomStringConvertible { + return custom.description + } + return "CustomPrivateKey" } } } @@ -225,6 +255,7 @@ extension Certificate.PrivateKey { case secKey(SecKeyWrapper) #endif case ed25519(Crypto.Curve25519.Signing.PrivateKey) + case custom(any CustomPrivateKey) @inlinable static func == (lhs: BackingPrivateKey, rhs: BackingPrivateKey) -> Bool { @@ -245,6 +276,8 @@ extension Certificate.PrivateKey { #endif case (.ed25519(let l), .ed25519(let r)): return l.rawRepresentation == r.rawRepresentation + case (.custom(let l), .custom(let r)): + return l.publicKey == r.publicKey default: return false } @@ -277,6 +310,8 @@ extension Certificate.PrivateKey { case .ed25519(let digest): hasher.combine(6) hasher.combine(digest.rawRepresentation) + case .custom(let key): + hasher.combine(key) } } } @@ -350,6 +385,13 @@ extension Certificate.PrivateKey { case .secKey(let key): return try key.pemDocument() #endif case .ed25519(let key): return key.pemRepresentation + case .custom(let key): + guard let key = key as? PEMSerializable else { + throw CertificateError.unsupportedPrivateKey( + reason: "custom private key can not be serialised as PEM" + ) + } + return try key.serializeAsPEM() } } } diff --git a/Sources/X509/CustomPrivateKey.swift b/Sources/X509/CustomPrivateKey.swift index 5f126dd..c028dd8 100644 --- a/Sources/X509/CustomPrivateKey.swift +++ b/Sources/X509/CustomPrivateKey.swift @@ -16,6 +16,7 @@ import FoundationEssentials #else import Foundation +import SwiftASN1 #endif @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) @@ -25,6 +26,8 @@ public protocol CustomPrivateKey: Sendable, Hashable { /// this private key. var publicKey: Certificate.PublicKey { get } + var defaultSignatureAlgorithm: Certificate.SignatureAlgorithm { get } + /// Use the private key to sign the provided bytes with a given signature algorithm. /// /// - Parameters: From 0df7335186f9a01d8def03cffecda864088125a8 Mon Sep 17 00:00:00 2001 From: Justin Oroz Date: Wed, 29 Oct 2025 11:06:36 -0700 Subject: [PATCH 17/29] signSynchronously --- Sources/X509/CertificatePrivateKey.swift | 2 +- Sources/X509/CustomPrivateKey.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/X509/CertificatePrivateKey.swift b/Sources/X509/CertificatePrivateKey.swift index 23c6709..92dcce2 100644 --- a/Sources/X509/CertificatePrivateKey.swift +++ b/Sources/X509/CertificatePrivateKey.swift @@ -120,7 +120,7 @@ extension Certificate { case .ed25519(let ed25519): return try ed25519.signature(for: bytes, signatureAlgorithm: signatureAlgorithm) case .custom(let custom): - return try custom.sign(bytes: bytes, signatureAlgorithm: signatureAlgorithm) + return try custom.signSynchronously(bytes: bytes, signatureAlgorithm: signatureAlgorithm) } } diff --git a/Sources/X509/CustomPrivateKey.swift b/Sources/X509/CustomPrivateKey.swift index c028dd8..51a290f 100644 --- a/Sources/X509/CustomPrivateKey.swift +++ b/Sources/X509/CustomPrivateKey.swift @@ -35,7 +35,7 @@ public protocol CustomPrivateKey: Sendable, Hashable { /// - signatureAlgorithm: The signature algorithm to use. /// - Returns: The signature. @inlinable - func sign( + func signSynchronously( bytes: some DataProtocol, signatureAlgorithm: Certificate.SignatureAlgorithm ) throws -> Certificate.Signature @@ -61,7 +61,7 @@ extension CustomPrivateKey { bytes: some DataProtocol, signatureAlgorithm: Certificate.SignatureAlgorithm ) async throws -> Certificate.Signature { - try sign(bytes: bytes, signatureAlgorithm: signatureAlgorithm) + try signSynchronously(bytes: bytes, signatureAlgorithm: signatureAlgorithm) } } From 2f2bb60472ec04506414551ea0055aee47ec17cb Mon Sep 17 00:00:00 2001 From: Justin Oroz Date: Wed, 29 Oct 2025 11:08:07 -0700 Subject: [PATCH 18/29] require PEMSerializable adoption --- Sources/X509/CertificatePrivateKey.swift | 8 +------- Sources/X509/CustomPrivateKey.swift | 2 +- 2 files changed, 2 insertions(+), 8 deletions(-) diff --git a/Sources/X509/CertificatePrivateKey.swift b/Sources/X509/CertificatePrivateKey.swift index 92dcce2..6e1c3b3 100644 --- a/Sources/X509/CertificatePrivateKey.swift +++ b/Sources/X509/CertificatePrivateKey.swift @@ -385,13 +385,7 @@ extension Certificate.PrivateKey { case .secKey(let key): return try key.pemDocument() #endif case .ed25519(let key): return key.pemRepresentation - case .custom(let key): - guard let key = key as? PEMSerializable else { - throw CertificateError.unsupportedPrivateKey( - reason: "custom private key can not be serialised as PEM" - ) - } - return try key.serializeAsPEM() + case .custom(let key): return try key.serializeAsPEM() } } } diff --git a/Sources/X509/CustomPrivateKey.swift b/Sources/X509/CustomPrivateKey.swift index 51a290f..e9715f0 100644 --- a/Sources/X509/CustomPrivateKey.swift +++ b/Sources/X509/CustomPrivateKey.swift @@ -20,7 +20,7 @@ import SwiftASN1 #endif @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) -public protocol CustomPrivateKey: Sendable, Hashable { +public protocol CustomPrivateKey: Sendable, Hashable, PEMSerializable { /// Obtain the ``Certificate/PublicKey-swift.struct`` corresponding to /// this private key. From bdaa2749b1a3f32d755619fa65297b88b25d548f Mon Sep 17 00:00:00 2001 From: Justin Oroz Date: Mon, 10 Nov 2025 14:33:12 -0800 Subject: [PATCH 19/29] Update Sources/X509/CustomPrivateKey.swift Co-authored-by: Cory Benfield --- Sources/X509/CustomPrivateKey.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/X509/CustomPrivateKey.swift b/Sources/X509/CustomPrivateKey.swift index e9715f0..aa15b50 100644 --- a/Sources/X509/CustomPrivateKey.swift +++ b/Sources/X509/CustomPrivateKey.swift @@ -2,7 +2,7 @@ // // This source file is part of the SwiftCertificates open source project // -// Copyright (c) 2022 Apple Inc. and the SwiftCertificates project authors +// Copyright (c) 2025 Apple Inc. and the SwiftCertificates project authors // Licensed under Apache License v2.0 // // See LICENSE.txt for license information From d5aee235a63945c6e81146405beedb0edbdcc21c Mon Sep 17 00:00:00 2001 From: Justin Oroz Date: Thu, 30 Oct 2025 09:58:25 -0700 Subject: [PATCH 20/29] explicit self --- Sources/X509/CustomPrivateKey.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/X509/CustomPrivateKey.swift b/Sources/X509/CustomPrivateKey.swift index aa15b50..9ab808c 100644 --- a/Sources/X509/CustomPrivateKey.swift +++ b/Sources/X509/CustomPrivateKey.swift @@ -61,7 +61,7 @@ extension CustomPrivateKey { bytes: some DataProtocol, signatureAlgorithm: Certificate.SignatureAlgorithm ) async throws -> Certificate.Signature { - try signSynchronously(bytes: bytes, signatureAlgorithm: signatureAlgorithm) + try self.signSynchronously(bytes: bytes, signatureAlgorithm: signatureAlgorithm) } } From 327fd88229270c69272199977d475fa242495329 Mon Sep 17 00:00:00 2001 From: Justin Oroz Date: Thu, 30 Oct 2025 10:00:01 -0700 Subject: [PATCH 21/29] always import import SwiftASN1 for CustomPrivateKey --- Sources/X509/CustomPrivateKey.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/X509/CustomPrivateKey.swift b/Sources/X509/CustomPrivateKey.swift index 9ab808c..b74f761 100644 --- a/Sources/X509/CustomPrivateKey.swift +++ b/Sources/X509/CustomPrivateKey.swift @@ -16,8 +16,8 @@ import FoundationEssentials #else import Foundation -import SwiftASN1 #endif +import SwiftASN1 @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) public protocol CustomPrivateKey: Sendable, Hashable, PEMSerializable { From 6269c6db17d37c313a408a890c28d216ede86a7b Mon Sep 17 00:00:00 2001 From: Justin Oroz Date: Thu, 30 Oct 2025 10:34:41 -0700 Subject: [PATCH 22/29] differentiate async and sync intializers --- Sources/X509/CSR/CertificateSigningRequest.swift | 6 +++--- Sources/X509/Certificate.swift | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/Sources/X509/CSR/CertificateSigningRequest.swift b/Sources/X509/CSR/CertificateSigningRequest.swift index 084d12d..c8d2464 100644 --- a/Sources/X509/CSR/CertificateSigningRequest.swift +++ b/Sources/X509/CSR/CertificateSigningRequest.swift @@ -165,20 +165,20 @@ public struct CertificateSigningRequest { public init( version: Version, subject: DistinguishedName, - privateKey: Certificate.PrivateKey, + asyncPrivateKey: Certificate.PrivateKey, attributes: Attributes, signatureAlgorithm: Certificate.SignatureAlgorithm ) async throws { self.info = CertificationRequestInfo( version: version, subject: subject, - publicKey: privateKey.publicKey, + publicKey: asyncPrivateKey.publicKey, attributes: attributes ) self.signatureAlgorithm = signatureAlgorithm let infoBytes = try DER.Serializer.serialized(element: self.info) - self.signature = try await privateKey.signAsynchronously( + self.signature = try await asyncPrivateKey.signAsynchronously( bytes: infoBytes, signatureAlgorithm: signatureAlgorithm ) diff --git a/Sources/X509/Certificate.swift b/Sources/X509/Certificate.swift index 97a90f6..858598a 100644 --- a/Sources/X509/Certificate.swift +++ b/Sources/X509/Certificate.swift @@ -245,7 +245,7 @@ public struct Certificate { subject: DistinguishedName, signatureAlgorithm: SignatureAlgorithm, extensions: Extensions, - issuerPrivateKey: PrivateKey + issuerAsyncPrivateKey: PrivateKey ) async throws { self.tbsCertificate = TBSCertificate( version: version, @@ -263,7 +263,7 @@ public struct Certificate { self.signatureAlgorithm = signatureAlgorithm let tbsCertificateBytes = try DER.Serializer.serialized(element: self.tbsCertificate)[...] - self.signature = try await issuerPrivateKey.signAsynchronously( + self.signature = try await issuerAsyncPrivateKey.signAsynchronously( bytes: tbsCertificateBytes, signatureAlgorithm: signatureAlgorithm ) From 0bb57aa4dc67682088b898524c42bb2af3e4a707 Mon Sep 17 00:00:00 2001 From: Justin Oroz Date: Thu, 30 Oct 2025 10:36:07 -0700 Subject: [PATCH 23/29] Update docs to match new arg names --- Sources/X509/CSR/CertificateSigningRequest.swift | 2 +- Sources/X509/Certificate.swift | 6 +++--- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/X509/CSR/CertificateSigningRequest.swift b/Sources/X509/CSR/CertificateSigningRequest.swift index c8d2464..a134bb6 100644 --- a/Sources/X509/CSR/CertificateSigningRequest.swift +++ b/Sources/X509/CSR/CertificateSigningRequest.swift @@ -158,7 +158,7 @@ public struct CertificateSigningRequest { /// - Parameters: /// - version: The CSR version. /// - subject: The ``DistinguishedName`` of the subject of this CSR - /// - privateKey: The private key associated with this CSR. + /// - asyncPrivateKey: The private key associated with this CSR. /// - attributes: The attributes associated with this CSR /// - signatureAlgorithm: The signature algorithm to use for the signature on this CSR. @inlinable diff --git a/Sources/X509/Certificate.swift b/Sources/X509/Certificate.swift index 858598a..b5dad20 100644 --- a/Sources/X509/Certificate.swift +++ b/Sources/X509/Certificate.swift @@ -218,10 +218,10 @@ public struct Certificate { /// /// 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`. + /// automatically, using `issuerAsyncPrivateKey`. /// /// This API can be used to construct a self-signed key by passing the private key for `publicKey` as the - /// `issuerPrivateKey` argument. + /// `issuerAsyncPrivateKey` argument. /// /// - Parameters: /// - version: The X.509 specification version for this certificate. @@ -233,7 +233,7 @@ public struct Certificate { /// - subject: The ``DistinguishedName`` of the subject of this certificate. /// - signatureAlgorithm: The signature algorithm that will be used to produce `signature`. Must be compatible with the private key type. /// - extensions: The extensions on this certificate. - /// - issuerPrivateKey: The private key to use to sign this certificate. + /// - issuerAsyncPrivateKey: The private key to use to sign this certificate. @inlinable public init( version: Version, From b9e729ab3e4f98e4f1bf48a4e2fc804df1f16c54 Mon Sep 17 00:00:00 2001 From: Justin Oroz Date: Mon, 10 Nov 2025 10:21:26 -0800 Subject: [PATCH 24/29] Make signAsynchronously default implementation --- Sources/X509/CustomPrivateKey.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/Sources/X509/CustomPrivateKey.swift b/Sources/X509/CustomPrivateKey.swift index b74f761..d995576 100644 --- a/Sources/X509/CustomPrivateKey.swift +++ b/Sources/X509/CustomPrivateKey.swift @@ -57,8 +57,8 @@ public protocol CustomPrivateKey: Sendable, Hashable, PEMSerializable { @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) extension CustomPrivateKey { - func signAsynchronously( bytes: some DataProtocol, + public func signAsynchronously( signatureAlgorithm: Certificate.SignatureAlgorithm ) async throws -> Certificate.Signature { try self.signSynchronously(bytes: bytes, signatureAlgorithm: signatureAlgorithm) From 84da6baa85f29c4f13e3234ddffa4cbfaa135cfc Mon Sep 17 00:00:00 2001 From: Justin Oroz Date: Mon, 10 Nov 2025 10:21:58 -0800 Subject: [PATCH 25/29] Async method bytes must be sendable --- Sources/X509/CertificatePrivateKey.swift | 2 +- Sources/X509/CustomPrivateKey.swift | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/Sources/X509/CertificatePrivateKey.swift b/Sources/X509/CertificatePrivateKey.swift index 6e1c3b3..ad42687 100644 --- a/Sources/X509/CertificatePrivateKey.swift +++ b/Sources/X509/CertificatePrivateKey.swift @@ -131,7 +131,7 @@ extension Certificate { /// - signatureAlgorithm: The signature algorithm to use. /// - Returns: The signature. @inlinable - public func signAsynchronously( + public func signAsynchronously( bytes: Bytes, signatureAlgorithm: SignatureAlgorithm ) async throws -> Signature { diff --git a/Sources/X509/CustomPrivateKey.swift b/Sources/X509/CustomPrivateKey.swift index d995576..7a8e707 100644 --- a/Sources/X509/CustomPrivateKey.swift +++ b/Sources/X509/CustomPrivateKey.swift @@ -48,7 +48,7 @@ public protocol CustomPrivateKey: Sendable, Hashable, PEMSerializable { /// - Returns: The signature. @inlinable func signAsynchronously( - bytes: some DataProtocol, + bytes: some DataProtocol & Sendable, signatureAlgorithm: Certificate.SignatureAlgorithm ) async throws -> Certificate.Signature @@ -57,8 +57,8 @@ public protocol CustomPrivateKey: Sendable, Hashable, PEMSerializable { @available(macOS 10.15, iOS 13, watchOS 6, tvOS 13, macCatalyst 13, visionOS 1.0, *) extension CustomPrivateKey { - bytes: some DataProtocol, public func signAsynchronously( + bytes: some DataProtocol & Sendable, signatureAlgorithm: Certificate.SignatureAlgorithm ) async throws -> Certificate.Signature { try self.signSynchronously(bytes: bytes, signatureAlgorithm: signatureAlgorithm) From ee0f9567fe424c17bc868a2eec91c872981903d5 Mon Sep 17 00:00:00 2001 From: Justin Oroz Date: Mon, 10 Nov 2025 10:27:51 -0800 Subject: [PATCH 26/29] an initializer would be a good idea --- Sources/X509/CertificatePrivateKey.swift | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/Sources/X509/CertificatePrivateKey.swift b/Sources/X509/CertificatePrivateKey.swift index ad42687..680d0c1 100644 --- a/Sources/X509/CertificatePrivateKey.swift +++ b/Sources/X509/CertificatePrivateKey.swift @@ -91,6 +91,13 @@ extension Certificate { } #endif + /// Construct a private key wrapping a custom private key. + /// - Parameter custom: The custom private key to wrap. + @inlinable + public init(_ custom: some CustomPrivateKey) { + self.backing = .custom(custom) + } + /// Use the private key to sign the provided bytes with a given signature algorithm. /// /// - Parameters: From 64c935214903898c84b75483e1e70588ba94aec4 Mon Sep 17 00:00:00 2001 From: Justin Oroz Date: Mon, 10 Nov 2025 10:42:00 -0800 Subject: [PATCH 27/29] CustomPrivateKeyTests --- Tests/X509Tests/CustomPrivateKeyTests.swift | 98 +++++++++++++++++++++ 1 file changed, 98 insertions(+) create mode 100644 Tests/X509Tests/CustomPrivateKeyTests.swift diff --git a/Tests/X509Tests/CustomPrivateKeyTests.swift b/Tests/X509Tests/CustomPrivateKeyTests.swift new file mode 100644 index 0000000..57c07af --- /dev/null +++ b/Tests/X509Tests/CustomPrivateKeyTests.swift @@ -0,0 +1,98 @@ +//===----------------------------------------------------------------------===// +// +// This source file is part of the SwiftCertificates open source project +// +// Copyright (c) 2025 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 CryptoKit +import SwiftASN1 +import X509 +import XCTest + +final class CustomPrivateKeyTests: XCTestCase { + + func testCustomPrivateKeyBackingProperties() { + let keyBacking = TestAsyncKey() + let privateKey = Certificate.PrivateKey(keyBacking) + XCTAssertEqual(privateKey.publicKey, keyBacking.publicKey) + XCTAssertEqual(privateKey.description, "CustomPrivateKey") + XCTAssertEqual(keyBacking.hashValue, privateKey.hashValue) + } + + func testCustomPrivateKeySigning() async throws { + let privateKey = Certificate.PrivateKey(TestAsyncKey()) + + _ = try await privateKey.signAsynchronously( + bytes: Data(), + signatureAlgorithm: .ecdsaWithSHA256 + ) + XCTAssertThrowsError( + try privateKey.sign( + bytes: Data(), + signatureAlgorithm: .ecdsaWithSHA256 + ) + ) + } + + func testCustomPrivateKeyBackingEquality() { + let keyBacking = TestAsyncKey() + let leftKey = Certificate.PrivateKey(keyBacking) + let rightKey = Certificate.PrivateKey(keyBacking) + XCTAssertEqual(leftKey, rightKey) + } + + func testCustomPrivateKeySerialization() { + let privateKey = Certificate.PrivateKey(TestAsyncKey()) + XCTAssertThrowsError(try privateKey.serializeAsPEM()) + } + +} + +/// A theoretical private key which only supports asynchronous signing. +private struct TestAsyncKey: CustomPrivateKey { + + var publicKey: Certificate.PublicKey { privateKey.publicKey } + + // Not required for CustomPrivateKey protocol. + private let privateKey = Certificate.PrivateKey(P256.Signing.PrivateKey()) + + let defaultSignatureAlgorithm: Certificate.SignatureAlgorithm = .sha256WithRSAEncryption + + func signSynchronously( + bytes: some DataProtocol, + signatureAlgorithm: Certificate.SignatureAlgorithm + ) throws -> Certificate.Signature { + throw MyError() + } + + func signAsynchronously( + bytes: some DataProtocol & Sendable, + signatureAlgorithm: Certificate.SignatureAlgorithm + ) async throws -> Certificate.Signature { + try await Task { + try privateKey.sign(bytes: bytes, signatureAlgorithm: signatureAlgorithm) + } + .value + } + + static let defaultPEMDiscriminator: String = "TestKey" + + func serializeAsPEM(discriminator: String) throws -> PEMDocument { + throw MyError() + } + + func serialize(into coder: inout DER.Serializer) throws { + throw MyError() + } + + struct MyError: Error {} + +} From ec20d98139f199fcd12687a636f97261bbe3804d Mon Sep 17 00:00:00 2001 From: Justin Oroz Date: Mon, 10 Nov 2025 10:45:38 -0800 Subject: [PATCH 28/29] verify defaultSignatureAlgorithm is forwarded properly --- Tests/X509Tests/CustomPrivateKeyTests.swift | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Tests/X509Tests/CustomPrivateKeyTests.swift b/Tests/X509Tests/CustomPrivateKeyTests.swift index 57c07af..53a4f94 100644 --- a/Tests/X509Tests/CustomPrivateKeyTests.swift +++ b/Tests/X509Tests/CustomPrivateKeyTests.swift @@ -14,7 +14,7 @@ import CryptoKit import SwiftASN1 -import X509 +@testable import X509 import XCTest final class CustomPrivateKeyTests: XCTestCase { @@ -25,6 +25,7 @@ final class CustomPrivateKeyTests: XCTestCase { XCTAssertEqual(privateKey.publicKey, keyBacking.publicKey) XCTAssertEqual(privateKey.description, "CustomPrivateKey") XCTAssertEqual(keyBacking.hashValue, privateKey.hashValue) + XCTAssertEqual(keyBacking.defaultSignatureAlgorithm, privateKey.defaultSignatureAlgorithm) } func testCustomPrivateKeySigning() async throws { From 8a254cd51125c7534b5fb6d08b69e1a59bea7992 Mon Sep 17 00:00:00 2001 From: Justin Oroz Date: Mon, 10 Nov 2025 13:01:27 -0800 Subject: [PATCH 29/29] switch to swift-testing for new tests --- Tests/X509Tests/CustomPrivateKeyTests.swift | 25 ++++++++++++--------- 1 file changed, 15 insertions(+), 10 deletions(-) diff --git a/Tests/X509Tests/CustomPrivateKeyTests.swift b/Tests/X509Tests/CustomPrivateKeyTests.swift index 53a4f94..f297fbc 100644 --- a/Tests/X509Tests/CustomPrivateKeyTests.swift +++ b/Tests/X509Tests/CustomPrivateKeyTests.swift @@ -13,19 +13,22 @@ //===----------------------------------------------------------------------===// import CryptoKit +import Foundation import SwiftASN1 +import Testing @testable import X509 -import XCTest -final class CustomPrivateKeyTests: XCTestCase { +@Suite +final class CustomPrivateKeyTests { + @Test("CustomPrivateKey Backing Properties") func testCustomPrivateKeyBackingProperties() { let keyBacking = TestAsyncKey() let privateKey = Certificate.PrivateKey(keyBacking) - XCTAssertEqual(privateKey.publicKey, keyBacking.publicKey) - XCTAssertEqual(privateKey.description, "CustomPrivateKey") - XCTAssertEqual(keyBacking.hashValue, privateKey.hashValue) - XCTAssertEqual(keyBacking.defaultSignatureAlgorithm, privateKey.defaultSignatureAlgorithm) + #expect(privateKey.publicKey == keyBacking.publicKey) + #expect(privateKey.description == "CustomPrivateKey") + #expect(keyBacking.hashValue == privateKey.hashValue) + #expect(keyBacking.defaultSignatureAlgorithm == privateKey.defaultSignatureAlgorithm) } func testCustomPrivateKeySigning() async throws { @@ -35,24 +38,26 @@ final class CustomPrivateKeyTests: XCTestCase { bytes: Data(), signatureAlgorithm: .ecdsaWithSHA256 ) - XCTAssertThrowsError( + #expect(throws: TestAsyncKey.MyError.self) { try privateKey.sign( bytes: Data(), signatureAlgorithm: .ecdsaWithSHA256 ) - ) + } } func testCustomPrivateKeyBackingEquality() { let keyBacking = TestAsyncKey() let leftKey = Certificate.PrivateKey(keyBacking) let rightKey = Certificate.PrivateKey(keyBacking) - XCTAssertEqual(leftKey, rightKey) + #expect(leftKey == rightKey) } func testCustomPrivateKeySerialization() { let privateKey = Certificate.PrivateKey(TestAsyncKey()) - XCTAssertThrowsError(try privateKey.serializeAsPEM()) + #expect(throws: TestAsyncKey.MyError.self) { + try privateKey.serializeAsPEM() + } } }