Using the ICS KeyChain API

Update: Sample app code is now available on github.

The recently released Android 4.0 (Ice Cream Sandwich, ICS) introduces a new, unified UI for both tablets and handsets, lots of 'people-centric' communication and sharing features and other convenient improvements such as a better camera app and the much-hyped face unlock. Since everyone is talking about those, we will have a look at some of the less-user visible, but nonetheless important security-related improvements.

Android is often said to be missing crucial security features to be seriously accepted in the corporate world, which has long been the  domain of RIM's BlackBerry. Two of those missing features were the ability to control the system's trusted CA certificates and offer a centralized secure credential storage. Since many companies use private PKI's, the ability to install trusted certificates system-wide is essential for using corporate services secured by those PKI's. Until now, the only way to use those was to embed the needed CA certificates in each application and create custom TrustStores to be able to connect using SSL. A system-wide credential storage has actually been available for a while, but it was only usable by the built-in VPN and WiFi (EAP) clients. One could install a private key/certificate pair using the Settings app, but there was no public API to access the installed keys from applications. ICS offers SDK API's for both trusted certificate management and the secure credential storage via the KeyChain class. We will have a look at how it is used in the following sections.

The KeyChain class is deceptively simple: it offers only 4 public static methods, but those are sufficient to do most certificate-related tasks. Let's first see how one would install a private key/certificate pair and use those to sign and verify some data. The KeyChain API lets you install a private key/certificate pair bundled in a PKCS#12 file. Instead of offering an API to directly install the key and certificate, KeyChain provides a factory method, createInstallIntent() that returns a system intent to parse and install keys/certificates (that is actually the same intent offered by the Settings app in previous versions). To install a PKCS#12 file, you have to read it to a binary array, store it under the EXTRA_PKCS12 key in the intent's extras, and start the associated activity:

Intent intent = KeyChain.createInstallIntent();
byte[] p12 = readFile("keystore-test.pfx");
intent.putExtra(KeyChain.EXTRA_PKCS12, p12);
startActivity(intent);

This will prompt you for the PKCS#12 password in order to extract and parse the key and certificate. If the password is correct, you will be prompted for a 'certificate name' as shown in the screenshot below. If the PKCS#12 has a friendly name attribute it will be shown as the default, if not you will just get a long hexadecimal hash string. The string you enter here is the key/certificate alias you will use to access those later via the KeyChain API. You will be prompted to set a lock screen PIN or password to protect the credential storage if you haven't already set one.


To use a private key stored in the system credential storage, you need to call KeyChain.choosePrivateKeyAlias() and provide a callback implementation that receives the selected alias:

public class KeystoreTest extends Activity implements OnClickListener,
     KeyChainAliasCallback {

    @Override
    public void onClick(View v) {
        KeyChain.choosePrivateKeyAlias(this, this, 
           new String[] { "RSA" }, null, null, -1, null);
    }

    @Override
    public void alias(final String alias) {
        Log.d(TAG, "Thread: " + Thread.currentThread().getName());
        Log.d(TAG, "selected alias: " + alias);
    }
}

The first parameter is the current context, the second -- the callback to invoke, and the third and forth specify the acceptable keys (RSA, DSA or null for any) and acceptable certificate issuers for the certificate matching the private key (Edit: it turns out both keyTypes and issuers are currently unused, so just pass null). The next two parameters are the host and port number of the server requesting a certificate, and the last one is the alias to preselect. We leave all but the key type as unspecified (null or -1) here to be able to select from all available certificates. One thing to note here is that the alias() callback will not be called on the main thread, so you shouldn't try to directly manipulate the UI (it is called on a binder thread).  Using the key requires user authorization, so Android will display a key selection dialog which also serves to allow access to the selected key.


In order to get a reference to a private key, you need to call the KeyChain.getPrivateKey() method passing the key alias name received in the previous step. This doesn't seem to be documented but if you try to call this method on the main thread you will get an exception saying that this may 'lead to a deadlock'. Here we call it on a background thread using AsyncTask (which is almost always the right thing to do when dealing with potentially time-consuming I/O operations).

