Book Excerpt: Public Key Validation
The following is an excerpt from Version 6.8 of “The Busy Coder’s Guide to Android Development”, with slight modifications to fit the blog format:
We sign our apps with signing keys all the time. By default, we are signing with a so-called “debug signing key”, created automatically by the build tools. For production, we sign with a different signing key. The primary use of that signing key is to determine equivalence of authorship:
-
Is this APK, representing an upgrade to an already-installed app, signed by the same signing key that signed that app?
-
Is this APK, that requests firmware-defined
signature
-level permissions, signed by the same signing key that signed the firmware?
However, as it turns out, information about the public key that signed an APK is visible
to us, for our own APK as well as for any other APK on the device. We can leverage
that to help determine whether a given APK was signed by something we recognize.
This goes above and beyond using Android’s built-in signature-based defenses (e.g., using
a custom signature
-level permission).
Scenarios
There are several scenarios in which we might imagine that we could employ our own public key validation. How well the technique will work, though, depends on what we are checking and the nature of the attack we are defending against.
Checking Yourself
You might consider checking your own app’s public key. After all, if your app is not signed with your production signing key, something very strange is going on, and the natural reaction is that “something strange” is unlikely to be a good thing for you.
However, there are some issues here.
First and foremost, checking your own signing key assumes that whatever caused you to not be signed by that key did not also modify your validation algorithm. For example, suppose that you validate your signing key to determine if somebody perhaps reverse-engineered and modified your app, perhaps to remove some license checks. This will only catch an attacker that removed the licensing checks and did not also remove your signature validation, or modify the validation to use the attacker’s signing key. While it is possible that an attacker will modify one part but not another, it remains unclear how well this defense will work in practice.
Also, bear in mind that you, as a developer, may be opting into services that intentionally change your app’s signature. Various providers will “wrap” your app, whether for interstitial ad banners or for quasi-DRM. There are three possible ways that they wrap your app:
-
They sign it with their signing key, which means that your runtime validation of the key will fail, as your app is now signed by their key, not yours. This is also very risky, as if for whatever reason you are no longer able to use their service (e.g., they go out of business), you may have difficulty in upgrading your app, as you will not have the right key to use.
-
They sign it with your signing key, either one that you upload, or one that they generate for you. In this case, your runtime public key validation logic could still work. On the other hand, now this other firm is perfectly capable of upgrading your app, or shipping other apps, signed with your production signing key, and this has its own set of risks.
-
They allow you to download the “wrapped” app and have you sign it yourself with your own signing key. This is the best alternative from a security standpoint, but it is the most tedious, as now you have additional work to do to publish your app.
Checking Arbitrary Other Apps
What will tend to be more reliable is to check other applications’ public keys. While they might have been cracked, it is unlikely that the same attacker also attacked your app, and so you can help detect problems in others.
For example, let us consider a specific scenario: a client-side JAR for integration to a third-party app.
This book outlines many forms of IPC, from content providers to remote services to broadcast
Intent
objects. If you are creating an app that offers such IPC endpoints, you may wish to
consider also shipping a JAR to make using those endpoints a bit easier. You might create
a library that handles all of the details of sending commands to your remote service,
or you might create a library that provides a wrapper around the AIDL-generated Java proxy
classes for remote binding.
Another thing such a JAR could do is check the integrity of your app. The JAR’s code is in the client’s app, not yours, and while your app might be cracked, the client’s app might not. You could check the validity of the public key of your own app from the client’s app, and fail if there is a detected problem.
This might be especially important depending upon the nature of the app and the JAR that is providing access to it. If the app is an app offering on-device payments (e.g., a Google Wallet sort of app), and the app offers an API for other apps to do payments, it is fairly important that those other apps can trust the payment app. By checking the public key, your JAR can help provide that level of trust… or at least ensure that nobody else has done something specifically to degrade that trust.
This is particularly important for avoiding device-hosted man-in-the-middle attacks on
your IPC from client apps to your app. In an ideal world, you would only allow IPC
via signature
-level permissions, but that will not work in cases where third parties
are writing the clients.
If your IPC is based upon a service (command pattern or binding
pattern), if multiple service implementations all advertise the same <intent-filter>
,
Android needs to decide which service will handle the request. First, it will take
into account the android:priority
value on the <intent-filter>
(even though this
behavior is currently undocumented). For multiple services with the same priority (e.g.,
no priority specified), the first one that was installed will be the one that is chosen.
In either case, the client has
no way to know, short of examining the service’s public key, whether the service that will
respond to the requests for IPC is the legitimate service or something else advertising
that it supports the same Intent
action. Even with Android 5.0 blocking your ability
to bind via an implicit Intent
, you wind up with the same sorts of problems
when you use resolveService()
to try to determine the ComponentName
of the service to make an explicit Intent
for it.
The Easy Solution: SignatureUtils
The author of this book has published the CWAC-Security library.
Among other things, this library has a SignatureUtils
class that makes it relatively
easy for you to compare the signature of some Android app to a known good value.
All you need to do is call the static getSignatureHash()
method, supplying some
Context
(any will do) and the package name of the app that you wish to check. This
will return the SHA-256 hash of the signing key of the app, as a set of capitalized,
colon-delimited hex values.
You can get the same sort of hash by running the Java 7 version of keytool
.
Hence, if the app you wish to test is another one of yours, perhaps signed
with a different signing key, you can use keytool
to get the value to compare
with the result of getSignatureHash()
. Or, during development, create a little utility app
that will dump the getSignatureHash()
value for the third-party app, and run it
on a device containing a known good version of that app (i.e., one that does not
appear to have been replaced by malware).
Ideally, over time, we will be able to get app developers to publish their SHA-256 hashes on their Web sites, as another means of getting a known value of the hash to compare at runtime.
If you determine that getSignatureHash()
does not return the right value, this
means that the app that is installed on the device is written by somebody other than the
app’s original author. Often times, this will mean the app has malware in it. It
is up to you to determine how you wish to respond to this scenario:
-
Alert the user?
-
Send data back to your server, or to your analytics collection point, with details of the bad APK?
-
Block usage of your app, or usage of features that depend upon the flawed third party?
-
Something else?
Examining Public Keys
Under the covers, SignatureUtils
uses PackageManager
and related classes to examine what they somewhat erroneously refer to as “signatures”. The
MiscSecurity/SigDump
sample project demonstrates how to browse the list of installed packages and see a decoded public
key on the screen for a package that we select, plus dump the “signature” as a binary file
for later comparison using another app.