Certificate blacklisting in Jelly Bean
The last two posts introduced app encryption, the new system key store and a few other security related features introduced in Jelly Bean. Browsing the ASOP code reveals another new feature which sits higher in the security stack than the previously discussed ones -- certificate blacklisting. In this article we will present some details about its implementation and introduce a sample app that allows us to test how blacklisting works in practice.
Why blacklist certificates?
Android certificate blacklisting
- a public key hash blacklist (to handle compromised CAs)
- a serial number blacklist (to handle compromised EE certificates)
content://settings/secure/pubkey_blacklist
content://settings/secure/serial_blacklist
CertiBlacklister
component which registers itself as a ContentObserver
for the two blacklist URIs. Whenever a new value is written to those, the CertBlacklister
gets notified and writes the value to a file on disk. The format of the files is simple: a comma delimited list of hex-encoded public key hashes or certificate serial numbers. The actual files are:- certificate blacklist:
/data/misc/keychain/pubkey_blacklist.txt
- serial number blacklist:
/data/misc/keychain/serial_blacklist.txt
PKIXCertPathValidatorSpi,
is part of the Bouncy Castle JCE provider, modified to handle certificate blacklists, which is an Android-specific feature and not defined in the standard CertPath API. The PKIX certificate validation algorithm the class implements is rather complex, but what Jelly Bean adds is fairly straightforward:- when verifying an EE (leaf) certificate, check if it's serial number is in the serial number blacklist. If it is, return the same error (exception) as if the certificate has been revoked.
- when verifying a CA certificate, check if the hash of it's public key is in the public key blacklist. If it is, return the same error as if the certificate has been revoked.
Using Android certificate blacklisting
/system/app
or be signed with the platform certificate. As usual, we choose the former for our tests. A screenshot of the app is shown below.KeyChain
API), verify a certificate chain (consisting of a the CA certificate and a single EE certificate), add either of the certificates to the system blacklist, and finally clear it so we can start over. The code is quite straightforward, see github repository for details. One thing to note is that it instantiates the low level org.bouncycastle.jce.provider.CertBlacklist
class in order to check directly whether modifying the blacklist succeeded. Since this class is not part of the public API, it is accessed using reflection.CertiBlacklister
observer works as expected and changes to the blacklists are immediately written to the corresponding files in /data/misc/keychain
, verifying the chain succeeds even after the certificates have been blacklisted. The reason for this is that, as all system classes, the certificate path validator class is pre-loaded and shared across all apps. Therefore it reads the blacklist files only at startup, and a system restart is needed to have it re-read the files. After a restart, validation fails with the expected error: 'Certificate revocation of serial XXXX'. Another issue is that while blacklisting by serial number works as expected, public key blacklisting doesn't appear to work in the current public build (JRO03C on Galaxy Nexus as of July 2012). This is a result of improper handling of the key hash format and will hopefully be fixed in a next JB maintenance release. Update: it is now fixed in AOSP master.Summary
Jelly Bean hardware-backed credential storage
Along with all the user facing new features everyone is talking about, the latest Android release has quite a bit of security improvements under the hood. Of those only app encryption has been properly announced, while the rest remain mostly covered up by upper level APIs. This, of course, is not fair, so let's call them up (the list is probably not exhaustive):
- RSA and DSA key generation and signatures are now implemented in native code for better performance
- TLS v1.2 support
- improved system key store
- new OpenSSL interface (engine) to the system key store
- new key management HAL component --
keymaster
- hardware-backed
keymaster
implementation on Galaxy Nexus and Nexus 7
System key store improvements
keystore
daemon simply stores opaque encrypted blobs and the only meatdata available (UID of owner and key name) was encoded in the file name under which blobs are stored. In Jelly Bean (JB), blobs also have a version field and a type field. The following key types are newly defined:TYPE_GENERIC
TYPE_MASTER_KEY
TYPE_KEY_PAIR
TYPE_GENERIC
is used for key blobs saved using the previous get/put interface, and TYPE_MASTER_KEY
is, of course, only used for the key store master key. The newly added TYPE_KEY_PAIR
is used for key blobs created using the new GENERATE
and IMPORT
commands. Before we go into more details, here are the keystore
commands added in Jelly Bean:GENERATE
IMPORT
SIGN
VERIFY
GET_PUBKEY
DEL_KEY
GRANT
UNGRANT
VERIFY
takes the key name, signed data and signature value as input, and outputs the verification result. GET_PUBKEY
works as expected -- it returns the public key in X.509 format. As mentioned above, the keystore
daemon does access control based on UID, and pre-JB a process could use only a key it had created itself. The new GRANT
/ UNGRANT
commands allow the OS to temporarily allow access to system keys to other processes. The grants are not persisted, so they are lost on restart.Key store OpenSSL engine
OpenSSLEngine
), which is used to implement the KeyChain.getPrivateKey()
API. Thus all apps that acquire a private key reference via the KeyChain
API get the benefit of using the new native implementation.keymaster
module overview
keymaster
module and its hardware-based implementation on Galaxy Nexus (and Nexus 7, but that currently has no relevant source code in AOSP, so we will focus on the GN). Jelly Bean introduces a new libhardware
(aka HAL) module, called keymaster
. It defines structures and methods for generating keys and signing/verifying data. The keymaster
module is meant to decouple Android from the actual device security hardware, and a typical implementation would use a vendor-provided library to communicate with the crypto-enabled hardware. Jelly Bean comes with a default softkeymaster
module that does all key operations in software only (using the ubiquitous OpenSSL). It is used on the emulator and probably will be included in devices that lack dedicated cryptographic hardware. The currently defined operations are listed below. Only RSA is supported at present.generate_keypair
import_keypair
sign_data
verify_data
get_keypair_public
delete_keypair
delete_all
keystore
commands listed in the previous section. All of the asymmetric key operations exposed by the keystore
daemon are implemented by calling the system keymaster
module. Thus if the keymaster
HAL module is backed by a hardware cryptographic device, all upper level commands and APIs that use the keystore
daemon interface automatically get to use hardware crypto.Galaxy Nexus keymaster
implementation
- trusted storage
- cryptographic operations
- time-related
- arithmetical (for dealing with big numbers)
libtf_crypto_sst.so
) uses the TEE Client API to communicate with a TA that implements hashing, key generation, encryption/decryption, signing/verification and random number generation. Since there doesn't seem to a 'official' name for the TA on the Galaxy Nexus, and its commands map pretty much one-to-one to PKCS#11 interfaces, we will be calling it the 'token TA' from now on. The GN keymaster
HAL module calls the PKCS#11 module to implement RSA key pair generation and import, as well as signing and verification. This in turn is used by the keystore
daemon to implement the corresponding commands.keymaster
module is not in the latest GN build (JRO03C
at the time of this writing. Update: according to this commit message, the reason for its being removed is that it has a power usage bug). Fortunately it is quite easy to build it and install it on the device (notice that the keymaster
module, for whatever reason, is actually called keystore.so
):$ make -j8 keystore.tuna
$ adb push out/product/maguro/system/lib/hw/keystore.tuna.so /mnt/sdcard
$ adb shell
$ su
# mount -o remount,rw /system
# cp /mnt/sdcard/keystore.tuna.so /system/lib/hw
keystore.default.so
). If we send a few keystore
commands, we see the following output (maybe a bit too verbose for a production device), confirming that cryptographic operations are actually executed by the TEE:V/TEEKeyMaster( 299): Opening subsession 0x414f2a88
V/TEEKeyMaster( 299): public handle = 0x60011, private handle = 0x60021
V/TEEKeyMaster( 299): Closing object handle 0x60021
V/TEEKeyMaster( 299): Closing object handle 0x60011
V/TEEKeyMaster( 299): Closing subsession 0x414f2a88: 0x0
I/keystore( 299): uid: 10164 action: a -> 1 state: 1 -> 1 retry: 4
V/TEEKeyMaster( 299): tee_sign_data(0x414ea008, 0xbea018fc, 36, 0xbea1195c, 256, 0xbea018c4, 0xbea018c8)
V/TEEKeyMaster( 299): Opening subsession 0x414f2ab8
V/TEEKeyMaster( 299): Found 1 object 0x60011 : class 0x2
V/TEEKeyMaster( 299): Found 1 object 0x60021 : class 0x3
V/TEEKeyMaster( 299): public handle = 0x60011, private handle = 0x60021
V/TEEKeyMaster( 299): tee_sign_data(0x414ea008, 0xbea018fc, 36, 0xbea1195c, 256, 0xbea018c4, 0xbea018c8)
=> 0x414f2838 size 256
V/TEEKeyMaster( 299): Closing object handle 0x60021
V/TEEKeyMaster( 299): Closing object handle 0x60011
V/TEEKeyMaster( 299): Closing subsession 0x414f2ab8: 0x0
I/keystore( 299): uid: 10164 action: n -> 1 state: 1 -> 1 retry: 4
This produces key files in the
keystore
daemon data directory, bus as you can see in the listing below, they are not large enough to store 2048 bit RSA keys. They only store a key identifier, as returned by the underlying PKCS#11 module. Keys are loaded based on this ID, and signing are verification are preformed within the token TA, without the keys being exported to the REE. # ls -l /data/misc/keystore/10164*
-rw------- keystore keystore 84 2012-07-12 14:15 10164_foobar
-rw------- keystore keystore 84 2012-07-12 14:15 10164_imported
/data/smc/user.bin
file. The format is, of course, proprietary, but it would be a safe bet that it is encrypted with a key stored on the SoC (or at least somehow protected by a hardware key). This allows to have practically an unlimited number of keys inside the TEE, without being bounded by the limited storage space on the physical chip.keymaster
usage and performance
KeyChain
API (or importing via Settings->Security->Insall from storage) will import the private key into the token TA and getting a private key object using KeyChain.getPrivateKey()
will return a reference to the stored key. Subsequent signature operations using this key object will be performed by the token TA and take advantage of the OMAP4 chip's cryptographic hardware. There are currently no public APIs or stock applications that use the generate key functionality, but if you want to generate a key protected by the token TA, you can call android.security.KeyStore.generate()
directly (via reflection or by duplicating the class in your project). This API can potentially be used for things like generating a CSR request from a browser and other types of PKI enrollment.Crypto Provider/Operation | Key generation | Signing | Verification |
---|---|---|---|
Bouncy Castle | 2176.20 [ms] | 34.60 [ms] | 1.90 [ms] |
OpenSSL | 2467.40 [ms] | 29.80 [ms] | 1.00 [ms] |
TEE | 3487.00 [ms] | 10.90 [ms] | 10.60 [ms] |
As you can see from the table above, Bouncy Castle and OpensSSL perform about the same, while the TEE takes more time to generate keys (most probably because it's using a hardware RNG, not a PRNG), but signing is about 3 times faster compared to the software implementations. Verification takes about the same time as signing, and is slower than software. It should be noted that this test is not exactly precise: calling the token TA via the
keystore
daemon causes a lot of TEE client API sessions to be open and closed which has its overhead. Getting more accurate times will require benchmarking using the Client API directly, but the order of the results should be the same. Summary
To sum things up: Jelly Bean finally has a standard hardware key storage and cryptographic operations API in thekeymater
HAL module definition. The implementation for each device is hardware-dependent, and the currently available implementations use the TEE Client API on the Galaxy Nexus and Nexus 7 to take advantage of the TEE capabilities of the respective SoC (OMAP4 and Tegra 3). The current interface and implementation only support generating/importing of RSA keys and signing/verification, but will probably be extended in the future with more key types and operations. It is integrated with the system credential storage (managed by the keystore
daemon) and allows us to generate, import and use RSA keys protected by the devices's TEE from Android applications.
Using app encryption in Jelly Bean
The latest Android version, 4.1 (Jelly Bean) was announced last week at Google I/O with a bunch of new features and improvements. One of the more interesting features is app encryption, but there haven't been any details besides the short announcement: 'From Jelly Bean and forward, paid apps in Google Play are encrypted with a device-specific key before they are delivered and stored on the device.'. The lack of details is of course giving rise to guesses and speculations, some people even fear that they will have to repurchase their paid apps when they get a new device. In this article we will look at how app encryption is implemented in the OS, show how you can install encrypted apps without going through Google Play, and take a peak at how Google Play delivers encrypted apps.
OS support for encrypted apps
Apps on Android can be installed in a few different ways:
- via an application store (e.g., the Google Play Store, aka Android Market)
- directly on the phone by opening app files or email attachments (if the 'Unknown sources' options is enabled)
- from a computer connected through USB by using the
adb install
SDK command
adb
usage output, we see that the install
command has gained a few new options in the latest SDK release:adb install [-l] [-r] [-s] [--algo <algorithm name> --key <hex-encoded key>
--iv <hex-encoded iv>] <file>
--algo
, --key
and --iv
parameters obviously have to do with encrypted apps, so before going into details lets first try to install an encrypted APK. Encrypting a file is quite easy to do using the enc
OpenSSL commands, usually already installed on most Linux systems. We'll use AES in CBC mode with a 128 bit key (a not very secure one, as you can see below), and specify an initialization vector (IV) which is the same as the key to make things simpler:$ openssl enc -aes-128-cbc -K 000102030405060708090A0B0C0D0E0F
-iv 000102030405060708090A0B0C0D0E0F -in my-app.apk -out my-app-enc.apk
Let's check if Android likes our newly encrypted app by trying to install it:
$ adb install --algo 'AES/CBC/PKCS5Padding' --key 000102030405060708090A0B0C0D0E0F
--iv 000102030405060708090A0B0C0D0E0F my-app-enc.apk
pkg: /data/local/tmp/my-app-enc.apk
Success
The 'Success' output seems promising, and sure enough the app's icon is in the system tray and it starts without errors. The actual apk file is copied in
/data/app
as usual, and comparing its hash value with our encrypted APK reveals that it's in fact a different file. The hash value is exactly the same as that of the original (unencrytped) APK though, so we can conclude that the APK is being decrytped at install time using the encryption parameters (algorithm, key and IV) we have provided. Let's look into how this is actually implemented. adb install
command simply calls the pm
Android command line utility which lets us list, install and delete packages (apps). The component responsible for installing apps on Android has traditionally been the PackageManagerService
and the pm
is just a convenient frontend for it. Apps usually access the package service through the facade class PackageManager
. Browsing through its code and checking for encryption related methods we find this: public abstract void installPackageWithVerification(Uri packageURI,
IPackageInstallObserver observer, int flags, String installerPackageName,
Uri verificationURI, ManifestDigest manifestDigest,
ContainerEncryptionParams encryptionParams);
The
ContainerEncryptionParams
class looks especially promising, so let's peek inside: public class ContainerEncryptionParams implements Parcelable {
private final String mEncryptionAlgorithm;
private final IvParameterSpec mEncryptionSpec;
private final SecretKey mEncryptionKey;
private final String mMacAlgorithm;
private final AlgorithmParameterSpec mMacSpec;
private final SecretKey mMacKey;
private final byte[] mMacTag;
private final long mAuthenticatedDataStart;
private final long mEncryptedDataStart;
}
The
adb install
parameters we used above neatly correspond to the first three fields of the class. In addition to that, the class also stores MAC related parameters, so it's safe to assume that Android can now check the integrity of application binaries. Unfortunately, the pm
command doesn't have any MAC-related parameters (it does actually, but for some reason those are disabled in the current build), so to try out the MAC support we need to call the installPackageWithVerification
method directly. The method is hidden from SDK applications, so the only way to call it from an app is to use reflection. It turns out that most of its parameter classes (
IPackageInstallObserver
, ManifestDigest
and ContainerEncryptionParams
) are also hidden, but that's only a minor snag. Android pre-loads framework classes, so even if you app bundles a framework class, the system copy will always be used at runtime. This means that all we have to do to get a handle for the installPackageWithVerification
method is add the required classes to the andorid.content.pm
package in our app. Once we have a method handle, we just need to instantiate the ContainerEncryptionParams
class, providing all the encryption and MAC related parameters. One thing to note is that since our entire file is encrypted, and the MAC is calculated over all of its contents (see below), we specify 0 for both the encrypted and authenticated data start, and the file size as the data end (see sample code). To calculate the MAC value (tag) we once again use OpenSSL:$ openssl dgst -hmac 'hmac_key_1' -sha1 -hex my-app-enc.apk
HMAC-SHA1(my-app-enc.apk)= 0dc53c04d33658ce554ade37de8013b2cff0a6a5
Note that the
dgst
command doesn't support specifying the HMAC key using hexadecimal or Base64, so you are limited to ASCII characters. This may not be a good idea for production use, so consider using a real key and calculating the MAC in some other way (using JCE, etc.). Our app is mostly ready now, but installing apps requires the
INSTALL_PACKAGES
permission, which is defined with protection level signatureOrSystem
. Thus it is granted only to apps signed with the system (ROM) key, or apps installed in the /system
partition. Building a Jelly Bean ROM is an interesting excercise, but for now, we'll simply copy our app to /system/app
in order to get the necessary permission to install packages (on the emulator or a rooted device). Once this is done, we can install an encrypted app via the PackageManager
and Android will both decrypt the APK and verify that the package hasn't been tampered with by comparing the specified MAC tag with value calculated based on the actual file contents. You can test that using the sample application by slightly changing the encryption and MAC parameters. This should result in an install error.The
android.content.pm
package has some more classes of interest, such as MacAuthenticatedInputStream
and ManifestDigest
, but the actual APK encryption and MAC verification is done by the DefaultContainerService$ApkContainer
, part of the DefaultContainerService
(aka, 'Package Access Helper'). Forward locking
/data/app
), and a package readable only by the system user, containing executable code (in /data/app-private
). The code package was protected by file system permissions, and while this made it inaccessible to users on most consumer devices, one only needed to gain root access to be able to extract it. This approach was quickly deprecated, and online Android Licensing (LVL) was introduced as a replacement. This, however, shifted app protection implementation from the OS to app developers, and has had mixed results.Jelly Bean implementation
While encrypted app containers as a forward locking mechanism are new to JB, the encrypted container idea has been around since Froyo. At the time (May 2010) most Android devices came with limited internal storage and a fairly large (a few GB) external storage, usually in the form of a micro SD card. To make file sharing easier, external storage was formatted using the FAT filesystem, which lacks file permissions. As a result, files on the SD card could be read and written by anyone (any app). To prevent users from simply copying paid apps off the SD card, Froyo created an encrypted filesystem image file and stored the APK in it when you opted to move the app to external storage. The image was then mounted at runtime using Linux'sdevice-mapper
and the system would load the app files from the newly created mount point (one per app). Building on this, JB makes the container EXT4, which allows for permissions. A typical forward locked app's mount point now looks like this:shell@android:/mnt/asec/org.mypackage-1 # ls -l
ls -l
drwxr-xr-x system system 2012-07-16 15:07 lib
drwx------ root root 1970-01-01 09:00 lost+found
-rw-r----- system u0_a96 1319057 2012-07-16 15:07 pkg.apk
-rw-r--r-- system system 526091 2012-07-16 15:07 res.zip
Here the
res.zip
holds app resources and is world-readable, while the pkg.apk
file which hold the full APK is only readable by the system and the app's dedicated user (u0_a96
). The actual app containers are stored in /data/app-asec
with filenames in the form pacakge.name-1.asec
. ASEC container management (creating/deleting and mounting/unmounting) is implemented int the system volume daemon (vold
) and framework services talk to it by sending commands via a local socket. We can use the vdc
utility to manage forward locked apps from the shell: # vdc asec list
vdc asec list
111 0 com.mypackage-1
111 0 org.foopackage-1
200 0 asec operation succeeded
# vdc asec unmount org.foopackage-1
200 0 asec operation succeeded
# vdc asec mount org.foopackage-1 000102030405060708090a0b0c0d0e0f 1000
org.foopackage-1 000102030405060708090a0b0c0d0e0f 1000
200 0 asec operation succeeded
# vdc asec path org.foopackage-1
vdc asec path org.foopackage-1
211 0 /mnt/asec/org.foopackage-1
All commands take a namespace ID (based on the package name in practice) as a parameter, and for the
mount
command you need to specify the encryption key and the mount point's owner UID (1000
is system
) as well. That about covers how apps are stored and used, what's left is to find out the actual encryption algorithm and the key. Both are unchanged from the original Froyo apps-to-SD implementation: Twofish with a 128-bit key stored in /data/misc/systemkeys
:shell@android:/data/misc/systemkeys # ls
ls
AppsOnSD.sks
shell@android:/data/misc/systemkeys # od -t x1 AppsOnSD.sks
od -t x1 AppsOnSD.sks
0000000 00 01 02 03 04 05 06 07 08 09 0a 0b 0c 0d 0e 0f
0000020
Forward locking an application is triggered by specifying the
-l
option of the pm install
command or specifying the INSTALL_FORWARD_LOCK
flag to PackageManager
's installPackage*
methods (see sample app).Encrypted apps and Google Play
EncryptionParams
which look very similar to the ContainerEncryptionParams
shown above:class AndroidAppDelivery$EncryptionParams {
private int cachedSize;
private String encryptionKey;
private String hmacKey;
private int version;
}
The encryption algorithm and the HMAC algorithm are always set to 'AES/CBC/PKCS5Padding' and 'HMACSHA1', respectively. The IV and the MAC tag are bundled with the encrypted APK in a single blob. Once all parameters are read and verified, they are essentially converted to a
ContainerEncryptionParams
instance, and the app is installed using the familiar PackageManager.installPackageWithVerification()
method. As might be expected, the INSTALL_FORWARD_LOCK
flag is set when installing a paid app. The OS takes it from here, and the process is the same as described in the previous section: free apps are decrypted and the APKs end up in /data/app
, while an encrypted container in /data/app-asec
is created and mounted under /mnt/asec/package.name
for paid apps.So what does all this mean in practice? Google Play can now claim that paid apps are always transferred and stored in encrypted form, and so can your own app distribution channel if you decide to implement it using the app encryption facilities Jelly Bean provides. The apps have to be made available to the OS at some point though, so if you have root access to a running Android device, extracting a forward-locked APK or the container encryption key is still possible, but that is true for all software-based solutions.
Update: while forward locking is making it harder to copy paid apps, it seems its integration with other services still has some issues. As reported by multiple developers and users here, it currently breaks apps that register their own account manager implementation, as well as most paid widgets. This is due to some services being initialized before
/mnt/asec
is mounted, and thus not being able to access it. A fix is said to be available (no Gerrit link though), and should be released in a Jelly Bean maintenance release.Update 2: It seems that the latest version of the Google Play client, 3.7.15, installs paid apps with widgets and possibly ones that manage accounts in
/data/app
as a (temporary?) workaround. The downloaded APK is still encrypted for transfer. For example: shell@android:/data/app # ls -l|grep -i beautiful
ls -l|grep -i beautiful
-rw-r--r-- system system 6046274 2012-08-06 10:45 com.levelup.beautifulwidgets-1.apk
That's about it for now. Hopefully, more detailed information both about the app encryption OS implementation and design and its usage by Google's Play Store will be available from official sources soon. Until then, get the sample project, fire up OpenSSL and give it a try.