Yubico / yubikit-ios

Yubico Mobile iOS SDK - YubiKit
Apache License 2.0
205 stars 49 forks source link

PEM-encoded Elliptic Curve public key conversion iOS #120

Closed Chralu closed 1 year ago

Chralu commented 1 year ago

In an iOS application, I would like to calculate a PIV secret.

To do so, I receive the peer PEM-encoded Elliptic curve public key. And I need 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 🙏

CyonAlexRDX commented 1 year ago

@Chralu Base64 decoding (e.g using this online tool) your Base64encoded string "MHYwEAYHKoZIzj0CAQYFK4EEACIDYgAEhYvCTeKdth6ffyCKReeO7cJSfN94BfieZ/9zkE6sDFz/ZifyMkgeg7mq8XB4UYn7aSEcsnqFNswROLnU4NqVFbmGDi5wAI0jRazdskGFBf+0R/zIPozZgJOSrREMEqi7" yields this hex:

3076301006072A8648CE3D020106052B8104002203620004858BC24DE29DB61E9F7F208A45E78EEDC2527CDF7805F89E67FF73904EAC0C5CFF6627F232481E83B9AAF170785189FB69211CB27A8536CC1138B9D4E0DA9515B9860E2E70008D2345ACDDB2418505FFB447FCC83E8CD9809392AD110C12A8BB

Which ASN1 decoded

shows that you have specified the SECG Elliptic Curve Called secp384r1 which is also called P-384.

Looking at osstatus.com error -26275 is errSecInvalidKey

If I were you I would avoid Keychain APIs and use CryptoKit (or the open source variant swift-crypto) instead. With some Data+Hex conversion tool in Swift, this unit tests passes:

    func test_p384() throws {
        let der = "3076301006072A8648CE3D020106052B8104002203620004858BC24DE29DB61E9F7F208A45E78EEDC2527CDF7805F89E67FF73904EAC0C5CFF6627F232481E83B9AAF170785189FB69211CB27A8536CC1138B9D4E0DA9515B9860E2E70008D2345ACDDB2418505FFB447FCC83E8CD9809392AD110C12A8BB"
        let pubKey = try CryptoKit.P384.Signing.PublicKey(derRepresentation: Data(hex: der))
        XCTAssertEqual(pubKey.rawRepresentation.hex(), "858bc24de29db61e9f7f208a45e78eedc2527cdf7805f89e67ff73904eac0c5cff6627f232481e83b9aaf170785189fb69211cb27a8536cc1138b9d4e0da9515b9860e2e70008d2345acddb2418505ffb447fcc83e8cd9809392ad110c12a8bb")
    }

So rawRepresentation (uncompressed: X || Y) of your public key is: 858bc24de29db61e9f7f208a45e78eedc2527cdf7805f89e67ff73904eac0c5cff6627f232481e83b9aaf170785189fb69211cb27a8536cc1138b9d4e0da9515b9860e2e70008d2345acddb2418505ffb447fcc83e8cd9809392ad110c12a8bb (in hex)

Chralu commented 1 year ago

@CyonAlexRDX Thank you for your help.

My question needs a bit more context about what I need to achieve.

My goal is to use YKFPIVSession -(void)calculateSecretKeyInSlot:(YKFPIVSlot)slot peerPublicKey:(SecKeyRef)peerPublicKey completion:(nonnull YKFPIVSessionCalculateSecretCompletionBlock)completion

If I understand correctly the documentation, I need a SecKeyRef object representing the peer public EC key.

That's why I'm trying to create a SecKeyRef from my PEM encoded public key.

CyonAlexRDX commented 1 year ago

@Chralu see https://developer.apple.com/forums/thread/680572

image

you’re working with Security framework, use SecKeyCreateWithData to import an SECG key. If you have a secp256r1 public key in X9.63 format

And:

Note I’m using secp256r1 as an example. The code in this section will work for the other SECG key types, secp384r1 and secp521r1.

Just so you know: P384 and secp384r1 is the same Curve

Chralu commented 1 year ago

Thank you @CyonAlexRDX , that forum post is a huge help 🙏

I'll use the Security Framework solution, to be compatible with iOS < 14.0. To get it work, I'll have to convert the public key from DER format to X9.63.

CyonAlexRDX commented 1 year ago

@Chralu use CrypoKit for DER -> X9.63 conversion!

Here is a DER initializer on P384.Signing.PublicKey

and then you can use this property to read out the data on x963Representation

Chralu commented 1 year ago

Unfortunately, this is iOS14+ compatible 😔 I need a wider compatibility.

CyonAlexRDX commented 1 year ago

Unfortunately, this is iOS14+ compatible 😔

I need a wider compatibility.

Then you can use open source version of CryptoKit! Swift-crypto, which has iOS13 support, check https://github.com/apple/swift-crypto/blob/main/Sources/Crypto/Key%20Agreement/ECDH.swift#L330

Chralu commented 1 year ago

Having troubles to use Swift-Crypto in a Cocoapods project (project is a Flutter plugin), I used ASN1Decoder to extract key data 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")