new AsyncTask<Void, Void, Boolean>() {

    private Exception error;

    @Override
    protected Boolean doInBackground(Void... arg) {
        try {
            PrivateKey pk = KeyChain.getPrivateKey(ctx,
                    alias);
            X509Certificate[] chain = KeyChain.getCertificateChain(ctx, 
                    alias);
       
            byte[] data = "foobar".getBytes("ASCII");
            Signature sig = Signature.getInstance("SHA1withRSA");
            sig.initSign(pk);
            sig.update(data);
            byte[] signed = sig.sign();

            PublicKey pubk = chain[0].getPublicKey();
            sig.initVerify(pubk);
            sig.update(data);
            boolean valid = sig.verify(signed);
            Log.d(TAG, "signature is valid: " + valid);

            return valid;
       } catch (Exception e) {
           e.printStackTrace();
           error = e;

           return null;
       }
   }

   @Override
   protected void onPostExecute(Boolean valid) {
        if (error != null) {
            Toast.makeText(ctx, "Error: " + error.getMessage(),
                    Toast.LENGTH_LONG).show();

            return;
        }

        Toast.makeText(ctx, "Signature is valid: " + valid,
            Toast.LENGTH_SHORT).show();
    }
}.execute();

We first get the private key and certificate chain using the key alias and then create and verify a signature to check if the key is actually usable. Since we are using a self-signed certificate the 'chain' consists of a single entry, but for a certificate signed by a CA you will need to find the actual end entity certificate in the returned array.

Installing a CA certificate is not very different from installing a PKCS#12 file: you load the certificate in a byte array and pass it as an extra to the install intent.

Intent intent = KeyChain.createInstallIntent();
intent.putExtra(KeyChain.EXTRA_CERTIFICATE, cert);
startActivity(intent);

Android will parse the certificate, and if it's Basic Constraints extension is set to CA:TRUE it will consider it a CA certificate and import it into the user trust store. You will need to authenticate to import the certificate, but the funny thing is that the import dialog does not show neither the certificate DN, nor its hash value. The user has no way of knowing what they are importing, until it's done. Very few people will bother to actually check, so this could be a potential security threat: malicious applications might trick people into installing rogue certificates. Here's how the import dialog looks:


After the certificate is imported, it will show up in the 'Trusted credentials' screen's 'User' tab (Settings->Security->Trusted credentials). Tapping the certificate entry displays a details dialog, where you can (finally!) check the subject, issuer, validity period, serial number and  SHA-1/SHA-256 fingerprints. You can also remove the certificate by pressing the 'Remove' button (scroll down to display it).


While you can delete individual CA certificates, there is no way to delete individual keys and user certificates. You can delete all by using the 'Clear credentials' option in the Credential storage section of the security settings. Another thing to note is that, as long as you have keys in the credential storage, you cannot remove the screen lock, since it is used to protect access to the keystore. In previous Android versions, there was a separate 'credential storage password', but it seems in ICS they decided to simplify things by using the screen lock password to protect credential storage as well.

The newly introduced KeyChain API lets you install and access private keys in a centralized and secure credential storage, as well as add system-wide trusted certificates. It doesn't provide low-level access to the underlying keystore, utilizing the Android intent dispatching mechanism instead to call a system activity that does the actual work. The CA certificate install dialog is missing a crucial feature (displaying details about the certificate), but all in all, providing the access to the system keystore service is a step in the right direction.

That wraps the first part of our Android keystore introduction. In the next part we will look into all that is hidden behind the KeyChain facade, and try to give some details about the underlying implementation.

Comments

Bhavya said…
Hi,

The explanation you have given is very easy-to-understand. Thanks for this post.
Is there any post you can direct me to on how to use the private key-certificate store on Android versions before the ICS?
Nikolay Elenkov said…
Thanks. As I mention at the start of the post, the actual key store has been available for a while, but there were no public API's allowing it to be used from non-system application. See the next post if you are interested in how the actual key storage is implemented.
Can you show a sample to make cacert.pem and keystore-test.pfx ?
Nikolay Elenkov said…
You can use OpenSSL. Pretty much every OpenSSL tutorial covers how to create a CA and issue a certificate, just follow one of those. For example this one:

http://www.g-loaded.eu/2005/11/10/be-your-own-ca/

To create a PKCS#12 file from the key and the cert, you can use the following command:

$ openssl pkcs12 -export -in keystore-test.pem -inkey keystore-test.key -out kestore-test.pfx
Peace on Earth said…
Hello, Nikolay
Very useful tutorial. However, I have one question. How to use existing CA Certs ? For example, if an application wants to use Verisign ? How should it be done ?
Nikolay Elenkov said…
The only change is that you don't need to import the CA certificate, since it's already included in the system trust store.
Unknown said…
Hi, Nikolay
I have imported the PKCS#12 containing private key and certificate.
Then i run your keystore-test app, select "Use private key".
After being prompted to select the certificate i choose the one just installed.

