Credential storage enhancements in Android 4.3

Our previous post was not related to Android security, but happened to coincide with the Android 4.3 announcement. Now that the post-release dust has settled, time to give it a proper welcome here as well. Being a minor update, there is nothing ground-breaking, but this 'revenge of the beans' brings some welcome enhancements and new APIs. Enough of those are related to security for some to even call 4.3 a 'security release'. Of course, the big star is SELinux, but credential storage, which has been a somewhat recurring topic on this blog, got a significant facelift too, so we'll look into it first. This post will focus mainly on the newly introduced features and interfaces, so you might want to review previous credential storage posts before continuing.

What's new in 4.3

First and foremost, the system credential store, now officially named 'Android Key Store' has a public API for storing and using app-private keys. This was possible before too, but not officially supported and somewhat clunky on pre-ICS devices. Next, while only the primary (owner) user could use the system key store pre-4.3, now it is multi-user compatible and each user gets their own keys. Finally, there is an API and even a system settings field that lets you check whether the credential store is hardware-backed (Nexus 4, Nexus 7) or software only (Galaxy Nexus). While the core functionality hasn't changed much since the previous release, the implementation strategy has evolved quite a bit, so we will look briefly into that too. That's a lot to cover, so lets' get started.

Public API

The API is outlined in the 'Security' section of the 4.3 new API introduction page, and details can be found in the official SDK reference, so we will only review it briefly. Instead of introducing yet another Android-specific API, key store access is exposed via standard JCE APIs, namely KeyGenerator and KeyStore. Both are backed by a new Android JCE provider, AndroidKeyStoreProvider and are accessed by passing "AndroidKeyStore" as the type parameter of the respective factory methods (those APIs were actually available in 4.2 as well, but were not public). For a full sample detailing their usage, refer to the BasicAndroidKeyStore project in the Android SDK. To introduce their usage briefly, first you create a KeyPairGeneratorSpec that describes the keys you want to generate (including a self-signed certificate), initialize a KeyPairGenerator with it and then generate the keys by calling generateKeyPair(). The most important parameter is the alias, which you then pass to KeyStore.getEntry() in order to get a handle to the generated keys later. There is currently no way to specify key size or type and generated keys default to 2048 bit RSA. Here's how all this looks like:

// generate a key pair
Context ctx = getContext();
Calendar notBefore = Calendar.getInstance()
Calendar notAfter = Calendar.getInstance();
notAfter.add(1, Calendar.YEAR);
KeyPairGeneratorSpec spec = new KeyPairGeneratorSpec.Builder(ctx)
.setAlias("key1")
.setSubject(
new X500Principal(String.format("CN=%s, OU=%s", alais,
ctx.getPackageName())))
.setSerialNumber(BigInteger.ONE).setStartDate(notBefore.getTime())
.setEndDate(notAfter.getTime()).build();

KeyPairGenerator kpGenerator = KeyPairGenerator.getInstance("RSA", "AndroidKeyStore");
kpGenerator.initialize(spec);
KeyPair kp = kpGenerator.generateKeyPair();

// in another part of the app, access the keys
KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
keyStore.load(null);
KeyStore.PrivateKeyEntry keyEntry = (KeyStore.PrivateKeyEntry)keyStore.getEntry("key1", null);
RSAPublicKey pubKey = (RSAPublicKey)keyEntry.getCertificate().getPublicKey();
RSAPrivateKey privKey = (RSAPrivateKey) keyEntry.getPrivateKey();

If the device has a hardware-backed key store implementation, keys will be generated outside of the Android OS and won't be directly accessible even to the system (or root user). If the implementation is software only, keys will be encrypted with a per-user key-encryption master key. We'll discuss key protection in detail later.

Android 4.3 implementation

