Apple, iOS 13, CryptoKit, Secure Enclave - Enforce biometric authentication ahead of private key usage - ios

I am working with Apple's new cryptokit library and am trying to get a basic use case to work.
Goal: I would like to create a private key in the secure enclave via the cryptokit, store the key's reference in the iOS device's key chain and ensure that the key can only be reinitialized in the secure enclave after the user has authenticated himself via some biometric authentication method.
Current state: So far, I am able to initialize a private key in the secure enclave via the following code:
var privateKeyReference = try CryptoKit.SecureEnclave.P256.KeyAgreement.PrivateKey.init();
Furthermore, I can store and retrieve the corresponding private key's reference from the key chain. After retrieving the reference, I can reinitialize the private key in the secure enclave with the following code:
var privateKeyReference = getPrivateKeyReferenceFromKeyChain();
var privateKey = try CryptoKit.SecureEnclave.P256.KeyAgreement.PrivateKey.init(
dataRepresentation: privateKeyReference
);
So far everything works as expected and all cryptographic operations with the private key succeed.
Now, as far as I understand the spare documentation by Apple, I should be able to modify the first initialization of the private key to something as follows.
let authContext = LAContext();
let accessCtrl = SecAccessControlCreateWithFlags(
kCFAllocatorDefault,
kSecAttrAccesibleWhenUnlockedThisDeviceOnly,
[.privateKeyUsage, .userPresence, .biometryCurrentSet],
nil
);
var privateKeyReference = try CryptoKit.SecureEnclave.P256.KeyAgreement.PrivateKey.init(
accessControl: accessCtrl!,
authenticationContext: authContext
);
Thereby, ensuring that the private key can only be reinitialized, when the user authenticates himself via some biometric authentication method. The initial initialization stil works without any errors.
Problem: However, adding the previous code, I do not get any biometric authentication prompt and can not use the private key at all after reinitialization. The following error is logged whenever I try to execute some cryptographic operation with the reinitialized key, here for example some signing:
Error Domain=CryptoTokenKit Code=-9 "setoken: unable to sign digest" UserInfo={NSLocalizedDescription=setoken: unable to sign digest})
As far as I could guess from here, I think that Code=-9 refers to the "authenticationNeeded" error.
Question: Can someone point me to some documentation or tutorial how to achieve what I am looking for or explain to me what I am missing?
Thanks!
Cross-Post: https://forums.developer.apple.com/message/387746

After a couple of days of patience I was able to obtain an answer from the Apple development support. They suggested the following method which only differs a little bit from my approach:
var error: Unmanaged<CFError>? = nil;
let accessCtrl = SecAccessControlCreateWithFlags(
nil,
kSecAttrAccesibleAfterFirstUnlockThisDeviceOnly,
[.privateKeyUsage, .biometryCurrentSet],
&error
);
var privateKeyReference = try CryptoKit.SecureEnclave.P256.KeyAgreement.PrivateKey.init(
accessControl: accessCtrl
);
Additionally, in the meantime iOS version 13.1.3 was released and, after upgrading my device, the above code started working. So either there is a subtle difference between mine and Apple's code or it is related to the update. Nevertheless, it is working now.

Related

GetStream iOS native - How to update Client token after first initialization

I'm looking for an API to update the Client.Config.token value for the GetStream Library on iOS. Seems like this token is the only way for the library to parse the user_id from the JWT, however it is only parsed at init time of the singleton, Client.shared.
What happens if we need to update the token if the user has logged out of one account and into a different account?
Thank you for your question. For now, the shared Client doesn't support Token update, but you can use a not shared instance of the Client. So, when another user logged in you can create a new Client instance with another token instead of the existing one.
To finish an instance client setup you need to create the current user like this:
if let currentUserId = client.currentUserId {
client.create(user: User(id: currentUserId)) { [weak client] result in
client?.currentUser = try? result.get()
}
}
We'll add a token update for the shared Client in future releases.

Xamarin.iOS KeyChain Identity not persisting

I have created a self-signed .pfx file that when I drag onto the emulator, the import screen appears, I can click through and then using select * from cert; on the emulators Keychain, I can see the imported certificate.
When I load this certificate in code and attempt to add it, I am getting a Success back from the save operation but on subsequent attempts to access the identity, it is returning ItemNotFound, further to that, when I inspect the KeyChain after the import there is no cert in the table either.
await certStream.CopyToAsync(ms);
var options = NSDictionary.FromObjectsAndKeys(new[]
{
NSObject.FromObject("CertPassword")
}, new[]
{
SecImportExport.Passphrase
});
var result = SecImportExport.ImportPkcs12(ms.ToArray(), options, out
NSDictionary[] imports);
if (result != SecStatusCode.Success)
throw new Exception("Failed importing certificate identity");
var identity = imports[0][SecImportExport.Identity];
certRecord.SetValueRef(new SecIdentity(identity.Handle));
var identityImportResult = SecKeyChain.Add(certRecord);
var savedIdentity = SecKeyChain.QueryAsRecord(new
SecRecord(SecKind.Identity)
{
Label = "ApplicationIdentityCertificate"
}, out SecStatusCode savedResult);
Reading round, I have ensured that there is a Entitlements associated with the emulator and that also, it has the keychain sharing entitlement as there appears to be anecdotal evidence that this can sometimes also impact the ability to save to the Keychain.
So I finally managed to get this working. It turns out I was missing a pretty simple method that is available on the SecKeyChain class.
var identity = imports[0][SecImportExport.Identity];
SecKeyChain.AddIdentity(new SecIdentity(identity.Handle));
The above snippet is the change I needed to make and now the identity is being stored in the Keychain with the correct bundle ID and our application can load the certificate and the private key, for whatever reason, adding it via the SecKeyChain.Add() method was not working for identities.

