diff --git a/README.md b/README.md index f5a2a7c..71a8362 100644 --- a/README.md +++ b/README.md @@ -10,6 +10,7 @@ let (privateKey, publicKey) = try! CC.RSA.generateKeyPair(2048) ### Convert them to PEM format ``` let privateKeyPEM = try SwKeyConvert.PrivateKey.derToPKCS1PEM(privateKey) +let privateKeyPEMPKCS8 = try SwKeyConvert.PrivateKey.derToPKCS8PEM(privateKey) let publicKeyPEM = SwKeyConvert.PublicKey.derToPKCS8PEM(publicKey) ``` ### Or read them from strings with PEM data diff --git a/SwCrypt/SwCrypt.swift b/SwCrypt/SwCrypt.swift index 5ea69cd..932f22d 100644 --- a/SwCrypt/SwCrypt.swift +++ b/SwCrypt/SwCrypt.swift @@ -115,6 +115,11 @@ open class SwKeyConvert { public static func derToPKCS1PEM(_ derKey: Data) -> String { return PEM.PrivateKey.toPEM(derKey) } + + public static func derToPKCS8PEM(_ derKey: Data) -> String { + let pkcs8Key = PKCS8.PrivateKey.addHeader(derKey) + return PEM.PrivateKey.toPEM(pkcs8Key) + } public typealias EncMode = PEM.EncryptedPrivateKey.EncMode @@ -177,6 +182,19 @@ open class SwKeyConvert { open class PKCS8 { open class PrivateKey { + private static let RSAVersion: [UInt8] = [0x02, 0x01, 0x00] // version 0 + private static let RSAOID: [UInt8] = [0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, + 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00] + + public static func addHeader(_ derKey: Data) -> Data { + let octetString: [UInt8] = [0x04] + encodeLength(derKey.count) + derKey.bytesView + + // assemble the PKCS#8 header + let sequence: [UInt8] = [0x30] + encodeLength(RSAVersion.count + RSAOID.count + octetString.count) + RSAVersion + RSAOID + octetString + + return Data(sequence) + } + // https://lapo.it/asn1js/ public static func getPKCS1DEROffset(_ derKey: Data) -> Int? { @@ -205,15 +223,12 @@ open class PKCS8 { return 0 } - let OID: [UInt8] = [0x30, 0x0d, 0x06, 0x09, 0x2a, 0x86, 0x48, 0x86, - 0xf7, 0x0d, 0x01, 0x01, 0x01, 0x05, 0x00] + guard bytes.length > offset + RSAOID.count else { return nil } + let slice = derKey.bytesViewRange(NSRange(location: offset, length: RSAOID.count)) - guard bytes.length > offset + OID.count else { return nil } - let slice = derKey.bytesViewRange(NSRange(location: offset, length: OID.count)) + guard RSAOID.elementsEqual(slice) else { return nil } - guard OID.elementsEqual(slice) else { return nil } - - offset += OID.count + offset += RSAOID.count guard bytes.length > offset else { return nil } guard bytes[offset] == 0x04 else { return nil } @@ -242,7 +257,16 @@ open class PKCS8 { public static func hasCorrectHeader(_ derKey: Data) -> Bool { return getPKCS1DEROffset(derKey) != nil } - + + // Helper function to encode length in DER + private static func encodeLength(_ length: Int) -> [UInt8] { + if length < 128 { + return [UInt8(length)] + } else { + let lengthBytes = withUnsafeBytes(of: length.bigEndian, Array.init).drop { $0 == 0 } + return [0x80 | UInt8(lengthBytes.count)] + lengthBytes + } + } } open class PublicKey { diff --git a/SwCryptTests/SwCryptTests.swift b/SwCryptTests/SwCryptTests.swift index fa33b83..50d8a99 100644 --- a/SwCryptTests/SwCryptTests.swift +++ b/SwCryptTests/SwCryptTests.swift @@ -135,6 +135,17 @@ class SwCryptTest: XCTestCase { XCTAssert($0 as? SwKeyConvert.SwError == SwKeyConvert.SwError.badPassphrase) } } + + func testPKCS8KeyPair() { + let (priv, pub) = keyPair! + + let privKeyPem = SwKeyConvert.PrivateKey.derToPKCS8PEM(priv) + + let privKey = try? SwKeyConvert.PrivateKey.pemToPKCS1DER(privKeyPem) + let genPubKey = try? CC.RSA.getPublicKeyFromPrivateKey(privKey!) + + XCTAssert(pub == genPubKey) + } func testOpenSSLKeyPair() { let bundle = Bundle(for: type(of: self))