In the previous post we introduced code signing as implemented in Android and saw that it is practically identical to JAR signing. Android requires all installed packages to be signed and makes heavy use of the attached code signing certificates in its security model. This is where the major differences with other platforms that use code signing lie, so we will explore the topic in more detail.
Java access control
policytool). At runtime a security manager (if installed) enforces access control by comparing code elements on the stack with the current policy. It throws a SecurityException if the permissions required to access a resource have not been granted to the requesting code source. Java code that runs (or is started in) the browser, such as applets or Java Web Start applications, is automatically run with a security manager installed, while for local applications you need to explicitly set the
java.security.managerin order to install one. In practice, a security manager for local code is only used with some applications servers, and it is usually disabled by default. A wide range of permissions are supported by the platform, the major ones being file and socket-oriented, as well as different types of runtime permissions which control operations ranging from class and library loading to managing the current security manager. By defining multiple code sources and assigning each one specific permissions one can implement fine grained access control for both local and remote code.
As we mentioned though, unless you are in the browser plugin or application server development business chances are you hadn't heard about any of this until the beginning of this year. Just when everyone thought that Java applets were for all intents and purposes dead, they made somewhat of a comeback as a malware distribution medium. A series of vulnerabilities were discovered in the Oracle Java implementation that allow applets to escape the sandbox they run in and reset the security manager, effectively granting themselves full privileges. The exploits used to achieve this employ techniques ranging from reflection recursion to direct memory manipulation to bypass runtime security checks. Oracle has responded by releasing a series of patches, changing the default applet execution policy and introducing more visible warnings to let users know that potentially harmful code is being executed. Naturally, different ways to bypass this are being discovered to catch up.
In short, Java has had full-featured code access control for some time, even though the most widely used implementation appears to be lacking in enforcing it. But let's (finally!) get back to Android now. As the Java code access control mechanism can use code signer identity to define code sources and grant permissions, and Android code is required to be signed, one might expect that our favourite mobile OS would be making use of the Java's security model in some form, just as it does with JAR files. As it turns out, this is not the case. Access control related classes are part of the Java API, and are indeed available in Android. However, looking at the implementation reveals that they are practically empty, with just enough code to compile. In addition, they feature a prominent 'Legacy security code; do not use.' notice. So why bother reviewing all of the above then? Even though Android's access control model is very different from the legacy Java one, it does borrow some of the same ideas, and a comparison is helpful when discussing the design decisions made.
Android security architecture basics
WRITE_SECURE_SETTINGS) have been introduced that can be granted or revoked on demand using the
pm grant/revokecommand (or matching system APIs). The system will show a confirmation dialog showing permissions requested by an app before installing. With the exception of the new 'development' permissions, all requested permissions are permanently granted if the the user allows the install. For a certain messaging app it looks like this in Jelly Bean:
Android permissions are typically implemented by mapping them to Linux groups that have the necessary read/write access to relevant system resources (files or sockets) and thus are ultimately enforced by the Linux kernel. Some permissions are enforced by system daemons or services by explicitly checking if the calling UID is whitelisted to perform a particular operation. The network access permission (
INTERNET) is somewhat of a hybrid: it is mapped to a group (
inet), but since network access is not associated with one particular socket, the kernel checks whether processes trying to open a socket are members of the
inetgroup on each related system call (known as 'paranoid network security').
Each permission has an associated 'protection level' that indicates how the system proceeds when deciding whether to grant or deny the permission. The two levels most relevant to our discussion are
signatureOrSystem. The former is granted only to apps signed with the same certificate as the package declaring the permission, while the latter is granted to apps that are in the Android system image, even if the signer is different.
Besides the built-in permissions, custom permissions can also be defined by declaring them in the app manifest file. Those can be enforced statically by the system or dynamically by app components. Permissions attached to components (activities, services, broadcast receivers or content providers) defined in
AndroidManifest.xmlare automatically enforced by the system. Components can also make use of framework APIs to check whether the calling UID has been granted a required permissions on a case-by-case basis (e.g., only for write operations, etc.). We will introduce other permission related details as necessary later, but you can refer to this Marakana presentation for a more complete and thorough discussion of Android permissions (and more). Of course, some official documentation is also available.
The role of code signing
So what are code signing certificates used for then? Two things: making sure updates for an app are coming from the same author (same origin policy), and establishing trust relationships between applications. Both are implemented by comparing the signing certificate of the currently installed target app with the certificate of the update or related application. Comparison boils down to calling
Arrays.equals()on the binary (DER) representation of both certificates. This method naturally knows nothing about CAs or expiration dates. One consequence of this is that once an app (identified by a unique package name) is installed, updates need to use the exact same signing certificates (with one exception, see next section). While multiple signatures on Android apps are not common, if the original application was signed by more than one signer, any updates need to be signed by the same signers, each using its original signing certificate. This means that if your signing certificate(s) expires, you cannot update your app and need to release a new one instead. This would result in not only losing any existing user base or ratings, but more importantly losing access to the legacy app's data and settings (again, there are some exceptions). The solution to this problem is quite simple: don't let your certificate expire. The currently recommended validity period is at least 25 years, and the Google Play Store requires validity until at least October 2033 (Y2K33?). While technically this only amounts to putting off the problem, proper certificate migration support might eventually be added to the platform. Unfortunately, this means that if your signing key is lost or compromised, you are currently out of luck.
Let's examine the major uses of code signing in Android in detail.
Application authenticity and identity
PacakgeManagerService, no matter if they are pre-installed, downloaded from an app market or side loaded. It keeps a database of currently installed apps, including their signing certificate(s), granted permissions and additional metadata in the
/data/system/packages.xmlfile. A typical entry for a user-installed app might look like this:
userid="10092" ut="13e204816ce" version="1453060">
As you can see above, a package entry specifies the package name, the location of the APK and associated libraries, assigned UID and some additional install metadata such as install and update time. This is followed by the number of signatures and the signing certificate as a hexadecimal string. Since a hex-encoded certificate will usually take up around 2K, the actual certificate contents is listed only once. All subsequent packages signed with the same certificate only refer to it by index, as is the case above. The
packages.xmlto decide whether an update is signed with the same certificate as the original app. The certificate is followed by the list of permissions the package has been granted. All of this information is cached on memory (keyed by package name) at runtime for performance reasons.
Just like user-installed apps, pre-installed apps (usually found in
/system/app) can be updated without a full-blown system update, usually via the Play Store or a similar app distribution service. As the
/systempartition is mounted read-only though, updates are installed in
/data, while the original app remains as is. In addition to a
<package/>entry, such an app will also have a
<updated-package>entry that might look like this:
ft="13cd6667b50" it="13ae93df638" ut="13cd6667b50"
<item name="android.permission.NFC" />
The update (in
/data/app) inherits the original app's permissions and UID. System apps receive another special treatment as well: if an updated APK is installed over the original one (in
/system/app) it is allowed to be signed with a different certificate. The rationale behind this is that if the installer has enough privileges to write to
/system, it can be trusted to change the signing certificate as well. The UID, and any files and permissions are retained. Again, there is an exception though: if the package is part of a shared user (discussed in the next section), the signature cannot be updated, because that would affect other apps as well. In the reverse case, when a new system app signed by a different certificate than that of the currently installed non-system app (with the same package name), the non-system app will be deleted first.
Speaking of system apps, most of those are signed by a number of so called 'platform keys'. There are four different keys in the current AOSP tree, named
releasekeyfor release builds). All packages considered part of the core platform (System UI, Settings, Phone, Bluetooth etc.) are signed with the
platformkey, launcher and contacts related packages -- with the
sharedkey, the gallery app and media related providers -- with the
mediakey, and everything else (including packages that don't explicitly specify the signing key) -- with the
testkey. One thing to note is that the keys distributed with AOSP are in no way special, even though they have 'Google' in the certificate DN. Using them to sign your apps will not give you any specific privileges, you will need the actual keys Google or your carrier/device manufacturer uses. Even though the associated certificates may happen to have the same DN as the ones in AOSP, they are different and very unlikely to be publicly accessible. Custom ROMs are often an exception though, and some, including CyanogenMod, use the AOSP keys, or publicly available keys, as is (there are plans to change this for CyanogenMod though). Sharing the signing key allows packages to work together and establish trust relationships, which we will discuss next.
Inter-application trust relationships
signatureprotection level. With this level, the permission is only granted if the requesting app is signed by the same signer as the package declaring the permission. This can be thought of as a limited form of mandatory access control (MAC). For custom (app-declared) permission, permissions are declared in the package's
AndroidManifest.xmlfile, and are added to the system when it is installed. Just as other package data, permissions are saved in the
/data/system/packages.xmlfile, as children of the
<permissions/>element. Here's how the declaration of a custom permission used by some Google apps looks like:
The entry has the permission name, declaring package and protection level (2 corresponds to
signature) as attributes. When installing a package that requests this permission, the
PackageManagerServicewill perform binary comparison (just as when upgrading packages) of its signing certificate against the certificate of the Google Login Service (the declaring package,
com.google.android.gsf.login) in order to decide whether to grant the permission. A noteworthy detail is that the system cannot grant a permission it doesn't know about. That is, if app A declares permission 'foo' and app B uses it, app B needs to be installed after app A, otherwise you will get a warning at install time and the permission won't be granted. Since app installation order typically cannot be guaranteed, the usual workaround for this situation is to declare the permission in both apps. Permissions can also be added and removed dynamically using the
PackageManger.addPermission()API (know as 'dynamic permissions'). However, packages can only add permissions to a permission tree they define (i.e., you cannot add permissions to another app).
That mostly explains custom permissions, but what about built-in, system permissions with
signatureprotection level? They work exactly as custom permissions, except that the package that defines them is special. They are defined in the
androidpackage, sometimes also referred as 'the framework' or 'the platform'. The core android framework is the set of classes shared by system services, some of them exposed via the public SDK. Those are packaged in JAR files found in
/system/framework. Interestingly, those JAR files are not signed: while Android borrows the JAR format to implement code signing, only APK files are signed, not actual JARs. The only APK file in the framework directory is
framework-res.apk. As the name implies, it packages framework resources (animation, drawables, layouts, etc.), but no actual code. Most importantly, it defines the
androidpackage and system permissions. Thus any app trying to request a system-level signature permission needs to be signed with the same certificate as the framework resource package. Not surprisingly, it is signed by the
platformkey discussed in the previous section (usually found in
build/target/product/security/platform.pk8|.x509.pem). The associated certificate may looks something like this for an AOSP build:
Version: 3 (0x2)
Serial Number: 12941516320735154170 (0xb3998086d056cffa)
Signature Algorithm: md5WithRSAEncryption
Issuer: C=US, ST=California, L=Mountain View, O=Android, OU=Android,
Not Before: Apr 15 22:40:50 2008 GMT
Not After : Sep 1 22:40:50 2035 GMT
Subject: C=US, ST=California, L=Mountain View, O=Android, OU=Android,
Shared user ID
AndroidManifest.xml's root element. The 'user ID' specified in the manifest needs to be in Java package format (containing at least one '.') and is used as an identifier, much like package names for applications. If the specified shared UID does not exist it is simply created, but if another package with the same shared UID is already installed, the signing certificate is compared to that of the existing package, and if they do not match, a
INSTALL_FAILED_SHARED_USER_INCOMPATIBLEerror is returned and installation fails. Adding the
sharedUserIdto the new version of an already installed app will cause it to change its UID, which would result in losing access to its own files (that was the case in some previous Android versions). Therefore, this is disallowed by the system, and it will reject the update with the
INSTALL_FAILED_UID_CHANGEDerror. In short, if you plan to use shared UID for your apps, you have to design for it from the start, and have them use it since the very first release.
A shared UID is a first class object in the system's
packages.xmland is treated much like apps are: it has associated signing certificate(s) and permissions. Android has 5 built-in shared UIDs, automatically added when the system is bootstrapped:
Here's how the
systemshared UID is defined:
<shared-user name="android.uid.system" userId="1000">
<cert index="4" />
<item name="android.permission.MASTER_CLEAR" />
<item name="android.permission.CLEAR_APP_USER_DATA" />
<item name="android.permission.MODIFY_NETWORK_ACCOUNTING" />
As you can see, apart from having a bunch of scary permissions (about 60 on a 4.2 device), the declaration is very similar to the
packagedeclarations we showed previously. Conversely, packages that are a part of a shared UID, do not have an associated granted permission list. They inherit the permissions of the shared UID, which are a union of the permissions requested by all currently installed packages with the same shared UID. A side effect of this is, that if a package is part of a shared UID, it can access APIs it hasn't explicitly requested permissions for, as long as some package with the same shared UID has already requested them. Permissions are dynamically removed from the
<shared-user/>declaration as packages are installed or uninstalled though, so the set of available permissions is neither guaranteed nor constant. Here's how the declaration of a system app (KeyChain) that runs under a shared ID looks like. It references the shared UID with the
sharedUserIdattribute and lacks explicit permission declarations:
<cert index="4" />
The shared UID is not just a package management construct, it actually maps to a shared Linux UID at runtime as well. Here is an example of two system apps running under the
system 5901 9852 845708 40972 ffffffff 00000000 S com.android.settings
system 6201 9852 824756 22256 ffffffff 00000000 S com.android.keychain
The ultimate trust level on Android is, of course, running in the same process. Since apps that are part of the same shared UID already have the same Linux UID and can access the same system resources, this is not a problem. It can be requested by specifying the same process name in the
processattribute of the
<application/>element in the manifest for all apps that need to run in one process. While the obvious result of this is that the apps can share memory and communicate directly instead of using RPC, some system services allow special access to components running in the same process (for example direct access to cached passwords or getting authentication tokens without showing UI prompts). Google apps take advantage of this by requesting to run in the same process as the login service in order to be able to sync data in the background, without user interaction (e.g., Play Services and the Google location service). Naturally, they are signed withe same certificate and part of the