ICS Trust Store Implementation

In the previous two posts we looked at the internal implementation of the Android credential storage, and how it is linked to the new KeyChain API introduced in ICS. As briefly mentioned in the second post, there is also a new TrustedCertificateStore class that manages user installed CA certificates. In this entry we will examine how the new trust store is implemented and how it is integrated in the framework and system applications.

Storing user credentials such as passwords and private keys securely is of course essential, but why should we care about the trust store? As the name implies, the trust store determines who we trust when connecting to Internet servers or validating signed messages. While credentials are usually used proactively only when we authenticate to a particular service, the trust store is used every time we connect to a secure server. For example, each time you check GMail, Android connects to Google's severs using SSL and validates their certificates based on the device's trust store. Most users are unaware of this, unless some error occurs. Since the trust store is used practically all the time, and usually in the background, one could argue that it's even more important then credential storage. Up till Android 4.0, the OS trust store was hard wired into the firmware, and users had no control over it whatsoever. Certificates bundled in the store were chosen solely by the device manufacturer or carrier. The only way to make changes was to root your device, re-package the trusted certificates file and replace the original one (instructions from cacert.org here). That is obviously not too practical, and a major obstacle to using Android in enterprise PKI's. In the wake of major CA's being compromised practically each month this year, tools that make changing the default trusted certificates in place have been developed, but using them still requires a rooted phone. Fortunately, ICS has made managing the trust store much more flexible, and gives the much needed control over who to trust to the user. Let's see what has changed.

Pre-ICS, the trust store was a single file: /system/etc/security/cacerts.bks, a Bouncy Castle (one of the JCE cryptographic providers used in Android) native keystore file. It contains all the CA certificates Android trusts and is used both by system apps such as the email client and browser, and applications developed using the SDK. Since it resides on the read-only system partition, it cannot be changed even by system-level applications. The newly introduced in ICS TrustedCertificateStore class still reads system trusted certificates from /system/etc/security, but adds two new, mutable locations to store CA certificates in /data/misc/keychain: the cacerts-added and cacerts-removed directories. Let's see what's inside:

ls -l /data/misc/keychain
drwxr-xr-x system   system            2011-11-30 12:56 cacerts-added
drwxr-xr-x system   system            2011-12-02 15:21 cacerts-removed
# ls -l /data/misc/keychain/cacerts-added
ls -l /data/misc/keychain/cacerts-added
-rw-r--r-- system   system        653 2011-11-29 18:34 30ef493b.0
-rw-r--r-- system   system        815 2011-11-30 12:56 9a8df086.0
# ls -l /data/misc/keychain/cacerts-removed
ls -l /data/misc/keychain/cacerts-removed
-rw-r--r-- system   system       1060 2011-12-02 15:21 00673b5b.0

Each file contains one CA certificate. The file names may look familiar: they are hashes of the CA subject names, as used in mod_ssl and other cryptographic software implemented using OpenSSL. This makes it easy to quickly find certificates without scanning the entire store. Also note the permissions of the directories: 0775 system system guarantees that only the system user is able to add or remove certificates, but anyone can read them. As can be expected, adding trusted CA certificates is implemented by storing the certificate in cacerts-added under the appropriate file name. The two files above, 30ef493b.0 and 9a8df086.0, correspond to the certificates displayed in the 'User' tab of the Trusted credential system application (Settings->Security->Trusted credentials). But how are OS-trusted certificates disabled? Since pre-installed CA certificates are still stored in /system/etc/security (read-only), a CA is marked as not trusted by placing a copy of its certificate in cacerts-removed. Re-enabling is performed by simply removing the file. In this particular case, 00673b5b.0 is the thawte Primary Root CA, shown as disabled in the 'System' tab:


TrustedCertificateStore is not available in the SDK, but it has a wrapper accessible via the standard JCE KeyStore API, TrustedCertificateKeyStoreSpi, that applications can use. Here's how we can use it to get the current list of trusted certificates::