This hardware-backed design was initially implemented in the original Jelly Bean release (4.1), so what's new here? Credential storage has traditionally (since the Donut days), been implemented as a native keystore daemon that used a local socket as its IPC interface. The daemon has finally been retired and replaced with a 'real' Binder service, which implements the IKeyStoreService interface. What's interesting here is that the service is implemented in C++, which is somewhat rare in Android. See the interface definition for details, but compared to the original keymaster-based implementation, IKeyStoreService gets 4 new operations: getmtime(), duplicate(), is_hardware_backed() and clear_uid(). As expected, getmtime() returns the key modification time and duplicate() copies a key blob (used internally for key migration). is_hardware_backed will query the underlying keymaster implementation and return true when it is hardware-backed. The last new operation, clear_uid(), is a bit more interesting. As we mentioned, the key store now supports multi-user devices and each user gets their own set of keys, stored in /data/misc/keystore/user_N, where N is the Android user ID. Keys names (aliases) are mapped to filenames as before, and the owner app UID now reflects the Android user ID as well. When an app that owns key store-managed keys is uninstalled for a user, only keys created by that user are deleted. If an app is completely removed from the system, its keys are deleted for all users. Since key access is tied to the app UID, this prevents a different app that happens to get the same UID from accessing an uninstalled app's keys. Key store reset, which deletes both key files and the master key, also affects only the current user. Here's how key files for the primary user might look like:

1000_CACERT_ca
1000_CACERT_cacert
10248_USRCERT_myKey
10248_USRPKEY_myKey
10293_USRCERT_rsa_key0
10293_USRPKEY_rsa_key0

The actual files are owned by the keystore service (which runs as the keystore Linux user) and it checks the calling UID to decide whether to grant or deny access to a key file, just as before. If the keys are protected by hardware, key files may contain only a reference to the actual key and deleting them may not destroy the underlying keys. Therefore, the del_key() operation is optional and may not be implemented.

The hardware in 'hardware-backed'

To give some perspective to the whole 'hardware-backed' idea, let's briefly discuss how it is implemented on the Nexus 4. As you may now, the Nexus 4 is based on Qualcomm's Snapdragon S4 Pro APQ8064 SoC. Like most recent ARM SoC's it is TrustZone-enabled and Qualcomm implement their Secure Execution Environment (QSEE) on top of it. Details are, as usual, quite scarce, but trusted application are separated from the main OS and the only way to interact with them is through the controlled interface the /dev/qseecom device provides. Android applications that wish to interact with the QSEE load the proprietary libQSEEComAPI.so library and use the functions it provides to send 'commands' to the QSEE. As with most other SEEs, the QSEECom communication API is quite low-level and basically only allows for exchanging binary blobs (typically commands and replies), whose contents entirely depends on the secure app you are communicating with. In the case of the Nexus 4 keymaster, the used commands are: GENERATE_KEYPAIR, IMPORT_KEYPAIR, SIGN_DATA and VERIFY_DATA. The keymaster implementation merely creates command structures, sends them via the QSEECom API and parses the replies. It does not contain any cryptographic code itself.

An interesting detail is that, the QSEE keystore trusted app (which may not be a dedicated app, but part of more general purpose trusted application) doesn't return simple references to protected keys, but instead uses proprietary encrypted key blobs (not unlike nCipher Thales HSMs). In this model, the only thing that is actually protected by hardware is some form of 'master' key-encryption key (KEK), and user-generated keys are only indirectly protected by being encrypted with the KEK. This allows for practically unlimited number of protected keys, but has the disadvantage that if the KEK is compromised, all externally stored key blobs are compromised as well (of course, the actual implementation might generate a dedicated KEK for each key blob created or the key can be fused in hardware; either way no details are available). Qualcomm keymaster key blobs are defined in AOSP code as shown below. This suggest that private exponents are encrypted using AES, most probably in CBC mode, with an added HMAC-SHA256 to check encrypted data integrity. Those might be further encrypted with the Android key store master key when stored on disk.

