Skip to content

Commit eaed035

Browse files
baardeLukasa
andauthored
Add default value for signature algorithm (#221)
Most of the time, the signature algorithm that should be used is dictated by the type of the private key. * Ed25519 keys support only one signature algorithm. * RFC 5753 section 8 recommends that "[the P-256 curve] be used with SHA-256; the P-384 curve be used with SHA-384; and the P-521 curve be used with SHA-512". * RSA keys support 4 signature algorithms. But most people use RSA with SHA-256 and nobody should use RSA with SHA-1 anymore. More over, Certificate.PrivateKey is opaque to the user, who may not know what type of private key they're using and what the appropriate signature algorithm is. For those reasons, we add convenience wrappers around methods with a signature algorithm to provide a reasonable default value. --------- Co-authored-by: Cory Benfield <lukasa@apple.com>
1 parent 2c25efa commit eaed035

12 files changed

+662
-348
lines changed

Sources/X509/CSR/CertificateSigningRequest.swift

Lines changed: 29 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -145,6 +145,35 @@ public struct CertificateSigningRequest {
145145
self.signatureBytes = try DER.Serializer.serialized(element: ASN1BitString(self.signature))[...]
146146
}
147147

148+
/// Construct a CSR for a specific private key.
149+
///
150+
/// This API can be used to construct a certificate signing request that can be passed to a certificate
151+
/// authority. It will correctly generate a signature over the request.
152+
///
153+
/// A default signature algorithm to use for the signature of this CSR is automatically chosen based on
154+
/// the type of the private key.
155+
///
156+
/// - Parameters:
157+
/// - version: The CSR version.
158+
/// - subject: The ``DistinguishedName`` of the subject of this CSR
159+
/// - privateKey: The private key associated with this CSR.
160+
/// - attributes: The attributes associated with this CSR
161+
@inlinable
162+
public init(
163+
version: Version,
164+
subject: DistinguishedName,
165+
privateKey: Certificate.PrivateKey,
166+
attributes: Attributes
167+
) throws {
168+
try self.init(
169+
version: version,
170+
subject: subject,
171+
privateKey: privateKey,
172+
attributes: attributes,
173+
signatureAlgorithm: privateKey.defaultSignatureAlgorithm
174+
)
175+
}
176+
148177
@inlinable
149178
internal init(
150179
info: CertificationRequestInfo,

Sources/X509/Certificate.swift

Lines changed: 48 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -209,6 +209,54 @@ public struct Certificate {
209209
self.signatureBytes = try DER.Serializer.serialized(element: ASN1BitString(self.signature))[...]
210210
}
211211

212+
/// Construct a certificate from constituent parts, signed by an issuer key.
213+
///
214+
/// This API can be used to construct a ``Certificate`` directly, without an intermediary
215+
/// Certificate Signing Request. The ``signature-swift.property`` for this certificate will be produced
216+
/// automatically, using `issuerPrivateKey`.
217+
///
218+
/// A default signature algorithm to use for the signature of this certificate is automatically chosen based
219+
/// on the type of the issuer's private key.
220+
///
221+
/// This API can be used to construct a self-signed key by passing the private key for `publicKey` as the
222+
/// `issuerPrivateKey` argument.
223+
///
224+
/// - Parameters:
225+
/// - version: The X.509 specification version for this certificate.
226+
/// - serialNumber: The serial number of this certificate.
227+
/// - publicKey: The public key associated with this certificate.
228+
/// - notValidBefore: The date before which this certificate is not valid.
229+
/// - notValidAfter: The date after which this certificate is not valid.
230+
/// - issuer: The ``DistinguishedName`` of the issuer of this certificate.
231+
/// - subject: The ``DistinguishedName`` of the subject of this certificate.
232+
/// - extensions: The extensions on this certificate.
233+
/// - issuerPrivateKey: The private key to use to sign this certificate.
234+
@inlinable
235+
public init(
236+
version: Version,
237+
serialNumber: SerialNumber,
238+
publicKey: PublicKey,
239+
notValidBefore: Date,
240+
notValidAfter: Date,
241+
issuer: DistinguishedName,
242+
subject: DistinguishedName,
243+
extensions: Extensions,
244+
issuerPrivateKey: PrivateKey
245+
) throws {
246+
try self.init(
247+
version: version,
248+
serialNumber: serialNumber,
249+
publicKey: publicKey,
250+
notValidBefore: notValidBefore,
251+
notValidAfter: notValidAfter,
252+
issuer: issuer,
253+
subject: subject,
254+
signatureAlgorithm: issuerPrivateKey.defaultSignatureAlgorithm,
255+
extensions: extensions,
256+
issuerPrivateKey: issuerPrivateKey
257+
)
258+
}
259+
212260
@inlinable
213261
init(
214262
tbsCertificate: TBSCertificate,

Sources/X509/CertificatePrivateKey.swift

Lines changed: 27 additions & 50 deletions
Original file line numberDiff line numberDiff line change
@@ -91,32 +91,23 @@ extension Certificate {
9191
bytes: Bytes,
9292
signatureAlgorithm: SignatureAlgorithm
9393
) throws -> Signature {
94-
try self.validateAlgorithmForKey(algorithm: signatureAlgorithm)
95-
9694
switch self.backing {
9795
case .p256(let p256):
98-
let digestAlgorithm = try AlgorithmIdentifier(digestAlgorithmFor: signatureAlgorithm)
99-
return try p256.signature(for: bytes, digestAlgorithm: digestAlgorithm)
96+
return try p256.signature(for: bytes, signatureAlgorithm: signatureAlgorithm)
10097
case .p384(let p384):
101-
let digestAlgorithm = try AlgorithmIdentifier(digestAlgorithmFor: signatureAlgorithm)
102-
return try p384.signature(for: bytes, digestAlgorithm: digestAlgorithm)
98+
return try p384.signature(for: bytes, signatureAlgorithm: signatureAlgorithm)
10399
case .p521(let p521):
104-
let digestAlgorithm = try AlgorithmIdentifier(digestAlgorithmFor: signatureAlgorithm)
105-
return try p521.signature(for: bytes, digestAlgorithm: digestAlgorithm)
100+
return try p521.signature(for: bytes, signatureAlgorithm: signatureAlgorithm)
106101
case .rsa(let rsa):
107-
let digestAlgorithm = try AlgorithmIdentifier(digestAlgorithmFor: signatureAlgorithm)
108-
let padding = try _RSA.Signing.Padding(forSignatureAlgorithm: signatureAlgorithm)
109-
return try rsa.signature(for: bytes, digestAlgorithm: digestAlgorithm, padding: padding)
102+
return try rsa.signature(for: bytes, signatureAlgorithm: signatureAlgorithm)
110103
#if canImport(Darwin)
111104
case .secureEnclaveP256(let secureEnclaveP256):
112-
let digestAlgorithm = try AlgorithmIdentifier(digestAlgorithmFor: signatureAlgorithm)
113-
return try secureEnclaveP256.signature(for: bytes, digestAlgorithm: digestAlgorithm)
105+
return try secureEnclaveP256.signature(for: bytes, signatureAlgorithm: signatureAlgorithm)
114106
case .secKey(let secKeyWrapper):
115-
let digestAlgorithm = try AlgorithmIdentifier(digestAlgorithmFor: signatureAlgorithm)
116-
return try secKeyWrapper.signature(for: bytes, digestAlgorithm: digestAlgorithm)
107+
return try secKeyWrapper.signature(for: bytes, signatureAlgorithm: signatureAlgorithm)
117108
#endif
118109
case .ed25519(let ed25519):
119-
return try ed25519.signature(for: bytes)
110+
return try ed25519.signature(for: bytes, signatureAlgorithm: signatureAlgorithm)
120111
}
121112
}
122113

@@ -145,51 +136,37 @@ extension Certificate {
145136
}
146137

147138
@inlinable
148-
func validateAlgorithmForKey(algorithm: SignatureAlgorithm) throws {
149-
switch self.backing {
150-
case .p256, .p384, .p521:
151-
if !algorithm.isECDSA {
152-
throw CertificateError.unsupportedSignatureAlgorithm(
153-
reason: "Cannot use \(algorithm) with ECDSA key \(self)"
154-
)
155-
}
139+
var defaultSignatureAlgorithm: SignatureAlgorithm {
140+
switch backing {
141+
case .p256:
142+
return .ecdsaWithSHA256
143+
case .p384:
144+
return .ecdsaWithSHA384
145+
case .p521:
146+
return .ecdsaWithSHA512
156147
case .rsa:
157-
if !algorithm.isRSA {
158-
throw CertificateError.unsupportedSignatureAlgorithm(
159-
reason: "Cannot use \(algorithm) with RSA key \(self)"
160-
)
161-
}
148+
return .sha256WithRSAEncryption
162149
#if canImport(Darwin)
163150
case .secureEnclaveP256:
164-
if !algorithm.isECDSA {
165-
throw CertificateError.unsupportedSignatureAlgorithm(
166-
reason: "Cannot use \(algorithm) with ECDSA key \(self)"
167-
)
168-
}
151+
return .ecdsaWithSHA256
169152
case .secKey(let key):
170153
switch key.type {
171-
case .ECDSA:
172-
if !algorithm.isECDSA {
173-
throw CertificateError.unsupportedSignatureAlgorithm(
174-
reason: "Cannot use \(algorithm) with ECDSA key \(self)"
175-
)
176-
}
177154
case .RSA:
178-
if !algorithm.isRSA {
179-
throw CertificateError.unsupportedSignatureAlgorithm(
180-
reason: "Cannot use \(algorithm) with RSA key \(self)"
181-
)
155+
return .sha256WithRSAEncryption
156+
case .ECDSA(let keySize):
157+
switch keySize {
158+
case .P256:
159+
return .ecdsaWithSHA256
160+
case .P384:
161+
return .ecdsaWithSHA384
162+
case .P521:
163+
return .ecdsaWithSHA512
182164
}
183165
}
184166
#endif
185167
case .ed25519:
186-
if algorithm != .ed25519 {
187-
throw CertificateError.unsupportedSignatureAlgorithm(
188-
reason: "Cannot use \(algorithm) with Ed25519 key \(self)"
189-
)
190-
}
168+
return .ed25519
191169
}
192-
193170
}
194171
}
195172
}

Sources/X509/CertificatePublicKey.swift

Lines changed: 11 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -137,32 +137,17 @@ extension Certificate.PublicKey {
137137
for bytes: Bytes,
138138
signatureAlgorithm: Certificate.SignatureAlgorithm
139139
) -> Bool {
140-
var digest: Digest?
141-
142-
if let digestAlgorithm = try? AlgorithmIdentifier(digestAlgorithmFor: signatureAlgorithm) {
143-
digest = try? Digest.computeDigest(for: bytes, using: digestAlgorithm)
144-
}
145-
146-
switch (self.backing, digest) {
147-
case (.p256(let p256), .some(let digest)):
148-
return p256.isValidSignature(signature, for: digest)
149-
case (.p384(let p384), .some(let digest)):
150-
return p384.isValidSignature(signature, for: digest)
151-
case (.p521(let p521), .some(let digest)):
152-
return p521.isValidSignature(signature, for: digest)
153-
case (.rsa(let rsa), .some(let digest)):
154-
// For now we don't support RSA PSS, as it's not deployed in the WebPKI.
155-
// We could, if there are sufficient user needs.
156-
do {
157-
let padding = try _RSA.Signing.Padding(forSignatureAlgorithm: signatureAlgorithm)
158-
return rsa.isValidSignature(signature, for: digest, padding: padding)
159-
} catch {
160-
return false
161-
}
162-
case (.ed25519(let ed25519), .none):
163-
return ed25519.isValidSignature(signature, for: bytes)
164-
default:
165-
return false
140+
switch self.backing {
141+
case .p256(let p256):
142+
return p256.isValidSignature(signature, for: bytes, signatureAlgorithm: signatureAlgorithm)
143+
case .p384(let p384):
144+
return p384.isValidSignature(signature, for: bytes, signatureAlgorithm: signatureAlgorithm)
145+
case .p521(let p521):
146+
return p521.isValidSignature(signature, for: bytes, signatureAlgorithm: signatureAlgorithm)
147+
case .rsa(let rsa):
148+
return rsa.isValidSignature(signature, for: bytes, signatureAlgorithm: signatureAlgorithm)
149+
case .ed25519(let ed25519):
150+
return ed25519.isValidSignature(signature, for: bytes, signatureAlgorithm: signatureAlgorithm)
166151
}
167152
}
168153
}
@@ -277,21 +262,6 @@ extension Certificate.PublicKey {
277262
}
278263
}
279264

280-
extension _RSA.Signing.Padding {
281-
@inlinable
282-
init(forSignatureAlgorithm signatureAlgorithm: Certificate.SignatureAlgorithm) throws {
283-
switch signatureAlgorithm {
284-
case .sha1WithRSAEncryption, .sha256WithRSAEncryption, .sha384WithRSAEncryption, .sha512WithRSAEncryption:
285-
self = .insecurePKCS1v1_5
286-
default:
287-
// Either this is RSA PSS, or we hit a bug. Either way, unsupported.
288-
throw CertificateError.unsupportedSignatureAlgorithm(
289-
reason: "Unable to determine RSA padding mode for \(signatureAlgorithm)"
290-
)
291-
}
292-
}
293-
}
294-
295265
extension P256.Signing.PublicKey {
296266
/// Create a P256 Public Key from a given ``Certificate/PublicKey-swift.struct``.
297267
///

Sources/X509/CryptographicMessageSyntax/CMSOperations.swift

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -51,6 +51,27 @@ public enum CMS {
5151
return try self.serializeSignedData(signedData)
5252
}
5353

54+
@_spi(CMS)
55+
@inlinable
56+
public static func sign<Bytes: DataProtocol>(
57+
_ bytes: Bytes,
58+
additionalIntermediateCertificates: [Certificate] = [],
59+
certificate: Certificate,
60+
privateKey: Certificate.PrivateKey,
61+
signingTime: Date? = nil,
62+
detached: Bool = true
63+
) throws -> [UInt8] {
64+
return try self.sign(
65+
bytes,
66+
signatureAlgorithm: privateKey.defaultSignatureAlgorithm,
67+
additionalIntermediateCertificates: additionalIntermediateCertificates,
68+
certificate: certificate,
69+
privateKey: privateKey,
70+
signingTime: signingTime,
71+
detached: detached
72+
)
73+
}
74+
5475
@inlinable
5576
static func signWithSigningTime<Bytes: DataProtocol>(
5677
_ bytes: Bytes,

0 commit comments

Comments
 (0)