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

Commit 759dbb7c authored by Khaled Abdelmohsen's avatar Khaled Abdelmohsen Committed by Android (Google) Code Review
Browse files

Merge "Extract source stamp during app install" into rvc-dev

parents e364529e 02067c2e
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);
    }
}