Merge "Add API to expose signing certificate proof-of-rotation."
diff --git a/api/current.txt b/api/current.txt
index 9667b901..ac6d80a 100644
--- a/api/current.txt
+++ b/api/current.txt
@@ -10873,7 +10873,8 @@
field public android.content.pm.ServiceInfo[] services;
field public java.lang.String sharedUserId;
field public int sharedUserLabel;
- field public android.content.pm.Signature[] signatures;
+ field public deprecated android.content.pm.Signature[] signatures;
+ field public android.content.pm.Signature[][] signingCertificateHistory;
field public java.lang.String[] splitNames;
field public int[] splitRevisionCodes;
field public deprecated int versionCode;
@@ -11076,6 +11077,8 @@
method public abstract android.graphics.drawable.Drawable getUserBadgedIcon(android.graphics.drawable.Drawable, android.os.UserHandle);
method public abstract java.lang.CharSequence getUserBadgedLabel(java.lang.CharSequence, android.os.UserHandle);
method public abstract android.content.res.XmlResourceParser getXml(java.lang.String, int, android.content.pm.ApplicationInfo);
+ method public boolean hasSigningCertificate(java.lang.String, byte[], int);
+ method public boolean hasSigningCertificate(int, byte[], int);
method public abstract boolean hasSystemFeature(java.lang.String);
method public abstract boolean hasSystemFeature(java.lang.String, int);
method public abstract boolean isInstantApp();
@@ -11101,6 +11104,8 @@
method public abstract void setInstallerPackageName(java.lang.String, java.lang.String);
method public abstract void updateInstantAppCookie(byte[]);
method public abstract void verifyPendingInstall(int, int);
+ field public static final int CERT_INPUT_RAW_X509 = 0; // 0x0
+ field public static final int CERT_INPUT_SHA256 = 1; // 0x1
field public static final int COMPONENT_ENABLED_STATE_DEFAULT = 0; // 0x0
field public static final int COMPONENT_ENABLED_STATE_DISABLED = 2; // 0x2
field public static final int COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED = 4; // 0x4
@@ -11219,7 +11224,8 @@
field public static final int GET_RESOLVED_FILTER = 64; // 0x40
field public static final int GET_SERVICES = 4; // 0x4
field public static final int GET_SHARED_LIBRARY_FILES = 1024; // 0x400
- field public static final int GET_SIGNATURES = 64; // 0x40
+ field public static final deprecated int GET_SIGNATURES = 64; // 0x40
+ field public static final int GET_SIGNING_CERTIFICATES = 134217728; // 0x8000000
field public static final deprecated int GET_UNINSTALLED_PACKAGES = 8192; // 0x2000
field public static final int GET_URI_PERMISSION_PATTERNS = 2048; // 0x800
field public static final int INSTALL_REASON_DEVICE_RESTORE = 2; // 0x2
diff --git a/core/java/android/app/ApplicationPackageManager.java b/core/java/android/app/ApplicationPackageManager.java
index 49d5224..cc68c05 100644
--- a/core/java/android/app/ApplicationPackageManager.java
+++ b/core/java/android/app/ApplicationPackageManager.java
@@ -699,6 +699,26 @@
}
@Override
+ public boolean hasSigningCertificate(
+ String packageName, byte[] certificate, @PackageManager.CertificateInputType int type) {
+ try {
+ return mPM.hasSigningCertificate(packageName, certificate, type);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
+ public boolean hasSigningCertificate(
+ int uid, byte[] certificate, @PackageManager.CertificateInputType int type) {
+ try {
+ return mPM.hasUidSigningCertificate(uid, certificate, type);
+ } catch (RemoteException e) {
+ throw e.rethrowFromSystemServer();
+ }
+ }
+
+ @Override
public String[] getPackagesForUid(int uid) {
try {
return mPM.getPackagesForUid(uid);
diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl
index cce6b84..379bff4 100644
--- a/core/java/android/content/pm/IPackageManager.aidl
+++ b/core/java/android/content/pm/IPackageManager.aidl
@@ -656,4 +656,8 @@
void setHarmfulAppWarning(String packageName, CharSequence warning, int userId);
CharSequence getHarmfulAppWarning(String packageName, int userId);
+
+ boolean hasSigningCertificate(String packageName, in byte[] signingCertificate, int flags);
+
+ boolean hasUidSigningCertificate(int uid, in byte[] signingCertificate, int flags);
}
diff --git a/core/java/android/content/pm/PackageInfo.java b/core/java/android/content/pm/PackageInfo.java
index 5a91e94..13ec4fd 100644
--- a/core/java/android/content/pm/PackageInfo.java
+++ b/core/java/android/content/pm/PackageInfo.java
@@ -246,9 +246,44 @@
* equivalent to being signed with certificates B and A. This means that
* in case multiple signatures are reported you cannot assume the one at
* the first position to be the same across updates.
+ *
+ * <strong>Deprecated</strong> This has been replaced by the
+ * {@link PackageInfo#signingCertificateHistory} field, which takes into
+ * account signing certificate rotation. For backwards compatibility in
+ * the event of signing certificate rotation, this will return the oldest
+ * reported signing certificate, so that an application will appear to
+ * callers as though no rotation occurred.
+ *
+ * @deprecated use {@code signingCertificateHistory} instead
*/
+ @Deprecated
public Signature[] signatures;
-
+
+ /**
+ * Array of all signatures arrays read from the package file, potentially
+ * including past signing certificates no longer used after signing
+ * certificate rotation. Though signing certificate rotation is only
+ * available for apps with a single signing certificate, this provides an
+ * array of arrays so that packages signed with multiple signing
+ * certificates can still return all signers. This is only filled in if
+ * the flag {@link PackageManager#GET_SIGNING_CERTIFICATES} was set.
+ *
+ * A package must be singed with at least one certificate, which is at
+ * position zero in the array. An application may be signed by multiple
+ * certificates, which would be in the array at position zero in an
+ * indeterminate order. A package may also have a history of certificates
+ * due to signing certificate rotation. In this case, the array will be
+ * populated by a series of single-entry arrays corresponding to a signing
+ * certificate of the package.
+ *
+ * <strong>Note:</strong> Signature ordering is not guaranteed to be
+ * stable which means that a package signed with certificates A and B is
+ * equivalent to being signed with certificates B and A. This means that
+ * in case multiple signatures are reported you cannot assume the one at
+ * the first position will be the same across updates.
+ */
+ public Signature[][] signingCertificateHistory;
+
/**
* Application specified preferred configuration
* {@link android.R.styleable#AndroidManifestUsesConfiguration
diff --git a/core/java/android/content/pm/PackageManager.java b/core/java/android/content/pm/PackageManager.java
index b81267a..67c9584 100644
--- a/core/java/android/content/pm/PackageManager.java
+++ b/core/java/android/content/pm/PackageManager.java
@@ -133,6 +133,7 @@
GET_SERVICES,
GET_SHARED_LIBRARY_FILES,
GET_SIGNATURES,
+ GET_SIGNING_CERTIFICATES,
GET_URI_PERMISSION_PATTERNS,
MATCH_UNINSTALLED_PACKAGES,
MATCH_DISABLED_COMPONENTS,
@@ -272,7 +273,10 @@
/**
* {@link PackageInfo} flag: return information about the
* signatures included in the package.
+ *
+ * @deprecated use {@code GET_SIGNING_CERTIFICATES} instead
*/
+ @Deprecated
public static final int GET_SIGNATURES = 0x00000040;
/**
@@ -488,6 +492,14 @@
public static final int MATCH_STATIC_SHARED_LIBRARIES = 0x04000000;
/**
+ * {@link PackageInfo} flag: return the signing certificates associated with
+ * this package. Each entry is a signing certificate that the package
+ * has proven it is authorized to use, usually a past signing certificate from
+ * which it has rotated.
+ */
+ public static final int GET_SIGNING_CERTIFICATES = 0x08000000;
+
+ /**
* Internal flag used to indicate that a system component has done their
* homework and verified that they correctly handle packages and components
* that come and go over time. In particular:
@@ -3781,7 +3793,7 @@
public abstract int getInstantAppCookieMaxBytes();
/**
- * @deprecated
+ * deprecated
* @hide
*/
public abstract int getInstantAppCookieMaxSize();
@@ -5914,4 +5926,60 @@
public CharSequence getHarmfulAppWarning(@NonNull String packageName) {
throw new UnsupportedOperationException("getHarmfulAppWarning not implemented in subclass");
}
+
+ /** @hide */
+ @IntDef(prefix = { "CERT_INPUT_" }, value = {
+ CERT_INPUT_RAW_X509,
+ CERT_INPUT_SHA256
+ })
+ @Retention(RetentionPolicy.SOURCE)
+ public @interface CertificateInputType {}
+
+ /**
+ * Certificate input bytes: the input bytes represent an encoded X.509 Certificate which could
+ * be generated using an {@code CertificateFactory}
+ */
+ public static final int CERT_INPUT_RAW_X509 = 0;
+
+ /**
+ * Certificate input bytes: the input bytes represent the SHA256 output of an encoded X.509
+ * Certificate.
+ */
+ public static final int CERT_INPUT_SHA256 = 1;
+
+ /**
+ * Searches the set of signing certificates by which the given package has proven to have been
+ * signed. This should be used instead of {@code getPackageInfo} with {@code GET_SIGNATURES}
+ * since it takes into account the possibility of signing certificate rotation, except in the
+ * case of packages that are signed by multiple certificates, for which signing certificate
+ * rotation is not supported.
+ *
+ * @param packageName package whose signing certificates to check
+ * @param certificate signing certificate for which to search
+ * @param type representation of the {@code certificate}
+ * @return true if this package was or is signed by exactly the certificate {@code certificate}
+ */
+ public boolean hasSigningCertificate(
+ String packageName, byte[] certificate, @CertificateInputType int type) {
+ throw new UnsupportedOperationException(
+ "hasSigningCertificate not implemented in subclass");
+ }
+
+ /**
+ * Searches the set of signing certificates by which the given uid has proven to have been
+ * signed. This should be used instead of {@code getPackageInfo} with {@code GET_SIGNATURES}
+ * since it takes into account the possibility of signing certificate rotation, except in the
+ * case of packages that are signed by multiple certificates, for which signing certificate
+ * rotation is not supported.
+ *
+ * @param uid package whose signing certificates to check
+ * @param certificate signing certificate for which to search
+ * @param type representation of the {@code certificate}
+ * @return true if this package was or is signed by exactly the certificate {@code certificate}
+ */
+ public boolean hasSigningCertificate(
+ int uid, byte[] certificate, @CertificateInputType int type) {
+ throw new UnsupportedOperationException(
+ "hasSigningCertificate not implemented in subclass");
+ }
}
diff --git a/core/java/android/content/pm/PackageParser.java b/core/java/android/content/pm/PackageParser.java
index 4efd081..5b5ccf5 100644
--- a/core/java/android/content/pm/PackageParser.java
+++ b/core/java/android/content/pm/PackageParser.java
@@ -801,13 +801,40 @@
}
}
}
+ // deprecated method of getting signing certificates
if ((flags&PackageManager.GET_SIGNATURES) != 0) {
- if (p.mSigningDetails.hasSignatures()) {
+ if (p.mSigningDetails.hasPastSigningCertificates()) {
+ // Package has included signing certificate rotation information. Return the oldest
+ // cert so that programmatic checks keep working even if unaware of key rotation.
+ pi.signatures = new Signature[1];
+ pi.signatures[0] = p.mSigningDetails.pastSigningCertificates[0];
+ } else if (p.mSigningDetails.hasSignatures()) {
+ // otherwise keep old behavior
int numberOfSigs = p.mSigningDetails.signatures.length;
pi.signatures = new Signature[numberOfSigs];
System.arraycopy(p.mSigningDetails.signatures, 0, pi.signatures, 0, numberOfSigs);
}
}
+
+ // replacement for GET_SIGNATURES
+ if ((flags & PackageManager.GET_SIGNING_CERTIFICATES) != 0) {
+ if (p.mSigningDetails.hasPastSigningCertificates()) {
+ // Package has included signing certificate rotation information. Convert each
+ // entry to an array
+ int numberOfSigs = p.mSigningDetails.pastSigningCertificates.length;
+ pi.signingCertificateHistory = new Signature[numberOfSigs][];
+ for (int i = 0; i < numberOfSigs; i++) {
+ pi.signingCertificateHistory[i] =
+ new Signature[] { p.mSigningDetails.pastSigningCertificates[i] };
+ }
+ } else if (p.mSigningDetails.hasSignatures()) {
+ // otherwise keep old behavior
+ int numberOfSigs = p.mSigningDetails.signatures.length;
+ pi.signingCertificateHistory = new Signature[1][numberOfSigs];
+ System.arraycopy(p.mSigningDetails.signatures, 0,
+ pi.signingCertificateHistory[0], 0, numberOfSigs);
+ }
+ }
return pi;
}
@@ -5759,6 +5786,11 @@
return signatures != null && signatures.length > 0;
}
+ /** Returns true if the signing details have past signing certificates. */
+ public boolean hasPastSigningCertificates() {
+ return pastSigningCertificates != null && pastSigningCertificates.length > 0;
+ }
+
/** Returns true if the signatures in this and other match exactly. */
public boolean signaturesMatchExactly(SigningDetails other) {
return Signature.areExactMatch(this.signatures, other.signatures);
diff --git a/core/java/android/util/PackageUtils.java b/core/java/android/util/PackageUtils.java
index e2e9d53..a5e3818 100644
--- a/core/java/android/util/PackageUtils.java
+++ b/core/java/android/util/PackageUtils.java
@@ -105,7 +105,7 @@
* @param data The data.
* @return The digest or null if an error occurs.
*/
- public static @Nullable String computeSha256Digest(@NonNull byte[] data) {
+ public static @Nullable byte[] computeSha256DigestBytes(@NonNull byte[] data) {
MessageDigest messageDigest;
try {
messageDigest = MessageDigest.getInstance("SHA256");
@@ -116,6 +116,15 @@
messageDigest.update(data);
- return ByteStringUtils.toHexString(messageDigest.digest());
+ return messageDigest.digest();
+ }
+
+ /**
+ * Computes the SHA256 digest of some data.
+ * @param data The data.
+ * @return The digest or null if an error occurs.
+ */
+ public static @Nullable String computeSha256Digest(@NonNull byte[] data) {
+ return ByteStringUtils.toHexString(computeSha256DigestBytes(data));
}
}
diff --git a/services/core/java/com/android/server/pm/PackageManagerService.java b/services/core/java/com/android/server/pm/PackageManagerService.java
index da1bdc7..42b6946 100644
--- a/services/core/java/com/android/server/pm/PackageManagerService.java
+++ b/services/core/java/com/android/server/pm/PackageManagerService.java
@@ -24,6 +24,8 @@
import static android.Manifest.permission.REQUEST_DELETE_PACKAGES;
import static android.Manifest.permission.WRITE_EXTERNAL_STORAGE;
import static android.Manifest.permission.WRITE_MEDIA_STORAGE;
+import static android.content.pm.PackageManager.CERT_INPUT_RAW_X509;
+import static android.content.pm.PackageManager.CERT_INPUT_SHA256;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DEFAULT;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED;
import static android.content.pm.PackageManager.COMPONENT_ENABLED_STATE_DISABLED_UNTIL_USED;
@@ -108,6 +110,8 @@
import static com.android.server.pm.PackageManagerServiceUtils.getCompressedFiles;
import static com.android.server.pm.PackageManagerServiceUtils.getLastModifiedTime;
import static com.android.server.pm.PackageManagerServiceUtils.logCriticalInfo;
+import static com.android.server.pm.PackageManagerServiceUtils.signingDetailsHasCertificate;
+import static com.android.server.pm.PackageManagerServiceUtils.signingDetailsHasSha256Certificate;
import static com.android.server.pm.PackageManagerServiceUtils.verifySignatures;
import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_FAILURE;
import static com.android.server.pm.permission.PermissionsState.PERMISSION_OPERATION_SUCCESS;
@@ -5457,6 +5461,73 @@
}
}
+ @Override
+ public boolean hasSigningCertificate(
+ String packageName, byte[] certificate, @PackageManager.CertificateInputType int type) {
+
+ synchronized (mPackages) {
+ final PackageParser.Package p = mPackages.get(packageName);
+ if (p == null || p.mExtras == null) {
+ return false;
+ }
+ final int callingUid = Binder.getCallingUid();
+ final int callingUserId = UserHandle.getUserId(callingUid);
+ final PackageSetting ps = (PackageSetting) p.mExtras;
+ if (filterAppAccessLPr(ps, callingUid, callingUserId)) {
+ return false;
+ }
+ switch (type) {
+ case CERT_INPUT_RAW_X509:
+ return signingDetailsHasCertificate(certificate, p.mSigningDetails);
+ case CERT_INPUT_SHA256:
+ return signingDetailsHasSha256Certificate(certificate, p.mSigningDetails);
+ default:
+ return false;
+ }
+ }
+ }
+
+ @Override
+ public boolean hasUidSigningCertificate(
+ int uid, byte[] certificate, @PackageManager.CertificateInputType int type) {
+ final int callingUid = Binder.getCallingUid();
+ final int callingUserId = UserHandle.getUserId(callingUid);
+ // Map to base uids.
+ uid = UserHandle.getAppId(uid);
+ // reader
+ synchronized (mPackages) {
+ final PackageParser.SigningDetails signingDetails;
+ final Object obj = mSettings.getUserIdLPr(uid);
+ if (obj != null) {
+ if (obj instanceof SharedUserSetting) {
+ final boolean isCallerInstantApp = getInstantAppPackageName(callingUid) != null;
+ if (isCallerInstantApp) {
+ return false;
+ }
+ signingDetails = ((SharedUserSetting)obj).signatures.mSigningDetails;
+ } else if (obj instanceof PackageSetting) {
+ final PackageSetting ps = (PackageSetting) obj;
+ if (filterAppAccessLPr(ps, callingUid, callingUserId)) {
+ return false;
+ }
+ signingDetails = ps.signatures.mSigningDetails;
+ } else {
+ return false;
+ }
+ } else {
+ return false;
+ }
+ switch (type) {
+ case CERT_INPUT_RAW_X509:
+ return signingDetailsHasCertificate(certificate, signingDetails);
+ case CERT_INPUT_SHA256:
+ return signingDetailsHasSha256Certificate(certificate, signingDetails);
+ default:
+ return false;
+ }
+ }
+ }
+
/**
* This method should typically only be used when granting or revoking
* permissions, since the app may immediately restart after this call.
diff --git a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
index cfc12de..021c4b8 100644
--- a/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
+++ b/services/core/java/com/android/server/pm/PackageManagerServiceUtils.java
@@ -54,6 +54,7 @@
import android.system.Os;
import android.util.ArraySet;
import android.util.Log;
+import android.util.PackageUtils;
import android.util.Slog;
import android.util.jar.StrictJarFile;
import android.util.proto.ProtoOutputStream;
@@ -74,6 +75,8 @@
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintWriter;
+import java.security.MessageDigest;
+import java.security.NoSuchAlgorithmException;
import java.security.cert.CertificateEncodingException;
import java.security.cert.CertificateException;
import java.text.SimpleDateFormat;
@@ -576,6 +579,69 @@
return true;
}
+
+ /**
+ * Checks the signing certificates to see if the provided certificate is a member. Invalid for
+ * {@code SigningDetails} with multiple signing certificates.
+ * @param certificate certificate to check for membership
+ * @param signingDetails signing certificates record whose members are to be searched
+ * @return true if {@code certificate} is in {@code signingDetails}
+ */
+ public static boolean signingDetailsHasCertificate(
+ byte[] certificate, PackageParser.SigningDetails signingDetails) {
+ if (signingDetails == PackageParser.SigningDetails.UNKNOWN) {
+ return false;
+ }
+ Signature signature = new Signature(certificate);
+ if (signingDetails.hasPastSigningCertificates()) {
+ for (int i = 0; i < signingDetails.pastSigningCertificates.length; i++) {
+ if (signingDetails.pastSigningCertificates[i].equals(signature)) {
+ return true;
+ }
+ }
+ } else {
+ // no signing history, just check the current signer
+ if (signingDetails.signatures.length == 1
+ && signingDetails.signatures[0].equals(signature)) {
+ return true;
+ }
+ }
+ return false;
+ }
+
+ /**
+ * Checks the signing certificates to see if the provided certificate is a member. Invalid for
+ * {@code SigningDetails} with multiple signing certificaes.
+ * @param sha256Certificate certificate to check for membership
+ * @param signingDetails signing certificates record whose members are to be searched
+ * @return true if {@code certificate} is in {@code signingDetails}
+ */
+ public static boolean signingDetailsHasSha256Certificate(
+ byte[] sha256Certificate, PackageParser.SigningDetails signingDetails ) {
+ if (signingDetails == PackageParser.SigningDetails.UNKNOWN) {
+ return false;
+ }
+ if (signingDetails.hasPastSigningCertificates()) {
+ for (int i = 0; i < signingDetails.pastSigningCertificates.length; i++) {
+ byte[] digest = PackageUtils.computeSha256DigestBytes(
+ signingDetails.pastSigningCertificates[i].toByteArray());
+ if (Arrays.equals(sha256Certificate, digest)) {
+ return true;
+ }
+ }
+ } else {
+ // no signing history, just check the current signer
+ if (signingDetails.signatures.length == 1) {
+ byte[] digest = PackageUtils.computeSha256DigestBytes(
+ signingDetails.signatures[0].toByteArray());
+ if (Arrays.equals(sha256Certificate, digest)) {
+ return true;
+ }
+ }
+ }
+ return false;
+ }
+
/** Returns true to force apk verification if the updated package (in /data) is a priv app. */
static boolean isApkVerificationForced(@Nullable PackageSetting disabledPs) {
return disabledPs != null && disabledPs.isPrivileged() &&
diff --git a/test-mock/src/android/test/mock/MockPackageManager.java b/test-mock/src/android/test/mock/MockPackageManager.java
index 41cde17..1ddc52c 100644
--- a/test-mock/src/android/test/mock/MockPackageManager.java
+++ b/test-mock/src/android/test/mock/MockPackageManager.java
@@ -1196,4 +1196,17 @@
public CharSequence getHarmfulAppWarning(String packageName) {
throw new UnsupportedOperationException();
}
+
+ @Override
+ public boolean hasSigningCertificate(
+ String packageName, byte[] certificate, @PackageManager.CertificateInputType int type) {
+ throw new UnsupportedOperationException();
+ }
+
+ @Override
+ public boolean hasSigningCertificate(
+ int uid, byte[] certificate, @PackageManager.CertificateInputType int type) {
+ throw new UnsupportedOperationException();
+ }
+
}