ICS Credential Storage Implementation

In the previous entry, we looked at how the new ICS KeyChain API is used and tried installing a user private key/certificate pair and a CA certificate. Now we'll will try to find out where  they are actually stored and how they are protected.

Looking at frameworks/base/keystore/java/android/security, we notice several interesting classes that are not mentioned in the SDK documentation. The most promising is the KeyStore class, so let's have a look. Sure enough, it is marked as hidden (using the dreaded @hide comment). It does have methods for interacting with the key store (get(), put(), delete(), reset(), etc.), but where is the actual key store? As it turns out, all methods send command to a local socket aptly named 'keystore'. With a little creative grepping, we find out that there is native daemon with the same name listening on that socket. The source is in frameworks/base/cmds/keystore/keystore.cpp, so let's have a look. The file has some helpful comments, and we learn that keys are encrypted, checksummed and saved as files (one key per file). But where are the actual files? Looking at /init.rc we find the keystore daemon startup command looks like this:

service keystore /system/bin/keystore /data/misc/keystore
    class main
    user keystore
    group keystore
    socket keystore stream 666

Next step is, of course, peeking into /data/misc/keystore

# ls -la /data/misc/keystore
-rw------- keystore keystore       84 2011-11-30 15:26 .masterkey
-rw------- keystore keystore      980 2011-11-30 15:56 1000_CACERT_testca
-rw------- keystore keystore      820 2011-11-30 15:55 1000_USRCERT_test
-rw------- keystore keystore      932 2011-11-30 15:55 1000_USRPKEY_test

Here each file name consists of the UID of the user that created it (1000 is system), the entry type (CA certificate, user certificate or private key), and the key name (alias) connected with underscores. And, of course, there is a .masterkey. Going back to the keystore daemon source, we find out that:
  • each key is encrypted with a 128-bit AES master key in CBC mode
  • each key blob contains an info header, the initial vector (IV) used for encryption, an MD5 hash value of the encrypted data and the encrypted data itself
  • the master key (in .masterkey) is itself encrypted with an AES key. The encryption key is derived from the password using the PBKDF2 key-derivation function with 8192 iterations (it may take a while...). The salt is randomly generated and is stored in the .masterkey file's info header.
What this means in practice is that the Android key store is pretty secure for a software solution: even if you had access to a rooted device and managed to extract the key blobs, you would still need the keystore password to derive the master key. Trying out different password to decrypt the master key would require at least 8192 iterations to derive a key, which is prohibitively expensive. In addition, the derivation function is seeded  with a 128-bit random number, so pre-calculated password tables cannot be used.

Key blobs are owned by the keystore user, so on a regular (not rooted) device, you need to go through the daemon to access the keys. As it turns out, there is a helpful command line utility that talks to the daemon and lets us manipulate the key store: keystore_cli. It has commands for initializing the key store, listing, getting and deleting keys, etc. Experimenting with it shows that the keystore daemon is additionally checking the calling process's UID to grant or deny access to each command:

# keystore_cli unlock
keystore_cli unlock
6 Permission denied
# keystore_cli get CACERT_testca
keystore_cli get CACERT_testca
1 No error
-----BEGIN CERTIFICATE-----
MIICiTCCAfKgAwI...

# su system
su system
$ keystore_cli insert foo bar
keystore_cli insert foo bar
1 No error
$ keystore_cli saw ""
keystore_cli saw ""
1 No error
foo
USRPKEY_test
USRCERT_test
CACERT_testca
$ keystore_cli get foo
keystore_cli get foo
1 No error
bar
$ exit

# su app_44
su app_44
$ keystore_cli saw ""
keystore_cli saw ""
1 No error
$ keystore_cli insert baz boo
keystore_cli insert baz boo
1 No error
$ keystore_cli get baz
keystore_cli get baz
1 No error
boo

This basically translates to:
  • root cannot lock/unlock the key store, but can access system keys
  • the system user can do pretty much anything (initialize or reset the key store, etc.)
  • regular users can insert, delete and access keys, but can only see their own keys

The android.security.KeyStore class we found while browsing the framework's source is almost a one-to-one port of the keystore_cli command's functionality to Java. By using it Java apps can get direct access to the keystore daemon, but as we said, that class is not part of the public API. There are a couple of reasons for this:

  • even if they had access to it, normal apps wouldn't have the needed permissions to initialize or unlock the key store
  • it's interface exposes the current implementation: keys are returned as raw blobs which wouldn't be possible if the key store and related cryptographic operations were implemented in hardware (such as in a TPM).

As mentioned in the previous article, most of the described credential storage functionality has been available in Android since at least Donut (1.5), but the key store was only accessible to system applications such as Settings, and the WiFi and VPN clients. What ICS adds are a few layers on top of this that make it possible to offer user applications access to the system key store and assert fine-grained control over what keys each app is allowed to use. In the next part of the series we will look at the implementation of the new credential storage functionality added in ICS.

