Implement signature-level security on Android services with more than one allowed signature
I'm developing on an application at the moment which contains quite a lot of personal user information - things like Facebook contacts, etc ... Now, one of the things I want to be able to do (and have done, quite effectively) is open up parts of the application to "3rd Party" applications, using Android's build-in inter-process communication protocol (AIDL). So far so good.
Here's the catch: because we're involved in handling quite a lot of personal information, we have to be quite careful about who can and can't access it; specifically, only "Trusted" applications should be able to do so. So the natural way to do this is to use a custom permission within the AndroidManifest.xml file where we declare the services. My problem is this: I want to be able to enact signature-level protection (similar to the normal "signature" permission level), but with a bit of a catch:
I don't only want application signed with our internal signature to be able to access the services. I'd like to be able to build a list of "trusted signatures" & at runtime (or if there's a better way, then maybe some other t开发者_Python百科ime?) be able to check incoming requests against this list of trusted keys.
This would satisfy the security constraints in the same way as the normal "signature" permission level I think - only programs on the "trusted keys list" would be able to access the services, and keys are hard to spoof (if possible at all?) - but with the added bonus that we wouldn't have to sign every application making use of the APIs with our internal team's key.
Is this possible at the moment in Android? And if so, are there any special requirements?
Thanks
I've now found the answer to this question, but I'll leave it for the sake of anyone looking in the future.
I opened up a discussion on android-security-discuss where it was answered. Link: http://groups.google.com/group/android-security-discuss/browse_thread/thread/e01f63c2c024a767
Short answer:
private boolean checkAuthorised(){
PackageManager pm = getPackageManager();
try {
for (Signature sig :
pm.getPackageInfo(pm.getNameForUid(getCallingUid()),
PackageManager.GET_SIGNATURES).signatures){
LogUtils.logD("Signature: " + sig.toCharsString());
if (Security.trustedSignatures.get(sig.toCharsString()) != null) {
return true;
}
}
} catch (NameNotFoundException e) {
// TODO Auto-generated catch block
e.printStackTrace();
}
LogUtils.logD("Couldn't find signature in list of trusted keys! Possibilities:");
for(String sigString : Security.trustedSignatures.keySet()){
LogUtils.logD(sigString);
}
/* Crash the calling application if it doesn't catch */
throw new SecurityException();
}
Where Security.trustedSignatures is a Map of the form:
Map<String,String>().put("public key","some description eg. name");
Put this method inside any code that is being called by the external process (ie. within your interface). Note that this will not have the desired effect inside the onBind() method of your RemoteService.
Great info jelford, but I would suggest instead of storing the entire string of the signature, to store/compare the SHA-1 of the certificate as shown in this answer from matreshkin.
This is similar to how Google handles the Maps Android API, and this will match the output shown via keytool.
private boolean checkAuthorized() throws SecurityException {
PackageManager pm = getPackageManager();
try {
PackageInfo packageInfo = pm.getPackageInfo(pm.getNameForUid(getCallingUid()),
PackageManager.GET_SIGNATURES);
Signature[] signatures = packageInfo.signatures;
byte[] certBytes = signatures[0].toByteArray();
CertificateFactory cf = CertificateFactory.getInstance("X509");
X509Certificate cert = (X509Certificate)cf.generateCertificate(
new ByteArrayInputStream(certBytes));
MessageDigest md = MessageDigest.getInstance("SHA1");
byte[] encodedCert = md.digest(cert.getEncoded());
String hexString = byte2HexFormatted(encodedCert);
Log.d("public certificate SHA-1: " + hexString);
String trustedAppName = trustedCerts.get(hexString);
if (trustedAppName != null) {
Log.d("Found public certificate SHA-1 for " + trustedAppName);
return true;
}
} catch (Exception e) {
Log.e(e, "Unable to get certificate from client");
}
Log.w("Couldn't find signature in list of trusted certs!");
/* Crash the calling application if it doesn't catch */
throw new SecurityException();
}
public static String byte2HexFormatted(byte[] arr) {
StringBuilder str = new StringBuilder(arr.length * 2);
for (int i = 0; i < arr.length; i++) {
String h = Integer.toHexString(arr[i]);
int l = h.length();
if (l == 1) h = "0" + h;
if (l > 2) h = h.substring(l - 2, l);
str.append(h.toUpperCase());
if (i < (arr.length - 1)) str.append(':');
}
return str.toString();
}
精彩评论