Merge "Add API to expose signing certificate proof-of-rotation."

This commit is contained in:
TreeHugger Robot
2018-01-24 03:00:27 +00:00
committed by Android (Google) Code Review
10 changed files with 331 additions and 7 deletions

View File

@@ -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

View File

@@ -698,6 +698,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 {

View File

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

View File

@@ -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

View File

@@ -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
@@ -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");
}
}

View File

@@ -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);

View File

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

View File

@@ -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.

View File

@@ -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() &&

View File

@@ -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();
}
}