Allow restoring of apps that rotated key

Restoring of apps that rotated key wouldn't be possible due to
explicit signature matching.
Amend signature matching strategies to take into account
apps that have rotated key.

Test: atest frameworks/base/services/tests/servicestests/src/com/android/server/backup/utils/AppBackupUtilsTest.java
Test: atest frameworks/base/services/tests/servicestests/src/com/android/server/pm/backup/BackupUtilsTest.java
Test: m -j RunFrameworksServicesRoboTests
Test: runtest -p com.android.server.backup frameworks-services
Bug: 64686581
Bug: 34345052
Change-Id: I91b5ae0afb6f2714ceae02b4d4dc202d6cd4fe4e
This commit is contained in:
Michal Karpinski
2018-02-07 17:47:10 +00:00
parent 53346e39ec
commit 313d225cd1
15 changed files with 626 additions and 164 deletions

View File

@@ -539,4 +539,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

@@ -42,6 +42,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;
@@ -56,6 +57,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;
@@ -497,7 +499,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.
@@ -612,7 +614,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);
}
}