KeyStore ks = KeyStore.getInstance("AndroidCAStore");
ks.load(null, null);
Enumeration aliases = ks.aliases();
while (aliases.hasMoreElements()) {
    String alias = aliases.nextElement();
    X09Certificate cert = (X509Certificate) 
       ks.getCertificate(alias);
    Log.d(TAG, "Subject DN: " + 
       cert.getSubjectDN().getName());
    Log.d(TAG, "Issuer DN: " + 
       cert.getIssuerDN().getName());
}

If you examine the output of this code, you would notice that certificate aliases start with either the user: (for user installed certificates) or system: (for pre-installed ones) prefix, followed by the subject's hash value. This lets us easily access the OS's trusted certificates, but a real word application would be more interested in whether it should trust a particular server certificate, not what the current trust anchors are. ICS makes this very easy by integrating the TrustedCertificateKeyStoreSpi with Android's JSSE (secure sockets) implementation. The default TrustManagerFactory uses it to get a list of trust anchors, thus automatically validating server certificates against the system's currently trusted certificates. Higher-level code that uses HttpsURLConnection or HttpClient (both built on top of JSSE) should thus just work without needing to worry about creating and initializing a custom SSLSocketFactory. Here's how we can use the TrustManager to validate a certificate issued by a private CA (the CA certificate is already installed in the user trust store).

X509Certificate[] chain = KeyChain.getCertificateChain(ctx,
    "keystore-test-ee");
Log.d(TAG, "chain length: " + chain.length);
for (X509Certificate x : chain) {
    Log.d(TAG, "Subject DN: "
        + x.getSubjectDN().getName());
    Log.d(TAG, "Issuer DN: "
        + x.getIssuerDN().getName());
}

TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init((KeyStore) null);

TrustManager[] tms = tmf.getTrustManagers();
X509TrustManager xtm = (X509TrustManager) tms[0];
Log.d(TAG, "checking chain with " + xtm);
xtm.checkClientTrusted(chain, "RSA");
Log.d(TAG, "chain is valid");

Works pretty well, but there is one major problem with this code: it does not check revocation. Android's default TrustManager explicitly turns off revocation when validating the certificate chain. So even if the certificate had a valid CDP (CRL distribution point) extension, pointing to a valid CRL, and the certificate was actually revoked, it would still validate fine in Android. What's missing here is the ability to dynamically fetch, cache and update revocation information as needed, based on information available in certificate extensions. Hopefully future version of Android will add this functionality to make Android's PKI support complete.

Of course, system applications such as the browser, email and VPN clients are also taking advantage of the new trust store, so connecting to a corporate Exchange server or a secure Web application should be as easy as installing the appropriate certificates. We'll see how well that works out in practice once I get a real ICS device (shouldn't be too long now...).

That concludes our discussion of the new credential and trust stores introduced in Android 4.0. To sum things up: users can now freely install and remove private keys and trusted certificates, as well as disable pre-installed CA certificates via the Settings app. Third-party applications can also do this via the new KeyChain API, if the user grants the app the necessary permissions. The key and trust stores are fully integrated into the OS, so using standard secure communication and cryptographic Java API's should just work, without the need for applications-specific key stores. A key element required for full PKI support -- revocation checking, is still missing, but the key and trust store functionality added in ICS is a huge step in making Android more secure, flexible and enterprise-friendly.

Comments

anakin said…
Great information.. thanks!
The below code will get the certChain for a particular alias (user-installed cert)
//X509Certificate[] chain = KeyChain.getCertificateChain(ctx,"keystore-test-ee");
In a use case where a server url is available and we want to validate the certificate chain for this url, then we could follow this procedure:
1) Create HttpsUrlConnection to this url.
2) Get the server certificates for this connection. (conn.getServerCertificates())
3) Look for certs that are instance of X509Certificate and create an array. This is the X509Certificate chain for this url.
4) Follow the procedure documented here to validate the cert chain using X509TrustManager.
Unknown said…
Glad you find it useful.

