24

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 time?) 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

jelford
  • 2,625
  • 1
  • 19
  • 33

2 Answers2

18

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.

jelford
  • 2,625
  • 1
  • 19
  • 33
  • can't we simply check the package name in the onbind and if the unauthorized app. package try to access then returning the null each returning the ibinder object. – kamal_tech_view Mar 19 '14 at 11:10
  • 1
    @kamal_tech_view Unfortunately I don't think that approach will work as you expect, since `onBind()` is only called once, by the operating system (see here: https://groups.google.com/d/msg/android-security-discuss/4B9jwsAkp2c/QJc4A5FSWCMJ). Note with regard to the advice to use a factory to protect the sensitive part of the system at the _first_ call-point, I haven't looked into it but I think you'd want to check the settings on the current `SecurityManager` (you might find the called can get your hidden fields via introspection & avoid your check). – jelford Mar 19 '14 at 17:34
  • Thanks for posting this solution. Do you populate the Map on each build you release, from a static list of trusted sources, or do you fetch the trusted sources periodically from a server? – brandall Mar 05 '16 at 18:47
  • @brandall we just baked the list of trusted keys into the application at build time, but there's no reason you couldn't fetch trusted sources from the server (so long as you did so over a secure channel). If you wanted this to be robust going forward, you might think about checking for revocation as well. – jelford Apr 21 '16 at 09:05
2

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();
}
Community
  • 1
  • 1
PRIZ3
  • 96
  • 5
  • I'd suggest there's no reason not to store the whole public key. SHA-ing (or using any hash before storing) it introduces the possibility of a key collision (unlikely to happen by accident, easy to cause by a malicious 3rd party). Since public keys aren't sensitive, you don't really gain much by doing this. – jelford Feb 12 '15 at 11:04
  • My understanding is that the whole point of using a cryptographic hash function like SHA-1 is to make it difficult/infeasible to find another input that matches the output. Though it would make sense to move to SHA-2/3 at this point instead of SHA-1 and Java's current keytool also shows SHA-256 of certificate. `MessageDigest md = MessageDigest.getInstance("SHA-256");` The main gain I see is in code readability, management of the trusted certs, and some level of obfuscation by avoiding passing around a long string if the app is decompiled. – PRIZ3 Feb 12 '15 at 22:19
  • I'd suggest there are better ways to improve code readability and key management than to hash your keys - put them in appropriate datastructures, and use the tools available. The reason that keys are often displayed as hashes is that it makes them - as you say - easier to read and recognise. But for cryptographic purposes, it's not a good idea to throw away the additional N bits of your key that you lose by hashing it. Public keys are - as the name suggests - completely public; there's nothing to be gained by obfuscating or obscuring them. – jelford Feb 24 '15 at 10:46