Friday, September 12, 2014

New kids on the block: WhenPasscodeSet policy on iOS8

... oki, its real name, a bit less glamour, is kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly.

TL;TR: What is it all about? A cool option which let you save sensitive data on Keychain only if the user's device configuration is set with passcode on. It's up to Keychain to deal with configuration changes (ie: user changes his phone's configuration from passcode on to passcode off) in a secure way.

Keychain

Keychain is a database, whose rows are called Keychain items. Keychain items have values, and those values are encrypted. You define keychain with attributes to later help you do queries. Typically Keychain is used to store passwords or encryption key, not large amount of data.

Keychain exists in OS X version and iOS version. Those versions are quire different. Here we're going to focus on iOS.

iOS Keychain

In iOS, an application always has access to its own keychain items and does not have access to any other application’s items. The system generates its own password for the keychain (no prompt for password), and stores the key on the device in such a way that it is not accessible to any application.

When a user backs up iPhone data, the keychain data is backed up but the secrets in the keychain remain encrypted in the backup. The keychain password is not included in the backup. Therefore, passwords and other secrets stored in the keychain on the iPhone cannot be used by someone who gains access to an iPhone backup.

All keychain items are protected by user's passcode plus device secret (unique secret for each UID only known by the device itself). An encrypted iCloud backup is available in case of stolen device.

Keychain wrapper

Here is a simple KeychainWrapper, that let you save a key/value item, in Swift, of course :)
public class KeychainWrap {
    public var serviceIdentifier: String
    
    public init() {
        if let bundle = NSBundle.mainBundle().bundleIdentifier {
            self.serviceIdentifier = bundle
        } else {
            self.serviceIdentifier = "unkown"
        }
    }
    
    func createQuery(# key: String, value: String? = nil) -> NSMutableDictionary {
        var dataFromString: NSData? = value?.dataUsingEncoding(NSUTF8StringEncoding)
        var keychainQuery = NSMutableDictionary()
        keychainQuery[kSecClass] = kSecClassGenericPassword
        keychainQuery[kSecAttrService] = self.serviceIdentifier
        keychainQuery[kSecAttrAccount] = key
        //keychainQuery[kSecAttrAccessible] = kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
        if let unwrapped = dataFromString {
            keychainQuery[kSecValueData] = unwrapped
        } else {
            keychainQuery[kSecReturnData] = true
        }
        return keychainQuery
    }
    
    public func addKey(key: String, value: String) -> Int {
        var statusAdd: OSStatus = SecItemAdd(createQuery(key: key, value: value), nil)
        return Int(statusAdd)
    }
    
    public func updateKey(key: String, value: String) -> Int {
        let attributesToUpdate = NSMutableDictionary()
        attributesToUpdate[kSecValueData] = value.dataUsingEncoding(NSUTF8StringEncoding)!
        var status: OSStatus = SecItemUpdate(createQuery(key: key, value: value), attributesToUpdate)
        return Int(status)
    }
    
    public func readKey(key: String) -> NSString? {
        
        var dataTypeRef: Unmanaged?
        let status: OSStatus = SecItemCopyMatching(createQuery(key: key), &dataTypeRef)
        
        var contentsOfKeychain: NSString?
        if (Int(status) != errSecSuccess) {
            contentsOfKeychain = "\(Int(status))"
            return contentsOfKeychain
        }
        
        let opaque = dataTypeRef?.toOpaque()
        if let op = opaque? {
            let retrievedData = Unmanaged.fromOpaque(op).takeUnretainedValue()
            // Convert the data retrieved from the keychain into a string
            contentsOfKeychain = NSString(data: retrievedData, encoding: NSUTF8StringEncoding)
        } else {
            println("Nothing was retrieved from the keychain. Status code \(status)")
        }
        
        return contentsOfKeychain
    }
}
Here we defined addKey, updateKey and readKey. I also provide a resetAll method (not shown in code snippet but use source code for reference). As line 18 is commented out, we're on kSecAttrAccessibleWhenUnlocked accessibility mode, the one per default.

I've used this KeychainWrapper in a small app KeychainTouchIDTest hosted on github very much inspired by the one used on session 711 of WWDC 2014. Now it's time to play with it and see how it behaves...

Let's play On/Off game

WhenUnlocked policy for keychain (with line 18 commented)
  • in Settings -> TouchID & Passcode, choose "Turn passcode on"
  • start KeychainTouchIDTest
  • click Add item, should return success
  • click Query item, should return success
  • in Settings -> TouchID & Passcode, choose "Turn passcode off"
  • switch to KeychainTouchIDTest
  • click Add item, should return success
  • click Query item, should return success
Here you touch the problem, you initially saved your credit car number in a very secure place (keychain), your phone being secured with passcode but as you remove authentication (passcode off) from you phone configuration, what happens?

You left the door wide open. Anyone can take you phone and use your credit card! Let's carry on doing some more testing to see how WhenPasswordSet behaves...

WhenPasscodeSet policy for keychain (with line 18 uncommented)
  • in Settings -> TouchID & Passcode, choose "Turn passcode on"
  • switch to KeychainTouchIDTest
  • click reset all
  • click Add item, should return success
  • click Query item, should return success
  • in Settings -> TouchID & Passcode, choose "Turn passcode off"
  • click Query item, should return not found error
  • click Add item, should return error
Much more secure.
Since you changed your passcode configuration to off, Keychain has securely removed the sensitive data. Switching it back to passcode on will not restore them. They're gone for good.

One step further: TouchID

Suppose you want to authenticate every times this sensitive data is accessed. Let's say it's a credit card information and you want to approve all transaction personally. In iOS8 it's possible using WhenPasscodeSet's little friend API: TouchID!

I won't spoil the pleasure after a too long blog, this will be for a second post. Stay tuned!

No comments:

Post a Comment

Note: Only a member of this blog may post a comment.