Merge "Implement methods in AppIntegrityManagerServiceImpl."
This commit is contained in:
@@ -50,6 +50,8 @@ import android.content.ContentCaptureOptions;
|
||||
import android.content.Context;
|
||||
import android.content.IRestrictionsManager;
|
||||
import android.content.RestrictionsManager;
|
||||
import android.content.integrity.AppIntegrityManager;
|
||||
import android.content.integrity.IAppIntegrityManager;
|
||||
import android.content.om.IOverlayManager;
|
||||
import android.content.om.OverlayManager;
|
||||
import android.content.pm.CrossProfileApps;
|
||||
@@ -1246,6 +1248,14 @@ public final class SystemServiceRegistry {
|
||||
IIncrementalManagerNative.Stub.asInterface(b));
|
||||
}});
|
||||
//CHECKSTYLE:ON IndentationCheck
|
||||
registerService(Context.APP_INTEGRITY_SERVICE, AppIntegrityManager.class,
|
||||
new CachedServiceFetcher<AppIntegrityManager>() {
|
||||
@Override
|
||||
public AppIntegrityManager createService(ContextImpl ctx)
|
||||
throws ServiceNotFoundException {
|
||||
IBinder b = ServiceManager.getServiceOrThrow(Context.APP_INTEGRITY_SERVICE);
|
||||
return new AppIntegrityManager(IAppIntegrityManager.Stub.asInterface(b));
|
||||
}});
|
||||
|
||||
sInitializing = true;
|
||||
try {
|
||||
|
||||
@@ -86,6 +86,19 @@ public final class AppInstallMetadata {
|
||||
return mIsPreInstalled;
|
||||
}
|
||||
|
||||
@Override
|
||||
public String toString() {
|
||||
return String.format(
|
||||
"AppInstallMetadata { PackageName = %s, AppCert = %s, InstallerName = %s,"
|
||||
+ " InstallerCert = %s, VersionCode = %d, PreInstalled = %b }",
|
||||
mPackageName,
|
||||
mAppCertificate,
|
||||
mInstallerName == null ? "null" : mInstallerName,
|
||||
mInstallerCertificate == null ? "null" : mInstallerCertificate,
|
||||
mVersionCode,
|
||||
mIsPreInstalled);
|
||||
}
|
||||
|
||||
/** Builder class for constructing {@link AppInstallMetadata} objects. */
|
||||
public static final class Builder {
|
||||
private String mPackageName;
|
||||
|
||||
@@ -37,7 +37,7 @@ public class AppIntegrityManagerService extends SystemService {
|
||||
|
||||
@Override
|
||||
public void onStart() {
|
||||
mService = new AppIntegrityManagerServiceImpl(mContext);
|
||||
// TODO: define and publish a binder service.
|
||||
mService = AppIntegrityManagerServiceImpl.create(mContext);
|
||||
publishBinderService(Context.APP_INTEGRITY_SERVICE, mService);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -17,38 +17,98 @@
|
||||
package com.android.server.integrity;
|
||||
|
||||
import static android.content.Intent.ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION;
|
||||
import static android.content.Intent.EXTRA_ORIGINATING_UID;
|
||||
import static android.content.Intent.EXTRA_PACKAGE_NAME;
|
||||
import static android.content.Intent.EXTRA_VERSION_CODE;
|
||||
import static android.content.integrity.AppIntegrityManager.EXTRA_STATUS;
|
||||
import static android.content.integrity.AppIntegrityManager.STATUS_FAILURE;
|
||||
import static android.content.integrity.AppIntegrityManager.STATUS_SUCCESS;
|
||||
import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.IntentSender;
|
||||
import android.content.integrity.AppInstallMetadata;
|
||||
import android.content.integrity.IAppIntegrityManager;
|
||||
import android.content.integrity.Rule;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.content.pm.Signature;
|
||||
import android.net.Uri;
|
||||
import android.os.Binder;
|
||||
import android.os.Handler;
|
||||
import android.os.HandlerThread;
|
||||
import android.os.RemoteException;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.integrity.engine.RuleEvaluationEngine;
|
||||
import com.android.server.integrity.model.IntegrityCheckResult;
|
||||
import com.android.server.integrity.model.RuleMetadata;
|
||||
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.File;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.cert.CertificateEncodingException;
|
||||
import java.security.cert.CertificateException;
|
||||
import java.security.cert.CertificateFactory;
|
||||
import java.security.cert.X509Certificate;
|
||||
|
||||
/** Implementation of {@link AppIntegrityManagerService}. */
|
||||
class AppIntegrityManagerServiceImpl {
|
||||
public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
|
||||
private static final String TAG = "AppIntegrityManagerServiceImpl";
|
||||
|
||||
private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
|
||||
private static final char[] HEX_CHARS = "0123456789ABCDEF".toCharArray();
|
||||
private static final String PACKAGE_INSTALLER = "com.google.android.packageinstaller";
|
||||
private static final String BASE_APK_FILE = "base.apk";
|
||||
|
||||
private static final String ADB_INSTALLER = "adb";
|
||||
private static final String UNKNOWN_INSTALLER = "";
|
||||
private static final String INSTALLER_CERT_NOT_APPLICABLE = "";
|
||||
|
||||
// Access to files inside mRulesDir is protected by mRulesLock;
|
||||
private final Context mContext;
|
||||
private final Handler mHandler;
|
||||
private final PackageManagerInternal mPackageManagerInternal;
|
||||
private final RuleEvaluationEngine mEvaluationEngine;
|
||||
private final IntegrityFileManager mIntegrityFileManager;
|
||||
|
||||
AppIntegrityManagerServiceImpl(Context context) {
|
||||
mContext = context;
|
||||
|
||||
/** Create an instance of {@link AppIntegrityManagerServiceImpl}. */
|
||||
public static AppIntegrityManagerServiceImpl create(Context context) {
|
||||
HandlerThread handlerThread = new HandlerThread("AppIntegrityManagerServiceHandler");
|
||||
handlerThread.start();
|
||||
mHandler = handlerThread.getThreadHandler();
|
||||
|
||||
mPackageManagerInternal = LocalServices.getService(PackageManagerInternal.class);
|
||||
return new AppIntegrityManagerServiceImpl(
|
||||
context,
|
||||
LocalServices.getService(PackageManagerInternal.class),
|
||||
RuleEvaluationEngine.getRuleEvaluationEngine(),
|
||||
IntegrityFileManager.getInstance(),
|
||||
handlerThread.getThreadHandler());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
AppIntegrityManagerServiceImpl(
|
||||
Context context,
|
||||
PackageManagerInternal packageManagerInternal,
|
||||
RuleEvaluationEngine evaluationEngine,
|
||||
IntegrityFileManager integrityFileManager,
|
||||
Handler handler) {
|
||||
mContext = context;
|
||||
mPackageManagerInternal = packageManagerInternal;
|
||||
mEvaluationEngine = evaluationEngine;
|
||||
mIntegrityFileManager = integrityFileManager;
|
||||
mHandler = handler;
|
||||
|
||||
IntentFilter integrityVerificationFilter = new IntentFilter();
|
||||
integrityVerificationFilter.addAction(ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION);
|
||||
@@ -74,14 +134,371 @@ class AppIntegrityManagerServiceImpl {
|
||||
mHandler);
|
||||
}
|
||||
|
||||
// protected broadcasts cannot be sent in the test.
|
||||
@VisibleForTesting
|
||||
void handleIntegrityVerification(Intent intent) {
|
||||
@Override
|
||||
public void updateRuleSet(
|
||||
String version, ParceledListSlice<Rule> rules, IntentSender statusReceiver)
|
||||
throws RemoteException {
|
||||
String ruleProvider = getCallerPackageNameOrThrow();
|
||||
|
||||
mHandler.post(
|
||||
() -> {
|
||||
boolean success = true;
|
||||
try {
|
||||
mIntegrityFileManager.writeRules(version, ruleProvider, rules.getList());
|
||||
} catch (Exception e) {
|
||||
Slog.e(TAG, "Error writing rules.", e);
|
||||
success = false;
|
||||
}
|
||||
|
||||
Intent intent = new Intent();
|
||||
intent.putExtra(EXTRA_STATUS, success ? STATUS_SUCCESS : STATUS_FAILURE);
|
||||
try {
|
||||
statusReceiver.sendIntent(
|
||||
mContext,
|
||||
/* code= */ 0,
|
||||
intent,
|
||||
/* onFinished= */ null,
|
||||
/* handler= */ null);
|
||||
} catch (IntentSender.SendIntentException e) {
|
||||
Slog.e(TAG, "Error sending status feedback.", e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCurrentRuleSetVersion() throws RemoteException {
|
||||
getCallerPackageNameOrThrow();
|
||||
|
||||
RuleMetadata ruleMetadata = mIntegrityFileManager.readMetadata();
|
||||
return (ruleMetadata != null && ruleMetadata.getVersion() != null)
|
||||
? ruleMetadata.getVersion()
|
||||
: "";
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getCurrentRuleSetProvider() throws RemoteException {
|
||||
getCallerPackageNameOrThrow();
|
||||
|
||||
RuleMetadata ruleMetadata = mIntegrityFileManager.readMetadata();
|
||||
return (ruleMetadata != null && ruleMetadata.getRuleProvider() != null)
|
||||
? ruleMetadata.getRuleProvider()
|
||||
: "";
|
||||
}
|
||||
|
||||
private void handleIntegrityVerification(Intent intent) {
|
||||
int verificationId = intent.getIntExtra(EXTRA_VERIFICATION_ID, -1);
|
||||
// TODO: implement this method.
|
||||
Slog.i(TAG, "Received integrity verification intent " + intent.toString());
|
||||
Slog.i(TAG, "Extras " + intent.getExtras());
|
||||
mPackageManagerInternal.setIntegrityVerificationResult(
|
||||
verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
|
||||
try {
|
||||
Slog.i(TAG, "Received integrity verification intent " + intent.toString());
|
||||
Slog.i(TAG, "Extras " + intent.getExtras());
|
||||
|
||||
AppInstallMetadata.Builder builder = new AppInstallMetadata.Builder();
|
||||
|
||||
String packageName = intent.getStringExtra(EXTRA_PACKAGE_NAME);
|
||||
String installerPackageName = getInstallerPackageName(intent);
|
||||
String appCert = getAppCertificateFingerprint(intent.getData());
|
||||
|
||||
builder.setPackageName(getPackageNameNormalized(packageName));
|
||||
builder.setAppCertificate(appCert == null ? "" : appCert);
|
||||
builder.setVersionCode(intent.getIntExtra(EXTRA_VERSION_CODE, -1));
|
||||
builder.setInstallerName(getPackageNameNormalized(installerPackageName));
|
||||
builder.setInstallerCertificate(
|
||||
getInstallerCertificateFingerprint(installerPackageName));
|
||||
builder.setIsPreInstalled(isSystemApp(packageName));
|
||||
|
||||
AppInstallMetadata appInstallMetadata = builder.build();
|
||||
|
||||
Slog.i(TAG, "To be verified: " + appInstallMetadata);
|
||||
IntegrityCheckResult result = mEvaluationEngine.evaluate(appInstallMetadata);
|
||||
Slog.i(
|
||||
TAG,
|
||||
"Integrity check result: "
|
||||
+ result.getEffect()
|
||||
+ " due to "
|
||||
+ result.getRule());
|
||||
mPackageManagerInternal.setIntegrityVerificationResult(
|
||||
verificationId,
|
||||
result.getEffect() == IntegrityCheckResult.Effect.ALLOW
|
||||
? PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW
|
||||
: PackageManagerInternal.INTEGRITY_VERIFICATION_REJECT);
|
||||
} catch (IllegalArgumentException e) {
|
||||
// This exception indicates something is wrong with the input passed by package manager.
|
||||
// e.g., someone trying to trick the system. We block installs in this case.
|
||||
Slog.e(TAG, "Invalid input to integrity verification", e);
|
||||
|
||||
mPackageManagerInternal.setIntegrityVerificationResult(
|
||||
verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_REJECT);
|
||||
} catch (Exception e) {
|
||||
// Other exceptions indicate an error within the integrity component implementation and
|
||||
// we allow them.
|
||||
Slog.e(TAG, "Error handling integrity verification", e);
|
||||
|
||||
mPackageManagerInternal.setIntegrityVerificationResult(
|
||||
verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Verify the UID and return the installer package name.
|
||||
*
|
||||
* @return the package name of the installer, or null if it cannot be determined or it is
|
||||
* installed via adb.
|
||||
*/
|
||||
@Nullable
|
||||
private String getInstallerPackageName(Intent intent) {
|
||||
String installer =
|
||||
intent.getStringExtra(PackageManager.EXTRA_VERIFICATION_INSTALLER_PACKAGE);
|
||||
if (installer == null) {
|
||||
return ADB_INSTALLER;
|
||||
}
|
||||
int installerUid = intent.getIntExtra(PackageManager.EXTRA_VERIFICATION_INSTALLER_UID, -1);
|
||||
if (installerUid < 0) {
|
||||
Slog.e(
|
||||
TAG,
|
||||
"Installer cannot be determined: installer: "
|
||||
+ installer
|
||||
+ " installer UID: "
|
||||
+ installerUid);
|
||||
return UNKNOWN_INSTALLER;
|
||||
}
|
||||
|
||||
try {
|
||||
int actualInstallerUid =
|
||||
mContext.getPackageManager().getPackageUid(installer, /* flags= */ 0);
|
||||
if (actualInstallerUid != installerUid) {
|
||||
// Installer package name can be faked but the installerUid cannot.
|
||||
Slog.e(
|
||||
TAG,
|
||||
"Installer "
|
||||
+ installer
|
||||
+ " has UID "
|
||||
+ actualInstallerUid
|
||||
+ " which doesn't match alleged installer UID "
|
||||
+ installerUid);
|
||||
return UNKNOWN_INSTALLER;
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Slog.e(TAG, "Installer package " + installer + " not found.");
|
||||
return UNKNOWN_INSTALLER;
|
||||
}
|
||||
|
||||
// At this time we can trust "installer".
|
||||
|
||||
// A common way for apps to install packages is to send an intent to PackageInstaller. In
|
||||
// that case, the installer will always show up as PackageInstaller which is not what we
|
||||
// want.
|
||||
if (installer.equals(PACKAGE_INSTALLER)) {
|
||||
int originatingUid = intent.getIntExtra(EXTRA_ORIGINATING_UID, -1);
|
||||
if (originatingUid < 0) {
|
||||
Slog.e(TAG, "Installer is package installer but originating UID not found.");
|
||||
return UNKNOWN_INSTALLER;
|
||||
}
|
||||
String[] installerPackages =
|
||||
mContext.getPackageManager().getPackagesForUid(originatingUid);
|
||||
if (installerPackages == null || installerPackages.length == 0) {
|
||||
Slog.e(TAG, "No package found associated with originating UID " + originatingUid);
|
||||
return UNKNOWN_INSTALLER;
|
||||
}
|
||||
// In the case of multiple package sharing a UID, we just return the first one.
|
||||
return installerPackages[0];
|
||||
}
|
||||
|
||||
return installer;
|
||||
}
|
||||
|
||||
/** We will use the SHA256 digest of a package name if it is more than 32 bytes long. */
|
||||
private String getPackageNameNormalized(String packageName) {
|
||||
if (packageName.length() <= 32) {
|
||||
return packageName;
|
||||
}
|
||||
|
||||
try {
|
||||
MessageDigest messageDigest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] hashBytes = messageDigest.digest(packageName.getBytes(StandardCharsets.UTF_8));
|
||||
return toHexString(hashBytes);
|
||||
} catch (NoSuchAlgorithmException e) {
|
||||
throw new RuntimeException("SHA-256 algorithm not found", e);
|
||||
}
|
||||
}
|
||||
|
||||
private String getAppCertificateFingerprint(Uri dataUri) {
|
||||
PackageInfo packageInfo = getPackageArchiveInfo(dataUri);
|
||||
return getFingerprint(getSignature(packageInfo));
|
||||
}
|
||||
|
||||
private String getInstallerCertificateFingerprint(String installer) {
|
||||
if (installer.equals(ADB_INSTALLER) || installer.equals(UNKNOWN_INSTALLER)) {
|
||||
return INSTALLER_CERT_NOT_APPLICABLE;
|
||||
}
|
||||
try {
|
||||
PackageInfo installerInfo =
|
||||
mContext.getPackageManager()
|
||||
.getPackageInfo(installer, PackageManager.GET_SIGNATURES);
|
||||
return getFingerprint(getSignature(installerInfo));
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
Slog.i(TAG, "Installer package " + installer + " not found.");
|
||||
return "";
|
||||
}
|
||||
}
|
||||
|
||||
private static Signature getSignature(PackageInfo packageInfo) {
|
||||
if (packageInfo.signatures == null || packageInfo.signatures.length < 1) {
|
||||
throw new IllegalArgumentException("Package signature not found in " + packageInfo);
|
||||
}
|
||||
// Only the first element is guaranteed to be present.
|
||||
return packageInfo.signatures[0];
|
||||
}
|
||||
|
||||
private static String getFingerprint(Signature cert) {
|
||||
InputStream input = new ByteArrayInputStream(cert.toByteArray());
|
||||
|
||||
CertificateFactory factory;
|
||||
try {
|
||||
factory = CertificateFactory.getInstance("X509");
|
||||
} catch (CertificateException e) {
|
||||
throw new RuntimeException("Error getting CertificateFactory", e);
|
||||
}
|
||||
X509Certificate certificate = null;
|
||||
try {
|
||||
if (factory != null) {
|
||||
certificate = (X509Certificate) factory.generateCertificate(input);
|
||||
}
|
||||
} catch (CertificateException e) {
|
||||
throw new RuntimeException("Error getting X509Certificate", e);
|
||||
}
|
||||
|
||||
if (certificate == null) {
|
||||
throw new RuntimeException("X509 Certificate not found");
|
||||
}
|
||||
|
||||
try {
|
||||
MessageDigest digest = MessageDigest.getInstance("SHA-256");
|
||||
byte[] publicKey = digest.digest(certificate.getEncoded());
|
||||
return toHexString(publicKey);
|
||||
} catch (NoSuchAlgorithmException | CertificateEncodingException e) {
|
||||
throw new IllegalArgumentException("Error error computing fingerprint", e);
|
||||
}
|
||||
}
|
||||
|
||||
private static String toHexString(byte[] bytes) {
|
||||
// each byte is represented by two hex chars
|
||||
StringBuffer hexString = new StringBuffer(bytes.length * 2);
|
||||
for (int i = 0; i < bytes.length; i++) {
|
||||
hexString.append(String.format("%02X", bytes[i]));
|
||||
}
|
||||
return new String(hexString);
|
||||
}
|
||||
|
||||
private PackageInfo getPackageArchiveInfo(Uri dataUri) {
|
||||
File installationPath = getInstallationPath(dataUri);
|
||||
if (installationPath == null) {
|
||||
throw new IllegalArgumentException("Installation path is null, package not found");
|
||||
}
|
||||
PackageInfo packageInfo;
|
||||
try {
|
||||
// The installation path will be a directory for a multi-apk install on L+
|
||||
if (installationPath.isDirectory()) {
|
||||
packageInfo = getMultiApkInfo(installationPath);
|
||||
} else {
|
||||
packageInfo =
|
||||
mContext.getPackageManager()
|
||||
.getPackageArchiveInfo(
|
||||
installationPath.getPath(), PackageManager.GET_SIGNATURES);
|
||||
}
|
||||
return packageInfo;
|
||||
} catch (Exception e) {
|
||||
throw new IllegalArgumentException("Exception reading " + dataUri, e);
|
||||
}
|
||||
}
|
||||
|
||||
private PackageInfo getMultiApkInfo(File multiApkDirectory) {
|
||||
// The base apk will normally be called base.apk
|
||||
File baseFile = new File(multiApkDirectory, BASE_APK_FILE);
|
||||
PackageInfo basePackageInfo =
|
||||
mContext.getPackageManager()
|
||||
.getPackageArchiveInfo(
|
||||
baseFile.getAbsolutePath(), PackageManager.GET_SIGNATURES);
|
||||
|
||||
if (basePackageInfo == null) {
|
||||
for (File apkFile : multiApkDirectory.listFiles()) {
|
||||
if (apkFile.isDirectory()) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// If we didn't find a base.apk, then try to parse each apk until we find the one
|
||||
// that succeeds.
|
||||
basePackageInfo =
|
||||
mContext.getPackageManager()
|
||||
.getPackageArchiveInfo(
|
||||
apkFile.getAbsolutePath(),
|
||||
PackageManager.GET_SIGNING_CERTIFICATES);
|
||||
if (basePackageInfo != null) {
|
||||
Slog.i(TAG, "Found package info from " + apkFile);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (basePackageInfo == null) {
|
||||
throw new IllegalArgumentException(
|
||||
"Base package info cannot be found from installation directory");
|
||||
}
|
||||
|
||||
return basePackageInfo;
|
||||
}
|
||||
|
||||
private File getInstallationPath(Uri dataUri) {
|
||||
if (dataUri == null) {
|
||||
throw new IllegalArgumentException("Null data uri");
|
||||
}
|
||||
|
||||
String scheme = dataUri.getScheme();
|
||||
if (!"file".equalsIgnoreCase(scheme)) {
|
||||
throw new IllegalArgumentException("Unsupported scheme for " + dataUri);
|
||||
}
|
||||
|
||||
File installationPath = new File(dataUri.getPath());
|
||||
if (!installationPath.exists()) {
|
||||
throw new IllegalArgumentException("Cannot find file for " + dataUri);
|
||||
}
|
||||
if (!installationPath.canRead()) {
|
||||
throw new IllegalArgumentException("Cannot read file for " + dataUri);
|
||||
}
|
||||
return installationPath;
|
||||
}
|
||||
|
||||
private String getCallerPackageNameOrThrow() {
|
||||
final String[] allowedRuleProviders =
|
||||
mContext.getResources()
|
||||
.getStringArray(R.array.config_integrityRuleProviderPackages);
|
||||
for (String packageName : allowedRuleProviders) {
|
||||
try {
|
||||
// At least in tests, getPackageUid gives "NameNotFound" but getPackagesFromUid
|
||||
// give the correct package name.
|
||||
int uid = mContext.getPackageManager().getPackageUid(packageName, 0);
|
||||
if (uid == Binder.getCallingUid()) {
|
||||
// Caller is allowed in the config.
|
||||
if (isSystemApp(packageName)) {
|
||||
return packageName;
|
||||
}
|
||||
}
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
// Ignore the exception. We don't expect the app to be necessarily installed.
|
||||
Slog.i(TAG, "Rule provider package " + packageName + " not installed.");
|
||||
}
|
||||
}
|
||||
throw new SecurityException(
|
||||
"Only system packages specified in config_integrityRuleProviderPackages are"
|
||||
+ " allowed to call this method.");
|
||||
}
|
||||
|
||||
private boolean isSystemApp(String packageName) {
|
||||
try {
|
||||
PackageInfo existingPackageInfo =
|
||||
mContext.getPackageManager().getPackageInfo(packageName, /* flags= */ 0);
|
||||
return existingPackageInfo.applicationInfo != null
|
||||
&& existingPackageInfo.applicationInfo.isSystemApp();
|
||||
} catch (PackageManager.NameNotFoundException e) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,169 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.integrity;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.content.integrity.AppInstallMetadata;
|
||||
import android.content.integrity.Rule;
|
||||
import android.os.Environment;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.internal.annotations.VisibleForTesting;
|
||||
import com.android.server.integrity.model.RuleMetadata;
|
||||
import com.android.server.integrity.parser.RuleBinaryParser;
|
||||
import com.android.server.integrity.parser.RuleMetadataParser;
|
||||
import com.android.server.integrity.parser.RuleParseException;
|
||||
import com.android.server.integrity.parser.RuleParser;
|
||||
import com.android.server.integrity.serializer.RuleBinarySerializer;
|
||||
import com.android.server.integrity.serializer.RuleMetadataSerializer;
|
||||
import com.android.server.integrity.serializer.RuleSerializeException;
|
||||
import com.android.server.integrity.serializer.RuleSerializer;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileOutputStream;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Optional;
|
||||
|
||||
/** Abstraction over the underlying storage of rules and other metadata. */
|
||||
public class IntegrityFileManager {
|
||||
private static final String TAG = "IntegrityFileManager";
|
||||
|
||||
// TODO: this is a prototype implementation of this class. Thus no tests are included.
|
||||
// Implementing rule indexing will likely overhaul this class and more tests should be included
|
||||
// then.
|
||||
|
||||
private static final String METADATA_FILE = "metadata";
|
||||
private static final String RULES_FILE = "rules";
|
||||
private static final Object RULES_LOCK = new Object();
|
||||
|
||||
private static IntegrityFileManager sInstance = null;
|
||||
|
||||
private final RuleParser mRuleParser;
|
||||
private final RuleSerializer mRuleSerializer;
|
||||
|
||||
// mRulesDir contains data of the actual rules currently stored.
|
||||
private final File mRulesDir;
|
||||
// mStagingDir is used to store the temporary rules / metadata during updating, since we want to
|
||||
// update rules atomically.
|
||||
private final File mStagingDir;
|
||||
|
||||
@Nullable private RuleMetadata mRuleMetadataCache;
|
||||
|
||||
/** Get the singleton instance of this class. */
|
||||
public static synchronized IntegrityFileManager getInstance() {
|
||||
if (sInstance == null) {
|
||||
sInstance = new IntegrityFileManager();
|
||||
}
|
||||
return sInstance;
|
||||
}
|
||||
|
||||
private IntegrityFileManager() {
|
||||
this(
|
||||
new RuleBinaryParser(),
|
||||
new RuleBinarySerializer(),
|
||||
Environment.getDataSystemDirectory());
|
||||
}
|
||||
|
||||
@VisibleForTesting
|
||||
IntegrityFileManager(RuleParser ruleParser, RuleSerializer ruleSerializer, File dataDir) {
|
||||
mRuleParser = ruleParser;
|
||||
mRuleSerializer = ruleSerializer;
|
||||
|
||||
mRulesDir = new File(dataDir, "integrity_rules");
|
||||
mStagingDir = new File(dataDir, "integrity_staging");
|
||||
|
||||
if (!mStagingDir.mkdirs() && mRulesDir.mkdirs()) {
|
||||
Slog.e(TAG, "Error creating staging and rules directory");
|
||||
// TODO: maybe throw an exception?
|
||||
}
|
||||
|
||||
File metadataFile = new File(mRulesDir, METADATA_FILE);
|
||||
if (metadataFile.exists()) {
|
||||
try (FileInputStream inputStream = new FileInputStream(metadataFile)) {
|
||||
mRuleMetadataCache = RuleMetadataParser.parse(inputStream);
|
||||
} catch (Exception e) {
|
||||
Slog.e(TAG, "Error reading metadata file.", e);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Write rules to persistent storage. */
|
||||
public void writeRules(String version, String ruleProvider, List<Rule> rules)
|
||||
throws IOException, RuleSerializeException {
|
||||
try {
|
||||
writeMetadata(mStagingDir, ruleProvider, version);
|
||||
} catch (IOException e) {
|
||||
Slog.e(TAG, "Error writing metadata.", e);
|
||||
// We don't consider this fatal so we continue execution.
|
||||
}
|
||||
|
||||
try (FileOutputStream fileOutputStream =
|
||||
new FileOutputStream(new File(mStagingDir, RULES_FILE))) {
|
||||
mRuleSerializer.serialize(rules, Optional.empty(), fileOutputStream);
|
||||
}
|
||||
|
||||
switchStagingRulesDir();
|
||||
}
|
||||
|
||||
/**
|
||||
* Read rules from persistent storage.
|
||||
*
|
||||
* @param appInstallMetadata information about the install used to select rules to read
|
||||
*/
|
||||
public List<Rule> readRules(AppInstallMetadata appInstallMetadata)
|
||||
throws IOException, RuleParseException {
|
||||
// TODO: select rules by index
|
||||
synchronized (RULES_LOCK) {
|
||||
try (FileInputStream inputStream =
|
||||
new FileInputStream(new File(mRulesDir, RULES_FILE))) {
|
||||
List<Rule> rules = mRuleParser.parse(inputStream);
|
||||
return rules;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/** Read the metadata of the current rules in storage. */
|
||||
@Nullable
|
||||
public RuleMetadata readMetadata() {
|
||||
return mRuleMetadataCache;
|
||||
}
|
||||
|
||||
private void switchStagingRulesDir() throws IOException {
|
||||
synchronized (RULES_LOCK) {
|
||||
File tmpDir = new File(Environment.getDataSystemDirectory(), "temp");
|
||||
|
||||
if (!(mRulesDir.renameTo(tmpDir)
|
||||
&& mStagingDir.renameTo(mRulesDir)
|
||||
&& tmpDir.renameTo(mStagingDir))) {
|
||||
throw new IOException("Error switching staging/rules directory");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void writeMetadata(File directory, String ruleProvider, String version)
|
||||
throws IOException {
|
||||
mRuleMetadataCache = new RuleMetadata(ruleProvider, version);
|
||||
|
||||
File metadataFile = new File(directory, METADATA_FILE);
|
||||
|
||||
try (FileOutputStream outputStream = new FileOutputStream(metadataFile)) {
|
||||
RuleMetadataSerializer.serialize(mRuleMetadataCache, outputStream);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -18,7 +18,9 @@ package com.android.server.integrity.engine;
|
||||
|
||||
import android.content.integrity.AppInstallMetadata;
|
||||
import android.content.integrity.Rule;
|
||||
import android.util.Slog;
|
||||
|
||||
import com.android.server.integrity.IntegrityFileManager;
|
||||
import com.android.server.integrity.model.IntegrityCheckResult;
|
||||
|
||||
import java.util.ArrayList;
|
||||
@@ -30,17 +32,23 @@ import java.util.List;
|
||||
* <p>Every app install is evaluated against rules (pushed by the verifier) by the evaluation engine
|
||||
* to allow/block that install.
|
||||
*/
|
||||
public final class RuleEvaluationEngine {
|
||||
public class RuleEvaluationEngine {
|
||||
private static final String TAG = "RuleEvaluation";
|
||||
|
||||
// The engine for loading rules, retrieving metadata for app installs, and evaluating app
|
||||
// installs against rules.
|
||||
private static RuleEvaluationEngine sRuleEvaluationEngine;
|
||||
|
||||
private final IntegrityFileManager mIntegrityFileManager;
|
||||
|
||||
private RuleEvaluationEngine(IntegrityFileManager integrityFileManager) {
|
||||
mIntegrityFileManager = integrityFileManager;
|
||||
}
|
||||
|
||||
/** Provide a singleton instance of the rule evaluation engine. */
|
||||
public static synchronized RuleEvaluationEngine getRuleEvaluationEngine() {
|
||||
if (sRuleEvaluationEngine == null) {
|
||||
return new RuleEvaluationEngine();
|
||||
return new RuleEvaluationEngine(IntegrityFileManager.getInstance());
|
||||
}
|
||||
return sRuleEvaluationEngine;
|
||||
}
|
||||
@@ -58,7 +66,11 @@ public final class RuleEvaluationEngine {
|
||||
}
|
||||
|
||||
private List<Rule> loadRules(AppInstallMetadata appInstallMetadata) {
|
||||
// TODO: Load rules
|
||||
return new ArrayList<>();
|
||||
try {
|
||||
return mIntegrityFileManager.readRules(appInstallMetadata);
|
||||
} catch (Exception e) {
|
||||
Slog.e(TAG, "Error loading rules.", e);
|
||||
return new ArrayList<>();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -0,0 +1,41 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.integrity.model;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
|
||||
/** Data class containing relevant metadata associated with a rule set. */
|
||||
public class RuleMetadata {
|
||||
|
||||
private final String mRuleProvider;
|
||||
private final String mVersion;
|
||||
|
||||
public RuleMetadata(String ruleProvider, String version) {
|
||||
mRuleProvider = ruleProvider;
|
||||
mVersion = version;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getRuleProvider() {
|
||||
return mRuleProvider;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public String getVersion() {
|
||||
return mVersion;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,67 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.integrity.parser;
|
||||
|
||||
import android.annotation.Nullable;
|
||||
import android.util.Xml;
|
||||
|
||||
import com.android.server.integrity.model.RuleMetadata;
|
||||
|
||||
import org.xmlpull.v1.XmlPullParser;
|
||||
import org.xmlpull.v1.XmlPullParserException;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/** Helper class for parsing rule metadata. */
|
||||
public class RuleMetadataParser {
|
||||
|
||||
public static final String RULE_PROVIDER_TAG = "P";
|
||||
public static final String VERSION_TAG = "V";
|
||||
|
||||
/** Parse the rule metadata from an input stream. */
|
||||
@Nullable
|
||||
public static RuleMetadata parse(InputStream inputStream)
|
||||
throws XmlPullParserException, IOException {
|
||||
|
||||
String ruleProvider = "";
|
||||
String version = "";
|
||||
|
||||
XmlPullParser xmlPullParser = Xml.newPullParser();
|
||||
xmlPullParser.setInput(inputStream, StandardCharsets.UTF_8.name());
|
||||
|
||||
int eventType;
|
||||
while ((eventType = xmlPullParser.next()) != XmlPullParser.END_DOCUMENT) {
|
||||
if (eventType == XmlPullParser.START_TAG) {
|
||||
String tag = xmlPullParser.getName();
|
||||
switch (tag) {
|
||||
case RULE_PROVIDER_TAG:
|
||||
ruleProvider = xmlPullParser.nextText();
|
||||
break;
|
||||
case VERSION_TAG:
|
||||
version = xmlPullParser.nextText();
|
||||
break;
|
||||
default:
|
||||
throw new IllegalStateException("Unknown tag in metadata: " + tag);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return new RuleMetadata(ruleProvider, version);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,52 @@
|
||||
/*
|
||||
* Copyright (C) 2019 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
|
||||
package com.android.server.integrity.serializer;
|
||||
|
||||
import static com.android.server.integrity.parser.RuleMetadataParser.RULE_PROVIDER_TAG;
|
||||
import static com.android.server.integrity.parser.RuleMetadataParser.VERSION_TAG;
|
||||
|
||||
import android.util.Xml;
|
||||
|
||||
import com.android.server.integrity.model.RuleMetadata;
|
||||
|
||||
import org.xmlpull.v1.XmlSerializer;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.io.OutputStream;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
|
||||
/** Helper class for writing rule metadata. */
|
||||
public class RuleMetadataSerializer {
|
||||
/** Serialize the rule metadata to an output stream. */
|
||||
public static void serialize(RuleMetadata ruleMetadata, OutputStream outputStream)
|
||||
throws IOException {
|
||||
XmlSerializer xmlSerializer = Xml.newSerializer();
|
||||
xmlSerializer.setOutput(outputStream, StandardCharsets.UTF_8.name());
|
||||
|
||||
serializeTaggedValue(xmlSerializer, RULE_PROVIDER_TAG, ruleMetadata.getRuleProvider());
|
||||
serializeTaggedValue(xmlSerializer, VERSION_TAG, ruleMetadata.getVersion());
|
||||
|
||||
xmlSerializer.endDocument();
|
||||
}
|
||||
|
||||
private static void serializeTaggedValue(XmlSerializer xmlSerializer, String tag, String value)
|
||||
throws IOException {
|
||||
xmlSerializer.startTag(/* namespace= */ null, tag);
|
||||
xmlSerializer.text(value);
|
||||
xmlSerializer.endTag(/* namespace= */ null, tag);
|
||||
}
|
||||
}
|
||||
Binary file not shown.
@@ -16,41 +16,369 @@
|
||||
|
||||
package com.android.server.integrity;
|
||||
|
||||
import static android.content.integrity.AppIntegrityManager.EXTRA_STATUS;
|
||||
import static android.content.integrity.AppIntegrityManager.STATUS_FAILURE;
|
||||
import static android.content.integrity.AppIntegrityManager.STATUS_SUCCESS;
|
||||
import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
|
||||
import static android.content.pm.PackageManager.EXTRA_VERIFICATION_INSTALLER_PACKAGE;
|
||||
import static android.content.pm.PackageManager.EXTRA_VERIFICATION_INSTALLER_UID;
|
||||
|
||||
import static org.junit.Assert.assertEquals;
|
||||
import static org.junit.Assert.assertFalse;
|
||||
import static org.mockito.ArgumentMatchers.any;
|
||||
import static org.mockito.ArgumentMatchers.anyInt;
|
||||
import static org.mockito.ArgumentMatchers.anyLong;
|
||||
import static org.mockito.ArgumentMatchers.eq;
|
||||
import static org.mockito.Mockito.doReturn;
|
||||
import static org.mockito.Mockito.doThrow;
|
||||
import static org.mockito.Mockito.mock;
|
||||
import static org.mockito.Mockito.spy;
|
||||
import static org.mockito.Mockito.verify;
|
||||
import static org.mockito.Mockito.when;
|
||||
import static org.mockito.internal.verification.VerificationModeFactory.times;
|
||||
|
||||
import android.content.BroadcastReceiver;
|
||||
import android.content.Context;
|
||||
import android.content.Intent;
|
||||
import android.content.IntentFilter;
|
||||
import android.content.IntentSender;
|
||||
import android.content.integrity.AppInstallMetadata;
|
||||
import android.content.integrity.AtomicFormula;
|
||||
import android.content.integrity.Rule;
|
||||
import android.content.pm.ApplicationInfo;
|
||||
import android.content.pm.PackageInfo;
|
||||
import android.content.pm.PackageManager;
|
||||
import android.content.pm.PackageManagerInternal;
|
||||
import android.content.pm.ParceledListSlice;
|
||||
import android.content.res.Resources;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Message;
|
||||
|
||||
import androidx.test.InstrumentationRegistry;
|
||||
import androidx.test.runner.AndroidJUnit4;
|
||||
|
||||
import com.android.internal.R;
|
||||
import com.android.server.LocalServices;
|
||||
import com.android.server.integrity.engine.RuleEvaluationEngine;
|
||||
import com.android.server.integrity.model.IntegrityCheckResult;
|
||||
import com.android.server.testutils.TestUtils;
|
||||
|
||||
import org.junit.After;
|
||||
import org.junit.Before;
|
||||
import org.junit.Rule;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.mockito.ArgumentCaptor;
|
||||
import org.mockito.Mock;
|
||||
import org.mockito.junit.MockitoJUnit;
|
||||
import org.mockito.junit.MockitoRule;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.nio.file.StandardCopyOption;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/** Unit test for {@link com.android.server.integrity.AppIntegrityManagerServiceImpl} */
|
||||
@RunWith(AndroidJUnit4.class)
|
||||
public class AppIntegrityManagerServiceImplTest {
|
||||
private static final String TEST_DIR = "AppIntegrityManagerServiceImplTest";
|
||||
|
||||
@Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
private static final String PACKAGE_MIME_TYPE = "application/vnd.android.package-archive";
|
||||
private static final String VERSION = "version";
|
||||
private static final String TEST_FRAMEWORK_PACKAGE = "com.android.frameworks.servicestests";
|
||||
|
||||
private static final String PACKAGE_NAME = "com.test.app";
|
||||
private static final int VERSION_CODE = 100;
|
||||
private static final String INSTALLER = TEST_FRAMEWORK_PACKAGE;
|
||||
// These are obtained by running the test and checking logcat.
|
||||
private static final String APP_CERT =
|
||||
"949ADC6CB92FF09E3784D6E9504F26F9BEAC06E60D881D55A6A81160F9CD6FD1";
|
||||
private static final String INSTALLER_CERT =
|
||||
"301AA3CB081134501C45F1422ABC66C24224FD5DED5FDC8F17E697176FD866AA";
|
||||
// We use SHA256 for package names longer than 32 characters.
|
||||
private static final String INSTALLER_SHA256 =
|
||||
"786933C28839603EB48C50B2A688DC6BE52C833627CB2731FF8466A2AE9F94CD";
|
||||
|
||||
@org.junit.Rule public MockitoRule mMockitoRule = MockitoJUnit.rule();
|
||||
|
||||
@Mock PackageManagerInternal mPackageManagerInternal;
|
||||
@Mock Context mMockContext;
|
||||
@Mock Resources mMockResources;
|
||||
@Mock RuleEvaluationEngine mRuleEvaluationEngine;
|
||||
@Mock IntegrityFileManager mIntegrityFileManager;
|
||||
@Mock Handler mHandler;
|
||||
|
||||
private PackageManager mSpyPackageManager;
|
||||
private File mTestApk;
|
||||
|
||||
private final Context mRealContext = InstrumentationRegistry.getTargetContext();
|
||||
// under test
|
||||
private AppIntegrityManagerServiceImpl mService;
|
||||
|
||||
@Before
|
||||
public void setup() {
|
||||
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
|
||||
public void setup() throws Exception {
|
||||
mTestApk = File.createTempFile("TestApk", /* suffix= */ null);
|
||||
mTestApk.deleteOnExit();
|
||||
try (InputStream inputStream = mRealContext.getAssets().open(TEST_DIR + "/test.apk")) {
|
||||
Files.copy(inputStream, mTestApk.toPath(), StandardCopyOption.REPLACE_EXISTING);
|
||||
}
|
||||
|
||||
mService = new AppIntegrityManagerServiceImpl(InstrumentationRegistry.getContext());
|
||||
mService =
|
||||
new AppIntegrityManagerServiceImpl(
|
||||
mMockContext,
|
||||
mPackageManagerInternal,
|
||||
mRuleEvaluationEngine,
|
||||
mIntegrityFileManager,
|
||||
mHandler);
|
||||
|
||||
mSpyPackageManager = spy(mRealContext.getPackageManager());
|
||||
// setup mocks to prevent NPE
|
||||
when(mMockContext.getPackageManager()).thenReturn(mSpyPackageManager);
|
||||
when(mMockContext.getResources()).thenReturn(mMockResources);
|
||||
when(mMockResources.getStringArray(anyInt())).thenReturn(new String[] {});
|
||||
}
|
||||
|
||||
@After
|
||||
public void tearDown() throws Exception {
|
||||
mTestApk.delete();
|
||||
}
|
||||
|
||||
// This is not a test of the class, but more of a safeguard that we don't block any install in
|
||||
// the default case. This is needed because we don't have any emergency kill switch to disable
|
||||
// this component.
|
||||
@Test
|
||||
public void default_allow() throws Exception {
|
||||
LocalServices.removeServiceForTest(PackageManagerInternal.class);
|
||||
LocalServices.addService(PackageManagerInternal.class, mPackageManagerInternal);
|
||||
mService = AppIntegrityManagerServiceImpl.create(mMockContext);
|
||||
ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
|
||||
ArgumentCaptor.forClass(BroadcastReceiver.class);
|
||||
verify(mMockContext, times(2))
|
||||
.registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
|
||||
Intent intent = makeVerificationIntent();
|
||||
|
||||
broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
|
||||
|
||||
// Since we are not mocking handler in this case, we must wait.
|
||||
// 2 seconds should be a sensible timeout.
|
||||
Thread.sleep(2000);
|
||||
verify(mPackageManagerInternal)
|
||||
.setIntegrityVerificationResult(
|
||||
1, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void noop() {
|
||||
// We need this test just as a place holder since an empty test suite is treated as error.
|
||||
public void updateRuleSet_notAuthorized() throws Exception {
|
||||
makeUsSystemApp();
|
||||
Rule rule =
|
||||
new Rule(
|
||||
new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true),
|
||||
Rule.DENY);
|
||||
TestUtils.assertExpectException(
|
||||
SecurityException.class,
|
||||
"Only system packages specified in config_integrityRuleProviderPackages are"
|
||||
+ " allowed to call this method.",
|
||||
() ->
|
||||
mService.updateRuleSet(
|
||||
VERSION,
|
||||
new ParceledListSlice<>(Arrays.asList(rule)),
|
||||
/* statusReceiver= */ null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateRuleSet_notSystemApp() throws Exception {
|
||||
whitelistUsAsRuleProvider();
|
||||
Rule rule =
|
||||
new Rule(
|
||||
new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true),
|
||||
Rule.DENY);
|
||||
TestUtils.assertExpectException(
|
||||
SecurityException.class,
|
||||
"Only system packages specified in config_integrityRuleProviderPackages are"
|
||||
+ " allowed to call this method.",
|
||||
() ->
|
||||
mService.updateRuleSet(
|
||||
VERSION,
|
||||
new ParceledListSlice<>(Arrays.asList(rule)),
|
||||
/* statusReceiver= */ null));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateRuleSet_authorized() throws Exception {
|
||||
whitelistUsAsRuleProvider();
|
||||
makeUsSystemApp();
|
||||
Rule rule =
|
||||
new Rule(
|
||||
new AtomicFormula.BooleanAtomicFormula(AtomicFormula.PRE_INSTALLED, true),
|
||||
Rule.DENY);
|
||||
|
||||
// no SecurityException
|
||||
mService.updateRuleSet(
|
||||
VERSION, new ParceledListSlice<>(Arrays.asList(rule)), mock(IntentSender.class));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateRuleSet_correctMethodCall() throws Exception {
|
||||
whitelistUsAsRuleProvider();
|
||||
makeUsSystemApp();
|
||||
IntentSender mockReceiver = mock(IntentSender.class);
|
||||
List<Rule> rules =
|
||||
Arrays.asList(
|
||||
new Rule(
|
||||
new AtomicFormula.StringAtomicFormula(
|
||||
AtomicFormula.PACKAGE_NAME,
|
||||
PACKAGE_NAME,
|
||||
/* isHashedValue= */ false),
|
||||
Rule.DENY));
|
||||
|
||||
mService.updateRuleSet(VERSION, new ParceledListSlice<>(rules), mockReceiver);
|
||||
runJobInHandler();
|
||||
|
||||
verify(mIntegrityFileManager).writeRules(VERSION, TEST_FRAMEWORK_PACKAGE, rules);
|
||||
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
|
||||
verify(mockReceiver).sendIntent(any(), anyInt(), intentCaptor.capture(), any(), any());
|
||||
assertEquals(STATUS_SUCCESS, intentCaptor.getValue().getIntExtra(EXTRA_STATUS, -1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void updateRuleSet_fail() throws Exception {
|
||||
whitelistUsAsRuleProvider();
|
||||
makeUsSystemApp();
|
||||
doThrow(new IOException()).when(mIntegrityFileManager).writeRules(any(), any(), any());
|
||||
IntentSender mockReceiver = mock(IntentSender.class);
|
||||
List<Rule> rules =
|
||||
Arrays.asList(
|
||||
new Rule(
|
||||
new AtomicFormula.StringAtomicFormula(
|
||||
AtomicFormula.PACKAGE_NAME,
|
||||
PACKAGE_NAME,
|
||||
/* isHashedValue= */ false),
|
||||
Rule.DENY));
|
||||
|
||||
mService.updateRuleSet(VERSION, new ParceledListSlice<>(rules), mockReceiver);
|
||||
runJobInHandler();
|
||||
|
||||
verify(mIntegrityFileManager).writeRules(VERSION, TEST_FRAMEWORK_PACKAGE, rules);
|
||||
ArgumentCaptor<Intent> intentCaptor = ArgumentCaptor.forClass(Intent.class);
|
||||
verify(mockReceiver).sendIntent(any(), anyInt(), intentCaptor.capture(), any(), any());
|
||||
assertEquals(STATUS_FAILURE, intentCaptor.getValue().getIntExtra(EXTRA_STATUS, -1));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void broadcastReceiverRegistration() throws Exception {
|
||||
ArgumentCaptor<IntentFilter> intentFilterCaptor =
|
||||
ArgumentCaptor.forClass(IntentFilter.class);
|
||||
|
||||
verify(mMockContext).registerReceiver(any(), intentFilterCaptor.capture(), any(), any());
|
||||
assertEquals(1, intentFilterCaptor.getValue().countActions());
|
||||
assertEquals(
|
||||
Intent.ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION,
|
||||
intentFilterCaptor.getValue().getAction(0));
|
||||
assertEquals(1, intentFilterCaptor.getValue().countDataTypes());
|
||||
assertEquals(PACKAGE_MIME_TYPE, intentFilterCaptor.getValue().getDataType(0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleBroadcast_correctArgs() throws Exception {
|
||||
ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
|
||||
ArgumentCaptor.forClass(BroadcastReceiver.class);
|
||||
verify(mMockContext)
|
||||
.registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
|
||||
Intent intent = makeVerificationIntent();
|
||||
when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow());
|
||||
|
||||
broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
|
||||
runJobInHandler();
|
||||
|
||||
ArgumentCaptor<AppInstallMetadata> metadataCaptor =
|
||||
ArgumentCaptor.forClass(AppInstallMetadata.class);
|
||||
verify(mRuleEvaluationEngine).evaluate(metadataCaptor.capture());
|
||||
AppInstallMetadata appInstallMetadata = metadataCaptor.getValue();
|
||||
assertEquals(PACKAGE_NAME, appInstallMetadata.getPackageName());
|
||||
assertEquals(APP_CERT, appInstallMetadata.getAppCertificate());
|
||||
assertEquals(INSTALLER_SHA256, appInstallMetadata.getInstallerName());
|
||||
assertEquals(INSTALLER_CERT, appInstallMetadata.getInstallerCertificate());
|
||||
assertEquals(VERSION_CODE, appInstallMetadata.getVersionCode());
|
||||
assertFalse(appInstallMetadata.isPreInstalled());
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleBroadcast_allow() throws Exception {
|
||||
ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
|
||||
ArgumentCaptor.forClass(BroadcastReceiver.class);
|
||||
verify(mMockContext)
|
||||
.registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
|
||||
Intent intent = makeVerificationIntent();
|
||||
when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow());
|
||||
|
||||
broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
|
||||
runJobInHandler();
|
||||
|
||||
verify(mPackageManagerInternal)
|
||||
.setIntegrityVerificationResult(
|
||||
1, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void handleBroadcast_reject() throws Exception {
|
||||
ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor =
|
||||
ArgumentCaptor.forClass(BroadcastReceiver.class);
|
||||
verify(mMockContext)
|
||||
.registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
|
||||
when(mRuleEvaluationEngine.evaluate(any()))
|
||||
.thenReturn(
|
||||
IntegrityCheckResult.deny(
|
||||
new Rule(
|
||||
new AtomicFormula.BooleanAtomicFormula(
|
||||
AtomicFormula.PRE_INSTALLED, false),
|
||||
Rule.DENY)));
|
||||
Intent intent = makeVerificationIntent();
|
||||
|
||||
broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
|
||||
runJobInHandler();
|
||||
|
||||
verify(mPackageManagerInternal)
|
||||
.setIntegrityVerificationResult(
|
||||
1, PackageManagerInternal.INTEGRITY_VERIFICATION_REJECT);
|
||||
}
|
||||
|
||||
private void whitelistUsAsRuleProvider() {
|
||||
Resources mockResources = mock(Resources.class);
|
||||
when(mockResources.getStringArray(R.array.config_integrityRuleProviderPackages))
|
||||
.thenReturn(new String[] {TEST_FRAMEWORK_PACKAGE});
|
||||
when(mMockContext.getResources()).thenReturn(mockResources);
|
||||
}
|
||||
|
||||
private void runJobInHandler() {
|
||||
ArgumentCaptor<Message> messageCaptor = ArgumentCaptor.forClass(Message.class);
|
||||
// sendMessageAtTime is the first non-final method in the call chain when "post" is invoked.
|
||||
verify(mHandler).sendMessageAtTime(messageCaptor.capture(), anyLong());
|
||||
messageCaptor.getValue().getCallback().run();
|
||||
}
|
||||
|
||||
private void makeUsSystemApp() throws Exception {
|
||||
PackageInfo packageInfo =
|
||||
mRealContext.getPackageManager().getPackageInfo(TEST_FRAMEWORK_PACKAGE, 0);
|
||||
packageInfo.applicationInfo.flags |= ApplicationInfo.FLAG_SYSTEM;
|
||||
doReturn(packageInfo)
|
||||
.when(mSpyPackageManager)
|
||||
.getPackageInfo(eq(TEST_FRAMEWORK_PACKAGE), anyInt());
|
||||
}
|
||||
|
||||
private Intent makeVerificationIntent() throws Exception {
|
||||
Intent intent = new Intent();
|
||||
intent.setDataAndType(Uri.fromFile(mTestApk), PACKAGE_MIME_TYPE);
|
||||
intent.setAction(Intent.ACTION_PACKAGE_NEEDS_INTEGRITY_VERIFICATION);
|
||||
intent.putExtra(EXTRA_VERIFICATION_ID, 1);
|
||||
intent.putExtra(Intent.EXTRA_PACKAGE_NAME, PACKAGE_NAME);
|
||||
intent.putExtra(EXTRA_VERIFICATION_INSTALLER_PACKAGE, INSTALLER);
|
||||
intent.putExtra(
|
||||
EXTRA_VERIFICATION_INSTALLER_UID,
|
||||
mRealContext.getPackageManager().getPackageUid(INSTALLER, /* flags= */ 0));
|
||||
intent.putExtra(Intent.EXTRA_VERSION_CODE, VERSION_CODE);
|
||||
return intent;
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user