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?
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)
}
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
}
If you love us? You can donate to us via Paypal or buy me a coffee so we can maintain and grow! Thank you!
Donate Us With