#define KM_MAGIC_NUM     (0x4B4D4B42)    /* "KMKB" Key Master Key Blob in hex */
#define KM_KEY_SIZE_MAX (512) /* 4096 bits */
#define KM_IV_LENGTH (16) /* AES128 CBC IV */
#define KM_HMAC_LENGTH (32) /* SHA2 will be used for HMAC */

struct qcom_km_key_blob {
uint32_t magic_num;
uint32_t version_num;
uint8_t modulus[KM_KEY_SIZE_MAX];
uint32_t modulus_size;
uint8_t public_exponent[KM_KEY_SIZE_MAX];
uint32_t public_exponent_size;
uint8_t iv[KM_IV_LENGTH];
uint8_t encrypted_private_exponent[KM_KEY_SIZE_MAX];
uint32_t encrypted_private_exponent_size;
uint8_t hmac[KM_HMAC_LENGTH];
};

So, in the case of the Nexus 4, the 'hardware' is simply the ARM SoC. Are other implementations possible? Theoretically, a hardware-backed keymaster implementation does not need to be based on TrustZone. Any dedicated device that can generate and store keys securely can be used, the usual suspects being embedded secure elements (SE) and TPMs. However, there are no mainstream Android devices with dedicated TPMs and recent flagship devices have began shipping without embedded SEs, most probably due to carrier pressure (price is hardly a factor, since embedded SEs are usually in the same package as the NFC controller). Of course, all mobile devices have some form of UICC (SIM card), which typically can generate and store keys, so why not use that? Well, Android still doesn't have a standard API to access the UICC, even though 'vendor' firmwares often include one. So while one could theoretically implement a UICC-based keymaster module compatible with the UICC's of your friendly neighbourhood MNO, it is not very likely to happen.

Security level

So how secure are you brand new hardware-backed keys? The answer is, as usual, it depends. If they are stored in a real, dedicated, tamper-resistant hardware module, such as an embedded SE, they are as secure as the SE. And since this technology has been around for over 40 years, and even recent attacks are only effective against SEs using weak encryption algorithms, that means fairly secure. Of course, as we mentioned in the previous section, there are no current keymaster implementations that use actual SEs, but we can only hope.

What about TrustZone? It is being aggressively marketed as a mobile security 'silver bullet' and streaming media companies have embraced it as an 'end-to-end' DRM solution, but does it really deliver? While the ARM TrustZone architecture might be sound at its core, in the end trusted applications are just software that runs at a slightly lower level than Android. As such, they can be readily reverse engineered, and of course vulnerabilities have been found. And since they run within the Secure World they can effectively access everything on the device, including other trusted applications. When exploited, this could lead to very effective and hard to discover rootkits. To sum this up, while TrustZone secure applications might provide effective protection against Android malware running on the device, given physical access, they, as well as the TrustZone kernel, are exploitable themselves. Applied to the Android key store, this means that if there is an exploitable vulnerability in any of the underlying trusted applications the keymaster module depends on, key-encryption keys could be extracted and 'hardware-backed' keys could be compromised.

Advanced usage

As we mentioned in the first section, Android 4.3 offers a well defined public API to the system key store. It should be sufficient for most use cases, but if needed you can connect to the keystore service directly (as always, not really recommended). Because it is not part of the Android SDK, the IKeyStoreService doesn't have wrapper 'Manager' class, so if you want to get a handle to it, you need to get one directly from the ServiceManager. That too is hidden from SDK apps, but, as usual, you can use reflection. From there, it's just a matter of calling the interface methods you need (see sample project on Github). Of course, if the calling UID doesn't have the necessary permission, access will be denied, but most operations are available to all apps.

Class smClass = Class.forName("android.os.ServiceManager");
Method getService = smClass.getMethod("getService", String.class);
IBinder binder = (IBinder) getService.invoke(null, "android.security.keystore");
IKeystoreService keystore = IKeystoreService.Stub.asInterface(binder);

