Logo Questions Linux Laravel Mysql Ubuntu Git Menu
 

How to use PackageInfo.GET_SIGNING_CERTIFICATES in API 28?

The documentation of PackageManager.GET_SIGNATURES says "This constant was deprecated in API level 28. Use GET_SIGNING_CERTIFICATES instead".

Unfortunately it was not secure and was easily hacked.

How can you use the new "GET_SIGNING_CERTIFICATES" introduced with Android P?

like image 882
Ettore Gallina Avatar asked Aug 27 '18 14:08

Ettore Gallina


2 Answers

In API28 or higher you should check for multipleSigners as well.

This function will do the job:
(Works for Android 9.0 and lower)

fun getApplicationSignature(packageName: String = context.packageName): List<String> {
    val signatureList: List<String>
    try {
        if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.P) {
            // New signature
            val sig = context.packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNING_CERTIFICATES).signingInfo
            signatureList = if (sig.hasMultipleSigners()) {
                // Send all with apkContentsSigners
                sig.apkContentsSigners.map {
                    val digest = MessageDigest.getInstance("SHA")
                    digest.update(it.toByteArray())
                    bytesToHex(digest.digest())
                }
            } else {
                // Send one with signingCertificateHistory
                sig.signingCertificateHistory.map {
                    val digest = MessageDigest.getInstance("SHA")
                    digest.update(it.toByteArray())
                    bytesToHex(digest.digest())
                }
            }
        } else {
            val sig = context.packageManager.getPackageInfo(packageName, PackageManager.GET_SIGNATURES).signatures
            signatureList = sig.map {
                val digest = MessageDigest.getInstance("SHA")
                digest.update(it.toByteArray())
                bytesToHex(digest.digest())
            }
        }

        return signatureList
    } catch (e: Exception) {
        // Handle error
    }
    return emptyList()
}

And byteToHex is:

fun bytesToHex(bytes: ByteArray): String {
    val hexArray = charArrayOf('0', '1', '2', '3', '4', '5', '6', '7', '8', '9', 'A', 'B', 'C', 'D', 'E', 'F')
    val hexChars = CharArray(bytes.size * 2)
    var v: Int
    for (j in bytes.indices) {
        v = bytes[j].toInt() and 0xFF
        hexChars[j * 2] = hexArray[v.ushr(4)]
        hexChars[j * 2 + 1] = hexArray[v and 0x0F]
    }
    return String(hexChars)
}
like image 104
Mahdi-Malv Avatar answered Nov 11 '22 22:11

Mahdi-Malv


TL;DR if your use case is that you're validating the calling package's signatures, you can still use GET_SIGNATURES in pre-api 28 securely as long as you validate all signers returned from the package manager (instead of stopping early when you find one that you trust). In fact, google patched it in lollipop (https://android.googlesource.com/platform/libcore/+/f8986a989759c43c155ae64f9a3b36f670602521).

Details: I believe your comment about GET_SIGNATURES being easily hacked is based on this vulnerability (https://www.blackhat.com/docs/us-14/materials/us-14-Forristal-Android-FakeID-Vulnerability-Walkthrough.pdf). Whereby android doesn't validate the trust chain prior to returning apk signers.

This is only a problem if you have code like this:

    private boolean validateCallingPackage(String: packageName) {
        PackageInfo packageInfo;
        try {
            packageInfo = context.getPackageManager().getPackageInfo(
                packageName,
                PackageManager.GET_SIGNATURES);
        } catch (PackageManager.NameNotFoundException e) {
            return false;
        }


        for (Signature signature : packageInfo.signatures) {
            String hashedSignature = Utility.sha256hash(signature.toByteArray());
            if (validAppSignatureHashes.contains(hashedSignature)) {
              return true;  //THIS is the problematic code
            }
        }
        return false
    }

The code is returning true if it finds any certificate that matches one from your whitelist. With the android vulnerability, if the signatures contained a signature from a malicious signer, your code still returns true.

The mitigation for this vulnerability is to instead check ALL signatures returned from the package manager and return false if any of them aren't in your whitelist. i.e.

    private boolean validateCallingPackage(String: packageName) {
        ...

        for (Signature signature : packageInfo.signatures) {
            String hashedSignature = Utility.sha256hash(signature.toByteArray());
            if (!validAppSignatureHashes.contains(hashedSignature)) {
              return false; //FIXED
            }
        }
        return true
    }
like image 9
ashoykh Avatar answered Nov 11 '22 22:11

ashoykh