Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit bdf2a66e authored by Automerger Merge Worker's avatar Automerger Merge Worker
Browse files

Merge "Extract source stamp during app install" into rvc-dev am: 759dbb7c

Change-Id: Ie239532d1387a52e47eca2cbf39c1a79ea53ce4c
parents 4edce1ab 759dbb7c
Loading
Loading
Loading
Loading
+48 −8
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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;
@@ -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);
    }
@@ -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;
@@ -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;
        }

@@ -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}.
         *
+3 −4
Original line number Diff line number Diff line
@@ -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) {
+74 −17
Original line number Diff line number Diff line
@@ -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;
@@ -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;
@@ -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) {
@@ -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());
    }

@@ -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();
@@ -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);
@@ -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);
@@ -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: "
@@ -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);
                    }
                }
@@ -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;

@@ -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;
+74 −30
Original line number Diff line number Diff line
@@ -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;
@@ -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;

@@ -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";
@@ -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";
@@ -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;
@@ -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());
@@ -181,6 +192,7 @@ public class AppIntegrityManagerServiceImplTest {
    public void tearDown() throws Exception {
        mTestApk.delete();
        mTestApkTwoCerts.delete();
        mTestApkSourceStamp.delete();
    }

    @Test
@@ -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);
@@ -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);
@@ -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);
@@ -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
@@ -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);
@@ -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);
    }
}