After:
protected Boolean[] doInBackground(Void... arg0) {
PrivateKey pk = KeyChain.getPrivateKey(ctx, alias);

I've added:
Log.d(TAG, "EncodedPrivateKey: " + pk.toString());

And it gives me the full content or the private key.

Does it mean that any application, once allowed by user (in the cert. selection dialog), can read any private key installed from .pfx file?

Is the following scenario possible by standard Android means - "administrator" installing the cert.+private key from the .pfx and the rights to use it are limited to the single app?
Pal said…
Hi
I have generated a pkcs12 file and I could install on 3.2 and was able to connect using EAP-TLS to my wifi but the same file when installed on 4.1 nexus device is not working.

Do you know if I am missing any other steps

thanks
Pal
Nikolay Elenkov said…
This most probably not a problem with the certificate, but a bug in Jelly Bean. It is fixed in master, so you can try building from there or use the latest CyanogenMod 10 beta ROM. See this for more details:

http://code.google.com/p/android/issues/detail?id=34212
Duy Hoang Tran said…
Hi
I want to push my private key and certificate chain to Android KeyStore. I see method Keystore.setKeyEntry, but in Android.security, it do not implement. In this case, i don't wat to use pfx file!

thanks

Chen
Nikolay Elenkov said…
See the other credential storage articles for details on how this can be done and what are the limitations. Generally, if you want the keys to be available to other applications, you need to have system permissions.
Kalin Kanev said…
Hi,
I want to trust HTTPS that does not have a CA in it's self-signed certificate. Can you please direct me what to do and how to achieve this ?

Thanks in advance,
Kalin.
Nikolay Elenkov said…
If you are using ICS and later you can just install the certificate in the trust store from Settings. For previous versions, you need to create a truststore with the certificate, load it in your app and use it to create a SocketFactory for HTTP connections. More details in this post: http://nelenkov.blogspot.com/2011/12/using-custom-certificate-trust-store-on.html
charlie said…
I have added a self signed cert to my trust store via the devices settings>security>install from device storage

Problem is I still cannot log in to the server on my android 4.0.4 phone. Using the default browser I get a warning about the cert: "Site name does not match the certificate name"

In logcat my app states hostname not verified. Any ideas?
Nikolay Elenkov said…
Please start a new thread, and do not reply to random comments. Additionally, comments are for questions regarding the blog post, not for general support.

Your certificate is already trusted, so you just need to make sure you have the correct hostname in the CN. If you are accessing the site by IP address, you need to add it to the certificate extensions.
Rk's Corner said…
Hi Nikolay,

thanks for sharing the example. I am very much able to sign and verify my data.

Now i want to generate key pair and make a pkcs10 request. Is there any support for it in android API or should i use openSSL for this?
Finally my aim is to generate pkcs7 signatures.

Anticipating a quick reply,
thanks
Rama
Nikolay Elenkov said…
Once you have a handle to a PrivateKey you can use it sign anything, including a PKCS#10 request. For CMS (PKCS#7) you will need to use Spongy Castle, but same applies.
nadavbaror said…
Hello Nikolay,

As reported by developers in many other forums - there is a bug in Android 4.1 in which calling KeyChain.getPrivateKey() fails because of memory management issues.
I have seen your comments in some forums in which you have reported that this issue was fixed in AOSP.

My questions is whether there is any way of working around this issue on 4.1 devices with a ROM that does not include the fix you have mentioned?

Thanks,
Nadav
Nikolay Elenkov said…
Not quite sure. Can you find the relevant issue at b.android.com? If you can get the key bytes (getEncoded()) you could probably make a copy and get around it. If you are getting an OpenSSL-backed key (basically a pointer), not much you can do.
Varun Shukla said…
h! nikolay

i want to access installed certificates which store in trusted credential on my device.......through programming how could i see those install certificates....
Rk's Corner said…
Hi Nikolay,

I have a question on Keychain. Can i import a keypair or symmetric key or a password with protection phrase into keychain instead of keystore and retrieve them back? If we can please explain me how

Thanks.
Rk's Corner said…
Hi Nikolay,

I got the answer here: http://nelenkov.blogspot.in/2012/04/using-password-based-encryption-on.html

thank you very much. Your blog is helping me alot.
Rk's Corner said…
Hi Nikolay,

I need to create a bks keystore in android phone's internal memory and it should be accessible by all other apps running on that mobile. (I am able to create it on sdcard or i can create it inside my app)
Scenario: I will create a customized bks, other developer or other APP will use my bks to do some crypto operations and some other APP or developer will use my keystore to store OTPs etc..
Is it practically possbile, if so please help me here...

Thanks you.
אלכס ר. said…
Hi Nikolay,
Is it possible to create a private key using android security provider and then import it into a keychain? Does it have to be pkcs12?
Nikolay Elenkov said…
Should be possible, but if you are working on the device, why not generate it directly inside the keychain (see article about 4.3 enhancements)? If you use the public API it has to be PKCS#12. If you use private API's you can import the key only in PKCS#8 format.
Nikolay Elenkov said…
If you are using 4.3+ you can add the key using KeyStore.setEntry() without converting to PKCS#12, but you still need a certificate ( a self-signed one would do).
Julian Howes said…
Thanks a lot for this article. I have one question, though:

Let's say I pass a certificate as an extra to the Intent created by KeyChain.createInstallIntent() and the user gives it a name and installs it. How can I get back at that certificate at any later point in time to, for example, get the public key and encrypt stuff with it?

Thanks!
Nikolay Elenkov said…
You can get installed certificates using KeyStore.getInstance("AndroidCAStore"), see the 'Trust Store Implementation' post for details. Not sure what your goal is, but encrypting stuff using a CA's public key is rarely the right thing to do.
anakin said…
Hi Nikolay,

A password protected client p12 certificate can be stored using a) KeyChain createInstallIntent API (available in ICS+) or b) KeyStore load API (available since API 1). In first case, you need user to provide alias and password through UI. In second case, this is done silently and an alias is automatically generated.
What is the difference between the two? Is it that when using Keychain API, it is stored in global keystore where it can be accessed by other applications. And, when using Keystore API, this certificate can only be accessed locally by the application that called load method? So, if I need to use the certificate only in my application, I should use the KeyStore API.

