Loading core/java/android/content/integrity/AppInstallMetadata.java +48 −8 Original line number Diff line number Diff line Loading @@ -42,6 +42,8 @@ public final class AppInstallMetadata { private final List<String> mInstallerCertificates; private final long mVersionCode; private final boolean mIsPreInstalled; private final boolean mIsStampPresent; private final boolean mIsStampVerified; private final boolean mIsStampTrusted; // Raw string encoding for the SHA-256 hash of the certificate of the stamp. private final String mStampCertificateHash; Loading @@ -54,6 +56,8 @@ public final class AppInstallMetadata { this.mInstallerCertificates = builder.mInstallerCertificates; this.mVersionCode = builder.mVersionCode; this.mIsPreInstalled = builder.mIsPreInstalled; this.mIsStampPresent = builder.mIsStampPresent; this.mIsStampVerified = builder.mIsStampVerified; this.mIsStampTrusted = builder.mIsStampTrusted; this.mStampCertificateHash = builder.mStampCertificateHash; this.mAllowedInstallersAndCertificates = builder.mAllowedInstallersAndCertificates; Loading Loading @@ -89,6 +93,16 @@ public final class AppInstallMetadata { return mIsPreInstalled; } /** @see AppInstallMetadata.Builder#setIsStampPresent(boolean) */ public boolean isStampPresent() { return mIsStampPresent; } /** @see AppInstallMetadata.Builder#setIsStampVerified(boolean) */ public boolean isStampVerified() { return mIsStampVerified; } /** @see AppInstallMetadata.Builder#setIsStampTrusted(boolean) */ public boolean isStampTrusted() { return mIsStampTrusted; Loading @@ -108,14 +122,16 @@ public final class AppInstallMetadata { public String toString() { return String.format( "AppInstallMetadata { PackageName = %s, AppCerts = %s, InstallerName = %s," + " InstallerCerts = %s, VersionCode = %d, PreInstalled = %b, " + "StampTrusted = %b, StampCert = %s }", + " InstallerCerts = %s, VersionCode = %d, PreInstalled = %b, StampPresent =" + " %b, StampVerified = %b, StampTrusted = %b, StampCert = %s }", mPackageName, mAppCertificates, mInstallerName == null ? "null" : mInstallerName, mInstallerCertificates == null ? "null" : mInstallerCertificates, mVersionCode, mIsPreInstalled, mIsStampPresent, mIsStampVerified, mIsStampTrusted, mStampCertificateHash == null ? "null" : mStampCertificateHash); } Loading @@ -128,6 +144,8 @@ public final class AppInstallMetadata { private List<String> mInstallerCertificates; private long mVersionCode; private boolean mIsPreInstalled; private boolean mIsStampPresent; private boolean mIsStampVerified; private boolean mIsStampTrusted; private String mStampCertificateHash; private Map<String, String> mAllowedInstallersAndCertificates; Loading Loading @@ -221,16 +239,24 @@ public final class AppInstallMetadata { } /** * Set certificate hash of the stamp embedded in the APK. * Set whether the stamp embedded in the APK is present or not. * * <p>It is represented as the raw string encoding for the SHA-256 hash of the certificate * of the stamp. * @see AppInstallMetadata#isStampPresent() */ @NonNull public Builder setIsStampPresent(boolean isStampPresent) { this.mIsStampPresent = isStampPresent; return this; } /** * Set whether the stamp embedded in the APK is verified or not. * * @see AppInstallMetadata#getStampCertificateHash() * @see AppInstallMetadata#isStampVerified() */ @NonNull public Builder setStampCertificateHash(@NonNull String stampCertificateHash) { this.mStampCertificateHash = Objects.requireNonNull(stampCertificateHash); public Builder setIsStampVerified(boolean isStampVerified) { this.mIsStampVerified = isStampVerified; return this; } Loading @@ -245,6 +271,20 @@ public final class AppInstallMetadata { return this; } /** * Set certificate hash of the stamp embedded in the APK. * * <p>It is represented as the raw string encoding for the SHA-256 hash of the certificate * of the stamp. * * @see AppInstallMetadata#getStampCertificateHash() */ @NonNull public Builder setStampCertificateHash(@NonNull String stampCertificateHash) { this.mStampCertificateHash = Objects.requireNonNull(stampCertificateHash); return this; } /** * Build {@link AppInstallMetadata}. * Loading core/java/android/content/integrity/AtomicFormula.java +3 −4 Original line number Diff line number Diff line Loading @@ -368,11 +368,10 @@ public abstract class AtomicFormula extends IntegrityFormula { "Key %s cannot be used with StringAtomicFormula", keyToString(key))); mValue = hashValue(key, value); mIsHashedValue = key == APP_CERTIFICATE (key == APP_CERTIFICATE || key == INSTALLER_CERTIFICATE || key == STAMP_CERTIFICATE_HASH ? true : !mValue.equals(value); || key == STAMP_CERTIFICATE_HASH) || !mValue.equals(value); } StringAtomicFormula(Parcel in) { Loading services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java +74 −17 Original line number Diff line number Diff line Loading @@ -52,7 +52,10 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.UserHandle; import android.provider.Settings; import android.security.FileIntegrityManager; import android.util.Slog; import android.util.apk.SourceStampVerificationResult; import android.util.apk.SourceStampVerifier; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -108,8 +111,10 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { private static final String ALLOWED_INSTALLER_DELIMITER = ","; private static final String INSTALLER_PACKAGE_CERT_DELIMITER = "\\|"; private static final Set<String> PACKAGE_INSTALLER = new HashSet<>( Arrays.asList("com.google.android.packageinstaller", "com.android.packageinstaller")); private static final Set<String> PACKAGE_INSTALLER = new HashSet<>( Arrays.asList( "com.google.android.packageinstaller", "com.android.packageinstaller")); // Access to files inside mRulesDir is protected by mRulesLock; private final Context mContext; Loading @@ -117,6 +122,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { private final PackageManagerInternal mPackageManagerInternal; private final RuleEvaluationEngine mEvaluationEngine; private final IntegrityFileManager mIntegrityFileManager; private final FileIntegrityManager mFileIntegrityManager; /** Create an instance of {@link AppIntegrityManagerServiceImpl}. */ public static AppIntegrityManagerServiceImpl create(Context context) { Loading @@ -128,6 +134,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { LocalServices.getService(PackageManagerInternal.class), RuleEvaluationEngine.getRuleEvaluationEngine(), IntegrityFileManager.getInstance(), (FileIntegrityManager) context.getSystemService(Context.FILE_INTEGRITY_SERVICE), handlerThread.getThreadHandler()); } Loading @@ -137,11 +144,13 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { PackageManagerInternal packageManagerInternal, RuleEvaluationEngine evaluationEngine, IntegrityFileManager integrityFileManager, FileIntegrityManager fileIntegrityManager, Handler handler) { mContext = context; mPackageManagerInternal = packageManagerInternal; mEvaluationEngine = evaluationEngine; mIntegrityFileManager = integrityFileManager; mFileIntegrityManager = fileIntegrityManager; mHandler = handler; IntentFilter integrityVerificationFilter = new IntentFilter(); Loading Loading @@ -183,8 +192,11 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { success = false; } FrameworkStatsLog.write(FrameworkStatsLog.INTEGRITY_RULES_PUSHED, success, ruleProvider, version); FrameworkStatsLog.write( FrameworkStatsLog.INTEGRITY_RULES_PUSHED, success, ruleProvider, version); Intent intent = new Intent(); intent.putExtra(EXTRA_STATUS, success ? STATUS_SUCCESS : STATUS_FAILURE); Loading Loading @@ -242,8 +254,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { String installerPackageName = getInstallerPackageName(intent); // Skip integrity verification if the verifier is doing the install. if (!integrityCheckIncludesRuleProvider() && isRuleProvider(installerPackageName)) { if (!integrityCheckIncludesRuleProvider() && isRuleProvider(installerPackageName)) { Slog.i(TAG, "Verifier doing the install. Skipping integrity check."); mPackageManagerInternal.setIntegrityVerificationResult( verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW); Loading Loading @@ -274,15 +285,17 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { builder.setInstallerCertificates(installerCertificates); builder.setIsPreInstalled(isSystemApp(packageName)); builder.setAllowedInstallersAndCert(getAllowedInstallers(packageInfo)); extractSourceStamp(intent.getData(), builder); AppInstallMetadata appInstallMetadata = builder.build(); Slog.i( TAG, "To be verified: " + appInstallMetadata + " installers " + getAllowedInstallers( packageInfo)); IntegrityCheckResult result = mEvaluationEngine.evaluate(appInstallMetadata); "To be verified: " + appInstallMetadata + " installers " + getAllowedInstallers(packageInfo)); IntegrityCheckResult result = mEvaluationEngine.evaluate(appInstallMetadata); Slog.i( TAG, "Integrity check result: " Loading Loading @@ -442,7 +455,8 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { String cert = packageAndCert[1]; packageCertMap.put(packageName, cert); } else if (packageAndCert.length == 1) { packageCertMap.put(getPackageNameNormalized(packageAndCert[0]), packageCertMap.put( getPackageNameNormalized(packageAndCert[0]), INSTALLER_CERTIFICATE_NOT_EVALUATED); } } Loading @@ -452,6 +466,41 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { return packageCertMap; } /** Extract the source stamp embedded in the APK, if present. */ private void extractSourceStamp(Uri dataUri, AppInstallMetadata.Builder appInstallMetadata) { File installationPath = getInstallationPath(dataUri); if (installationPath == null) { throw new IllegalArgumentException("Installation path is null, package not found"); } SourceStampVerificationResult sourceStampVerificationResult = SourceStampVerifier.verify(installationPath.getAbsolutePath()); appInstallMetadata.setIsStampPresent(sourceStampVerificationResult.isPresent()); appInstallMetadata.setIsStampVerified(sourceStampVerificationResult.isVerified()); if (sourceStampVerificationResult.isVerified()) { X509Certificate sourceStampCertificate = (X509Certificate) sourceStampVerificationResult.getCertificate(); // Sets source stamp certificate digest. try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] certificateDigest = digest.digest(sourceStampCertificate.getEncoded()); appInstallMetadata.setStampCertificateHash(getHexDigest(certificateDigest)); } catch (NoSuchAlgorithmException | CertificateEncodingException e) { throw new IllegalArgumentException( "Error computing source stamp certificate digest", e); } // Checks if the source stamp certificate is trusted. try { appInstallMetadata.setIsStampTrusted( mFileIntegrityManager.isApkVeritySupported() && mFileIntegrityManager.isAppSourceCertificateTrusted( sourceStampCertificate)); } catch (CertificateEncodingException e) { throw new IllegalArgumentException( "Error checking if source stamp certificate is trusted", e); } } } private static Signature[] getSignatures(@NonNull PackageInfo packageInfo) { SigningInfo signingInfo = packageInfo.signingInfo; Loading Loading @@ -505,8 +554,16 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { ParsedPackage pkg = parser.parsePackage(installationPath, 0, false); int flags = PackageManager.GET_SIGNING_CERTIFICATES | PackageManager.GET_META_DATA; pkg.setSigningDetails(ParsingPackageUtils.collectCertificates(pkg, false)); return PackageInfoUtils.generate(pkg, null, flags, 0, 0, null, new PackageUserState(), UserHandle.getCallingUserId(), null); return PackageInfoUtils.generate( pkg, null, flags, 0, 0, null, new PackageUserState(), UserHandle.getCallingUserId(), null); } catch (Exception e) { Slog.w(TAG, "Exception reading " + dataUri, e); return null; Loading services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/SourceStampTestApk.apk 0 → 100644 +16.5 KiB File added.No diff preview for this file type. View file services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java +74 −30 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; Loading Loading @@ -61,6 +62,7 @@ import android.net.Uri; import android.os.Handler; import android.os.Message; import android.provider.Settings; import android.security.FileIntegrityManager; import androidx.test.InstrumentationRegistry; Loading Loading @@ -96,6 +98,9 @@ public class AppIntegrityManagerServiceImplTest { private static final String TEST_APP_TWO_CERT_PATH = "AppIntegrityManagerServiceImplTest/DummyAppTwoCerts.apk"; private static final String TEST_APP_SOURCE_STAMP_PATH = "AppIntegrityManagerServiceImplTest/SourceStampTestApk.apk"; 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"; Loading @@ -111,6 +116,8 @@ public class AppIntegrityManagerServiceImplTest { // We use SHA256 for package names longer than 32 characters. private static final String INSTALLER_SHA256 = "30F41A7CBF96EE736A54DD6DF759B50ED3CC126ABCEF694E167C324F5976C227"; private static final String SOURCE_STAMP_CERTIFICATE_HASH = "681B0E56A796350C08647352A4DB800CC44B2ADC8F4C72FA350BD05D4D50264D"; private static final String DUMMY_APP_TWO_CERTS_CERT_1 = "C0369C2A1096632429DFA8433068AECEAD00BAC337CA92A175036D39CC9AFE94"; Loading @@ -121,27 +128,22 @@ public class AppIntegrityManagerServiceImplTest { private static final String ADB_INSTALLER = "adb"; private static final String PLAY_STORE_CERT = "play_store_cert"; @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; @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; FileIntegrityManager mFileIntegrityManager; private final Context mRealContext = InstrumentationRegistry.getTargetContext(); private PackageManager mSpyPackageManager; private File mTestApk; private File mTestApkTwoCerts; private File mTestApkSourceStamp; // under test private AppIntegrityManagerServiceImpl mService; Loading @@ -158,12 +160,21 @@ public class AppIntegrityManagerServiceImplTest { Files.copy(inputStream, mTestApkTwoCerts.toPath(), REPLACE_EXISTING); } mTestApkSourceStamp = File.createTempFile("AppIntegritySourceStamp", ".apk"); try (InputStream inputStream = mRealContext.getAssets().open(TEST_APP_SOURCE_STAMP_PATH)) { Files.copy(inputStream, mTestApkSourceStamp.toPath(), REPLACE_EXISTING); } mFileIntegrityManager = (FileIntegrityManager) mRealContext.getSystemService(Context.FILE_INTEGRITY_SERVICE); mService = new AppIntegrityManagerServiceImpl( mMockContext, mPackageManagerInternal, mRuleEvaluationEngine, mIntegrityFileManager, mFileIntegrityManager, mHandler); mSpyPackageManager = spy(mRealContext.getPackageManager()); Loading @@ -181,6 +192,7 @@ public class AppIntegrityManagerServiceImplTest { public void tearDown() throws Exception { mTestApk.delete(); mTestApkTwoCerts.delete(); mTestApkSourceStamp.delete(); } @Test Loading Loading @@ -241,7 +253,8 @@ public class AppIntegrityManagerServiceImplTest { IntentSender mockReceiver = mock(IntentSender.class); List<Rule> rules = Arrays.asList( new Rule(IntegrityFormula.Application.packageNameEquals(PACKAGE_NAME), new Rule( IntegrityFormula.Application.packageNameEquals(PACKAGE_NAME), Rule.DENY)); mService.updateRuleSet(VERSION, new ParceledListSlice<>(rules), mockReceiver); Loading @@ -261,7 +274,8 @@ public class AppIntegrityManagerServiceImplTest { IntentSender mockReceiver = mock(IntentSender.class); List<Rule> rules = Arrays.asList( new Rule(IntegrityFormula.Application.packageNameEquals(PACKAGE_NAME), new Rule( IntegrityFormula.Application.packageNameEquals(PACKAGE_NAME), Rule.DENY)); mService.updateRuleSet(VERSION, new ParceledListSlice<>(rules), mockReceiver); Loading Loading @@ -305,8 +319,7 @@ public class AppIntegrityManagerServiceImplTest { ArgumentCaptor<AppInstallMetadata> metadataCaptor = ArgumentCaptor.forClass(AppInstallMetadata.class); verify(mRuleEvaluationEngine) .evaluate(metadataCaptor.capture()); verify(mRuleEvaluationEngine).evaluate(metadataCaptor.capture()); AppInstallMetadata appInstallMetadata = metadataCaptor.getValue(); assertEquals(PACKAGE_NAME, appInstallMetadata.getPackageName()); assertThat(appInstallMetadata.getAppCertificates()).containsExactly(APP_CERT); Loading Loading @@ -341,8 +354,33 @@ public class AppIntegrityManagerServiceImplTest { ArgumentCaptor.forClass(AppInstallMetadata.class); verify(mRuleEvaluationEngine).evaluate(metadataCaptor.capture()); AppInstallMetadata appInstallMetadata = metadataCaptor.getValue(); assertThat(appInstallMetadata.getAppCertificates()).containsExactly( DUMMY_APP_TWO_CERTS_CERT_1, DUMMY_APP_TWO_CERTS_CERT_2); assertThat(appInstallMetadata.getAppCertificates()) .containsExactly(DUMMY_APP_TWO_CERTS_CERT_1, DUMMY_APP_TWO_CERTS_CERT_2); } @Test public void handleBroadcast_correctArgs_sourceStamp() throws Exception { whitelistUsAsRuleProvider(); makeUsSystemApp(); ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); verify(mMockContext) .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any()); Intent intent = makeVerificationIntent(); intent.setDataAndType(Uri.fromFile(mTestApkSourceStamp), PACKAGE_MIME_TYPE); 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(); assertTrue(appInstallMetadata.isStampPresent()); assertTrue(appInstallMetadata.isStampVerified()); assertFalse(appInstallMetadata.isStampTrusted()); assertEquals(SOURCE_STAMP_CERTIFICATE_HASH, appInstallMetadata.getStampCertificateHash()); } @Test Loading Loading @@ -478,8 +516,8 @@ public class AppIntegrityManagerServiceImplTest { PackageInfo packageInfo = mRealContext .getPackageManager() .getPackageInfo(TEST_FRAMEWORK_PACKAGE, PackageManager.GET_SIGNING_CERTIFICATES); .getPackageInfo( TEST_FRAMEWORK_PACKAGE, PackageManager.GET_SIGNING_CERTIFICATES); doReturn(packageInfo).when(mSpyPackageManager).getPackageInfo(eq(INSTALLER), anyInt()); doReturn(1).when(mSpyPackageManager).getPackageUid(eq(INSTALLER), anyInt()); return makeVerificationIntent(INSTALLER); Loading @@ -501,10 +539,16 @@ public class AppIntegrityManagerServiceImplTest { private void setIntegrityCheckIncludesRuleProvider(boolean shouldInclude) throws Exception { int value = shouldInclude ? 1 : 0; Settings.Global.putInt(mRealContext.getContentResolver(), Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER, value); assertThat(Settings.Global.getInt(mRealContext.getContentResolver(), Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER, -1) == 1).isEqualTo( shouldInclude); Settings.Global.putInt( mRealContext.getContentResolver(), Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER, value); assertThat( Settings.Global.getInt( mRealContext.getContentResolver(), Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER, -1) == 1) .isEqualTo(shouldInclude); } } Loading
core/java/android/content/integrity/AppInstallMetadata.java +48 −8 Original line number Diff line number Diff line Loading @@ -42,6 +42,8 @@ public final class AppInstallMetadata { private final List<String> mInstallerCertificates; private final long mVersionCode; private final boolean mIsPreInstalled; private final boolean mIsStampPresent; private final boolean mIsStampVerified; private final boolean mIsStampTrusted; // Raw string encoding for the SHA-256 hash of the certificate of the stamp. private final String mStampCertificateHash; Loading @@ -54,6 +56,8 @@ public final class AppInstallMetadata { this.mInstallerCertificates = builder.mInstallerCertificates; this.mVersionCode = builder.mVersionCode; this.mIsPreInstalled = builder.mIsPreInstalled; this.mIsStampPresent = builder.mIsStampPresent; this.mIsStampVerified = builder.mIsStampVerified; this.mIsStampTrusted = builder.mIsStampTrusted; this.mStampCertificateHash = builder.mStampCertificateHash; this.mAllowedInstallersAndCertificates = builder.mAllowedInstallersAndCertificates; Loading Loading @@ -89,6 +93,16 @@ public final class AppInstallMetadata { return mIsPreInstalled; } /** @see AppInstallMetadata.Builder#setIsStampPresent(boolean) */ public boolean isStampPresent() { return mIsStampPresent; } /** @see AppInstallMetadata.Builder#setIsStampVerified(boolean) */ public boolean isStampVerified() { return mIsStampVerified; } /** @see AppInstallMetadata.Builder#setIsStampTrusted(boolean) */ public boolean isStampTrusted() { return mIsStampTrusted; Loading @@ -108,14 +122,16 @@ public final class AppInstallMetadata { public String toString() { return String.format( "AppInstallMetadata { PackageName = %s, AppCerts = %s, InstallerName = %s," + " InstallerCerts = %s, VersionCode = %d, PreInstalled = %b, " + "StampTrusted = %b, StampCert = %s }", + " InstallerCerts = %s, VersionCode = %d, PreInstalled = %b, StampPresent =" + " %b, StampVerified = %b, StampTrusted = %b, StampCert = %s }", mPackageName, mAppCertificates, mInstallerName == null ? "null" : mInstallerName, mInstallerCertificates == null ? "null" : mInstallerCertificates, mVersionCode, mIsPreInstalled, mIsStampPresent, mIsStampVerified, mIsStampTrusted, mStampCertificateHash == null ? "null" : mStampCertificateHash); } Loading @@ -128,6 +144,8 @@ public final class AppInstallMetadata { private List<String> mInstallerCertificates; private long mVersionCode; private boolean mIsPreInstalled; private boolean mIsStampPresent; private boolean mIsStampVerified; private boolean mIsStampTrusted; private String mStampCertificateHash; private Map<String, String> mAllowedInstallersAndCertificates; Loading Loading @@ -221,16 +239,24 @@ public final class AppInstallMetadata { } /** * Set certificate hash of the stamp embedded in the APK. * Set whether the stamp embedded in the APK is present or not. * * <p>It is represented as the raw string encoding for the SHA-256 hash of the certificate * of the stamp. * @see AppInstallMetadata#isStampPresent() */ @NonNull public Builder setIsStampPresent(boolean isStampPresent) { this.mIsStampPresent = isStampPresent; return this; } /** * Set whether the stamp embedded in the APK is verified or not. * * @see AppInstallMetadata#getStampCertificateHash() * @see AppInstallMetadata#isStampVerified() */ @NonNull public Builder setStampCertificateHash(@NonNull String stampCertificateHash) { this.mStampCertificateHash = Objects.requireNonNull(stampCertificateHash); public Builder setIsStampVerified(boolean isStampVerified) { this.mIsStampVerified = isStampVerified; return this; } Loading @@ -245,6 +271,20 @@ public final class AppInstallMetadata { return this; } /** * Set certificate hash of the stamp embedded in the APK. * * <p>It is represented as the raw string encoding for the SHA-256 hash of the certificate * of the stamp. * * @see AppInstallMetadata#getStampCertificateHash() */ @NonNull public Builder setStampCertificateHash(@NonNull String stampCertificateHash) { this.mStampCertificateHash = Objects.requireNonNull(stampCertificateHash); return this; } /** * Build {@link AppInstallMetadata}. * Loading
core/java/android/content/integrity/AtomicFormula.java +3 −4 Original line number Diff line number Diff line Loading @@ -368,11 +368,10 @@ public abstract class AtomicFormula extends IntegrityFormula { "Key %s cannot be used with StringAtomicFormula", keyToString(key))); mValue = hashValue(key, value); mIsHashedValue = key == APP_CERTIFICATE (key == APP_CERTIFICATE || key == INSTALLER_CERTIFICATE || key == STAMP_CERTIFICATE_HASH ? true : !mValue.equals(value); || key == STAMP_CERTIFICATE_HASH) || !mValue.equals(value); } StringAtomicFormula(Parcel in) { Loading
services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java +74 −17 Original line number Diff line number Diff line Loading @@ -52,7 +52,10 @@ import android.os.Handler; import android.os.HandlerThread; import android.os.UserHandle; import android.provider.Settings; import android.security.FileIntegrityManager; import android.util.Slog; import android.util.apk.SourceStampVerificationResult; import android.util.apk.SourceStampVerifier; import com.android.internal.R; import com.android.internal.annotations.VisibleForTesting; Loading Loading @@ -108,8 +111,10 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { private static final String ALLOWED_INSTALLER_DELIMITER = ","; private static final String INSTALLER_PACKAGE_CERT_DELIMITER = "\\|"; private static final Set<String> PACKAGE_INSTALLER = new HashSet<>( Arrays.asList("com.google.android.packageinstaller", "com.android.packageinstaller")); private static final Set<String> PACKAGE_INSTALLER = new HashSet<>( Arrays.asList( "com.google.android.packageinstaller", "com.android.packageinstaller")); // Access to files inside mRulesDir is protected by mRulesLock; private final Context mContext; Loading @@ -117,6 +122,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { private final PackageManagerInternal mPackageManagerInternal; private final RuleEvaluationEngine mEvaluationEngine; private final IntegrityFileManager mIntegrityFileManager; private final FileIntegrityManager mFileIntegrityManager; /** Create an instance of {@link AppIntegrityManagerServiceImpl}. */ public static AppIntegrityManagerServiceImpl create(Context context) { Loading @@ -128,6 +134,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { LocalServices.getService(PackageManagerInternal.class), RuleEvaluationEngine.getRuleEvaluationEngine(), IntegrityFileManager.getInstance(), (FileIntegrityManager) context.getSystemService(Context.FILE_INTEGRITY_SERVICE), handlerThread.getThreadHandler()); } Loading @@ -137,11 +144,13 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { PackageManagerInternal packageManagerInternal, RuleEvaluationEngine evaluationEngine, IntegrityFileManager integrityFileManager, FileIntegrityManager fileIntegrityManager, Handler handler) { mContext = context; mPackageManagerInternal = packageManagerInternal; mEvaluationEngine = evaluationEngine; mIntegrityFileManager = integrityFileManager; mFileIntegrityManager = fileIntegrityManager; mHandler = handler; IntentFilter integrityVerificationFilter = new IntentFilter(); Loading Loading @@ -183,8 +192,11 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { success = false; } FrameworkStatsLog.write(FrameworkStatsLog.INTEGRITY_RULES_PUSHED, success, ruleProvider, version); FrameworkStatsLog.write( FrameworkStatsLog.INTEGRITY_RULES_PUSHED, success, ruleProvider, version); Intent intent = new Intent(); intent.putExtra(EXTRA_STATUS, success ? STATUS_SUCCESS : STATUS_FAILURE); Loading Loading @@ -242,8 +254,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { String installerPackageName = getInstallerPackageName(intent); // Skip integrity verification if the verifier is doing the install. if (!integrityCheckIncludesRuleProvider() && isRuleProvider(installerPackageName)) { if (!integrityCheckIncludesRuleProvider() && isRuleProvider(installerPackageName)) { Slog.i(TAG, "Verifier doing the install. Skipping integrity check."); mPackageManagerInternal.setIntegrityVerificationResult( verificationId, PackageManagerInternal.INTEGRITY_VERIFICATION_ALLOW); Loading Loading @@ -274,15 +285,17 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { builder.setInstallerCertificates(installerCertificates); builder.setIsPreInstalled(isSystemApp(packageName)); builder.setAllowedInstallersAndCert(getAllowedInstallers(packageInfo)); extractSourceStamp(intent.getData(), builder); AppInstallMetadata appInstallMetadata = builder.build(); Slog.i( TAG, "To be verified: " + appInstallMetadata + " installers " + getAllowedInstallers( packageInfo)); IntegrityCheckResult result = mEvaluationEngine.evaluate(appInstallMetadata); "To be verified: " + appInstallMetadata + " installers " + getAllowedInstallers(packageInfo)); IntegrityCheckResult result = mEvaluationEngine.evaluate(appInstallMetadata); Slog.i( TAG, "Integrity check result: " Loading Loading @@ -442,7 +455,8 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { String cert = packageAndCert[1]; packageCertMap.put(packageName, cert); } else if (packageAndCert.length == 1) { packageCertMap.put(getPackageNameNormalized(packageAndCert[0]), packageCertMap.put( getPackageNameNormalized(packageAndCert[0]), INSTALLER_CERTIFICATE_NOT_EVALUATED); } } Loading @@ -452,6 +466,41 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { return packageCertMap; } /** Extract the source stamp embedded in the APK, if present. */ private void extractSourceStamp(Uri dataUri, AppInstallMetadata.Builder appInstallMetadata) { File installationPath = getInstallationPath(dataUri); if (installationPath == null) { throw new IllegalArgumentException("Installation path is null, package not found"); } SourceStampVerificationResult sourceStampVerificationResult = SourceStampVerifier.verify(installationPath.getAbsolutePath()); appInstallMetadata.setIsStampPresent(sourceStampVerificationResult.isPresent()); appInstallMetadata.setIsStampVerified(sourceStampVerificationResult.isVerified()); if (sourceStampVerificationResult.isVerified()) { X509Certificate sourceStampCertificate = (X509Certificate) sourceStampVerificationResult.getCertificate(); // Sets source stamp certificate digest. try { MessageDigest digest = MessageDigest.getInstance("SHA-256"); byte[] certificateDigest = digest.digest(sourceStampCertificate.getEncoded()); appInstallMetadata.setStampCertificateHash(getHexDigest(certificateDigest)); } catch (NoSuchAlgorithmException | CertificateEncodingException e) { throw new IllegalArgumentException( "Error computing source stamp certificate digest", e); } // Checks if the source stamp certificate is trusted. try { appInstallMetadata.setIsStampTrusted( mFileIntegrityManager.isApkVeritySupported() && mFileIntegrityManager.isAppSourceCertificateTrusted( sourceStampCertificate)); } catch (CertificateEncodingException e) { throw new IllegalArgumentException( "Error checking if source stamp certificate is trusted", e); } } } private static Signature[] getSignatures(@NonNull PackageInfo packageInfo) { SigningInfo signingInfo = packageInfo.signingInfo; Loading Loading @@ -505,8 +554,16 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { ParsedPackage pkg = parser.parsePackage(installationPath, 0, false); int flags = PackageManager.GET_SIGNING_CERTIFICATES | PackageManager.GET_META_DATA; pkg.setSigningDetails(ParsingPackageUtils.collectCertificates(pkg, false)); return PackageInfoUtils.generate(pkg, null, flags, 0, 0, null, new PackageUserState(), UserHandle.getCallingUserId(), null); return PackageInfoUtils.generate( pkg, null, flags, 0, 0, null, new PackageUserState(), UserHandle.getCallingUserId(), null); } catch (Exception e) { Slog.w(TAG, "Exception reading " + dataUri, e); return null; Loading
services/tests/servicestests/assets/AppIntegrityManagerServiceImplTest/SourceStampTestApk.apk 0 → 100644 +16.5 KiB File added.No diff preview for this file type. View file
services/tests/servicestests/src/com/android/server/integrity/AppIntegrityManagerServiceImplTest.java +74 −30 Original line number Diff line number Diff line Loading @@ -28,6 +28,7 @@ import static com.google.common.truth.Truth.assertThat; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertFalse; import static org.junit.Assert.assertTrue; import static org.mockito.ArgumentMatchers.any; import static org.mockito.ArgumentMatchers.anyInt; import static org.mockito.ArgumentMatchers.anyLong; Loading Loading @@ -61,6 +62,7 @@ import android.net.Uri; import android.os.Handler; import android.os.Message; import android.provider.Settings; import android.security.FileIntegrityManager; import androidx.test.InstrumentationRegistry; Loading Loading @@ -96,6 +98,9 @@ public class AppIntegrityManagerServiceImplTest { private static final String TEST_APP_TWO_CERT_PATH = "AppIntegrityManagerServiceImplTest/DummyAppTwoCerts.apk"; private static final String TEST_APP_SOURCE_STAMP_PATH = "AppIntegrityManagerServiceImplTest/SourceStampTestApk.apk"; 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"; Loading @@ -111,6 +116,8 @@ public class AppIntegrityManagerServiceImplTest { // We use SHA256 for package names longer than 32 characters. private static final String INSTALLER_SHA256 = "30F41A7CBF96EE736A54DD6DF759B50ED3CC126ABCEF694E167C324F5976C227"; private static final String SOURCE_STAMP_CERTIFICATE_HASH = "681B0E56A796350C08647352A4DB800CC44B2ADC8F4C72FA350BD05D4D50264D"; private static final String DUMMY_APP_TWO_CERTS_CERT_1 = "C0369C2A1096632429DFA8433068AECEAD00BAC337CA92A175036D39CC9AFE94"; Loading @@ -121,27 +128,22 @@ public class AppIntegrityManagerServiceImplTest { private static final String ADB_INSTALLER = "adb"; private static final String PLAY_STORE_CERT = "play_store_cert"; @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; @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; FileIntegrityManager mFileIntegrityManager; private final Context mRealContext = InstrumentationRegistry.getTargetContext(); private PackageManager mSpyPackageManager; private File mTestApk; private File mTestApkTwoCerts; private File mTestApkSourceStamp; // under test private AppIntegrityManagerServiceImpl mService; Loading @@ -158,12 +160,21 @@ public class AppIntegrityManagerServiceImplTest { Files.copy(inputStream, mTestApkTwoCerts.toPath(), REPLACE_EXISTING); } mTestApkSourceStamp = File.createTempFile("AppIntegritySourceStamp", ".apk"); try (InputStream inputStream = mRealContext.getAssets().open(TEST_APP_SOURCE_STAMP_PATH)) { Files.copy(inputStream, mTestApkSourceStamp.toPath(), REPLACE_EXISTING); } mFileIntegrityManager = (FileIntegrityManager) mRealContext.getSystemService(Context.FILE_INTEGRITY_SERVICE); mService = new AppIntegrityManagerServiceImpl( mMockContext, mPackageManagerInternal, mRuleEvaluationEngine, mIntegrityFileManager, mFileIntegrityManager, mHandler); mSpyPackageManager = spy(mRealContext.getPackageManager()); Loading @@ -181,6 +192,7 @@ public class AppIntegrityManagerServiceImplTest { public void tearDown() throws Exception { mTestApk.delete(); mTestApkTwoCerts.delete(); mTestApkSourceStamp.delete(); } @Test Loading Loading @@ -241,7 +253,8 @@ public class AppIntegrityManagerServiceImplTest { IntentSender mockReceiver = mock(IntentSender.class); List<Rule> rules = Arrays.asList( new Rule(IntegrityFormula.Application.packageNameEquals(PACKAGE_NAME), new Rule( IntegrityFormula.Application.packageNameEquals(PACKAGE_NAME), Rule.DENY)); mService.updateRuleSet(VERSION, new ParceledListSlice<>(rules), mockReceiver); Loading @@ -261,7 +274,8 @@ public class AppIntegrityManagerServiceImplTest { IntentSender mockReceiver = mock(IntentSender.class); List<Rule> rules = Arrays.asList( new Rule(IntegrityFormula.Application.packageNameEquals(PACKAGE_NAME), new Rule( IntegrityFormula.Application.packageNameEquals(PACKAGE_NAME), Rule.DENY)); mService.updateRuleSet(VERSION, new ParceledListSlice<>(rules), mockReceiver); Loading Loading @@ -305,8 +319,7 @@ public class AppIntegrityManagerServiceImplTest { ArgumentCaptor<AppInstallMetadata> metadataCaptor = ArgumentCaptor.forClass(AppInstallMetadata.class); verify(mRuleEvaluationEngine) .evaluate(metadataCaptor.capture()); verify(mRuleEvaluationEngine).evaluate(metadataCaptor.capture()); AppInstallMetadata appInstallMetadata = metadataCaptor.getValue(); assertEquals(PACKAGE_NAME, appInstallMetadata.getPackageName()); assertThat(appInstallMetadata.getAppCertificates()).containsExactly(APP_CERT); Loading Loading @@ -341,8 +354,33 @@ public class AppIntegrityManagerServiceImplTest { ArgumentCaptor.forClass(AppInstallMetadata.class); verify(mRuleEvaluationEngine).evaluate(metadataCaptor.capture()); AppInstallMetadata appInstallMetadata = metadataCaptor.getValue(); assertThat(appInstallMetadata.getAppCertificates()).containsExactly( DUMMY_APP_TWO_CERTS_CERT_1, DUMMY_APP_TWO_CERTS_CERT_2); assertThat(appInstallMetadata.getAppCertificates()) .containsExactly(DUMMY_APP_TWO_CERTS_CERT_1, DUMMY_APP_TWO_CERTS_CERT_2); } @Test public void handleBroadcast_correctArgs_sourceStamp() throws Exception { whitelistUsAsRuleProvider(); makeUsSystemApp(); ArgumentCaptor<BroadcastReceiver> broadcastReceiverCaptor = ArgumentCaptor.forClass(BroadcastReceiver.class); verify(mMockContext) .registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any()); Intent intent = makeVerificationIntent(); intent.setDataAndType(Uri.fromFile(mTestApkSourceStamp), PACKAGE_MIME_TYPE); 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(); assertTrue(appInstallMetadata.isStampPresent()); assertTrue(appInstallMetadata.isStampVerified()); assertFalse(appInstallMetadata.isStampTrusted()); assertEquals(SOURCE_STAMP_CERTIFICATE_HASH, appInstallMetadata.getStampCertificateHash()); } @Test Loading Loading @@ -478,8 +516,8 @@ public class AppIntegrityManagerServiceImplTest { PackageInfo packageInfo = mRealContext .getPackageManager() .getPackageInfo(TEST_FRAMEWORK_PACKAGE, PackageManager.GET_SIGNING_CERTIFICATES); .getPackageInfo( TEST_FRAMEWORK_PACKAGE, PackageManager.GET_SIGNING_CERTIFICATES); doReturn(packageInfo).when(mSpyPackageManager).getPackageInfo(eq(INSTALLER), anyInt()); doReturn(1).when(mSpyPackageManager).getPackageUid(eq(INSTALLER), anyInt()); return makeVerificationIntent(INSTALLER); Loading @@ -501,10 +539,16 @@ public class AppIntegrityManagerServiceImplTest { private void setIntegrityCheckIncludesRuleProvider(boolean shouldInclude) throws Exception { int value = shouldInclude ? 1 : 0; Settings.Global.putInt(mRealContext.getContentResolver(), Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER, value); assertThat(Settings.Global.getInt(mRealContext.getContentResolver(), Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER, -1) == 1).isEqualTo( shouldInclude); Settings.Global.putInt( mRealContext.getContentResolver(), Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER, value); assertThat( Settings.Global.getInt( mRealContext.getContentResolver(), Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER, -1) == 1) .isEqualTo(shouldInclude); } }