From 5cdda3425ccf3c62e400a1646615f4479a8266af Mon Sep 17 00:00:00 2001 From: Daniel Cashman Date: Fri, 19 Jan 2018 07:22:52 -0800 Subject: Add API to expose signing certificate proof-of-rotation. With the addition of APK Signature Scheme v3, the platform now can support key rotation by using the proof-of-rotation provided by the new scheme. Create a new API which allows checking of the entire provided history of an APK's signing certificates, not just the current signer. This should allow for changes of APK signing certificates without fear of losing access to resources that would have been provided under the old signing certificate. Change getPackageInfo(GET_SIGNATURES) to return the oldest signing certificate in the chain so that apps which do programmatic checks, but are not updated to use the new API, still get the same information they would have gotten had there been no rotation. Bug: 64686581 Test: Builds, boots. Change-Id: I8982fd4cce60f5d85a6180d157a6e2a661b1a6d7 --- api/current.txt | 10 ++- .../android/app/ApplicationPackageManager.java | 20 ++++++ core/java/android/content/pm/IPackageManager.aidl | 4 ++ core/java/android/content/pm/PackageInfo.java | 37 ++++++++++- core/java/android/content/pm/PackageManager.java | 70 ++++++++++++++++++++- core/java/android/content/pm/PackageParser.java | 34 ++++++++++- core/java/android/util/PackageUtils.java | 13 +++- .../android/server/pm/PackageManagerService.java | 71 ++++++++++++++++++++++ .../server/pm/PackageManagerServiceUtils.java | 66 ++++++++++++++++++++ .../src/android/test/mock/MockPackageManager.java | 13 ++++ 10 files changed, 331 insertions(+), 7 deletions(-) diff --git a/api/current.txt b/api/current.txt index bb4c383e4c9a..fe6004f5d063 100644 --- a/api/current.txt +++ b/api/current.txt @@ -10870,7 +10870,8 @@ package android.content.pm { 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; @@ -11073,6 +11074,8 @@ package android.content.pm { 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(); @@ -11098,6 +11101,8 @@ package android.content.pm { 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 @@ -11216,7 +11221,8 @@ package android.content.pm { 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 4048e6596cfc..b72f002e09ee 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -689,6 +689,26 @@ public class ApplicationPackageManager extends PackageManager { } } + @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 { diff --git a/core/java/android/content/pm/IPackageManager.aidl b/core/java/android/content/pm/IPackageManager.aidl index cce6b848e6ae..379bff49c911 100644 --- a/core/java/android/content/pm/IPackageManager.aidl +++ b/core/java/android/content/pm/IPackageManager.aidl @@ -656,4 +656,8 @@ interface IPackageManager { 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 5a91e94781d7..13ec4fdb7956 100644 --- a/core/java/android/content/pm/PackageInfo.java +++ b/core/java/android/content/pm/PackageInfo.java @@ -246,9 +246,44 @@ public class PackageInfo implements Parcelable { * 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. + * + * Deprecated 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. + * + * Note: 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 bcf80eeaaee7..cab1e746c4b7 100644 --- a/core/java/android/content/pm/PackageManager.java +++ b/core/java/android/content/pm/PackageManager.java @@ -133,6 +133,7 @@ public abstract class PackageManager { 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 @@ public abstract class PackageManager { /** * {@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; /** @@ -487,6 +491,14 @@ public abstract class PackageManager { */ 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 @@ -3766,7 +3778,7 @@ public abstract class PackageManager { public abstract int getInstantAppCookieMaxBytes(); /** - * @deprecated + * deprecated * @hide */ public abstract int getInstantAppCookieMaxSize(); @@ -5899,4 +5911,60 @@ public abstract class PackageManager { 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 4efd08134065..5b5ccf547b54 100644 --- a/core/java/android/content/pm/PackageParser.java +++ b/core/java/android/content/pm/PackageParser.java @@ -801,13 +801,40 @@ public class PackageParser { } } } + // 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 @@ public class PackageParser { 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 e2e9d53e7e9e..a5e38189f39b 100644 --- a/core/java/android/util/PackageUtils.java +++ b/core/java/android/util/PackageUtils.java @@ -105,7 +105,7 @@ public final class PackageUtils { * @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 @@ public final class PackageUtils { 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 b8720a592a74..f6d924c81fce 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.READ_EXTERNAL_STORAGE; 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.dumpCriticalInfo; 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; @@ -5427,6 +5431,73 @@ Slog.e("TODD", } } + @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 cfc12de02267..021c4b8a24c1 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.ErrnoException; 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.IOException; 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 @@ public class PackageManagerServiceUtils { 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 13e3693cada0..c0d148fb1b77 100644 --- a/test-mock/src/android/test/mock/MockPackageManager.java +++ b/test-mock/src/android/test/mock/MockPackageManager.java @@ -1190,4 +1190,17 @@ public class MockPackageManager extends PackageManager { 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(); + } + } -- cgit v1.2.3-59-g8ed1b