Comments

Omega31337 said…
hi

you are writing:
> regular users can ... delete ... own keys

what is the command syntax for removing a key?
for example your "foo" entry?

i tried to remove a set (cacert, cert + key) with no luck

error is all the time "key not found" :(
Nikolay Elenkov said…
What exactly did you try? keystore_cli saw "" should give you a list, and keystore_cli delete foo (IIRC) should delete.
Omega31337 said…
1|u0_a42@android:/ $ su system
system@android:/ $ keystore_cli saw ""
1 No error
USRPKEY_galaxy-s2-XXX
USRCERT_galaxy-s2-XXX
CACERT_galaxy-s2-XXX
USRPKEY_YYY
USRCERT_YYY
CACERT_YYY
USRKEY_galaxy-s2-XXX <
7 Key not found
system@android:/ $ keystore_cli delete USRPKEY_ga*
7 Key not found
system@android:/ $ keystore_cli delete USRPKEY_galaxy-s2-XXX
7 Key not found
system@android:/ $
Nikolay Elenkov said…
Try this instead:

$ keystore_cli d foo
keystore_cli d foo
1 No error
Rafael Roa said…
Hi Nikolay,

I'm currently developing an SCEP client for Android. I'm trying to get access to the Android KeyStore by terminal, but it's no keystore_cli file on my device. Shall I reinstall or make a factory reset on my device?

If it is not an error, How can I manage to get access to the KeyStore without keystore_cli?

Thanks in advance.
Nikolay Elenkov said…
Read the other related articles, you can communicate from Java and there are public APIs for most of the things you need. If you really need the CLI util, download AOSP source and build it yourself.
Rafael Roa said…
Thanks for your fast answer. I've read the other related articles, but those public APIs don't allow me to delete or remove user credentials programatically. I think that keystore_cli would do the trick to this problem... I will download AOSP source and build that client.

Again, thank you for your answer.
Rafael Roa said…
I've noticed that I can't build keystore_cli. I'm trying to access to the daemon through KeyStore.java, and it does the work I'm looking to, but how can I add user certificates in the KeyStore (and then appear when getting certificates on Security menu)?
Nikolay Elenkov said…
Not sure what the problem is, but if you can build the platform, keystore_cli gets built as well. Keystore access is different on different versions (4.3+ have a service, not a daemon), see the android-keystore project on my Gihub for details. To get certificates to appear in Settings you an just use the KeyChain API. Or if you want to skip the dialog, look at the implementation and do something similar. Also check the article on trust store implementation.
Saurav said…
Hi Nikolay,

thanks for the wonderful and informative blog posts.

Our current implementation is loading the certificate from the downloads area. Then we encrypt and save the private key and certificate in the private area. Once authentication is done we delete the certificate from downloads area because no one else should access it. We load the key and certificate in the PKCS backed keystore from the private area location for subsequent log ons.

We want to move to Key chain based implementation. Our product standard says to have a 2nd layer encryption i.e. on top of the OS, app must also encrypt the certificate and the key. If we use the key chain then the app itself is not encrypting it. I know the credential storage has 1st layer encryption reading this blog post.
But can another application implementing the key chain apis get hold of the private key from my certificate. ? Can we somehow provide the 2nd layer app level protection for the key chain. ?

cheers,
Saurav
Nikolay Elenkov said…
Your question is not really related to this blog post. In the mean time things have changed quite a bit, and Android now supports hardware backed storage. If the key is protected by hardware, it cannot be extracted, so no way to do second level encryption, etc.

You might want to look at the later posts on the subject.
Harish N said…
Hi Nikolay,
when install certificate(.p12) on VPN side clear credentials is enabled.
But when same certificate is installed on Wifi side clear credentials is not enabled in Android side.

May i know exact reason for this. Reference to Issue 153326​
Harish N said…
Settings->security-> install from sdcard -> Credential use -> WIFI -> ok
Will not enable clear credentials
Remove Certificate from Trusted user will remove certificate from setting -> WiFi-> Ca certificate dropdown
Harish N said…
In KeyStore.java This code returns KEY_NOT_FOUND if certificate is installed on WiFi side and Returns NO_ERROR if certificate is installed on vpn side

return mBinder.zero() == KEY_NOT_FOUND;
Today (moments ago) Delete comment
Harish N said…
If Certificate is install as VPN then as WIFI clear Credential does not go grey out return NO_ERROR
Installed p12 certificate and when login through chrome browser prompts the dialog for selecting the certificates but when login through webview displaying error message (your current browser session is invalid)

Popular posts from this blog

Password storage in Android M

Decrypting Android M adopted storage

Unpacking Android backups