diff options
| author | 2018-01-24 03:00:27 +0000 | |
|---|---|---|
| committer | 2018-01-24 03:00:27 +0000 | |
| commit | 064f16638be7304e589177386aa09f542a07ff88 (patch) | |
| tree | 809d3fba0dfc525f58afbd3ba6f03c40fa2c1a5d | |
| parent | cf8f8026b515c5ead886fe830a13bf97aa3a8959 (diff) | |
| parent | 5cdda3425ccf3c62e400a1646615f4479a8266af (diff) | |
Merge "Add API to expose signing certificate proof-of-rotation."
| -rw-r--r-- | api/current.txt | 10 | ||||
| -rw-r--r-- | core/java/android/app/ApplicationPackageManager.java | 20 | ||||
| -rw-r--r-- | core/java/android/content/pm/IPackageManager.aidl | 4 | ||||
| -rw-r--r-- | core/java/android/content/pm/PackageInfo.java | 37 | ||||
| -rw-r--r-- | core/java/android/content/pm/PackageManager.java | 70 | ||||
| -rw-r--r-- | core/java/android/content/pm/PackageParser.java | 34 | ||||
| -rw-r--r-- | core/java/android/util/PackageUtils.java | 13 | ||||
| -rw-r--r-- | services/core/java/com/android/server/pm/PackageManagerService.java | 71 | ||||
| -rw-r--r-- | services/core/java/com/android/server/pm/PackageManagerServiceUtils.java | 66 | ||||
| -rw-r--r-- | test-mock/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 9667b901732f..ac6d80a78d74 100644 --- a/api/current.txt +++ b/api/current.txt @@ -10873,7 +10873,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; @@ -11076,6 +11077,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(); @@ -11101,6 +11104,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 @@ -11219,7 +11224,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 49d522440fc4..cc68c0518b40 100644 --- a/core/java/android/app/ApplicationPackageManager.java +++ b/core/java/android/app/ApplicationPackageManager.java @@ -699,6 +699,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 { return mPM.getPackagesForUid(uid); 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. + * + * <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 b81267ab46f9..67c9584b01f7 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; /** @@ -488,6 +492,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 * that come and go over time. In particular: @@ -3781,7 +3793,7 @@ public abstract class PackageManager { public abstract int getInstantAppCookieMaxBytes(); /** - * @deprecated + * deprecated * @hide */ public abstract int getInstantAppCookieMaxSize(); @@ -5914,4 +5926,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 da1bdc7bb15a..42b69462c9e5 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; @@ -5457,6 +5461,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 41cde175759d..1ddc52c9f7b4 100644 --- a/test-mock/src/android/test/mock/MockPackageManager.java +++ b/test-mock/src/android/test/mock/MockPackageManager.java @@ -1196,4 +1196,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(); + } + } |