PackageManager.GET_SIGNATURESのドキュメントには、「この定数はAPIレベル28で廃止されました。代わりにGET_SIGNING_CERTIFICATESを使用してください」と記載されています。
残念ながら、それは安全ではなく、簡単にハッキングされました。
Android Pで導入された新しい「GET_SIGNING_CERTIFICATES」をどのように使用できますか?
API28では、multipleSignersもチェックする必要があります。
この関数は、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()
}
そしてbyteToHex
は:
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)
}
これはアプリの署名を処理しますAndroid 9(またはそれ以下)
私の解決策は:
Gradleビルドセット「compileSdkVersion 28」と「targetSdkVersion 28」では、次のサンプルコードを使用できます。
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();
}
不思議なことにAndroid Studioが定数GET_SIGNING_CERTIFICATESを認識しない場合は、@ SuppressLint( "WrongConstant")アノテーションを使用できます。
TL; DR呼び出しパッケージの署名を検証している場合は、プリAPI 28でGET_SIGNATURESを安全に使用できます。信頼できる署名者を見つけたときに早期に停止するのではなく、パッケージマネージャから返されたすべての署名者を検証します。実際、GoogleはLollipopでパッチを適用しました( https://Android.googlesource.com/platform/libcore/+/f8986a989759c43c155ae64f9a3b36f670602521 )。
詳細:ハッキングされやすいGET_SIGNATURESに関するコメントはこの脆弱性に基づいていると思います( https://www.blackhat.com/docs /us-14/materials/us-14-Forristal-Android-FakeID-Vulnerability-Walkthrough.pdf )。これにより、Androidは、apk署名者を返す前に信頼チェーンを検証しません。
これは、次のようなコードがある場合にのみ問題になります。
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
}
ホワイトリストの証明書と一致する証明書が見つかった場合、コードはtrueを返します。 Android脆弱性により、署名に悪意のある署名者の署名が含まれている場合でも、コードはtrueを返します。
この脆弱性の軽減策は、代わりにパッケージマネージャーから返されたすべての署名をチェックし、ホワイトリストにない署名がある場合はfalseを返すことです。つまり.
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
}