Nativescript: Generate EC keypair for iOS in NativeScript plugin

Created on 5 Dec 2017  路  6Comments  路  Source: NativeScript/NativeScript

Did you verify this is a real problem by searching Stack Overflow and the other open issues in this repo?

Yes, I searched for similar topics and aforegoing asked a question on SO and some days ago opened a thread in the nativescript forum before opening this issue. All without response.

Tell us about the problem

I want to generate an elliptic curve key pair and use this for signature creation and verification. Further the private key should be stored in Secure Enclave, respectively in Keychain as fallback. This functionality will be implemented in a nativescript plugin for reuse.
The model for this comes from the Apple Developer Documentation about generation of new cryptographic keys.
When following this guideline I always get an error code -50 (errParamInvalid => parameter is/are invalid) when I want to persist the private key in the keychain. Without this flag the key gets generated but I am not able to persist it later on, resulting in the same error message when using SecItemAdd with the key reference.
So the first step would be to successfully persist a private key in the keychain with the kSecAttrIsPermanent flag without receiving the -50 error code.
As I stumbled over a similar problem on Android, maybe it is syntax problem I am not aware of, again?

Update

I am working with an physical iPhone, now. This changed the behavior: with the given code I can generate keys and implicitly store them to the keychain with the kSecAttrIsPermanent while using SecKeyCreateRandomKey.

Which platform(s) does your issue occur on?

iOS

Please provide the following version numbers that your issue occurs with:

  • CLI: 3.2.1
  • Cross-platform modules: 3.3.0
  • Runtime(s): 3.0.0
  • Plugin(s):

Please tell us how to recreate the issue in as much detail as possible.

I made a dummy project which entails the same code as in my plugin to show the use case and problem I am stumbling over: https://play.nativescript.org/?template=play-ng&id=ge3oht

Is there code involved? If so, please share the minimal amount of code needed to recreate the problem.

See the Playground link above.

Other notes

Currently I am only able to test the code on iOS simulator (iPhone 5s with iOS 11.1).

ios

All 6 comments

Hi @dartmann,
I tested the provided functionality for creating EC key pair while using latest CLI 3.3.1 tns-core-modules

3.3.0 and runtime 3.3.0 and the result returned from the method seems to be valid. Could you upgrade your project and environment to latest NAtiveScript and to verify if you will have the same problem. For your convenience, I am attaching the sample project, which I used for testing and the return result in the console.
Archive.zip
Result:

CONSOLE LOG file:///app/item/items.component.js:33:24: Key returned:  <SecKeyRef curve type: kSecECCurveSecp256r1, algorithm id: 3, key type: ECPrivateKey, version: 4, block size: 256 bits, addr: 0x7fccca65ddc0>
17:01:45 - Compilation complete. Watching for file changes.


@tsonevn thanks for the fast response.
As I mentioned the Playground example returns a private key if you comment the following line
privAttrs.setValueForKey(kCFBooleanTrue, kSecAttrIsPermanent);
but if you uncomment it, the key generation fails with error code -50.

To give it a try I updated nativescript to the latest version and afterwards updated the project. Unfortunately this does not bring the desired success.

But there is an update: I am able to test it with a physical device now (iPhone 6s). And this brought me to my goal. It seems as that the iOS simulator is causing this problem...
I've tested it once, but to assure that everything works as expected, I'll test this more comprehensively, tomorrow. Afterwards I'll provide feedback here.

Hi @dartmann,
We investigated the issue with our developer's team and found that if you replace kCFBooleanTrue with 1. For example:

privAttrs.setValueForKey(1, kSecAttrIsPermanent);

the method will be executed properly on both simulator and device. We will investigate why kCFBooleanTrue is not working properly on a simulator and will provide more info.

@tsonevn great. Thank you very much for your efforts! 馃槃

In the meantime I did some more experiments with different parameters. This brought me some new insights, explained as follows.
With the following snippet you would check for an existing key in the keychain:

const searchQuery = NSMutableDictionary.new();
searchQuery.setValueForKey(kSecClassKey, kSecClass);
searchQuery.setValueForKey(this.keyType, kSecAttrKeyType);
searchQuery.setValueForKey(kCFBooleanTrue, kSecReturnRef);
searchQuery.setValueForKey(tag, kSecAttrApplicationTag);
searchQuery.setValueForKey(kSecAttrKeyClassPrivate, kSecAttrKeyClass);
searchQuery.setValueForKey(1, kSecAttrIsPermanent);
const privKeyRef = new interop.Reference<any>();
const status = SecItemCopyMatching(searchQuery, privKeyRef);

