This is the first of a series of posts about Android security. This one will describe the security around the applications, their signature/certificates as well as their permissions.
Introduction & Theory
Android requires that each application be signed with the developer’s digital keys to enforce signature permissions and application requests to use shared user ID or target process.
The core Android platform uses four keys to maintain security of core platform components:
- platform: a key for packages that are part of the core platform.
- shared: a key for things that are shared in the home/contacts process.
- media: a key for packages that are part of the media/download system.
- testkey: the default key to sign with if not otherwise specified.
These keys are used to sign applications separately for release images and are not used by the Android build system. The build system signs packages with the testkeys provided by default in build/target/product/security/, but this setting can be overwritten by specifying another path to PRODUCT_DEFAULT_DEV_CERTIFICATE.
In our builds, we use the keys located under device/fsl/common/security/ as specified in device/fsl/imx6/imx6.mk. Because those keys are part of a publicly available source tree, they should never be used for production devices. Instead, you should generate your own private keys for building your production images.
Each key comes in two files: the certificate, which has the extension .x509.pem, and the private key, which has the extension .pk8. The private key should be kept secret and is needed to sign a package. The key may itself be protected by a password — a reasonable strategy is to store your keys in source control along with the code — but keep them protected by a password known only to the people who make final releases. The certificate, in contrast, contains only the public half of the key, so it can be distributed widely. It is used to verify a package has been signed by the corresponding private key.
One more thing to be noted is that Android’s Package Manager uses an .apk signature in two ways:
- When an application is replaced, it must be signed by the same key as the old application in order to get access to the old application’s data.
- If you change the certificate of an app already installed, it has to be un-installed first
- If two or more applications want to share a user ID (so they can share data, etc.), they must be signed with the same key.
First the term “permissions” can mean two very different things:
- Standard UNIX/Linux file system permissions, or
- Android (JAVA) API permissions as described on the Android developer website
~/$ adb shell 'ls -l /dev/ttymxc2' crw-rw---- bluetooth net_bt_stack 207, 18 1970-01-02 00:00 ttymxc2
The above means that ttymxc2 can only be accessed by a process having a bluetooth UID or the net_bt_stack GID. Those nodes permissions are set in the ueventd.rc.
Now looking at the process list:
~/$ adb shell 'ps | grep bluetooth' bluetooth 190 1 956 204 800edf18 76efb8bc S /system/bin/uim-sysfs bluetooth 832 194 714732 23564 ffffffff 2b071834 S com.android.bluetooth
This means that only those two applications can read/write for that node.
What about a standard JAVA application UID? Android’s package manager creates a unique user id (UID) and group (GID) when it installs an application and these are retained until the application is un-installed.
~/$ adb shell 'ps | grep gallery' u0_a27 1107 194 692700 19260 ffffffff 2b071834 S com.android.gallery3d
This output shows a typical UID assigned to a standard application (u0_a27), however some critical apps can require a specific UID such as system:
~/$ adb shell 'ps | grep keychain' system 1136 194 688724 18220 ffffffff 2b071834 S com.android.keychain
These parts (UID and GID) allow the kernel to enforce restrictions on files and devices. It is possible (and tempting) to allow “World” access to particular files, and we do it on occasion (see /dev/ttymxc0 in ueventd.rc), but this is somewhat dangerous.
The proper way to communicate to a driver in Android is for the JAVA application to call a standard API that uses a system service with the right permissions for the hardware. This brings us to the more Android-specific API permissions.
A basic Android application has no permissions associated with it by default, meaning it cannot do anything that would adversely impact the user experience or any data on the device. To make use of protected features of the device, you must include in your AndroidManifest.xml one or more tags declaring the permissions that your application needs.
Here is an example, if an application needs to get frames from the camera, it needs to ask for the CAMERA permission in its AndroidManifest.xml.
However, looking at the list of permissions you can notice that some permissions are marked as “Not for use by third-party applications“. This means that said permission can only be requested by a “system application” which means that it need to be signed with the platform keys. A good example is the REBOOT permissions which we are glad not every application can use.
It is assumed below that you are in possession of our last release source tree under your ~/myandroid folder.
How do I generate my own keys?
~/myandroid$ development/tools/make_key testkey '/C=US/ST=Arizona/L=Chandler/O=Boundary Devices/emailAddressemail@example.com' ~/myandroid$ development/tools/make_key platform '/C=US/ST=Arizona/L=Chandler/O=Boundary Devices/emailAddressfirstname.lastname@example.org' ~/myandroid$ development/tools/make_key shared '/C=US/ST=Arizona/L=Chandler/O=Boundary Devices/emailAddressemail@example.com' ~/myandroid$ development/tools/make_key media '/C=US/ST=Arizona/L=Chandler/O=Boundary Devices/emailAddressfirstname.lastname@example.org'
Once this is done, you can either overwrite the keys located under device/fsl/common/security/ or copy them in your own device folder and modify the PRODUCT_DEFAULT_DEV_CERTIFICATE variable in device/fsl/imx6/imx6.mk.
How do I re-sign an application with my keys?
In the source tree resides a JAVA tool that allow to sign/re-sign applications and package. This tool, signapk.jar, is built automatically when building any target available. It can be used as follows to sign an application:
~/myandroid$ java -jar out/host/linux-x86/framework/signapk.jar -w .x509.pem .pk8 app_name.apk app_name-signed.apk
Note for Android 7 (Nougat) and above: you now need to provide some extra parameters to the java tool:
~/myandroid$ java -Xmx2048m -Djava.library.path=out/host/linux-x86/lib64 -jar out/host/linux-x86/framework/signapk.jar -w .x509.pem .pk8 app_name.apk app_name-signed.apk
How do I re-sign an update package with my keys?
The same goes for an update package (zip file):
~/myandroid$ java -jar out/host/linux-x86/framework/signapk.jar -w .x509.pem .pk8 package.zip package-signed.zip
How do I make my application a system app?
As said in the first section, an application considered a system app when signed with platform keys. It has nothing to do with its UID nor its location. Many people only suggest to copy the apk file from /data/app to /system/app but it doesn’t make it a system application per se. Here is an example to sign with the keys used in our build:
~/myandroid$ java -jar out/host/linux-x86/framework/signapk.jar -w device/fsl/common/security/platform.x509.pem device/fsl/common/security/platform.pk8 app_name.apk app_name-signed.apk
How do I grant my app the system uid?
It is actually very simple, you just need to add one line to your AndroidManifest.xml.
However this also requires your application to be signed with the platform keys.
Should all my apps be signed with platform keys then?
Signing with the platform keys gives access to many API that can be dangerous so you have to be careful. Moreover, it depends if your application is to be used across many different platforms or just one. As soon as your application requires to be signed with the platform keys, it will either only work to this specific platform or need to be re-sign for other products.
Remember that with great power comes great responsibility.
What about root uid?
A JAVA application cannot have a root UID but as you may have seen in the past, some applications rely on the su binary to execute some commands with root permissions. A su solution as been integrated in our latest release.
But it is to be noted that the init process has a root uid which means that any process forked from it would have the same uid by default. So if you have a native process that requires root permissions, declare it as a service in init.rc in order to be forked with the right permissions.
In order to demonstrate was has been said above, a repository containing a test application has been created:
More specifically there are two branches of interest:
- reboot_app: very simple application which only has one button that triggers a reboot() call that requires the REBOOT permission
- reboot_app_systemuid: same application but with the system uid
Why two different branches? Because we don’t want people to be confused about the “system app” term that doesn’t mean system uid. But we still wanted you to witness the system uid and how simple it is. So here is a step-by-step process to try the app:
- Download the source code
~/$ git clone email@example.com:boundarydevices/android-tests.git -b reboot_app
- Import the app into Android Studio
- When Android Studio starts, click on “Import project (Eclipse, gradle etc…)”
- Select the root of the android-tests source code
- Build and deploy the app by click on the play icon (please don’t make fun of the UI)
- Click on the Reboot button
- You can see the application crashed as expected because the application requires a permissions that only system app. It is time to sign it with the proper key:
~/android-tests$ java -jar security/signapk.jar -w security/platform.x509.pem security/platform.pk8 app/build/outputs/apk/app-debug.apk app-debug-signed.apk ~/android-tests$ adb shell 'pm uninstall com.boundarydevices.rebootapp' ~/android-tests$ adb install -r app-debug-signed.apk
- Note that we had to un-install the previous package first as the certificates have changed
- We can check our application uid and see it hasn’t changed, just its signature:
~/android-tests$ adb shell 'ps | grep boundary' u0_a47 1518 194 832992 26316 ffffffff 2b071834 S com.boundarydevices.rebootapp
- Click again on the reboot button
From this moment on, our application can access any API marked as “Not for use by third-party applications“. The README under the security folder of android-tests also describes how to get Android Studio to automatically sign your application with the platform keys.
But we know some customers are interested in having an app with system uid, here is the procedure to achieve it.
- Switch to the second branch
~/android-tests$ git checkout origin/reboot_app_systemuid -b reboot_app_systemuid
- Build the application and sign it with platform keys as described above
- Same you have to first un-install the previous app as Package Manager would otherwise complain that the uid changed
- Start the application and check the process uid
~/android-tests$ adb shell 'ps | grep boundary' system 1371 194 834036 26272 ffffffff 2b0b3834 S com.boundarydevices.rebootapp
Finally, for those who do not have Android Studio but the AOSP source tree, an Android.mk has been added to build that single package easily, see the Useful Tips from our latest release.