In this article I’m trying to study how keychain works.
Metaphor
There once lived a monkey ð George. He was a nice fellow, but his memory suck too much and caused him a lot of trouble. He also was very absent-minded and has lost some of his secret keys. He met a bird ð Marvin and said: “Marvin, my memory is no good, here are all my keys ðððð , I will just retain this small key-card ðī which I will show you to prove it’s really me. Whenever I need a key, I’ll show you this card. You give me the key ð temporary and then take it back after I used it ð .”. Marvin was a very responsible guy and he agreed.
securityd
daemon is working hard.
It’s like Marvin ð : deciding who is entitled to get what ð. It requires at least two out of three access cards to decide: keychain-access-groups
ðŦ , application-identifier
ðī, and application-group
ðïļ.
Below is the Apple’s diagram:
kSecAttrAccessible
Keychain is a database which is accessed via special API. Upon item creation you decide when this item is going to be used by your application via kSecAttrAccessible
attribute.
var query: [String: Any] = [kSecClass as String: kSecClassInternetPassword,
kSecAttrAccount as String: account,
kSecAttrServer as String: server,
kSecAttrAccessible as String: kSecAttrAccessibleWhenUnlocked,
kSecValueData as String: password]
There are several possible options. I would devide them in two main groups: backed up and not backed up. They distinguish by the ThisDeviceOnly
at the end of the name of an attribute. These are not backed up since they don’t have ThisDeviceOnly
postfix:
- [deprecated since iOS 12]
kSecAttrAccessibleAlways
ð - data can be accessed even when the device is locked. kSecAttrAccessibleAfterFirstUnlock
ð - After the first unlock, the data remains accessible until the next restart. Useful for background processes.kSecAttrAccessibleWhenUnlocked
ð- data can be accessed only while unlocked.
These are backed up, since they have ThisDeviceOnly
postfix. They are pretty much the same, but they do not migrate to a new device since they are not backed up thus after restoring from a backup ðž of a different device, these items will not be present:
-
[deprecated since iOS 12]
kSecAttrAccessibleAlwaysThisDeviceOnly
ð ðž -
kSecAttrAccessibleAfterFirstUnlockThisDeviceOnly
- ððž -
kSecAttrAccessibleWhenUnlockedThisDeviceOnly
- ððž -
kSecAttrAccessibleWhenPasscodeSetThisDeviceOnly
- same askSecAttrAccessibleWhenUnlockedThisDeviceOnly
(previous) but available only if a passcode is set on the device. â Keep in mind, that key will be deleted when the device passcode has been removed. â Private key is stored in SecureEnclave ðĄïļ
AccessControlFlags
Appart from mode of operation, developers define how this item must be accessed. ðĄïļ indicated that when using this attribute it’s stored in Secure Enclave therefore the data itself is not stored in Keychain, only token is present there:
kSecAccessControlDevicePasscode
- access fith a passcode.kSecAccessControlBiometryAny
ðĄïļ - access with a fingerprint. If you adding or removing a fingerprint, the item still can be accessed.kSecAccessControlBiometryCurrentSet
ðĄïļ- same, but adding or removing a fingerprint will deem the item unaccessible.kSecAccessControlUserPresence
- either passcode or a fingerprint.
Security concerns
Keychain is retained when application is uninstalled. So, it’s generally a bad idea to store something very valuable there.
As far as I understand, you can’t just store any rubbish in Secure Enclave. It supports only 256-bit elliptic curve private keys.
https://developer.apple.com/documentation/security/ksecattrtokenidsecureenclave
The only keychain items supported by the Secure Enclave are 256-bit elliptic curve private keys (those that have key type
kSecAttrKeyTypeEC
). Such keys must be generated directly on the Secure Enclave using theSecKeyGeneratePair(_:_:_:)
function with thekSecAttrTokenID
key set tokSecAttrTokenIDSecureEnclave
in the parameters dictionary.Important
It is not possible to import pre-existing keys into the Secure Enclave.
So, same as with Apple in general, in that not even Apple knows your super-duper secret device key and therefore can’t decrypt the data without a passcode, the same is here: not even the application itself know the key, it only asks to generate one and then subsequently asks to decrypt something or sign.
Generating keys example:
// private key parameters
let privateKeyParams = [
kSecAttrLabel as String: "privateLabel",
kSecAttrIsPermanent as String: true,
kSecAttrApplicationTag as String: "applicationTag",
] as CFDictionary
// public key parameters
let publicKeyParams = [
kSecAttrLabel as String: "publicLabel",
kSecAttrIsPermanent as String: false,
kSecAttrApplicationTag as String: "applicationTag",
] as CFDictionary
// global parameters
let parameters = [
kSecAttrKeyType as String: kSecAttrKeyTypeEC,
kSecAttrKeySizeInBits as String: 256,
kSecAttrTokenID as String: kSecAttrTokenIDSecureEnclave,
kSecPublicKeyAttrs as String: publicKeyParams,
kSecPrivateKeyAttrs as String: privateKeyParams,
] as CFDictionary
var pubKey, privKey: SecKey?
let status = SecKeyGeneratePair(parameters, &pubKey, &privKey)
if status != errSecSuccess {
// Keys created successfully
}
From the presentation of David Linder “Don’t Touch Me That Way” -
kSecAccessControlUserPresence
kSecAccessControlApplicationPassword
kSecAccessControlDevicePasscode
kSecAccessControlPrivateKeyUsage
kSecAccessControlTouchIDAny
kSecAccessControlTouchIDCurrentSet
Sharing KeyChain
Sandboxing on iOS is accomplished via entitlements, but this sandboxing environment can be loosen a bit. The same developer can share KeyChain items and Shared Preferences with other applications. This does violate “sandboxing”, but this allows different applications share information, including credentials. As usual, there is always balancing between usability and security.
TouchID and Secure Enclave
ðĄ Good artifact is Keychain. Dump it with ./keychain_dumper and you can see saved WiFi passwords.