Thanks!
Nikolay Elenkov said…
In short, yes, the KeyChain stores keys and certificates in the system scope, and you need to explicitly grant access (via the dialog) to apps. java.security.KeyStore is indeed available from API 1, but it you can only store keys and certificates in files before Android 4.3. 4.3 allows you to store them in the system keystore, and be accessible only to the original application. Read the 'Credential storage enhancements in Android 4.3' post for details.

Which one you should use depends on what you are trying to do.
André Vannucci said…
Nikolay thank you for your post, helped me a lot, and in the case of an application to exchange certificates with a webservice would be possible to install certificates automatically?
Nikolay Elenkov said…
Not sure what you mean by 'automatically', but if you mean without user confirmation, no, it's not possible. Android 4.4 provides an API for this, but it can only be used by system applications.
André Vannucci said…
Yes it was, without user confirmation. Thank you very much.
Saurav said…
Hi Nikolay,

For API Level 16 Intent of action "ACTION_STORAGE_CHANGED" is broadcasted when the credentials are erased.

What is the best way to tackle for pre API Level 16 apps ? Do we need to try to fetch the certificates and then handle it through the KeyChainException ?

cheers,
Saurav
Unknown said…
Hello Nikolay,

This thread is related to a problem Im trying to solve. Basically I need to store a secret key locally what is the most secure way to do this. I know that the only 100% solution is to not store the information locally but aside from that what is the best method? At first I thought the KeyChain class would help but there should be no UI operation involved in accessing the secret key. Thanks.
Logan Guo said…
Hi Nikolay,
I am trying to implement mutual authentication on Android platform. By default, only server certificate verification is needed. But for those servers have configure client certificate auth, the client side needs to support check the requirement (or exceptions when requesting to server) and read client certificate in order to setup a SSLContext. My problem is how to make the client side to detect such exceptions? I learnt that IOS platform has callbacks like, SessionDidReceiveAuthenticationChallengeBlock and NSURLAuthenticationMethodClientCertificate. I wonder how to implement this for Android.

Sorry that my question is not exactly match the topic in the article besides that client side certificate stuff.

Popular posts from this blog

Password storage in Android M

Decrypting Android M adopted storage

Unpacking Android backups