How to use PackageInfo.GET_SIGNING_CERTIFICATES in API 28?

你说的曾经没有我的故事 提交于 2020-03-18 05:15:55

问题


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?


回答1:


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

This function will do the job (It's in Kotlin):

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)
}

This'll handle App signature in android 9 (and lower)




回答2:


My solution is:

In the gradle build set "compileSdkVersion 28" and "targetSdkVersion 28", now you can use this sample code:

try {
    if(Build.VERSION.SDK_INT >= 28) {
        @SuppressLint("WrongConstant") final PackageInfo packageInfo = getPackageManager().getPackageInfo(getPackageName(), PackageManager.GET_SIGNING_CERTIFICATES);
        final Signature[] signatures = packageInfo.signingInfo.getApkContentsSigners();
        final MessageDigest md = MessageDigest.getInstance("SHA");
        for (Signature signature : signatures) {
            md.update(signature.toByteArray());
            final String signatureBase64 = new String(Base64.encode(md.digest(), Base64.DEFAULT));
            Log.d("Signature Base64", signatureBase64);
        }
    }
} catch (PackageManager.NameNotFoundException | NoSuchAlgorithmException e) {
    e.printStackTrace();
}

If strangely Android Studio does not recognize the constant GET_SIGNING_CERTIFICATES you can use the @SuppressLint ("WrongConstant") annotation.




回答3:


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
    }


来源:https://stackoverflow.com/questions/52041805/how-to-use-packageinfo-get-signing-certificates-in-api-28

易学教程内所有资源均来自网络或用户发布的内容,如有违反法律规定的内容欢迎反馈
该文章没有解决你所遇到的问题?点击提问,说说你的问题,让更多的人一起探讨吧!