You are generally right, but the system validates HTTPS server certificates automatically (unless you change default behaviour). See the 'Using a custom certificate store' post for some background and details. The JSSE reference explains how things work in Java (same on Android, mostly) in detail.
Unknown said…
Hi,

I got one question. In the key chain api dialog there is only support for the PKCS#12 files. I have exported a certificate with the ending .cer. It doesn't show up in the key chain api-dialog and the access is therefore denied to this certificate.
Do you know how I can solve this problem?

best regards,
jens
Unknown said…
Added: The question is how can I grant access to this certificate.
Unknown said…
This should really be a new comment, not a reply to a random unrelated one.
Certificate import is supported, as long as the file is in DER format. What do you mean by 'grant access'?
Unknown said…
sorry for that.

To give you a litte more background.
I am writing an android application which has to authenticate to an WCF-Webservice. I am using a client certificate (SSO) to authenticate the user.
In addition to that I am also using a secure communication over SSL.
That all works fine.

The Problem is that the TrustManager-class I wrote accepts - all - which is not very save.

--------------------------------------------------------------------------

1.)
Windows offers a certificate export assistent. I can export a DER-coded binary X.509 file, which has the ending (.cer).

2.)
To getting my trust store working I executed the following methode (as you stated above)

X509Certificate[] chain = KeyChain.getCertificateChain(ctx,
"trust-certificate");

But I am getting a permission denied error message.

3.)
I couldn't grant access to the certificate, because the certificate (.cer) I just imported didn't show up in the keychain-api dialog.

Is the keychain-api-dialog only for certificates with public + private key?
My other certificate (SSO-Certificate) showed up and I could grant access to this certificate.

best regards,

jens
Anonymous said…
I too have a very similar problem as Jens.

I have a self-signed server certificate (.cer) that I was able to install via the Settings->Security screen. I can see it under /data/misc/keystore so I know it was installed but how do we access it? Doing KeyChain.getCertificateChain as Jens indicates gives a Permission issue (uid does not have access to that resource) but we can't get the permission granted because choosePrivateKeyAlias is used for what the name suggests, private key / certificate pair. In my case it's even more important to get the user to "grant" and "choose" the cert because the alias is not known (ie. the server is user defined and installation of the cert needs to be done after apk is built / installed).

Any help would be appreciated!
Unknown said…
You guys might be confused about how this works. You either add a certificate to the trsut store, which makes all system call related to certificate validation treat it as trusted (basically this post), or you add a private key+certificate and access it through the KeyChain API so you can use it authenticate or sign data. There is no 'granting' or 'choosing' of trusted certificates, this is handled automatically by the system.

This line in the post above is simply getting the certificate chain of a private key installed in the keystore (EE certificate and CA certificate). There is nothing special there, it might as well be coming from a server:

X509Certificate[] chain = KeyChain.getCertificateChain(ctx, "keystore-test-ee");

Read this for some background:

http://nelenkov.blogspot.jp/2011/12/using-custom-certificate-trust-store-on.html
Unknown said…
Hello Nikolay,

I read your post http://nelenkov.blogspot.jp/2011/12/using-custom-certificate-trust-store-on.html,
which regards pre-ICS-devices. In my scenario all devices have at least android 4.

I need client authentication as well as server authentication.

You stated:
"The default TrustManagerFactory uses it to get a list of trust anchors,
thus automatically validating server certificates against the system's currently trusted certificates."

I am still a little bit confused. I don’t’ understand the automatically validating part because the init-method needs
trust manager object.

sslContext.init(new KeyManager[]{keyManager},
//CustomTrustManager.getCustomTrustAllTrustManagers(), // trust all works
CustomTrustManager.getCustomTrustManagers(context),
new SecureRandom());

In the getCustomTrustManagers-Method I tried your code above.

"Here's how we can use the TrustManager to validate a certificate issued by a
private CA (the CA certificate is already installed in the user trust store)."

But this leads to a permission-exception as I already mentioned in my previous post.

The question is can I pass a trust manager object which is null and then the validation will be automatic?

