Merge "Allow restoring of apps that rotated key"

This commit is contained in:
Michal Karpinski
2018-02-28 10:08:14 +00:00
committed by Android (Google) Code Review
15 changed files with 626 additions and 164 deletions

View File

@@ -544,4 +544,16 @@ public abstract class PackageManagerInternal {
/** Updates the flags for the given permission. */
public abstract void updatePermissionFlagsTEMP(@NonNull String permName,
@NonNull String packageName, int flagMask, int flagValues, int userId);
/**
* Returns true if it's still safe to restore data backed up from this app's version
* that was signed with restoringFromSigHash.
*/
public abstract boolean isDataRestoreSafe(byte[] restoringFromSigHash, String packageName);
/**
* Returns true if it's still safe to restore data backed up from this app's version
* that was signed with restoringFromSig.
*/
public abstract boolean isDataRestoreSafe(Signature restoringFromSig, String packageName);
}

View File

@@ -23,6 +23,7 @@ import android.content.ComponentName;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ResolveInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.Signature;
@@ -30,6 +31,7 @@ import android.os.Build;
import android.os.ParcelFileDescriptor;
import android.util.Slog;
import com.android.server.LocalServices;
import com.android.server.backup.utils.AppBackupUtils;
import java.io.BufferedInputStream;
@@ -235,7 +237,7 @@ public class PackageManagerBackupAgent extends BackupAgent {
if (home != null) {
try {
homeInfo = mPackageManager.getPackageInfo(home.getPackageName(),
PackageManager.GET_SIGNATURES);
PackageManager.GET_SIGNING_CERTIFICATES);
homeInstaller = mPackageManager.getInstallerPackageName(home.getPackageName());
homeVersion = homeInfo.getLongVersionCode();
homeSigHashes = BackupUtils.hashSignatureArray(homeInfo.signatures);
@@ -252,10 +254,11 @@ public class PackageManagerBackupAgent extends BackupAgent {
// 2. the home app [or absence] we now use differs from the prior state,
// OR 3. it looks like we use the same home app + version as before, but
// the signatures don't match so we treat them as different apps.
PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
final boolean needHomeBackup = (homeVersion != mStoredHomeVersion)
|| !Objects.equals(home, mStoredHomeComponent)
|| (home != null
&& !BackupUtils.signaturesMatch(mStoredHomeSigHashes, homeInfo));
&& !BackupUtils.signaturesMatch(mStoredHomeSigHashes, homeInfo, pmi));
if (needHomeBackup) {
if (DEBUG) {
Slog.i(TAG, "Home preference changed; backing up new state " + home);

View File

@@ -36,11 +36,13 @@ import android.app.backup.IFullBackupRestoreObserver;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
import android.os.ParcelFileDescriptor;
import android.os.RemoteException;
import android.util.Slog;
import com.android.server.LocalServices;
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.FileMetadata;
import com.android.server.backup.KeyValueAdbRestoreEngine;
@@ -207,8 +209,11 @@ public class FullRestoreEngine extends RestoreEngine {
if (info.path.equals(BACKUP_MANIFEST_FILENAME)) {
Signature[] signatures = tarBackupReader.readAppManifestAndReturnSignatures(
info);
PackageManagerInternal pmi = LocalServices.getService(
PackageManagerInternal.class);
RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy(
mBackupManagerService.getPackageManager(), allowApks, info, signatures);
mBackupManagerService.getPackageManager(), allowApks, info, signatures,
pmi);
mManifestSignatures.put(info.packageName, signatures);
mPackagePolicies.put(pkg, restorePolicy);
mPackageInstallers.put(pkg, info.installerPackageName);

View File

@@ -40,6 +40,7 @@ import android.app.backup.IFullBackupRestoreObserver;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager.NameNotFoundException;
import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
import android.os.Environment;
import android.os.ParcelFileDescriptor;
@@ -47,6 +48,7 @@ import android.os.RemoteException;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import com.android.server.backup.BackupManagerService;
import com.android.server.backup.FileMetadata;
import com.android.server.backup.KeyValueAdbRestoreEngine;
@@ -470,9 +472,11 @@ public class PerformAdbRestoreTask implements Runnable {
if (info.path.equals(BACKUP_MANIFEST_FILENAME)) {
Signature[] signatures = tarBackupReader.readAppManifestAndReturnSignatures(
info);
PackageManagerInternal pmi = LocalServices.getService(
PackageManagerInternal.class);
RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy(
mBackupManagerService.getPackageManager(), allowApks,
info, signatures);
info, signatures, pmi);
mManifestSignatures.put(info.packageName, signatures);
mPackagePolicies.put(pkg, restorePolicy);
mPackageInstallers.put(pkg, info.installerPackageName);

View File

@@ -43,6 +43,7 @@ import android.app.backup.RestoreDescription;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageManager.NameNotFoundException;
import android.os.Bundle;
import android.os.Message;
@@ -57,6 +58,7 @@ import android.util.Slog;
import com.android.internal.backup.IBackupTransport;
import com.android.server.AppWidgetBackupBridge;
import com.android.server.EventLogTags;
import com.android.server.LocalServices;
import com.android.server.backup.BackupRestoreTask;
import com.android.server.backup.BackupUtils;
import com.android.server.backup.PackageManagerBackupAgent;
@@ -504,7 +506,7 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
try {
mCurrentPackage = backupManagerService.getPackageManager().getPackageInfo(
pkgName, PackageManager.GET_SIGNATURES);
pkgName, PackageManager.GET_SIGNING_CERTIFICATES);
} catch (NameNotFoundException e) {
// Whoops, we thought we could restore this package but it
// turns out not to be present. Skip it.
@@ -619,7 +621,8 @@ public class PerformUnifiedRestoreTask implements BackupRestoreTask {
}
Metadata metaInfo = mPmAgent.getRestoredMetadata(packageName);
if (!BackupUtils.signaturesMatch(metaInfo.sigHashes, mCurrentPackage)) {
PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
if (!BackupUtils.signaturesMatch(metaInfo.sigHashes, mCurrentPackage, pmi)) {
Slog.w(TAG, "Signature mismatch restoring " + packageName);
mMonitor = BackupManagerMonitorUtils.monitorEvent(mMonitor,
BackupManagerMonitor.LOG_EVENT_ID_SIGNATURE_MISMATCH, mCurrentPackage,

View File

@@ -25,6 +25,7 @@ import android.app.backup.BackupTransport;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
import android.os.Process;
import android.util.Slog;
@@ -37,6 +38,9 @@ import com.android.server.backup.transport.TransportClient;
* Utility methods wrapping operations on ApplicationInfo and PackageInfo.
*/
public class AppBackupUtils {
private static final boolean DEBUG = false;
/**
* Returns whether app is eligible for backup.
*
@@ -88,7 +92,8 @@ public class AppBackupUtils {
public static boolean appIsRunningAndEligibleForBackupWithTransport(
@Nullable TransportClient transportClient, String packageName, PackageManager pm) {
try {
PackageInfo packageInfo = pm.getPackageInfo(packageName, PackageManager.GET_SIGNATURES);
PackageInfo packageInfo = pm.getPackageInfo(packageName,
PackageManager.GET_SIGNING_CERTIFICATES);
ApplicationInfo applicationInfo = packageInfo.applicationInfo;
if (!appIsEligibleForBackup(applicationInfo, pm)
|| appIsStopped(applicationInfo)
@@ -165,12 +170,18 @@ public class AppBackupUtils {
*
* <ul>
* <li>Source and target have at least one signature each
* <li>Target contains all signatures in source
* <li>Target contains all signatures in source, and nothing more
* </ul>
*
* or if both source and target have exactly one signature, and they don't match, we check
* if the app was ever signed with source signature (i.e. app has rotated key)
* Note: key rotation is only supported for apps ever signed with one key, and those apps will
* not be allowed to be signed by more certificates in the future
*
* Note that if {@param target} is null we return false.
*/
public static boolean signaturesMatch(Signature[] storedSigs, PackageInfo target) {
public static boolean signaturesMatch(Signature[] storedSigs, PackageInfo target,
PackageManagerInternal pmi) {
if (target == null) {
return false;
}
@@ -187,33 +198,52 @@ public class AppBackupUtils {
return true;
}
Signature[] deviceSigs = target.signatures;
if (MORE_DEBUG) {
Slog.v(TAG, "signaturesMatch(): stored=" + storedSigs + " device=" + deviceSigs);
}
// Don't allow unsigned apps on either end
if (ArrayUtils.isEmpty(storedSigs) || ArrayUtils.isEmpty(deviceSigs)) {
if (ArrayUtils.isEmpty(storedSigs)) {
return false;
}
// Signatures can be added over time, so the target-device apk needs to contain all the
// source-device apk signatures, but not necessarily the other way around.
int nStored = storedSigs.length;
int nDevice = deviceSigs.length;
Signature[][] deviceHistorySigs = target.signingCertificateHistory;
if (ArrayUtils.isEmpty(deviceHistorySigs)) {
Slog.w(TAG, "signingCertificateHistory is empty, app was either unsigned or the flag" +
" PackageManager#GET_SIGNING_CERTIFICATES was not specified");
return false;
}
for (int i = 0; i < nStored; i++) {
boolean match = false;
for (int j = 0; j < nDevice; j++) {
if (storedSigs[i].equals(deviceSigs[j])) {
match = true;
break;
if (DEBUG) {
Slog.v(TAG, "signaturesMatch(): stored=" + storedSigs + " device=" + deviceHistorySigs);
}
final int nStored = storedSigs.length;
if (nStored == 1) {
// if the app is only signed with one sig, it's possible it has rotated its key
// (the checks with signing history are delegated to PackageManager)
// TODO: address the case that app has declared restoreAnyVersion and is restoring
// from higher version to lower after having rotated the key (i.e. higher version has
// different sig than lower version that we want to restore to)
return pmi.isDataRestoreSafe(storedSigs[0], target.packageName);
} else {
// the app couldn't have rotated keys, since it was signed with multiple sigs - do
// a comprehensive 1-to-1 signatures check
// since app hasn't rotated key, we only need to check with deviceHistorySigs[0]
Signature[] deviceSigs = deviceHistorySigs[0];
int nDevice = deviceSigs.length;
// ensure that each stored sig matches an on-device sig
for (int i = 0; i < nStored; i++) {
boolean match = false;
for (int j = 0; j < nDevice; j++) {
if (storedSigs[i].equals(deviceSigs[j])) {
match = true;
break;
}
}
if (!match) {
return false;
}
}
if (!match) {
return false;
}
// we have found a match for all stored sigs
return true;
}
return true;
}
}

View File

@@ -30,6 +30,7 @@ import android.content.pm.PackageInstaller;
import android.content.pm.PackageInstaller.Session;
import android.content.pm.PackageInstaller.SessionParams;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
import android.os.Bundle;
import android.os.IBinder;
@@ -37,6 +38,7 @@ import android.os.Process;
import android.util.Slog;
import com.android.internal.annotations.GuardedBy;
import com.android.server.LocalServices;
import com.android.server.backup.FileMetadata;
import com.android.server.backup.restore.RestoreDeleteObserver;
import com.android.server.backup.restore.RestorePolicy;
@@ -142,9 +144,8 @@ public class RestoreUtils {
uninstall = true;
} else {
try {
PackageInfo pkg = packageManager.getPackageInfo(
info.packageName,
PackageManager.GET_SIGNATURES);
PackageInfo pkg = packageManager.getPackageInfo(info.packageName,
PackageManager.GET_SIGNING_CERTIFICATES);
if ((pkg.applicationInfo.flags & ApplicationInfo.FLAG_ALLOW_BACKUP)
== 0) {
Slog.w(TAG, "Restore stream contains apk of package "
@@ -154,7 +155,9 @@ public class RestoreUtils {
} else {
// So far so good -- do the signatures match the manifest?
Signature[] sigs = manifestSignatures.get(info.packageName);
if (AppBackupUtils.signaturesMatch(sigs, pkg)) {
PackageManagerInternal pmi = LocalServices.getService(
PackageManagerInternal.class);
if (AppBackupUtils.signaturesMatch(sigs, pkg, pmi)) {
// If this is a system-uid app without a declared backup agent,
// don't restore any of the file data.
if ((pkg.applicationInfo.uid < Process.FIRST_APPLICATION_UID)

View File

@@ -50,6 +50,7 @@ import android.app.backup.IBackupManagerMonitor;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
import android.os.Bundle;
import android.os.Process;
@@ -385,7 +386,8 @@ public class TarBackupReader {
* @return a restore policy constant.
*/
public RestorePolicy chooseRestorePolicy(PackageManager packageManager,
boolean allowApks, FileMetadata info, Signature[] signatures) {
boolean allowApks, FileMetadata info, Signature[] signatures,
PackageManagerInternal pmi) {
if (signatures == null) {
return RestorePolicy.IGNORE;
}
@@ -395,7 +397,7 @@ public class TarBackupReader {
// Okay, got the manifest info we need...
try {
PackageInfo pkgInfo = packageManager.getPackageInfo(
info.packageName, PackageManager.GET_SIGNATURES);
info.packageName, PackageManager.GET_SIGNING_CERTIFICATES);
// Fall through to IGNORE if the app explicitly disallows backup
final int flags = pkgInfo.applicationInfo.flags;
if ((flags & ApplicationInfo.FLAG_ALLOW_BACKUP) != 0) {
@@ -411,7 +413,7 @@ public class TarBackupReader {
// such packages are signed with the platform cert instead of
// the app developer's cert, so they're different on every
// device.
if (AppBackupUtils.signaturesMatch(signatures, pkgInfo)) {
if (AppBackupUtils.signaturesMatch(signatures, pkgInfo, pmi)) {
if ((pkgInfo.applicationInfo.flags
& ApplicationInfo.FLAG_RESTORE_ANY_VERSION) != 0) {
Slog.i(TAG, "Package has restoreAnyVersion; taking data");

View File

@@ -18,9 +18,12 @@ package com.android.server.backup;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
import android.util.Slog;
import com.android.internal.util.ArrayUtils;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
@@ -30,9 +33,10 @@ import java.util.List;
public class BackupUtils {
private static final String TAG = "BackupUtils";
private static final boolean DEBUG = false; // STOPSHIP if true
private static final boolean DEBUG = false;
public static boolean signaturesMatch(ArrayList<byte[]> storedSigHashes, PackageInfo target) {
public static boolean signaturesMatch(ArrayList<byte[]> storedSigHashes, PackageInfo target,
PackageManagerInternal pmi) {
if (target == null) {
return false;
}
@@ -47,48 +51,54 @@ public class BackupUtils {
return true;
}
// Allow unsigned apps, but not signed on one device and unsigned on the other
// !!! TODO: is this the right policy?
Signature[] deviceSigs = target.signatures;
if (DEBUG) Slog.v(TAG, "signaturesMatch(): stored=" + storedSigHashes
+ " device=" + deviceSigs);
if ((storedSigHashes == null || storedSigHashes.size() == 0)
&& (deviceSigs == null || deviceSigs.length == 0)) {
return true;
}
if (storedSigHashes == null || deviceSigs == null) {
// Don't allow unsigned apps on either end
if (ArrayUtils.isEmpty(storedSigHashes)) {
return false;
}
// !!! TODO: this demands that every stored signature match one
// that is present on device, and does not demand the converse.
// Is this this right policy?
final int nStored = storedSigHashes.size();
final int nDevice = deviceSigs.length;
// hash each on-device signature
ArrayList<byte[]> deviceHashes = new ArrayList<byte[]>(nDevice);
for (int i = 0; i < nDevice; i++) {
deviceHashes.add(hashSignature(deviceSigs[i]));
Signature[][] deviceHistorySigs = target.signingCertificateHistory;
if (ArrayUtils.isEmpty(deviceHistorySigs)) {
Slog.w(TAG, "signingCertificateHistory is empty, app was either unsigned or the flag" +
" PackageManager#GET_SIGNING_CERTIFICATES was not specified");
return false;
}
// now ensure that each stored sig (hash) matches an on-device sig (hash)
for (int n = 0; n < nStored; n++) {
boolean match = false;
final byte[] storedHash = storedSigHashes.get(n);
for (int i = 0; i < nDevice; i++) {
if (Arrays.equals(storedHash, deviceHashes.get(i))) {
match = true;
break;
if (DEBUG) {
Slog.v(TAG, "signaturesMatch(): stored=" + storedSigHashes
+ " device=" + deviceHistorySigs);
}
final int nStored = storedSigHashes.size();
if (nStored == 1) {
// if the app is only signed with one sig, it's possible it has rotated its key
// the checks with signing history are delegated to PackageManager
// TODO: address the case that app has declared restoreAnyVersion and is restoring
// from higher version to lower after having rotated the key (i.e. higher version has
// different sig than lower version that we want to restore to)
return pmi.isDataRestoreSafe(storedSigHashes.get(0), target.packageName);
} else {
// the app couldn't have rotated keys, since it was signed with multiple sigs - do
// a comprehensive 1-to-1 signatures check
// since app hasn't rotated key, we only need to check with deviceHistorySigs[0]
ArrayList<byte[]> deviceHashes = hashSignatureArray(deviceHistorySigs[0]);
int nDevice = deviceHashes.size();
// ensure that each stored sig matches an on-device sig
for (int i = 0; i < nStored; i++) {
boolean match = false;
for (int j = 0; j < nDevice; j++) {
if (Arrays.equals(storedSigHashes.get(i), deviceHashes.get(j))) {
match = true;
break;
}
}
if (!match) {
return false;
}
}
// match is false when no on-device sig matched one of the stored ones
if (!match) {
return false;
}
// we have found a match for all stored sigs
return true;
}
return true;
}
public static byte[] hashSignature(byte[] signature) {

View File

@@ -176,6 +176,7 @@ import android.content.pm.PackageParser.PackageLite;
import android.content.pm.PackageParser.PackageParserException;
import android.content.pm.PackageParser.ParseFlags;
import android.content.pm.PackageParser.ServiceIntentInfo;
import android.content.pm.PackageParser.SigningDetails;
import android.content.pm.PackageParser.SigningDetails.SignatureSchemeVersion;
import android.content.pm.PackageStats;
import android.content.pm.PackageUserState;
@@ -23319,6 +23320,39 @@ Slog.v(TAG, ":: stepped forward, applying functor at tag " + parser.getName());
permName, packageName, flagMask, flagValues, userId);
}
@Override
public boolean isDataRestoreSafe(byte[] restoringFromSigHash, String packageName) {
SigningDetails sd = getSigningDetails(packageName);
if (sd == null) {
return false;
}
return sd.hasSha256Certificate(restoringFromSigHash,
SigningDetails.CertCapabilities.INSTALLED_DATA);
}
@Override
public boolean isDataRestoreSafe(Signature restoringFromSig, String packageName) {
SigningDetails sd = getSigningDetails(packageName);
if (sd == null) {
return false;
}
return sd.hasCertificate(restoringFromSig,
SigningDetails.CertCapabilities.INSTALLED_DATA);
}
private SigningDetails getSigningDetails(String packageName) {
synchronized (mPackages) {
if (packageName == null) {
return null;
}
PackageParser.Package p = mPackages.get(packageName);
if (p == null) {
return null;
}
return p.mSigningDetails;
}
}
@Override
public int getPermissionFlagsTEMP(String permName, String packageName, int userId) {
return PackageManagerService.this.getPermissionFlags(permName, packageName, userId);

View File

@@ -18,10 +18,12 @@ package com.android.server.pm;
import android.annotation.NonNull;
import android.annotation.UserIdInt;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManagerInternal;
import android.content.pm.ShortcutInfo;
import android.util.Slog;
import com.android.internal.annotations.VisibleForTesting;
import com.android.server.LocalServices;
import com.android.server.backup.BackupUtils;
import libcore.util.HexEncoding;
@@ -136,7 +138,8 @@ class ShortcutPackageInfo {
//@DisabledReason
public int canRestoreTo(ShortcutService s, PackageInfo currentPackage, boolean anyVersionOkay) {
if (!BackupUtils.signaturesMatch(mSigHashes, currentPackage)) {
PackageManagerInternal pmi = LocalServices.getService(PackageManagerInternal.class);
if (!BackupUtils.signaturesMatch(mSigHashes, currentPackage, pmi)) {
Slog.w(TAG, "Can't restore: Package signature mismatch");
return ShortcutInfo.DISABLED_REASON_SIGNATURE_MISMATCH;
}

View File

@@ -3125,7 +3125,8 @@ public class ShortcutService extends IShortcutService.Stub {
try {
return mIPackageManager.getPackageInfo(
packageName, PACKAGE_MATCH_FLAGS
| (getSignatures ? PackageManager.GET_SIGNATURES : 0), userId);
| (getSignatures ? PackageManager.GET_SIGNING_CERTIFICATES : 0),
userId);
} catch (RemoteException e) {
// Shouldn't happen.
Slog.wtf(TAG, "RemoteException", e);

View File

@@ -18,9 +18,14 @@ package com.android.server.backup.utils;
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManager;
import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
import android.os.Process;
import android.platform.test.annotations.Presubmit;
@@ -30,6 +35,7 @@ import android.support.test.runner.AndroidJUnit4;
import com.android.server.backup.BackupManagerService;
import com.android.server.backup.testutils.PackageManagerStub;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
@@ -45,7 +51,14 @@ public class AppBackupUtilsTest {
private static final Signature SIGNATURE_3 = generateSignature((byte) 3);
private static final Signature SIGNATURE_4 = generateSignature((byte) 4);
private final PackageManagerStub mPackageManagerStub = new PackageManagerStub();
private PackageManagerStub mPackageManagerStub;
private PackageManagerInternal mMockPackageManagerInternal;
@Before
public void setUp() throws Exception {
mPackageManagerStub = new PackageManagerStub();
mMockPackageManagerInternal = mock(PackageManagerInternal.class);
}
@Test
public void appIsEligibleForBackup_backupNotAllowed_returnsFalse() throws Exception {
@@ -358,7 +371,8 @@ public class AppBackupUtilsTest {
@Test
public void signaturesMatch_targetIsNull_returnsFalse() throws Exception {
boolean result = AppBackupUtils.signaturesMatch(new Signature[] {SIGNATURE_1}, null);
boolean result = AppBackupUtils.signaturesMatch(new Signature[] {SIGNATURE_1}, null,
mMockPackageManagerInternal);
assertThat(result).isFalse();
}
@@ -369,7 +383,8 @@ public class AppBackupUtilsTest {
packageInfo.applicationInfo = new ApplicationInfo();
packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
boolean result = AppBackupUtils.signaturesMatch(new Signature[0], packageInfo);
boolean result = AppBackupUtils.signaturesMatch(new Signature[0], packageInfo,
mMockPackageManagerInternal);
assertThat(result).isTrue();
}
@@ -378,10 +393,11 @@ public class AppBackupUtilsTest {
public void signaturesMatch_disallowsUnsignedApps_storedSignatureNull_returnsFalse()
throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.signatures = new Signature[] {SIGNATURE_1};
packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}};
packageInfo.applicationInfo = new ApplicationInfo();
boolean result = AppBackupUtils.signaturesMatch(null, packageInfo);
boolean result = AppBackupUtils.signaturesMatch(null, packageInfo,
mMockPackageManagerInternal);
assertThat(result).isFalse();
}
@@ -390,10 +406,11 @@ public class AppBackupUtilsTest {
public void signaturesMatch_disallowsUnsignedApps_storedSignatureEmpty_returnsFalse()
throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.signatures = new Signature[] {SIGNATURE_1};
packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}};
packageInfo.applicationInfo = new ApplicationInfo();
boolean result = AppBackupUtils.signaturesMatch(new Signature[0], packageInfo);
boolean result = AppBackupUtils.signaturesMatch(new Signature[0], packageInfo,
mMockPackageManagerInternal);
assertThat(result).isFalse();
}
@@ -404,11 +421,11 @@ public class AppBackupUtilsTest {
signaturesMatch_disallowsUnsignedApps_targetSignatureEmpty_returnsFalse()
throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.signatures = new Signature[0];
packageInfo.signingCertificateHistory = new Signature[0][0];
packageInfo.applicationInfo = new ApplicationInfo();
boolean result = AppBackupUtils.signaturesMatch(new Signature[] {SIGNATURE_1},
packageInfo);
boolean result = AppBackupUtils.signaturesMatch(new Signature[] {SIGNATURE_1}, packageInfo,
mMockPackageManagerInternal);
assertThat(result).isFalse();
}
@@ -418,11 +435,11 @@ public class AppBackupUtilsTest {
signaturesMatch_disallowsUnsignedApps_targetSignatureNull_returnsFalse()
throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.signatures = null;
packageInfo.signingCertificateHistory = null;
packageInfo.applicationInfo = new ApplicationInfo();
boolean result = AppBackupUtils.signaturesMatch(new Signature[] {SIGNATURE_1},
packageInfo);
boolean result = AppBackupUtils.signaturesMatch(new Signature[] {SIGNATURE_1}, packageInfo,
mMockPackageManagerInternal);
assertThat(result).isFalse();
}
@@ -431,10 +448,11 @@ public class AppBackupUtilsTest {
public void signaturesMatch_disallowsUnsignedApps_bothSignaturesNull_returnsFalse()
throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.signatures = null;
packageInfo.signingCertificateHistory = null;
packageInfo.applicationInfo = new ApplicationInfo();
boolean result = AppBackupUtils.signaturesMatch(null, packageInfo);
boolean result = AppBackupUtils.signaturesMatch(null, packageInfo,
mMockPackageManagerInternal);
assertThat(result).isFalse();
}
@@ -443,10 +461,11 @@ public class AppBackupUtilsTest {
public void signaturesMatch_disallowsUnsignedApps_bothSignaturesEmpty_returnsFalse()
throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.signatures = new Signature[0];
packageInfo.signingCertificateHistory = new Signature[0][0];
packageInfo.applicationInfo = new ApplicationInfo();
boolean result = AppBackupUtils.signaturesMatch(new Signature[0], packageInfo);
boolean result = AppBackupUtils.signaturesMatch(new Signature[0], packageInfo,
mMockPackageManagerInternal);
assertThat(result).isFalse();
}
@@ -458,11 +477,14 @@ public class AppBackupUtilsTest {
Signature signature3Copy = new Signature(SIGNATURE_3.toByteArray());
PackageInfo packageInfo = new PackageInfo();
packageInfo.signatures = new Signature[]{SIGNATURE_1, SIGNATURE_2, SIGNATURE_3};
packageInfo.signingCertificateHistory = new Signature[][] {
{SIGNATURE_1, SIGNATURE_2, SIGNATURE_3}
};
packageInfo.applicationInfo = new ApplicationInfo();
boolean result = AppBackupUtils.signaturesMatch(
new Signature[]{signature3Copy, signature1Copy, signature2Copy}, packageInfo);
new Signature[] {signature3Copy, signature1Copy, signature2Copy}, packageInfo,
mMockPackageManagerInternal);
assertThat(result).isTrue();
}
@@ -473,11 +495,14 @@ public class AppBackupUtilsTest {
Signature signature2Copy = new Signature(SIGNATURE_2.toByteArray());
PackageInfo packageInfo = new PackageInfo();
packageInfo.signatures = new Signature[]{SIGNATURE_1, SIGNATURE_2, SIGNATURE_3};
packageInfo.signingCertificateHistory = new Signature[][] {
{SIGNATURE_1, SIGNATURE_2, SIGNATURE_3}
};
packageInfo.applicationInfo = new ApplicationInfo();
boolean result = AppBackupUtils.signaturesMatch(
new Signature[]{signature2Copy, signature1Copy}, packageInfo);
new Signature[]{signature2Copy, signature1Copy}, packageInfo,
mMockPackageManagerInternal);
assertThat(result).isTrue();
}
@@ -488,11 +513,14 @@ public class AppBackupUtilsTest {
Signature signature2Copy = new Signature(SIGNATURE_2.toByteArray());
PackageInfo packageInfo = new PackageInfo();
packageInfo.signatures = new Signature[]{signature1Copy, signature2Copy};
packageInfo.signingCertificateHistory = new Signature[][] {
{signature1Copy, signature2Copy}
};
packageInfo.applicationInfo = new ApplicationInfo();
boolean result = AppBackupUtils.signaturesMatch(
new Signature[]{SIGNATURE_1, SIGNATURE_2, SIGNATURE_3}, packageInfo);
new Signature[]{SIGNATURE_1, SIGNATURE_2, SIGNATURE_3}, packageInfo,
mMockPackageManagerInternal);
assertThat(result).isFalse();
}
@@ -503,11 +531,76 @@ public class AppBackupUtilsTest {
Signature signature2Copy = new Signature(SIGNATURE_2.toByteArray());
PackageInfo packageInfo = new PackageInfo();
packageInfo.signatures = new Signature[]{SIGNATURE_1, SIGNATURE_2, SIGNATURE_3};
packageInfo.signingCertificateHistory = new Signature[][] {
{SIGNATURE_1, SIGNATURE_2, SIGNATURE_3}
};
packageInfo.applicationInfo = new ApplicationInfo();
boolean result = AppBackupUtils.signaturesMatch(
new Signature[]{signature1Copy, signature2Copy, SIGNATURE_4}, packageInfo);
new Signature[]{signature1Copy, signature2Copy, SIGNATURE_4}, packageInfo,
mMockPackageManagerInternal);
assertThat(result).isFalse();
}
@Test
public void signaturesMatch_singleStoredSignatureNoRotation_returnsTrue()
throws Exception {
Signature signature1Copy = new Signature(SIGNATURE_1.toByteArray());
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}};
packageInfo.applicationInfo = new ApplicationInfo();
doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(signature1Copy,
packageInfo.packageName);
boolean result = AppBackupUtils.signaturesMatch(new Signature[] {signature1Copy},
packageInfo, mMockPackageManagerInternal);
assertThat(result).isTrue();
}
@Test
public void signaturesMatch_singleStoredSignatureWithRotationAssumeDataCapability_returnsTrue()
throws Exception {
Signature signature1Copy = new Signature(SIGNATURE_1.toByteArray());
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}, {SIGNATURE_2}};
packageInfo.applicationInfo = new ApplicationInfo();
// we know signature1Copy is in history, and we want to assume it has
// SigningDetails.CertCapabilities.INSTALLED_DATA capability
doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(signature1Copy,
packageInfo.packageName);
boolean result = AppBackupUtils.signaturesMatch(new Signature[] {signature1Copy},
packageInfo, mMockPackageManagerInternal);
assertThat(result).isTrue();
}
@Test
public void
signaturesMatch_singleStoredSignatureWithRotationAssumeNoDataCapability_returnsFalse()
throws Exception {
Signature signature1Copy = new Signature(SIGNATURE_1.toByteArray());
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}, {SIGNATURE_2}};
packageInfo.applicationInfo = new ApplicationInfo();
// we know signature1Copy is in history, but we want to assume it does not have
// SigningDetails.CertCapabilities.INSTALLED_DATA capability
doReturn(false).when(mMockPackageManagerInternal).isDataRestoreSafe(signature1Copy,
packageInfo.packageName);
boolean result = AppBackupUtils.signaturesMatch(new Signature[] {signature1Copy},
packageInfo, mMockPackageManagerInternal);
assertThat(result).isFalse();
}

View File

@@ -28,6 +28,9 @@ import static android.app.backup.BackupManagerMonitor.LOG_EVENT_ID_VERSION_OF_BA
import static com.google.common.truth.Truth.assertThat;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import static org.mockito.Mockito.verify;
import static org.mockito.Mockito.verifyNoMoreInteractions;
import static org.mockito.Mockito.verifyZeroInteractions;
@@ -37,6 +40,7 @@ import android.app.backup.IBackupManagerMonitor;
import android.content.Context;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManagerInternal;
import android.content.pm.Signature;
import android.os.Bundle;
import android.os.Process;
@@ -79,6 +83,7 @@ public class TarBackupReaderTest {
@Mock private BytesReadListener mBytesReadListenerMock;
@Mock private IBackupManagerMonitor mBackupManagerMonitorMock;
@Mock private PackageManagerInternal mMockPackageManagerInternal;
private final PackageManagerStub mPackageManagerStub = new PackageManagerStub();
private Context mContext;
@@ -139,7 +144,8 @@ public class TarBackupReaderTest {
Signature[] signatures = tarBackupReader.readAppManifestAndReturnSignatures(
fileMetadata);
RestorePolicy restorePolicy = tarBackupReader.chooseRestorePolicy(
mPackageManagerStub, false /* allowApks */, fileMetadata, signatures);
mPackageManagerStub, false /* allowApks */, fileMetadata, signatures,
mMockPackageManagerInternal);
assertThat(restorePolicy).isEqualTo(RestorePolicy.IGNORE);
assertThat(fileMetadata.packageName).isEqualTo(TEST_PACKAGE_NAME);
@@ -152,7 +158,8 @@ public class TarBackupReaderTest {
signatures = tarBackupReader.readAppManifestAndReturnSignatures(
fileMetadata);
restorePolicy = tarBackupReader.chooseRestorePolicy(
mPackageManagerStub, false /* allowApks */, fileMetadata, signatures);
mPackageManagerStub, false /* allowApks */, fileMetadata, signatures,
mMockPackageManagerInternal);
assertThat(restorePolicy).isEqualTo(RestorePolicy.IGNORE);
assertThat(fileMetadata.packageName).isEqualTo(TEST_PACKAGE_NAME);
@@ -214,7 +221,8 @@ public class TarBackupReaderTest {
mBytesReadListenerMock, mBackupManagerMonitorMock);
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
true /* allowApks */, new FileMetadata(), null /* signatures */);
true /* allowApks */, new FileMetadata(), null /* signatures */,
mMockPackageManagerInternal);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
verifyZeroInteractions(mBackupManagerMonitorMock);
@@ -234,7 +242,8 @@ public class TarBackupReaderTest {
PackageManagerStub.sPackageInfo = null;
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
true /* allowApks */, info, new Signature[0] /* signatures */);
true /* allowApks */, info, new Signature[0] /* signatures */,
mMockPackageManagerInternal);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT_IF_APK);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -258,7 +267,8 @@ public class TarBackupReaderTest {
PackageManagerStub.sPackageInfo = null;
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
true /* allowApks */, info, new Signature[0] /* signatures */);
true /* allowApks */, info, new Signature[0] /* signatures */,
mMockPackageManagerInternal);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT_IF_APK);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -283,7 +293,8 @@ public class TarBackupReaderTest {
PackageManagerStub.sPackageInfo = null;
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */);
false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */,
mMockPackageManagerInternal);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -307,7 +318,8 @@ public class TarBackupReaderTest {
PackageManagerStub.sPackageInfo = packageInfo;
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */);
false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */,
mMockPackageManagerInternal);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -333,7 +345,8 @@ public class TarBackupReaderTest {
PackageManagerStub.sPackageInfo = packageInfo;
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */);
false /* allowApks */, new FileMetadata(), new Signature[0] /* signatures */,
mMockPackageManagerInternal);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -358,11 +371,11 @@ public class TarBackupReaderTest {
packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
packageInfo.applicationInfo.uid = Process.FIRST_APPLICATION_UID;
packageInfo.applicationInfo.backupAgentName = null;
packageInfo.signatures = new Signature[]{FAKE_SIGNATURE_2};
packageInfo.signingCertificateHistory = new Signature[][] {{FAKE_SIGNATURE_2}};
PackageManagerStub.sPackageInfo = packageInfo;
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
false /* allowApks */, new FileMetadata(), signatures);
false /* allowApks */, new FileMetadata(), signatures, mMockPackageManagerInternal);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -383,16 +396,19 @@ public class TarBackupReaderTest {
Signature[] signatures = new Signature[]{FAKE_SIGNATURE_1};
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
packageInfo.applicationInfo = new ApplicationInfo();
packageInfo.applicationInfo.flags |=
ApplicationInfo.FLAG_ALLOW_BACKUP | ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
packageInfo.applicationInfo.uid = Process.SYSTEM_UID;
packageInfo.applicationInfo.backupAgentName = "backup.agent";
packageInfo.signatures = new Signature[]{FAKE_SIGNATURE_1};
packageInfo.signingCertificateHistory = new Signature[][] {{FAKE_SIGNATURE_1}};
PackageManagerStub.sPackageInfo = packageInfo;
doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
packageInfo.packageName);
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
false /* allowApks */, new FileMetadata(), signatures);
false /* allowApks */, new FileMetadata(), signatures, mMockPackageManagerInternal);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -412,16 +428,19 @@ public class TarBackupReaderTest {
Signature[] signatures = new Signature[]{FAKE_SIGNATURE_1};
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
packageInfo.applicationInfo = new ApplicationInfo();
packageInfo.applicationInfo.flags |=
ApplicationInfo.FLAG_ALLOW_BACKUP | ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
packageInfo.applicationInfo.uid = Process.FIRST_APPLICATION_UID;
packageInfo.applicationInfo.backupAgentName = null;
packageInfo.signatures = new Signature[]{FAKE_SIGNATURE_1};
packageInfo.signingCertificateHistory = new Signature[][] {{FAKE_SIGNATURE_1}};
PackageManagerStub.sPackageInfo = packageInfo;
doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
packageInfo.packageName);
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
false /* allowApks */, new FileMetadata(), signatures);
false /* allowApks */, new FileMetadata(), signatures, mMockPackageManagerInternal);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -444,17 +463,20 @@ public class TarBackupReaderTest {
info.version = 1;
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
packageInfo.applicationInfo = new ApplicationInfo();
packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
packageInfo.applicationInfo.flags &= ~ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
packageInfo.applicationInfo.uid = Process.FIRST_APPLICATION_UID;
packageInfo.applicationInfo.backupAgentName = null;
packageInfo.signatures = new Signature[]{FAKE_SIGNATURE_1};
packageInfo.signingCertificateHistory = new Signature[][] {{FAKE_SIGNATURE_1}};
packageInfo.versionCode = 2;
PackageManagerStub.sPackageInfo = packageInfo;
doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
packageInfo.packageName);
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
false /* allowApks */, info, signatures);
false /* allowApks */, info, signatures, mMockPackageManagerInternal);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);
@@ -479,17 +501,20 @@ public class TarBackupReaderTest {
info.hasApk = true;
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
packageInfo.applicationInfo = new ApplicationInfo();
packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
packageInfo.applicationInfo.flags &= ~ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
packageInfo.applicationInfo.uid = Process.FIRST_APPLICATION_UID;
packageInfo.applicationInfo.backupAgentName = null;
packageInfo.signatures = new Signature[]{FAKE_SIGNATURE_1};
packageInfo.signingCertificateHistory = new Signature[][] {{FAKE_SIGNATURE_1}};
packageInfo.versionCode = 1;
PackageManagerStub.sPackageInfo = packageInfo;
doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
packageInfo.packageName);
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
true /* allowApks */, info, signatures);
true /* allowApks */, info, signatures, mMockPackageManagerInternal);
assertThat(policy).isEqualTo(RestorePolicy.ACCEPT_IF_APK);
verifyNoMoreInteractions(mBackupManagerMonitorMock);
@@ -510,17 +535,20 @@ public class TarBackupReaderTest {
info.version = 2;
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
packageInfo.applicationInfo = new ApplicationInfo();
packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_ALLOW_BACKUP;
packageInfo.applicationInfo.flags &= ~ApplicationInfo.FLAG_RESTORE_ANY_VERSION;
packageInfo.applicationInfo.uid = Process.FIRST_APPLICATION_UID;
packageInfo.applicationInfo.backupAgentName = null;
packageInfo.signatures = new Signature[]{FAKE_SIGNATURE_1};
packageInfo.signingCertificateHistory = new Signature[][] {{FAKE_SIGNATURE_1}};
packageInfo.versionCode = 1;
PackageManagerStub.sPackageInfo = packageInfo;
doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(FAKE_SIGNATURE_1,
packageInfo.packageName);
RestorePolicy policy = tarBackupReader.chooseRestorePolicy(mPackageManagerStub,
false /* allowApks */, info, signatures);
false /* allowApks */, info, signatures, mMockPackageManagerInternal);
assertThat(policy).isEqualTo(RestorePolicy.IGNORE);
ArgumentCaptor<Bundle> bundleCaptor = ArgumentCaptor.forClass(Bundle.class);

View File

@@ -15,73 +15,298 @@
*/
package com.android.server.pm.backup;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertFalse;
import static org.junit.Assert.assertTrue;
import static org.junit.Assert.assertEquals;
import static org.mockito.Mockito.doReturn;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.when;
import android.content.pm.ApplicationInfo;
import android.content.pm.PackageInfo;
import android.content.pm.PackageManagerInternal;
import android.content.pm.PackageParser.Package;
import android.content.pm.Signature;
import android.test.AndroidTestCase;
import android.test.MoreAsserts;
import android.test.suitebuilder.annotation.SmallTest;
import android.platform.test.annotations.Presubmit;
import android.support.test.filters.SmallTest;
import android.support.test.runner.AndroidJUnit4;
import com.android.server.backup.BackupUtils;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import java.util.ArrayList;
import java.util.Arrays;
@SmallTest
public class BackupUtilsTest extends AndroidTestCase {
@Presubmit
@RunWith(AndroidJUnit4.class)
public class BackupUtilsTest {
private Signature[] genSignatures(String... signatures) {
final Signature[] sigs = new Signature[signatures.length];
for (int i = 0; i < signatures.length; i++){
sigs[i] = new Signature(signatures[i].getBytes());
}
return sigs;
private static final Signature SIGNATURE_1 = generateSignature((byte) 1);
private static final Signature SIGNATURE_2 = generateSignature((byte) 2);
private static final Signature SIGNATURE_3 = generateSignature((byte) 3);
private static final Signature SIGNATURE_4 = generateSignature((byte) 4);
private static final byte[] SIGNATURE_HASH_1 = BackupUtils.hashSignature(SIGNATURE_1);
private static final byte[] SIGNATURE_HASH_2 = BackupUtils.hashSignature(SIGNATURE_2);
private static final byte[] SIGNATURE_HASH_3 = BackupUtils.hashSignature(SIGNATURE_3);
private static final byte[] SIGNATURE_HASH_4 = BackupUtils.hashSignature(SIGNATURE_4);
private PackageManagerInternal mMockPackageManagerInternal;
@Before
public void setUp() throws Exception {
mMockPackageManagerInternal = mock(PackageManagerInternal.class);
}
private PackageInfo genPackage(String... signatures) {
final PackageInfo pi = new PackageInfo();
pi.packageName = "package";
pi.applicationInfo = new ApplicationInfo();
pi.signatures = genSignatures(signatures);
@Test
public void signaturesMatch_targetIsNull_returnsFalse() throws Exception {
ArrayList<byte[]> storedSigHashes = new ArrayList<>();
storedSigHashes.add(SIGNATURE_HASH_1);
boolean result = BackupUtils.signaturesMatch(storedSigHashes, null,
mMockPackageManagerInternal);
return pi;
assertThat(result).isFalse();
}
public void testSignaturesMatch() {
final ArrayList<byte[]> stored1 = BackupUtils.hashSignatureArray(Arrays.asList(
"abc".getBytes()));
final ArrayList<byte[]> stored2 = BackupUtils.hashSignatureArray(Arrays.asList(
"abc".getBytes(), "def".getBytes()));
@Test
public void signaturesMatch_systemApplication_returnsTrue() throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.applicationInfo = new ApplicationInfo();
packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
PackageInfo pi;
ArrayList<byte[]> storedSigHashes = new ArrayList<>();
storedSigHashes.add(SIGNATURE_HASH_1);
boolean result = BackupUtils.signaturesMatch(storedSigHashes, packageInfo,
mMockPackageManagerInternal);
// False for null package.
assertFalse(BackupUtils.signaturesMatch(stored1, null));
// If it's a system app, signatures don't matter.
pi = genPackage("xyz");
pi.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
assertTrue(BackupUtils.signaturesMatch(stored1, pi));
// Non system apps.
assertTrue(BackupUtils.signaturesMatch(stored1, genPackage("abc")));
// Superset is okay.
assertTrue(BackupUtils.signaturesMatch(stored1, genPackage("abc", "xyz")));
assertTrue(BackupUtils.signaturesMatch(stored1, genPackage("xyz", "abc")));
assertFalse(BackupUtils.signaturesMatch(stored1, genPackage("xyz")));
assertFalse(BackupUtils.signaturesMatch(stored1, genPackage("xyz", "def")));
assertTrue(BackupUtils.signaturesMatch(stored2, genPackage("def", "abc")));
assertTrue(BackupUtils.signaturesMatch(stored2, genPackage("x", "def", "abc", "y")));
// Subset is not okay.
assertFalse(BackupUtils.signaturesMatch(stored2, genPackage("abc")));
assertFalse(BackupUtils.signaturesMatch(stored2, genPackage("def")));
assertThat(result).isTrue();
}
@Test
public void signaturesMatch_disallowsUnsignedApps_storedSignatureNull_returnsFalse()
throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}};
packageInfo.applicationInfo = new ApplicationInfo();
boolean result = BackupUtils.signaturesMatch(null, packageInfo,
mMockPackageManagerInternal);
assertThat(result).isFalse();
}
@Test
public void signaturesMatch_disallowsUnsignedApps_storedSignatureEmpty_returnsFalse()
throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}};
packageInfo.applicationInfo = new ApplicationInfo();
ArrayList<byte[]> storedSigHashes = new ArrayList<>();
boolean result = BackupUtils.signaturesMatch(storedSigHashes, packageInfo,
mMockPackageManagerInternal);
assertThat(result).isFalse();
}
@Test
public void
signaturesMatch_disallowsUnsignedApps_targetSignatureEmpty_returnsFalse()
throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.signingCertificateHistory = new Signature[0][0];
packageInfo.applicationInfo = new ApplicationInfo();
ArrayList<byte[]> storedSigHashes = new ArrayList<>();
storedSigHashes.add(SIGNATURE_HASH_1);
boolean result = BackupUtils.signaturesMatch(storedSigHashes, packageInfo,
mMockPackageManagerInternal);
assertThat(result).isFalse();
}
@Test
public void
signaturesMatch_disallowsUnsignedApps_targetSignatureNull_returnsFalse()
throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.signingCertificateHistory = null;
packageInfo.applicationInfo = new ApplicationInfo();
ArrayList<byte[]> storedSigHashes = new ArrayList<>();
storedSigHashes.add(SIGNATURE_HASH_1);
boolean result = BackupUtils.signaturesMatch(storedSigHashes, packageInfo,
mMockPackageManagerInternal);
assertThat(result).isFalse();
}
@Test
public void signaturesMatch_disallowsUnsignedApps_bothSignaturesNull_returnsFalse()
throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.signingCertificateHistory = null;
packageInfo.applicationInfo = new ApplicationInfo();
boolean result = BackupUtils.signaturesMatch(null, packageInfo,
mMockPackageManagerInternal);
assertThat(result).isFalse();
}
@Test
public void signaturesMatch_disallowsUnsignedApps_bothSignaturesEmpty_returnsFalse()
throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.signingCertificateHistory = new Signature[0][0];
packageInfo.applicationInfo = new ApplicationInfo();
ArrayList<byte[]> storedSigHashes = new ArrayList<>();
boolean result = BackupUtils.signaturesMatch(storedSigHashes, packageInfo,
mMockPackageManagerInternal);
assertThat(result).isFalse();
}
@Test
public void signaturesMatch_equalSignatures_returnsTrue() throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.signingCertificateHistory = new Signature[][] {
{SIGNATURE_1, SIGNATURE_2, SIGNATURE_3}
};
packageInfo.applicationInfo = new ApplicationInfo();
ArrayList<byte[]> storedSigHashes = new ArrayList<>();
storedSigHashes.add(SIGNATURE_HASH_1);
storedSigHashes.add(SIGNATURE_HASH_2);
storedSigHashes.add(SIGNATURE_HASH_3);
boolean result = BackupUtils.signaturesMatch(storedSigHashes, packageInfo,
mMockPackageManagerInternal);
assertThat(result).isTrue();
}
@Test
public void signaturesMatch_extraSignatureInTarget_returnsTrue() throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.signingCertificateHistory = new Signature[][] {
{SIGNATURE_1, SIGNATURE_2, SIGNATURE_3}
};
packageInfo.applicationInfo = new ApplicationInfo();
ArrayList<byte[]> storedSigHashes = new ArrayList<>();
storedSigHashes.add(SIGNATURE_HASH_1);
storedSigHashes.add(SIGNATURE_HASH_2);
boolean result = BackupUtils.signaturesMatch(storedSigHashes, packageInfo,
mMockPackageManagerInternal);
assertThat(result).isTrue();
}
@Test
public void signaturesMatch_extraSignatureInStored_returnsFalse() throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1, SIGNATURE_2}};
packageInfo.applicationInfo = new ApplicationInfo();
ArrayList<byte[]> storedSigHashes = new ArrayList<>();
storedSigHashes.add(SIGNATURE_HASH_1);
storedSigHashes.add(SIGNATURE_HASH_2);
storedSigHashes.add(SIGNATURE_HASH_3);
boolean result = BackupUtils.signaturesMatch(storedSigHashes, packageInfo,
mMockPackageManagerInternal);
assertThat(result).isFalse();
}
@Test
public void signaturesMatch_oneNonMatchingSignature_returnsFalse() throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.signingCertificateHistory = new Signature[][] {
{SIGNATURE_1, SIGNATURE_2, SIGNATURE_3}
};
packageInfo.applicationInfo = new ApplicationInfo();
ArrayList<byte[]> storedSigHashes = new ArrayList<>();
storedSigHashes.add(SIGNATURE_HASH_1);
storedSigHashes.add(SIGNATURE_HASH_2);
storedSigHashes.add(SIGNATURE_HASH_4);
boolean result = BackupUtils.signaturesMatch(storedSigHashes, packageInfo,
mMockPackageManagerInternal);
assertThat(result).isFalse();
}
@Test
public void signaturesMatch_singleStoredSignatureNoRotation_returnsTrue()
throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}};
packageInfo.applicationInfo = new ApplicationInfo();
doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(SIGNATURE_HASH_1,
packageInfo.packageName);
ArrayList<byte[]> storedSigHashes = new ArrayList<>();
storedSigHashes.add(SIGNATURE_HASH_1);
boolean result = BackupUtils.signaturesMatch(storedSigHashes, packageInfo,
mMockPackageManagerInternal);
assertThat(result).isTrue();
}
@Test
public void signaturesMatch_singleStoredSignatureWithRotationAssumeDataCapability_returnsTrue()
throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}, {SIGNATURE_2}};
packageInfo.applicationInfo = new ApplicationInfo();
// we know SIGNATURE_1 is in history, and we want to assume it has
// SigningDetails.CertCapabilities.INSTALLED_DATA capability
doReturn(true).when(mMockPackageManagerInternal).isDataRestoreSafe(SIGNATURE_HASH_1,
packageInfo.packageName);
ArrayList<byte[]> storedSigHashes = new ArrayList<>();
storedSigHashes.add(SIGNATURE_HASH_1);
boolean result = BackupUtils.signaturesMatch(storedSigHashes, packageInfo,
mMockPackageManagerInternal);
assertThat(result).isTrue();
}
@Test
public void
signaturesMatch_singleStoredSignatureWithRotationAssumeNoDataCapability_returnsFalse()
throws Exception {
PackageInfo packageInfo = new PackageInfo();
packageInfo.packageName = "test";
packageInfo.signingCertificateHistory = new Signature[][] {{SIGNATURE_1}, {SIGNATURE_2}};
packageInfo.applicationInfo = new ApplicationInfo();
// we know SIGNATURE_1 is in history, but we want to assume it does not have
// SigningDetails.CertCapabilities.INSTALLED_DATA capability
doReturn(false).when(mMockPackageManagerInternal).isDataRestoreSafe(SIGNATURE_HASH_1,
packageInfo.packageName);
ArrayList<byte[]> storedSigHashes = new ArrayList<>();
storedSigHashes.add(SIGNATURE_HASH_1);
boolean result = BackupUtils.signaturesMatch(storedSigHashes, packageInfo,
mMockPackageManagerInternal);
assertThat(result).isFalse();
}
@Test
public void testHashSignature() {
final byte[] sig1 = "abc".getBytes();
final byte[] sig2 = "def".getBytes();
@@ -115,4 +340,10 @@ public class BackupUtilsTest extends AndroidTestCase {
MoreAsserts.assertEquals(hash2a, listA.get(1));
MoreAsserts.assertEquals(hash2a, listB.get(1));
}
private static Signature generateSignature(byte i) {
byte[] signatureBytes = new byte[256];
signatureBytes[0] = i;
return new Signature(signatureBytes);
}
}