Search code examples
iosswiftpemasn.1seckeyref

PEM-encoded Elliptic Curve public key conversion iOS


In an iOS application, I receive a PEM-encoded Elliptic curve public key. I would like to create a SecKey object from it.

This question has been very useful to get RSA key parsing to work.

But I struggle adapting it to work with an EC key.

Example working with an RSA key

var secKeyCreateError : Unmanaged<CFError>?
guard
    let stringPublicKey = Data(
        base64Encoded: "MIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAhT0OXGhPWpbrZBTIScIFQVooi/Qo/NyTYRnrIyZ42nksKCBeSOBu+FPOHCI5U4RUSc2cUOe83dyuKmboU2Kdc1dTq9HDAau3dhpE7VLzZKzMHay+8XW5V6kQJ2oOIGKJphsjJLDM5KxCr5etHEHE5rfrPIBZA0sgcvyT0TsavOAhr55Eu4U2fu8SefxM4CWobXKANiWbmSzzYbo2EIZrfhhe2RncwnH5kr0PMk6Q+kEcuRt58VyYoDAa7vRQvY+KDwxE81CCkIjKpJ55f4uN0/VDclXzFjK8FeOgIiH3n8KD6xqtkvmFc+M8tEJYlzdHWIRN7VoNqbn4IoevnziYhQIDAQAB"
    ),
    let peerPublicKey = SecKeyCreateWithData(
        stringPublicKey as CFData,
        [
            kSecAttrKeyType as String: kSecAttrKeyTypeRSA,
            kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
        ] as CFDictionary,
        &secKeyCreateError
    )
else {
    NSLog("Failed to create SecKey : %@", secKeyCreateError!.takeRetainedValue().localizedDescription)
    return
}

NSLog("SecKey successfully created")

Example failing with an EC key

var secKeyCreateError : Unmanaged<CFError>?
guard
    let stringPublicKey = Data(
        base64Encoded: "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEhYvCTeKdth6ffyCKReeO7cJSfN94BfieZ/9zkE6sDFz/ZifyMkgeg7mq8XB4UYn7aSEcsnqFNswROLnU4NqVFbmGDi5wAI0jRazdskGFBf+0R/zIPozZgJOSrREMEqi7"
    ),
    let peerPublicKey = SecKeyCreateWithData(
        stringPublicKey as CFData,
        [
            kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
            kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
        ] as CFDictionary,
        &secKeyCreateError
    )
else {
    NSLog("Failed to create SecKey : %@", secKeyCreateError!.takeRetainedValue().localizedDescription)
    return
}

NSLog("SecKey successfully created")

Execution returns the following logs :

[seckey] SecKeyCreate init(ECPublicKey) failed: -26275
Failed to create SecKey : The operation couldn’t be completed. (OSStatus error -50 - EC public key creation from data failed)

For information, I used https://mkjwk.org/ to generate public keys.

What else did I try

I tried to extract the DER BIT STRING using ASN1Decoder and ASN1Swift without success.

Would you have any idea of what's going on with those EC keys ? Thanks a lot 🙏


Solution

  • As pointed out by CyonAlexRdx here, a SECG key must be in X9.63 format to be imported using Security Framework.

    Here, we are in trouble because Key is in PEM format. I identified 2 solutions.

    Your project uses Swift Package Manager

    you may import it using CryptoKit, then map it to a Security Framework object.

    Detailed explainations from eskimo on developer.apple.com forum

    1. Import the PEM key using Apple CryptoKit.
    2. Get the X9.63 representation.
    3. Create the Security framework key from that.

    For example, the following routine imports a PEM secp256r1 private key and returns a SecKey object:

    func createSecKeyWithPEMSecp256r1Private(_ pem: String) throws -> SecKey {
        let privateKeyCK = try P256.Signing.PrivateKey(pemRepresentation: pem)
        let x963Data = privateKeyCK.x963Representation
        var errorQ: Unmanaged<CFError>? = nil
        guard let privateKeySF = SecKeyCreateWithData(x963Data as NSData, [
            kSecAttrKeyType: kSecAttrKeyTypeECSECPrimeRandom,
            kSecAttrKeyClass: kSecAttrKeyClassPrivate,
        ] as NSDictionary, &errorQ) else {
            throw errorQ!.takeRetainedValue()
        }
        return privateKeySF
    }
    

    Your project uses cocoapods

    You can use ASN1Decoder to extract key data (in x9.63 format) from DER.

    import ASN1Decoder
    
    class DerDecoder {
        func decodePublicKey(_ data: Data,  _ error: UnsafeMutablePointer<Unmanaged<CFError>?>?) -> SecKey? {
            guard
                let asn1 = try? ASN1DERDecoder.decode(data: data),
                let keyData = asn1.first?.sub(1)?.value as? Data
            else {
                return nil
            }
            return SecKeyCreateWithData(
                keyData as CFData,
                [
                    kSecAttrKeyType as String: kSecAttrKeyTypeECSECPrimeRandom,
                    kSecAttrKeyClass as String: kSecAttrKeyClassPublic,
                ] as CFDictionary,
                error
            )
        }
    }
    
    
    var secKeyCreateError : Unmanaged<CFError>?
    guard
        let stringPublicKey = Data(
            base64Encoded: "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEhYvCTeKdth6ffyCKReeO7cJSfN94BfieZ/9zkE6sDFz/ZifyMkgeg7mq8XB4UYn7aSEcsnqFNswROLnU4NqVFbmGDi5wAI0jRazdskGFBf+0R/zIPozZgJOSrREMEqi7"
        ),
        let peerPublicKey = DerDecoder().decodePublicKey(
            stringPublicKey,
            &secKeyCreateError,
        )
    else {
        NSLog("Failed to create SecKey : %@", secKeyCreateError!.takeRetainedValue().localizedDescription)
        return
    }
    
    NSLog("SecKey successfully created")