Generic selectors
Exact matches only
Search in title
Search in content

How to access iOS Keychain data from the Apple Watch

04/21/2015 / 0 Comments

Saving confidential data on the iPhone should be done via the keychain. This is a protected area on your iPhone which allows only your app to read the data and only under specific accessibility conditions. If you adding Apple Watch support to your app you will probably need to change the accessibility mode of your keychain items.

This is how to how to access iOS Keychain data from the Apple Watch:

Keychain Accessibility Modes

There are six different accessibility constants for saving items to the Keychain.

They determine when the data in the Keychain can be accessed by your app or your app extension (e.g. the Apple Watch Extension). There are three different types of accessibility modes:

1) Always allow access (kSecAttrAccessibleAlways and kSecAttrAccessibleAlwaysThisDeviceOnly)
This mode should not be used as this is not recommended by Apple because you should limit the access to the items in the keychain as much as possible.

2) Only allow access when device is unlocked by the user (kSecAttrAccessibleWhenUnlocked and kSecAttrAccessibleWhenUnlockedThisDeviceOnly)
This is the mode you are probably using right now without an Apple Watch. This is the recommended mode if you want to access the keychain with an foreground app and it’s also the default value for keychain access.

3) Allow access after first unlock until the device reboots (kSecAttrAccessibleAfterFirstUnlock and kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly)
This is the mode we are needing for Apple Watch access as this allows us the read the items in the keychain when our app is in the background.

Each of this modes also has a second corresponding „This Device Only“ mode, which states that this data item should not be migrated to a new device by means of backup.

Keychain Access by Apple Watch (WatchKit Extension)

So as you are using the Apple Watch while your iPhone is in your pocket, you need to give it background access to the keychain. This means you have to use kSecAttrAccessibleAfterFirstUnlock (or kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly if it shouldn’t be included in the backup) to protect your keychain items.

If you already have items in the keychain with a kSecAttrAccessibleWhenUnlocked accessibility, you need to resave them once when your app opens in foreground. Read the items from keychain, delete the items and resave them with the new accessibility mode.

Source Code

Here is some example source code to save, to read (also to check if it exists in the keychain) and to delete a private key in the keychain.

Saving a private key:

 NSData* keyData = ...;
 NSString* keyName = ...;
 const id keys[] = { (__bridge id)(kSecClass),
 (__bridge id)(kSecAttrKeyClass),
 (__bridge id)(kSecAttrLabel),
 (__bridge id)(kSecAttrIsPermanent),
 (__bridge id)(kSecAttrAccessible),
 (__bridge id)(kSecValueData) };
 const id values[] = {(__bridge id)(kSecClassKey),
 (__bridge id)(kSecAttrKeyClassPrivate),
 keyName,
 (id)kCFBooleanTrue,
 (__bridge id)(kSecAttrAccessibleAfterFirstUnlock),
 keyData };
 NSDictionary* attributes = [[NSDictionary alloc] initWithObjects:values forKeys:keys count:6];
 CFTypeRef result;
 NSError* error = nil;
 OSStatus osStatus = SecItemAdd((__bridge CFDictionaryRef)attributes, &result);
 if (osStatus != noErr) {
   error = [[NSError alloc] initWithDomain:NSOSStatusErrorDomain code:osStatus userInfo:nil];
   NSLog(@„Adding key to keychain failed with OSError %d:%@.", (int) osStatus, error);
 }

Reading a private key (also checking if item exists):

 const id keys[] = { (__bridge id)(kSecClass),
   (__bridge id)(kSecAttrKeyClass),
   (__bridge id)(kSecAttrLabel),
   (__bridge id)(kSecReturnData) };
 const id values[] = { (__bridge id)(kSecClassKey),
   (__bridge id)(keyClass),
   keyName,
   (id)kCFBooleanTrue };
 NSDictionary* query = [[NSDictionary alloc] initWithObjects:values forKeys:keys count:4];
 CFTypeRef result;
 NSError* error = nil;
 OSStatus osStatus = SecItemCopyMatching((__bridge CFDictionaryRef)query, &result);
 if ((osStatus == noErr) && (result != nil)) {
 NSData* keyData = (__bridge NSData *)result;
   //do something with the key
 }
 else {
    error = [[NSError alloc] initWithDomain:NSOSStatusErrorDomain code:osStatus userInfo:nil];
    NSLog(@"Getting data of key with label “%@” from keychain failed with OSError %d: %@.", keyName, (int) osStatus, error);
    if (osStatus == errSecItemNotFound)
      NSLog(@"Item does not exist in keychain“);
 }

Deleting a private key:

 const id keys[] = { (__bridge id)(kSecClass),
   (__bridge id)(kSecAttrKeyClass),
   (__bridge id)(kSecAttrLabel)};
 const id values[] = { (__bridge id)(kSecClassKey),
   (__bridge id)(kSecAttrKeyClassPrivate),
   keyName };
 NSDictionary* query = [[NSDictionary alloc] initWithObjects:values forKeys:keys count:3];
 NSError* error = nil;
 OSStatus osStatus = SecItemDelete((__bridge CFDictionaryRef)query);
 if (osStatus != noErr) {
    error = [[NSError alloc] initWithDomain:NSOSStatusErrorDomain code:osStatus userInfo:nil];
    NSLog(@"Deleting key with label “%@” from keychain failed with OSError %d: %@.", keyName, (int)osStatus, error);
 }
 

SecSign 2FA