web-dev-qa-db-fra.com

Comment utiliser PackageInfo.GET_SIGNING_CERTIFICATES dans l'API 28?

La documentation de PackageManager.GET_SIGNATURES indique "Cette constante est déconseillée au niveau de l'API 28. Utilisez plutôt GET_SIGNING_CERTIFICATES".

Malheureusement, il n'était pas sécurisé et a été facilement piraté.

Comment pouvez-vous utiliser le nouveau "GET_SIGNING_CERTIFICATES" introduit avec Android P?

7
Ettore

Dans API28, vous devez également rechercher plusieurs signataires.

Cette fonction fera le travail(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()
    }

Et byteToHex est:

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

Cela gérera signature de l'application in Android 9 (ou inférieur)

9
Mahdi-Malv

Ma solution est:

Dans le jeu de compilation gradle "compileSdkVersion 28" et "targetSdkVersion 28", vous pouvez maintenant utiliser cet exemple de 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();
}

Si étrangement Android Studio ne reconnaît pas la constante GET_SIGNING_CERTIFICATES, vous pouvez utiliser l'annotation @SuppressLint ("WrongConstant")).

6
Ettore

TL; DR si votre cas d'utilisation est que vous validez les signatures du package appelant, vous pouvez toujours utiliser GET_SIGNATURES dans la pré-api 28 en toute sécurité tant que vous validez tous les signataires renvoyés par le gestionnaire de paquets (au lieu de vous arrêter tôt lorsque vous en trouvez un en qui vous avez confiance). En fait, Google l'a corrigé dans Lollipop ( https://Android.googlesource.com/platform/libcore/+/f8986a989759c43c155ae64f9a3b36f670602521 ).

Détails: Je pense que votre commentaire sur le fait que GET_SIGNATURES soit facilement piraté est basé sur cette vulnérabilité ( https://www.blackhat.com/docs /us-14/materials/us-14-Forristal-Android-FakeID-Vulnerability-Walkthrough.pdf ). Où Android ne valide pas la chaîne de confiance avant de renvoyer les signataires apk.

Ce n'est un problème que si vous avez du code comme celui-ci:

    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
    }

Le code retourne vrai s'il trouve un certificat qui correspond à un de votre liste blanche. Avec la vulnérabilité Android, si les signatures contenaient une signature d'un signataire malveillant, votre code retourne toujours vrai.

L'atténuation de cette vulnérabilité consiste à vérifier à la place TOUTES les signatures renvoyées par le gestionnaire de packages et à retourner false si aucune d'entre elles ne figure dans votre liste blanche. c'est à dire.

    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
    }
1
ashoykh