signature
-level permissions are great, but fairly inflexible: the apps have to be signed by the same signing key. There are many cases in which we will want to check to see if another app is signed by the expected key, but that key is not our key. In your case, you have two keys. In other cases, one might be checking the signature of some partner app — confirming, for example, that the PayPal app you are about to send the user to is really the PayPal app and not malware that has replaced the PayPal app.
To validate the signature of another app, you can use PackageManager
. For example, here is the now-current edition of my SignatureUtils
class from my CWAC-Security library:
/***
Copyright (c) 2014 CommonsWare, LLC
Licensed under the Apache License, Version 2.0 (the "License"); you may
not use this file except in compliance with the License. You may obtain
a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package com.commonsware.cwac.security;
import android.content.Context;
import android.content.pm.PackageManager;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
public class SignatureUtils {
public static String getOwnSignatureHash(Context ctxt)
throws NameNotFoundException,
NoSuchAlgorithmException {
return(getSignatureHash(ctxt, ctxt.getPackageName()));
}
public static String getSignatureHash(Context ctxt, String packageName)
throws NameNotFoundException,
NoSuchAlgorithmException {
MessageDigest md=MessageDigest.getInstance("SHA-256");
Signature sig=
ctxt.getPackageManager()
.getPackageInfo(packageName, PackageManager.GET_SIGNATURES).signatures[0];
return(toHexStringWithColons(md.digest(sig.toByteArray())));
}
// based on https://mcmap.net/q/24240/-in-java-how-do-i-convert-a-byte-array-to-a-string-of-hex-digits-while-keeping-leading-zeros-duplicate
public static String toHexStringWithColons(byte[] bytes) {
char[] hexArray=
{ '0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B',
'C', 'D', 'E', 'F' };
char[] hexChars=new char[(bytes.length * 3) - 1];
int v;
for (int j=0; j < bytes.length; j++) {
v=bytes[j] & 0xFF;
hexChars[j * 3]=hexArray[v / 16];
hexChars[j * 3 + 1]=hexArray[v % 16];
if (j < bytes.length - 1) {
hexChars[j * 3 + 2]=':';
}
}
return new String(hexChars);
}
}
I use PackageManager
to get the Signature
for the given package, given its application ID. Despite the name, Signature
is really the public key of the keypair used to sign the app. The output of getSignatureHash()
is a colon-delimited set of hex character pairs, representing the SHA256 hash of the public key... the same value you get from using the Java 7+ keytool
command.
In your case, you are trying to determine, on the fly, whether an incoming operation is from the desired app, and if that desired app is the right one (versus repackaged malware, for example). In your case, Binder.getCallingUid()
will give you the Linux UID of the app that triggered the IPC that triggered your code. PackageManager
can give you the application ID of the app for that UID (ignoring android:sharedUserId
scenarios). You would then see if that application ID is the expected value, and if it is, check to see if the hashed signing key is the expected value. If either test fails, in the words of an arm-flailing robot, "Danger, Will Robinson! Danger!".
One significant caveat is that some developers will publish apps through channels where those apps get re-signed. Most notable here is the Amazon AppStore for Android, where Amazon intentionally wraps your app in their own quasi-DRM and signs that app using a key that they generate on your behalf. That's not the same key that you would necessarily be using elsewhere, so there may be multiple valid signature hashes that you would need to compare.
getCallingUid()
returns from inside some API method of your provider (e.g.,query()
). You might be able to use that to validate the caller and return empty results (and ignore insert/update/delete ops) if the caller is not one of your apps. – NakasujiSignatureUtils
class: github.com/commonsguy/cwac-security/#usage-signatureutils – Nakasuji