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.

Post a Comment