// Copyright 2015 The Chromium Authors // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package org.chromium.base; import android.annotation.SuppressLint; import android.content.Context; import android.content.pm.PackageInfo; import android.content.pm.PackageManager; import android.content.pm.Signature; import androidx.annotation.Nullable; import androidx.annotation.VisibleForTesting; import java.io.ByteArrayInputStream; import java.io.InputStream; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; import java.util.ArrayList; import java.util.List; /** This class provides package checking related methods. */ public class PackageUtils { private static final String TAG = "PackageUtils"; private static final char[] HEX_CHAR_LOOKUP = "0123456789ABCDEF".toCharArray(); /** Retrieves the PackageInfo for the given package, or null if it is not installed. */ public static @Nullable PackageInfo getPackageInfo(String packageName, int flags) { PackageManager pm = ContextUtils.getApplicationContext().getPackageManager(); try { return pm.getPackageInfo(packageName, flags); } catch (PackageManager.NameNotFoundException e) { return null; } } /** * Retrieves the version of the given package installed on the device. * * @param packageName Name of the package to find. * @return The package's version code if found, -1 otherwise. */ public static int getPackageVersion(String packageName) { // TODO(agrieve): Return a long and move BuildInfo.packageVersionCode() to this class. PackageInfo packageInfo = getPackageInfo(packageName, 0); if (packageInfo != null) return packageInfo.versionCode; return -1; } // TODO(agrieve): Delete downstream references. public static int getPackageVersion(Context unused, String packageName) { return getPackageVersion(packageName); } /** * Checks if the app has been installed on the system. * @return true if the PackageManager reports that the app is installed, false otherwise. * @param packageName Name of the package to check. */ public static boolean isPackageInstalled(String packageName) { return getPackageInfo(packageName, 0) != null; } /** Returns the PackageInfo for the current app, as retrieve by PackageManager. */ public static PackageInfo getApplicationPackageInfo(int flags) { PackageInfo ret = getPackageInfo(BuildInfo.getInstance().packageName, flags); assert ret != null; return ret; } /** * Computes the SHA256 certificates for the given package name. The app with the given package * name has to be installed on device. The output will be a list of 30 long HEX strings with : * between each value. There will be one string for each signature the app is signed with. * @param packageName The package name to query the signature for. * @return The SHA256 certificate for the package name. */ @SuppressLint("PackageManagerGetSignatures") // https://stackoverflow.com/questions/39192844/android-studio-warning-when-using-packagemanager-get-signatures public static List getCertificateSHA256FingerprintForPackage(String packageName) { PackageInfo packageInfo = getPackageInfo(packageName, PackageManager.GET_SIGNATURES); if (packageInfo == null) return null; ArrayList fingerprints = new ArrayList<>(packageInfo.signatures.length); for (Signature signature : packageInfo.signatures) { InputStream input = new ByteArrayInputStream(signature.toByteArray()); String hexString = null; try { X509Certificate certificate = (X509Certificate) CertificateFactory.getInstance("X509").generateCertificate(input); hexString = byteArrayToHexString( MessageDigest.getInstance("SHA256") .digest(certificate.getEncoded())); } catch (CertificateException | NoSuchAlgorithmException e) { Log.w(TAG, "Exception", e); return null; } fingerprints.add(hexString); } return fingerprints; } /** * Converts a byte array to hex string with : inserted between each element. * @param byteArray The array to be converted. * @return A string with two letters representing each byte and : in between. */ @VisibleForTesting static String byteArrayToHexString(byte[] byteArray) { StringBuilder hexString = new StringBuilder(byteArray.length * 3 - 1); for (int i = 0; i < byteArray.length; ++i) { hexString.append(HEX_CHAR_LOOKUP[(byteArray[i] & 0xf0) >>> 4]); hexString.append(HEX_CHAR_LOOKUP[byteArray[i] & 0xf]); if (i < (byteArray.length - 1)) hexString.append(':'); } return hexString.toString(); } private PackageUtils() { // Hide constructor } }