By using the IKeyStoreService directly you can store symmetric keys or other secret data in the system key store by using the put() method, which the current java.security.KeyStore implementation does not allow (it can only store PrivateKey's). Such data is only encrypted by the key store master key, and even the system key store is hardware-backed, data is not protected by hardware in any way.

Accessing hidden services is not the only way to augment the system key store functionality. Since the sign() operation implements a 'raw' signature operation (RSASP1 in RFC 3447), key store-managed (including hardware-backed) keys can be used to implement signature algorithms not natively supported by Android. You don't need to use the IKeyStoreService interface, because this operation is available through the standard JCE Cipher interface:

KeyStore ks = KeyStore.getInstance("AndroidKeyStore");
ks.load(null);
KeyStore.Entry keyEntry = keyStore.getEntry("key1", null);
RSAPrivteKey privKey = (RSAPrivateKey) keyEntry.getPrivateKey();

Cipher c = Cipher.getInstance("RSA/ECB/NoPadding");
cipher.init(Cipher.ENCRYPT_MODE, i privateKey);
byte[] result = cipher.doFinal(in, o, in.length);

If you use this primitive to implement, for example, Bouncy Castle's AsymmetricBlockCipher interface, you can use any signature algorithm available in the Bouncy Castle lightweight API (we actually use Spongy Castle to stay compatible with Android 2.x without too much hastle). For example, if you want to use a more modern (and provably secure) signature algorithm than Android's default PKCS#1.5 implementation, such as RSA-PSS you can accomplish it with something like this (see sample project for AndroidRsaEngine):

AndroidRsaEngine rsa = new AndroidRsaEngine("key1", true);

Digest digest = new SHA512Digest();
Digest mgf1digest = new SHA512Digest();
PSSSigner signer = new PSSSigner(rsa, digest, mgf1digest, 512 / 8);
RSAKeyParameters params = new RSAKeyParameters(false,
pubKey.getModulus(), pubKey.getPublicExponent());

signer.init(true, params);
signer.update(signedData, 0, signedData.length);
byte[] signature = signer.generateSignature();

Likewise, if you need to implement RSA key exchange, you can easily make use of OAEP padding like this:

AndroidRsaEngine rsa = new AndroidRsaEngine("key1", false);

Digest digest = new SHA512Digest();
Digest mgf1digest = new SHA512Digest();
OAEPEncoding oaep = new OAEPEncoding(rsa, digest, mgf1digest, null);

oaep.init(true, null);
byte[] cipherText = oaep.processBlock(plainBytes, 0, plainBytes.length);

The sample application shows how to tie all of those APIs together and features an elegant and fully Holo-compatible user interface:



An added benefit of using hardware-backed keys is that, since they are not generated using Android's default SecureRandom implementation, they should not be affected by the recently announced SecureRandom vulnerability (of course, since the implementation is closed, we can only hope that trusted apps' RNG actually works...). However, Bouncy Castle's PSS and OAEP implementations do use SecureRandom internally, so you might want to seed the PRNG 'manually' before starting your app to make sure it doesn't start with the same PRNG state as other apps. The keystore daemon/service uses /dev/urandom directly as a source of randomness, when generating master keys used for key file encryption, so they should not be affected. RSA keys generated by the softkeymaster OpenSSL-based software implementation might be affected, because OpenSSL uses RAND_bytes() to generate primes, but are probably OK since the keystore daemon/service runs in a dedicated process and the OpenSSL PRNG automatically seeds itself from /dev/urandom on first access (unfortunately there are no official details about the 'insecure SecureRandom' problem, so we can't be certain).

Summary

Android 4.3 offers a standard SDK API for generating and accessing app-private RSA keys, which makes it easier for non-system apps to store their keys securely, without implementing key protection themselves. The new Jelly Bean also offers hardware-backed key storage on supported devices, which guarantees that even system or root apps cannot extract the keys. Protection against physical access attacks depends on the implementation, with most (all?) current implementations being TrustZone-based. Low-level RSA operations with key store managed keys are also possible, which enables apps to use cryptographic algorithms not provided by Android's built-in JCE providers.

Tidak ada komentar:

Posting Komentar