Cannot set PubNub auth key Swift iOS

I have a webapp and the iOS app built in Swift. The thing is I don't know Swift and I'm trying to modify the iOS app in order to add the authorization key to the PubNub client before subscribing/publishing to channels.
Link to PubNub docs
PRE:
Access Manager is enabled
my_auth_key is hardcoded and already enabled form the server for the corresponding channel I want to subscribe.
Here is the code
What's the correct way to set the auth key?
Thanks in advance
Polak, mentioned docs code snippet refer to previously created instance to get it's pre-configured PNConfiguration instance and change required field. This practice can be used in case if you need change something at run-time.
If you have data for authKey at the moment of client initialization, you can set it:
var pubnubClient: PubNub = {
let configuration = PNConfiguration(publishKey: UCConstants.PubNub.publishKey, subscribeKey: UCConstants.PubNub.subscribeKey)
configuration.authKey = "my_auth_key"
return PubNub.clientWithConfiguration(configuration)
}()
Also, I've tried exact your code and don't have any issues with setting of authKey, because I can see it with subscribe request.
If you still will have troubles with PAM and auth key usage, please contact us on support#pubnub.com
Looks like you can:
let configuration = PNConfiguration(
authKey: "super_secret",
publishKey: UCConstants.PubNub.publishKey,
subscribeKey: UCConstants.PubNub.subscribeKey
)
Based on the Obj-C code: https://github.com/pubnub/objective-c/blob/8c1f0876b5b34176f33681d22844e8d763019635/PubNub/Data/PNConfiguration.m#L174-L181

How does this function/variable work?

I'm using the following code to retrieve NSURLCredentials:
let credentials: NSURLCredential? = {
let loginProtectionSpace = NSURLProtectionSpace(host: host, port: 0, protocol: NSURLProtectionSpaceHTTP, realm: nil, authenticationMethod: NSURLAuthenticationMethodDefault)
let credentials = NSURLCredentialStorage.sharedCredentialStorage().defaultCredentialForProtectionSpace(loginProtectionSpace)
return credentials
}()
This is called when the user opens the app. The credentials returned are nil. Then I set these credentials and try to print credentials out again and it's still nil. However, if I restart the app, the printed credentials are there. What's going on here?
This is a lazy variable. The code gets executed once when you first access the property. After that the initially returned value is "remembered" and returned on future calls.
If you set the credentials yourself in the NSURLCredentialStorage then on the next start of the app the first access of the property once again executes the code and retrieves the stored credentials. Note that during the run where you first set the credentials up the actual 3 lines of code retrieving the credentials from the storage are not executed a second time and therefore during that run of the app the property still is nil while there actually is a value in the storage. A similar thing will happen if you modify the existing credentials - during the run where you change them, the credentials will still hold a reference to the previous ones.
If you want to be able to requery the store, you should either
create a function for this instead of a lazy variable
or take a look at this answer: Re-initialize a lazy initialized variable in Swift
I suspect it's because credentials is immutable. The code block gets executed once to assign credentials its value. Which is why it has the credentials after you restarted the app.

Authentication with iOS/Swift

I have a very simple question, I have a node.js/express server that will handle backend authentication part, it is using token not cookies, the server part is working correctly, whenever someone register/login it would return with a JSON web token.
For example:
{
"token" : "eyJ0eXAiOiJKV1QiLCJhbGciOiJIUzI1NiJ9.eyJzdW"
}
I'm using Alamofire to handle the HTTP request from iOS itself. The real question lies is how do I persist the token in the iOS/Swift itself?
What is the simplest way to do this?
You should use the iOS Keychain to save sensitive information.
You should not use NSUserDefaults to store an authentication token or any other potentially sensitive information. It's unencrypted and easily accessible on a rooted device.
How would you like someone getting your authentication token and making requests to your private API at will (e.g. on the command line using curl)?
I've used the KeychainAccess CocoaPod and its usage is simple:
static let keychain = Keychain(service: "com.example.myapp")
keychain["secret_code"] = secretCode // Save something
let secretCode = keychain["secret_code"] // Retrieve something
The simplest way is to store it in NSUserDefaults like this:
Writing:
let defaults = NSUserDefaults.standardUserDefaults()
defaults.setObject("Your Variable Value", forKey: "token")
Reading:
let defaults = NSUserDefaults.standardUserDefaults()
if let token = defaults.stringForKey("token") {
print(name)
}
The simplest way may be NSUserDefaults, but the most secure way would be to store the token in the iOS Keychain. Note there are several wrapper libraries (for example) available to make working with the keychain easier in Swift. The API can be a bit intimidating at times.

Resources