diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java index 4d976838361e2..6c80a884edb65 100644 --- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java +++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java @@ -25,6 +25,7 @@ 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.NonNull; import android.annotation.Nullable; import android.content.BroadcastReceiver; import android.content.Context; @@ -41,6 +42,7 @@ import android.content.pm.ParceledListSlice; import android.content.pm.Signature; import android.net.Uri; import android.os.Binder; +import android.os.Bundle; import android.os.Handler; import android.os.HandlerThread; import android.os.RemoteException; @@ -63,6 +65,8 @@ import java.security.cert.CertificateEncodingException; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; +import java.util.HashMap; +import java.util.Map; /** Implementation of {@link AppIntegrityManagerService}. */ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { @@ -72,6 +76,9 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { 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 ALLOWED_INSTALLERS_METADATA_NAME = "allowed-installers"; + private static final String ALLOWED_INSTALLER_DELIMITER = ","; + private static final String INSTALLER_PACKAGE_CERT_DELIMITER = "\\|"; private static final String ADB_INSTALLER = "adb"; private static final String UNKNOWN_INSTALLER = ""; @@ -191,11 +198,21 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { 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); + + PackageInfo packageInfo = getPackageArchiveInfo(intent.getData()); + if (packageInfo == null) { + Slog.w(TAG, "Cannot parse package " + packageName); + // We can't parse the package. + mPackageManagerInternal.setIntegrityVerificationResult( + verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW); + return; + } + String installerPackageName = getInstallerPackageName(intent); - String appCert = getAppCertificateFingerprint(intent.getData()); + String appCert = getCertificateFingerprint(packageInfo); + + AppInstallMetadata.Builder builder = new AppInstallMetadata.Builder(); builder.setPackageName(getPackageNameNormalized(packageName)); builder.setAppCertificate(appCert == null ? "" : appCert); @@ -208,7 +225,9 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { AppInstallMetadata appInstallMetadata = builder.build(); Slog.i(TAG, "To be verified: " + appInstallMetadata); - IntegrityCheckResult result = mEvaluationEngine.evaluate(appInstallMetadata); + IntegrityCheckResult result = + mEvaluationEngine.evaluate( + appInstallMetadata, getAllowedInstallers(packageInfo)); Slog.i( TAG, "Integrity check result: " @@ -224,14 +243,12 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { // 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); } @@ -320,8 +337,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { } } - private String getAppCertificateFingerprint(Uri dataUri) { - PackageInfo packageInfo = getPackageArchiveInfo(dataUri); + private String getCertificateFingerprint(@NonNull PackageInfo packageInfo) { return getFingerprint(getSignature(packageInfo)); } @@ -333,14 +349,50 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { PackageInfo installerInfo = mContext.getPackageManager() .getPackageInfo(installer, PackageManager.GET_SIGNATURES); - return getFingerprint(getSignature(installerInfo)); + return getCertificateFingerprint(installerInfo); } catch (PackageManager.NameNotFoundException e) { Slog.i(TAG, "Installer package " + installer + " not found."); return ""; } } - private static Signature getSignature(PackageInfo packageInfo) { + /** Get the allowed installers and their associated certificate hashes from tag. */ + private Map getAllowedInstallers(@NonNull PackageInfo packageInfo) { + Map packageCertMap = new HashMap<>(); + if (packageInfo.applicationInfo != null && packageInfo.applicationInfo.metaData != null) { + Bundle metaData = packageInfo.applicationInfo.metaData; + String allowedInstallers = metaData.getString(ALLOWED_INSTALLERS_METADATA_NAME); + if (allowedInstallers != null) { + // parse the metadata for certs. + String[] installerCertPairs = allowedInstallers.split(ALLOWED_INSTALLER_DELIMITER); + for (String packageCertPair : installerCertPairs) { + String[] packageAndCert = + packageCertPair.split(INSTALLER_PACKAGE_CERT_DELIMITER); + if (packageAndCert.length == 2) { + String packageName = packageAndCert[0]; + String cert = packageAndCert[1]; + packageCertMap.put(packageName, cert); + } + } + } + } + + Slog.i("DEBUG", "allowed installers map " + packageCertMap); + return packageCertMap; + } + + private boolean getPreInstalled(String packageName) { + try { + PackageInfo existingPackageInfo = + mContext.getPackageManager().getPackageInfo(packageName, 0); + return existingPackageInfo.applicationInfo != null + && existingPackageInfo.applicationInfo.isSystemApp(); + } catch (PackageManager.NameNotFoundException e) { + return false; + } + } + + private static Signature getSignature(@NonNull PackageInfo packageInfo) { if (packageInfo.signatures == null || packageInfo.signatures.length < 1) { throw new IllegalArgumentException("Package signature not found in " + packageInfo); } @@ -402,7 +454,9 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { packageInfo = mContext.getPackageManager() .getPackageArchiveInfo( - installationPath.getPath(), PackageManager.GET_SIGNATURES); + installationPath.getPath(), + PackageManager.GET_SIGNATURES + | PackageManager.GET_META_DATA); } return packageInfo; } catch (Exception e) { diff --git a/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java b/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java index 1933679f50e43..0ea6efc41f425 100644 --- a/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java +++ b/services/core/java/com/android/server/integrity/engine/RuleEvaluationEngine.java @@ -25,6 +25,7 @@ import com.android.server.integrity.model.IntegrityCheckResult; import java.util.ArrayList; import java.util.List; +import java.util.Map; /** * The engine used to evaluate rules against app installs. @@ -60,7 +61,8 @@ public class RuleEvaluationEngine { * against. * @return result of the integrity check */ - public IntegrityCheckResult evaluate(AppInstallMetadata appInstallMetadata) { + public IntegrityCheckResult evaluate( + AppInstallMetadata appInstallMetadata, Map allowedInstallers) { List rules = loadRules(appInstallMetadata); return RuleEvaluator.evaluateRules(rules, appInstallMetadata); } diff --git a/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/test.apk b/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/test.apk index e4c422951dc37..6345c986efc29 100644 Binary files a/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/test.apk and b/services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/test.apk differ diff --git a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java index 222dac9535686..a2376a6bef1c5 100644 --- a/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java +++ b/services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java @@ -79,7 +79,9 @@ import java.io.InputStream; import java.nio.file.Files; import java.nio.file.StandardCopyOption; import java.util.Arrays; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** Unit test for {@link com.android.server.integrity.AppIntegrityManagerServiceImpl} */ @RunWith(AndroidJUnit4.class) @@ -288,21 +290,30 @@ public class AppIntegrityManagerServiceImplTest { verify(mMockContext) .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any()); Intent intent = makeVerificationIntent(); - when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow()); + when(mRuleEvaluationEngine.evaluate(any(), any())).thenReturn(IntegrityCheckResult.allow()); broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent); runJobInHandler(); ArgumentCaptor metadataCaptor = ArgumentCaptor.forClass(AppInstallMetadata.class); - verify(mRuleEvaluationEngine).evaluate(metadataCaptor.capture()); + Map allowedInstallers = new HashMap<>(); + ArgumentCaptor> allowedInstallersCaptor = + ArgumentCaptor.forClass(allowedInstallers.getClass()); + verify(mRuleEvaluationEngine) + .evaluate(metadataCaptor.capture(), allowedInstallersCaptor.capture()); AppInstallMetadata appInstallMetadata = metadataCaptor.getValue(); + allowedInstallers = allowedInstallersCaptor.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()); + // These are hardcoded in the test apk + assertEquals(2, allowedInstallers.size()); + assertEquals("cert_1", allowedInstallers.get("store_1")); + assertEquals("cert_2", allowedInstallers.get("store_2")); } @Test @@ -312,7 +323,7 @@ public class AppIntegrityManagerServiceImplTest { verify(mMockContext) .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any()); Intent intent = makeVerificationIntent(); - when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow()); + when(mRuleEvaluationEngine.evaluate(any(), any())).thenReturn(IntegrityCheckResult.allow()); broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent); runJobInHandler(); @@ -328,7 +339,7 @@ public class AppIntegrityManagerServiceImplTest { ArgumentCaptor.forClass(BroadcastReceiver.class); verify(mMockContext) .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any()); - when(mRuleEvaluationEngine.evaluate(any())) + when(mRuleEvaluationEngine.evaluate(any(), any())) .thenReturn( IntegrityCheckResult.deny( new Rule(