Skip to content

Commit 999fd70

Browse files
authored
Add new Certificate.PrivateKey init from PKCS8 DER bytes (apple#248)
This PR adds a new `init(derBytes:)` to initialise a private key in PKCS8 format from an array of DER bytes.
1 parent 86eca75 commit 999fd70

File tree

2 files changed

+73
-18
lines changed

2 files changed

+73
-18
lines changed

Sources/X509/CertificatePrivateKey.swift

Lines changed: 26 additions & 18 deletions
Original file line numberDiff line numberDiff line change
@@ -303,24 +303,7 @@ extension Certificate.PrivateKey {
303303
self = try .init(ecdsaAlgorithm: sec1.algorithm, rawEncodedPrivateKey: sec1.privateKey.bytes)
304304

305305
case Self.pemDiscriminatorForPKCS8:
306-
let pkcs8 = try PKCS8PrivateKey(derEncoded: pemDocument.derBytes)
307-
switch pkcs8.algorithm {
308-
case .ecdsaP256, .ecdsaP384, .ecdsaP521:
309-
let sec1 = try SEC1PrivateKey(derEncoded: pkcs8.privateKey.bytes)
310-
if let innerAlgorithm = sec1.algorithm, innerAlgorithm != pkcs8.algorithm {
311-
throw ASN1Error.invalidASN1Object(
312-
reason: "algorithm mismatch. PKCS#8 is \(pkcs8.algorithm) but inner SEC1 is \(innerAlgorithm)"
313-
)
314-
}
315-
self = try .init(ecdsaAlgorithm: pkcs8.algorithm, rawEncodedPrivateKey: sec1.privateKey.bytes)
316-
317-
case .rsaKey:
318-
self = try .init(_CryptoExtras._RSA.Signing.PrivateKey(derRepresentation: pkcs8.privateKey.bytes))
319-
case .ed25519:
320-
self = try .init(Curve25519.Signing.PrivateKey(pkcs8Key: pkcs8))
321-
default:
322-
throw CertificateError.unsupportedPrivateKey(reason: "unknown algorithm \(pkcs8.algorithm)")
323-
}
306+
self = try .init(derBytes: pemDocument.derBytes)
324307

325308
default:
326309
throw ASN1Error.invalidPEMDocument(
@@ -364,3 +347,28 @@ extension Certificate.PrivateKey {
364347
}
365348
}
366349
}
350+
351+
@available(macOS 11.0, iOS 14, tvOS 14, watchOS 7, macCatalyst 14, visionOS 1.0, *)
352+
extension Certificate.PrivateKey {
353+
/// Initialize a new certificate private key from PKCS8-format DER bytes.
354+
public init(derBytes: [UInt8]) throws {
355+
let pkcs8 = try PKCS8PrivateKey(derEncoded: derBytes)
356+
switch pkcs8.algorithm {
357+
case .ecdsaP256, .ecdsaP384, .ecdsaP521:
358+
let sec1 = try SEC1PrivateKey(derEncoded: pkcs8.privateKey.bytes)
359+
if let innerAlgorithm = sec1.algorithm, innerAlgorithm != pkcs8.algorithm {
360+
throw ASN1Error.invalidASN1Object(
361+
reason: "algorithm mismatch. PKCS#8 is \(pkcs8.algorithm) but inner SEC1 is \(innerAlgorithm)"
362+
)
363+
}
364+
self = try .init(ecdsaAlgorithm: pkcs8.algorithm, rawEncodedPrivateKey: sec1.privateKey.bytes)
365+
366+
case .rsaKey:
367+
self = try .init(_CryptoExtras._RSA.Signing.PrivateKey(derRepresentation: pkcs8.privateKey.bytes))
368+
case .ed25519:
369+
self = try .init(Curve25519.Signing.PrivateKey(pkcs8Key: pkcs8))
370+
default:
371+
throw CertificateError.unsupportedPrivateKey(reason: "unknown algorithm \(pkcs8.algorithm)")
372+
}
373+
}
374+
}

Tests/X509Tests/CertificateDERTests.swift

Lines changed: 47 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -761,3 +761,50 @@ final class CertificateDERTests: XCTestCase {
761761
XCTAssertNoThrow(try decoded.extensions.nameConstraints)
762762
}
763763
}
764+
765+
final class CertificatePrivateKeyDEREncodedTests: XCTestCase {
766+
func testECDSAP256() throws {
767+
let key = P256.Signing.PrivateKey()
768+
let derBytes = Array(key.derRepresentation)
769+
let parsedKey = try Certificate.PrivateKey(derBytes: derBytes)
770+
771+
XCTAssertEqual(parsedKey.backing, .p256(key))
772+
}
773+
774+
func testECDSAP384() throws {
775+
let key = P384.Signing.PrivateKey()
776+
let derBytes = Array(key.derRepresentation)
777+
let parsedKey = try Certificate.PrivateKey(derBytes: derBytes)
778+
779+
XCTAssertEqual(parsedKey.backing, .p384(key))
780+
}
781+
782+
func testECDSAP521() throws {
783+
let key = P521.Signing.PrivateKey()
784+
let derBytes = Array(key.derRepresentation)
785+
let parsedKey = try Certificate.PrivateKey(derBytes: derBytes)
786+
787+
XCTAssertEqual(parsedKey.backing, .p521(key))
788+
}
789+
790+
func testED25519() throws {
791+
let key = Curve25519.Signing.PrivateKey()
792+
let derBytes = key.derRepresentation
793+
let parsedKey = try Certificate.PrivateKey(derBytes: derBytes)
794+
795+
XCTAssertEqual(parsedKey.backing, .ed25519(key))
796+
}
797+
798+
func testRSA() throws {
799+
// Unlike other algorithms, RSA's bytes representation is not in PKCS#8 format, so we have
800+
// to bridge it by first serialising the key as a PKCS#8 PEM document, and then getting
801+
// its DER bytes.
802+
let key = try _CryptoExtras._RSA.Signing.PrivateKey(keySize: .bits2048)
803+
let pkcs8 = key.pkcs8PEMRepresentation
804+
let pemDoc = try PEMDocument(pemString: pkcs8)
805+
let derBytes = pemDoc.derBytes
806+
let parsedKey = try Certificate.PrivateKey(derBytes: derBytes)
807+
808+
XCTAssertEqual(parsedKey.backing, .rsa(key))
809+
}
810+
}

0 commit comments

Comments
 (0)