The key kSecReturnRef uses the same value, you already addressed. But if I pass a 1 instead of kCFBooleanTrue the procedure fails. Processed as above the code works. So this seems to be unique to the kSecAttrIsPermanent attribute?

To give a fully working example of how to create an elliptic curve keypair I'll provide the following lines of code.

const flag: SecAccessControlCreateFlags =
            this.hasSE ? SecAccessControlCreateFlags.kSecAccessControlPrivateKeyUsage
                | SecAccessControlCreateFlags.kSecAccessControlUserPresence
                : SecAccessControlCreateFlags.kSecAccessControlUserPresence;
const error = new interop.Reference<NSError>();
const accessControlRef /*SecAccessControlRef*/ = SecAccessControlCreateWithFlags(kCFAllocatorDefault,
            kSecAttrAccessibleWhenUnlockedThisDeviceOnly, flag, error);
if (isNullOrUndefined(accessControlRef) && !isNullOrUndefined(error.value)
            && (<NSError>error.value).code !== errSecSuccess) {
    console.log("An error occurred while creating access object:", error.value);
    return null;
}

const privKeyAttr = NSMutableDictionary.new();
privKeyAttr.setObjectForKey(accessControlRef, kSecAttrAccessControl);
// Important: do not use the native kCFBooleanTrue here as discussed in the prior answers of this issue
privKeyAttr.setValueForKey(true, kSecAttrIsPermanent);
privKeyAttr.setValueForKey(tag, kSecAttrApplicationTag);

const createQuery = NSMutableDictionary.new();
createQuery.setValueForKey(kSecClassKey, kSecClass);
createQuery.setValueForKey(this.keyType, kSecAttrKeyType);
createQuery.setValueForKey(256, kSecAttrKeySizeInBits);
createQuery.setObjectForKey(privKeyAttr, kSecPrivateKeyAttrs);
if (this.hasSE)
    createQuery.setValueForKey(kSecAttrTokenIDSecureEnclave, kSecAttrTokenID);

if (this.sysVersion >= 10) {
    const error = new interop.Reference<NSError>();
    const privateKey = SecKeyCreateRandomKey(createQuery, error);
    if (isNullOrUndefined(privateKey))
        console.log("SecKeyCreateRandomKey returned null:", error.value);
    const publicKey = SecKeyCopyPublicKey(privateKey);
    if (isNullOrUndefined(publicKey))
        console.log("SecKeyCopyPublicKey returned null, maybe the private key was null?");

    console.log("Private key:", privateKey);
    console.log("Public key:", publicKey);
} else {// Legacy function SecKeyGeneratePair for devices below iOS 10
    const pubKeyAttr = NSMutableDictionary.new();
    pubKeyAttr.setValueForKey(true, kSecAttrIsPermanent);
    pubKeyAttr.setValueForKey(tag + ".public", kSecAttrApplicationTag);

    createQuery.setObjectForKey(pubKeyAttr, kSecPublicKeyAttrs);

    const privKeyRef = new interop.Reference<any>();
    const pubKeyRef = new interop.Reference<any>();
    const status = SecKeyGeneratePair(createQuery, pubKeyRef, privKeyRef);
    if (status !== errSecSuccess)
        console.log("SecKeyGeneratePair failed:", status);

    console.log("Legacy private key:", privKeyRef.value);
    console.log("Legacy public key:", pubKeyRef.value);
}

Special fields:

  • _this.hasSE_: states if the device has a Secure Enclave or not.
  • _this.keyType_: either kSecAttrKeyTypeECSECPrimeRandom for devices with iOS 10 or higher or kSecAttrKeyTypeEC for devices running iOS below version 10.
  • _this.sysVersion_: contains the iOS version of the current device (e.g. 11.1).

Given this information, I think the issue can be closed.

Best regards, David

This thread has been automatically locked since there has not been any recent activity after it was closed. Please open a new issue for related bugs.

Was this page helpful?
0 / 5 - 0 ratings

Related issues

yclau picture yclau  路  3Comments

Pourya8366 picture Pourya8366  路  3Comments

nirsalon picture nirsalon  路  3Comments

kn9ts picture kn9ts  路  3Comments

fmmsilva picture fmmsilva  路  3Comments