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?

In a perfect world, a working Public Key Infrastructure (PKI) takes care of issuing, distributing and revoking certificates as necessary. All that a system needs to verify the identities of previously unknown machines and users are a few trust anchor certificates. In practice, though, there are number of issues. Those have been known for some time, but the recent breaches in top-level CAs have shown that the problems and their consequences are far from theoretical. Probably the biggest PKI issue is that revocation of root certificates is not really supported. Most OSes and browsers come with a pre-configured set of trusted CA certificates (dozens of them!) and when a CA certificate is compromised there are two main ways to handle it: 1. tell users to remove it from the trust store; or, 2. issue an emergency update that removes the affected certificate. Expecting users to handle this is obviously unrealistic, so that leaves the second option. Windows modifies OS trust anchors by distributing patches via Windows Update, and browser vendors simply release a new patch version. However, even if an update removes a CA certificate from the system trust store, a user can still install it again, especially when presented with a 'do this, or you can't access this site' ultimatum. To make sure removed trust anchors are not brought back, the hashes of their public keys are added to a blacklist and the OS/browser rejects them even if they are in the user trust store. This approach effectively revokes CA certificates (within the scope of the OS/browser, of course) and takes care of PKI's inability to handle compromised trust anchors. However, it's not exactly ideal: even an emergency update takes some time to prepare, and even after it is out some users won't update right away, no matter how often they are being nagged about it. CA compromises are relatively rare and widely publicized though, so it seems to work OK in practice (for now, at least).

While CA breaches are fairly uncommon, end entity (EE) key compromise occurs much more often. Whether due to a server breach, stolen laptop or a lost smart card, it happens daily. Fortunately, modern PKI systems have been designed with this in mind -- CAs can revoke certificates and publish revocation information in the form of CRLs, or provide online revocation status using OCSP. Unfortunately, this doesn't really work in the real world. Revocation checking generally requires network access to a machine different from the one we are trying to connect to, and as such has a fairly high failure rate. To mitigate this most browsers do their best to fetch fresh revocation information, but if this fails for some reason, they simply ignore the error (soft-fail), or at best show some visual indication that revocation information is not available. To solve this Google Chrome has opted to disable online revocation checks altogether, and now uses its online update mechanism to proactively push revocation information to browsers, without requiring an application update or restart. Thus Chrome can have an up-to-date local cache of revocation information which makes certificate validation both faster and more reliable. This is yet another blacklist (Chrome calls it a 'CRL set'), this time based on information published by each CA. The browser vendor effectively managing revocation data on the user's behalf is quite novel, and not everyone thinks it's a good idea, but it has worked well so far.

Android certificate blacklisting

In Android versions prior to 4.0 (Ice Cream Sandwich, ICS), the system trust store was a single Bouncy Castle key store file. Modifying it without root permissions was impossible and the OS didn't have a supported way to amend it. That meant that adding new trust anchors or removing compromised ones required an OS update. Since, unlike regular desktop OSes, updates are generally handled by carriers and not the OS vendor, they are usually few and far between. What's more, if a device doesn't sell well, it may never get an official update. In practice this means that there are thousands of devices that still trust compromised CAs, or don't trust newer CAs that have issued hundreds of web site certificates. ICS changed this by making the system trust store mutable and adding an UI, as well as an SDK API, that allows for adding and removing trust anchors. This didn't quite solve PKI's number one problem though -- aside from the user manually disabling a comprised trust anchor, an OS update was still required to blacklist a CA certificate. Additionally, Android does not perform online revocation checks when validating certificate chains, so there was no way to detect compromised end entity certificates, even if they have been revoked.

This finally leads us to the topic of the article -- Android 4.1 (Jelly Bean, JB) has taken steps to allow for online update of system trust anchors and revocation information by introducing certificate blacklists. There are now two system blacklists:
  • a public key hash blacklist (to handle compromised CAs)
  • a serial number blacklist (to handle compromised EE certificates)
The certificate chain validator component takes those two lists in consideration when verifying web site or user certificates. Let's look at how this implemented in a bit more detail.

