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
Public API
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
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
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 currentkeymaster
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
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
This entry was posted on at 10:06 AM and is filed under android security. You can follow any responses to this entry through the RSS 2.0. You can