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.
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?
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.
iOS
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
See the Playground link above.
Currently I am only able to test the code on iOS simulator (iPhone 5s with iOS 11.1).
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:
kSecAttrKeyTypeECSECPrimeRandom for devices with iOS 10 or higher or kSecAttrKeyTypeEC for devices running iOS below version 10.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.