Android uses a content provider to store OS settings in a system databases. Some of those settings can be modified by third party apps holding the necessary permissions, while some are reserved for the system and can only be changed by going through the system settings UI, or by another system application. The latter are known as 'secure settings'. Jelly Bean adds two new secure settings under the following URIs:
  • content://settings/secure/pubkey_blacklist
  • content://settings/secure/serial_blacklist
As the names imply, the first one stores public key hashes of compromised CAs and the second one a list of EE certificate serial numbers. Additionally, the system server now starts a 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
Why write them to disk when they are already available in the settings database? Because the component that actually uses the blacklists is a standard Java CertPath API class that doesn't know anything about Android and it's system databases. The actual class, 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.
The certificate path validator component is used throughout the whole system, so blacklists affect both applications that use HTTP client classes and the native Android browser and WebView. As mentioned above, modifying the blacklists requires system permissions, so only core system apps can use it. There are no apps in the AOSP source that actually call those APIs, but a good candidate to manage blacklists are the Google services components, available on 'Google experience' devices (i.e., devices with the Play Store client pre-installed). Those manage Google accounts, access to Google services and provide push-style notifications (aka, Google Client Messaging, GCM). Since GCM allows for real-time server-initiated push notifications, it's a safe bet that those will be used to trigger certificate blacklist updates (in fact, some source code comments hint at that). This all sounds good on paper (well, screen actually), but let's see how well it works on a real device. Enough theory, on to

Using Android certificate blacklisting

As explained above, the API to update blacklists is rather simple: essentially two secure settings keys, the values being the actual blacklists in hex-encoded form. Using them requires system permissions though, so our test application needs to either live in /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.