best regards,

jens.
Unknown said…
This will give you the default TrustManager[], there is only one. It will use the system trust store to validate the server certificate. Passing null will not work.

TrustManagerFactory tmf = TrustManagerFactory.getInstance("X509");
tmf.init((KeyStore) null);

sslCtx.init(null, tmf.getTrustManagers(), null);

The above is equivalent to using the default implementation, it just explicitly initializes the SSLContext. If you want to do client authentication
using a private key managed by the KeyStore API, you need to supply a custom *KeyManager* that returns your key using *KeyChain.getPrivateKey()*. See here for a a sample:

http://stackoverflow.com/questions/13525445/ssl-client-authentication-in-android-4-x/

You do need to read on how SSL works in Java to get this, the only Android-specific part is the KeyChain class.
Unknown said…
Hi Nikolay,

thank you very much for the quick advice.
That worked.
I already have a Custom KeyManager-class.

I found some advice in the google mail app:
packages/apps/Email/emailcommon/src/com/android/emailcommon/utility/SSLUtils.java
There is a implementation of the X509ExtendedKeyManager-class.

regards,

jens.
Anonymous said…
Hello Nikola,

First of all, thank you very much for sharing these articles. Enjoying reading them, not many articles on the subject have this much depth. Very informative.

I'm relatively new to SSL with Java/Android so let me know if I'm making the wrong assumptions.

My question is: Assuming ICS only, would you be able to confirm for me if apps are able to use server self signed certificates installed through settings->security->install from storage without doing anything special? (only need server authentication/encryption)

Let's assume the certificate is installed successfully (by that I mean, I can see the cert under /data/misc/keystore and received the "successfully installed certificate" message).

Attempting to create a simple socket up to the server this way:

..
SSLSocketFactory factory = (SSLSocketFactory)SSLSocketFactory.getDefault();
serverSocket = (SSLSocket)factory.createSocket(server.getHost(),server.getPort());
outStream = new DataOutputStream(serverSocket.getOutputStream());
...

results in "java.security.cert.CertPathValidatorException: Trust anchor for certification path not found." when attempting to get the OutputStream. I could be wrong but to me this appears to mean that the system isn't able to find the server's certificate and it's probably only looking at the CA certs? Side note: it is not listed under the Trusted certs list in settings nor User certs (not surprising since I assume that list won't show self signed certs). Heck, perhaps I'm even misunderstanding the purpose of "Install from storage"?

In my project's case the certificate is not known until after the app is installed/configured by the user on a per user basis so the alternative method of installing the cert to the app's truststore in code (assuming the cert is in the app's resources, etc) won't quite work. That is why I'm trying to see if there was a way to have the user manually install the cert after.

Thanks in advance for your help.
Unknown said…
It doesn't matter if the certificate is self-signed, once you install it in the trust store it will be trusted. It should show up in the user certs tab as well. Try testing with the stock browser for HttpsURLConnection first, but it should work for sockets as well. You can also use KeyStore.getInstance("AndroidCAStore") to check programmatically if it is in the trustore.
Unknown said…
I want to make https client for android app which will hit https URL and read the data. Server is having self signed certificate.
Grazfather said…
Great article... This is making me more excited for your book. Small typo: s/thwate/thawte/g
Unknown said…
Thanks, fixed. BTW, the chapters about key and trust stores are already available in early access.
Martin said…
This comment has been removed by the author.
Ankit said…
Hey Nikolay, thanks for all the informative blogs for trust store and key store in android.
I have one query , is it possible to access truststore in native instead of using JAVA api?? I have a native daemon which want to authenticate server through trust store's certs.I dont want to use any java service to access the trust store.
like keystore daemon, is there any daemon for truststore also??
Unknown said…
The truststore is just a bunch of files, so just use your favorite C library :) You should be able to initialize OpenSSL by pointing to the directories storing certificates.

Popular posts from this blog

Decrypting Android M adopted storage

Unpacking Android backups

Using app encryption in Jelly Bean