The app allows us to install a CA certificate to the system trust store (using the 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.

Some experimentation reveals that while the 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

In Jelly Bean, Android takes steps to get on par with the Chrome browser with respect to managing certificate trust. It introduces features that allow for modifying blacklists dynamically: based on push notifications, and without requiring a system update. While the current implementation has some rough edges and does require a reboot to apply updates, once those are smoothed out, certificate blacklisting will definitely contribute to making Android more resilient to PKI-related attacks and vulnerabilities.

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
The first two features are mostly self-explanatory, but the rest merit some exploration. Let's look into each one in turn.

System key store improvements

As we have already discussed, the system key store in Android is provided by a native daemon that encrypts secrets using a key derived from the device unlock password, stores them on disk and regulates key access based on UID. In ICS and previous versions, the 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
In order to use a key stored using the pre-JB implementation, we needed to first export the raw key bytes, and then use them to initialize an actual key object. Thus even though the key blob is encrypted on disk, the plain text key eventually needed to be exposed (in memory). The new commands let us generate an RSA key pair and sign or verify data without the key ever leaving the key store. There is however no way to specify key size for generated keys, it is fixed at 2048 bits. There is no restriction for importing keys though, so shorter (or longer keys) can be used as well (confirmed for 512-4096 bit keys). Importing requires that keys are encoded using the PKCS#8 format. The sign operation doesn't do any automatic padding and therefore requires input data to be equal to the RSA key size (it's essentially performs raw RSA encryption using the private key). 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

The next addition to Android's security system is the keystore-backed OpenSSL engine (pluggable cryptographic module). It only supports loading of and signing with RSA private keys, but that is usually enough to implement key-based authentication (such as SSL client authentication). This small engine makes it possible for native code that uses OpenSSL APIs to use private keys saved in the system key store without any code modifications. It also has a Java wrapper (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

And finally, time for our feature presentation -- the 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
If those look familiar, this is because they are pretty much the same as the newly added 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

Let's look at how this is implemented on Galaxy Nexus, starting from the lowest level, the actual hardware. Galaxy Nexus is built using the Texas Instruments OMAP4460 SoC, which integrates TI's M-Shield (not to be confused with nShield) mobile security technology. Among other things, M-Shield provides cryptographic acceleration, a secure random number generator and secure on-chip key storage. On top of that sits TI's Security Middleware Component (SMC), which is essentially a Trusted Execution Environment (TEE, Global Platform specs and white paper) implementation. The actual software is by Trusted Logic Mobility, marketed under the name Trusted Foundations. Looking at this TI white paper, it looks like secure key storage was planned for ICS (Android 4.0), but apparently, it got pushed to back to Jelly Bean (4.1). Cf. this statement from the white paper: 'Android 4.0 also introduces a new keychain API and underlying encrypted storage that are protected by M-Shield hardware security on the OMAP 4 platform.'.  

With all the buzzwords and abbreviations out of the way, let's say a few words about TEE. As the name implies, TEE is defined as a logical execution environment, separate from the device's main OS, referred to as the REE (Rich Execution Environment). Its purpose is both to protect assets and execute trusted code. It is also required to be protected against certain physical attacks, although the level of protection is typically lower that that of a tamper-resistant module such as a Secure Element (SE). The TEE can host trusted applications (TAs) which utilize the TEE's services via the standardized internal APIs. Those fall under 4 categories:
  • trusted storage
  • cryptographic operations
  • time-related
  • arithmetical (for dealing with big numbers)
Applications running in the REE (the Android OS and apps) can only communicate with TAs via a low level Client API (essentially sending commands and receiving responses synchronously, where the protocol is defined by each TA). The Client API also lets the REE and TA applications share memory in a controlled manner for efficient data transfer.

Finally, let's see how all this is tied together in the GN build of Jelly Bean. A generic PKCS#11 module (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.

However, it turns out that the hardware-backed 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

Then all we need to do is reboot the device to have it load the new module (otherwise it will continue to use the software-only 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

So where are the actual keys? It turns out they are in the /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

Currently installing a PKCS#12 packaged key and certificate via the public 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.

The OMAP4 chip is advertised as having hardware accelerated cryptographic operations, so let's see how RSA key generation, signing and verification measure up against the default Android software implementations:

Average 2048-bit RSA operation speed on Galaxy Nexus
Crypto Provider/OperationKey generation Signing Verification
Bouncy Castle2176.20 [ms] 34.60 [ms]1.90 [ms]
OpenSSL2467.40 [ms] 29.80 [ms] 1.00 [ms]
TEE3487.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 the keymater 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

The previous version of this article was based on Eclipse framework source packages and binary system images, and was missing a few pieces. As Jelly Bean source has now been open sourced, the discussion below has been revised and is now based on the AOSP code (4.1.1_r1). If you are coming back you might want to re-read this post, focusing on the second part.

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
The first two don't provide any options or particular insight into the underlying implementation, so let's explore the third one. Looking at the 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>

The --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. 

The 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

'Forward locking' popped up around the time ringtones, wallpalers and other digital 'goods' started selling on mobile (feature) phones. The name comes from the intention -- stop users from forwarding files they have bought to their friends and family. The main digital content on Android were originally apps, and as paid apps gained popularity, sharing (and later re-selling those) was becoming a problem. Application packages (APKs) have been traditionally world readable on Android, which made extracting apps from even a production device relatively easy. While world-readable app files might sound like a bad idea, it's rooted in Android's open and extensible nature -- third party launchers, widget containers and utility apps can easily inspect APKs to extract icons, widget definitions available intents, etc. In an attempt to lock down paid apps without losing any of the OS flexibility, Android introduced forward locking (aka, 'copy protection'). The idea was to split app packages into two parts -- a world-readable part, containing resources and the manifest (in /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.

In Jelly Bean, the forward locking implementation has been re-designed and now offers the ability to store APKs in an encrypted container that requires a device-specific key to be mounted at runtime. Let's look into the implementation in a bit more detail.

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's device-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

All of this is quite interesting, but as we have seen, installing apps, encrypted or otherwise, requires system permissions, so it can only be used by custom carrier Android firmware and probably the next version of your friendly CyanogenMod ROM. Currently the only app that takes advantage of the new encrypted apps and forward locking infrastructure is the Play Store (who comes up with those names, really?) Android client. Describing exactly how the Google Play client works would require detailed knowledge of the underlying protocol (which is always a moving target), but a casual look at the newest Android client does reveal a few useful pieces of information. Google Play servers send quite a bit of metadata about the app you are about to download and install, such as download URL, APK file size, version code and refund window. New among those are the 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.