Merge "Implement the InstallerAllowedByManifest rule."

This commit is contained in:
Song Pan
2020-02-13 16:22:10 +00:00
committed by Android (Google) Code Review
8 changed files with 123 additions and 116 deletions

View File

@@ -16,6 +16,10 @@
package android.content.integrity;
import android.annotation.NonNull;
import android.os.Parcel;
import android.os.Parcelable;
import java.util.Map;
/**
@@ -25,7 +29,29 @@ import java.util.Map;
*
* @hide
*/
public class InstallerAllowedByManifestFormula extends IntegrityFormula {
public class InstallerAllowedByManifestFormula extends IntegrityFormula implements Parcelable {
public static final String INSTALLER_CERTIFICATE_NOT_EVALUATED = "";
public InstallerAllowedByManifestFormula() {
}
private InstallerAllowedByManifestFormula(Parcel in) {
}
@NonNull
public static final Creator<InstallerAllowedByManifestFormula> CREATOR =
new Creator<InstallerAllowedByManifestFormula>() {
@Override
public InstallerAllowedByManifestFormula createFromParcel(Parcel in) {
return new InstallerAllowedByManifestFormula(in);
}
@Override
public InstallerAllowedByManifestFormula[] newArray(int size) {
return new InstallerAllowedByManifestFormula[size];
}
};
@Override
public int getTag() {
@@ -54,10 +80,30 @@ public class InstallerAllowedByManifestFormula extends IntegrityFormula {
private static boolean installerInAllowedInstallersFromManifest(
AppInstallMetadata appInstallMetadata,
Map<String, String> allowedInstallersAndCertificates) {
return allowedInstallersAndCertificates.containsKey(appInstallMetadata.getInstallerName())
&& appInstallMetadata.getInstallerCertificates()
.contains(
allowedInstallersAndCertificates
.get(appInstallMetadata.getInstallerName()));
String installerPackage = appInstallMetadata.getInstallerName();
if (!allowedInstallersAndCertificates.containsKey(installerPackage)) {
return false;
}
// If certificate is not specified in the manifest, we do not check it.
if (!allowedInstallersAndCertificates.get(installerPackage)
.equals(INSTALLER_CERTIFICATE_NOT_EVALUATED)) {
return appInstallMetadata.getInstallerCertificates()
.contains(
allowedInstallersAndCertificates
.get(appInstallMetadata.getInstallerName()));
}
return true;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
}
}

View File

@@ -214,6 +214,8 @@ public abstract class IntegrityFormula {
return LongAtomicFormula.CREATOR.createFromParcel(in);
case BOOLEAN_ATOMIC_FORMULA_TAG:
return BooleanAtomicFormula.CREATOR.createFromParcel(in);
case INSTALLER_ALLOWED_BY_MANIFEST_FORMULA_TAG:
return InstallerAllowedByManifestFormula.CREATOR.createFromParcel(in);
default:
throw new IllegalArgumentException("Unknown formula tag " + tag);
}

View File

@@ -16,15 +16,20 @@
package android.content.integrity;
import static android.content.integrity.InstallerAllowedByManifestFormula.INSTALLER_CERTIFICATE_NOT_EVALUATED;
import static com.google.common.truth.Truth.assertThat;
import com.google.common.collect.ImmutableMap;
import org.testng.annotations.Test;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.junit.runners.JUnit4;
import java.util.Arrays;
import java.util.Collections;
@RunWith(JUnit4.class)
public class InstallerAllowedByManifestFormulaTest {
private static final InstallerAllowedByManifestFormula
@@ -70,7 +75,7 @@ public class InstallerAllowedByManifestFormulaTest {
}
@Test
public void testFormulaMatches_certificateNotInManifest() {
public void testFormulaMatches_certificateDoesNotMatchManifest() {
AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder()
.setInstallerName("installer1")
.setInstallerCertificates(Arrays.asList("installer_cert3", "random_cert"))
@@ -92,6 +97,19 @@ public class InstallerAllowedByManifestFormulaTest {
assertThat(FORMULA.matches(appInstallMetadata)).isTrue();
}
@Test
public void testFormulaMatches_certificateNotSpecifiedInManifest() {
AppInstallMetadata appInstallMetadata = getAppInstallMetadataBuilder()
.setInstallerName("installer1")
.setInstallerCertificates(Arrays.asList("installer_cert3", "random_cert"))
.setAllowedInstallersAndCert(ImmutableMap.of(
"installer1", INSTALLER_CERTIFICATE_NOT_EVALUATED,
"installer2", "installer_cert1"
)).build();
assertThat(FORMULA.matches(appInstallMetadata)).isTrue();
}
/** Returns a builder with all fields filled with some dummy data. */
private AppInstallMetadata.Builder getAppInstallMetadataBuilder() {
return new AppInstallMetadata.Builder()

View File

@@ -23,6 +23,7 @@ import static android.content.Intent.EXTRA_PACKAGE_NAME;
import static android.content.integrity.AppIntegrityManager.EXTRA_STATUS;
import static android.content.integrity.AppIntegrityManager.STATUS_FAILURE;
import static android.content.integrity.AppIntegrityManager.STATUS_SUCCESS;
import static android.content.integrity.InstallerAllowedByManifestFormula.INSTALLER_CERTIFICATE_NOT_EVALUATED;
import static android.content.integrity.IntegrityUtils.getHexDigest;
import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
@@ -95,7 +96,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
* This string will be used as the "installer" for formula evaluation when the app is being
* installed via ADB.
*/
private static final String ADB_INSTALLER = "adb";
public static final String ADB_INSTALLER = "adb";
private static final String TAG = "AppIntegrityManagerServiceImpl";
@@ -106,8 +107,6 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
private static final String ALLOWED_INSTALLER_DELIMITER = ",";
private static final String INSTALLER_PACKAGE_CERT_DELIMITER = "\\|";
private static final String INSTALLER_CERT_NOT_APPLICABLE = "";
// Access to files inside mRulesDir is protected by mRulesLock;
private final Context mContext;
private final Handler mHandler;
@@ -282,15 +281,16 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
builder.setInstallerName(getPackageNameNormalized(installerPackageName));
builder.setInstallerCertificates(installerCertificates);
builder.setIsPreInstalled(isSystemApp(packageName));
builder.setAllowedInstallersAndCert(getAllowedInstallers(packageInfo));
AppInstallMetadata appInstallMetadata = builder.build();
Map<String, String> allowedInstallers = getAllowedInstallers(packageInfo);
Slog.i(
TAG,
"To be verified: " + appInstallMetadata + " installers " + allowedInstallers);
"To be verified: " + appInstallMetadata + " installers " + getAllowedInstallers(
packageInfo));
IntegrityCheckResult result =
mEvaluationEngine.evaluate(appInstallMetadata, allowedInstallers);
mEvaluationEngine.evaluate(appInstallMetadata);
Slog.i(
TAG,
"Integrity check result: "
@@ -449,9 +449,9 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
String packageName = getPackageNameNormalized(packageAndCert[0]);
String cert = packageAndCert[1];
packageCertMap.put(packageName, cert);
} else if (packageAndCert.length == 1
&& packageAndCert[0].equals(ADB_INSTALLER)) {
packageCertMap.put(ADB_INSTALLER, INSTALLER_CERT_NOT_APPLICABLE);
} else if (packageAndCert.length == 1) {
packageCertMap.put(getPackageNameNormalized(packageAndCert[0]),
INSTALLER_CERTIFICATE_NOT_EVALUATED);
}
}
}

View File

@@ -17,9 +17,6 @@
package com.android.server.integrity.engine;
import android.content.integrity.AppInstallMetadata;
import android.content.integrity.AtomicFormula;
import android.content.integrity.CompoundFormula;
import android.content.integrity.IntegrityFormula;
import android.content.integrity.Rule;
import android.util.Slog;
@@ -28,10 +25,8 @@ import com.android.server.integrity.IntegrityFileManager;
import com.android.server.integrity.model.IntegrityCheckResult;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Optional;
/**
* The engine used to evaluate rules against app installs.
@@ -69,16 +64,15 @@ public class RuleEvaluationEngine {
* @return result of the integrity check
*/
public IntegrityCheckResult evaluate(
AppInstallMetadata appInstallMetadata, Map<String, String> allowedInstallers) {
AppInstallMetadata appInstallMetadata) {
List<Rule> rules = loadRules(appInstallMetadata);
allowedInstallersRule(allowedInstallers).ifPresent(rules::add);
return RuleEvaluator.evaluateRules(rules, appInstallMetadata);
}
private List<Rule> loadRules(AppInstallMetadata appInstallMetadata) {
if (!mIntegrityFileManager.initialized()) {
Slog.w(TAG, "Integrity rule files are not available. Evaluating only manifest rules.");
return new ArrayList<>();
Slog.w(TAG, "Integrity rule files are not available.");
return Collections.emptyList();
}
try {
@@ -88,41 +82,4 @@ public class RuleEvaluationEngine {
return new ArrayList<>();
}
}
private static Optional<Rule> allowedInstallersRule(Map<String, String> allowedInstallers) {
if (allowedInstallers.isEmpty()) {
return Optional.empty();
}
List<IntegrityFormula> formulas = new ArrayList<>(allowedInstallers.size());
allowedInstallers.forEach(
(installer, cert) -> {
formulas.add(allowedInstallerFormula(installer, cert));
});
// We need this special case since OR-formulas require at least two operands.
IntegrityFormula allInstallersFormula =
formulas.size() == 1
? formulas.get(0)
: new CompoundFormula(CompoundFormula.OR, formulas);
return Optional.of(
new Rule(
new CompoundFormula(
CompoundFormula.NOT, Arrays.asList(allInstallersFormula)),
Rule.DENY));
}
private static IntegrityFormula allowedInstallerFormula(String installer, String cert) {
return new CompoundFormula(
CompoundFormula.AND,
Arrays.asList(
new AtomicFormula.StringAtomicFormula(
AtomicFormula.INSTALLER_NAME,
installer,
/* isHashedValue= */ false),
new AtomicFormula.StringAtomicFormula(
AtomicFormula.INSTALLER_CERTIFICATE, cert, /* isHashedValue= */
false)));
}
}

View File

@@ -19,6 +19,7 @@ package com.android.server.integrity;
import static android.content.integrity.AppIntegrityManager.EXTRA_STATUS;
import static android.content.integrity.AppIntegrityManager.STATUS_FAILURE;
import static android.content.integrity.AppIntegrityManager.STATUS_SUCCESS;
import static android.content.integrity.InstallerAllowedByManifestFormula.INSTALLER_CERTIFICATE_NOT_EVALUATED;
import static android.content.pm.PackageManager.EXTRA_VERIFICATION_ID;
import static android.content.pm.PackageManager.EXTRA_VERIFICATION_INSTALLER_PACKAGE;
import static android.content.pm.PackageManager.EXTRA_VERIFICATION_INSTALLER_UID;
@@ -77,7 +78,6 @@ import org.mockito.junit.MockitoRule;
import java.io.File;
import java.io.IOException;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
@@ -268,20 +268,16 @@ public class AppIntegrityManagerServiceImplTest {
verify(mMockContext)
.registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
Intent intent = makeVerificationIntent();
when(mRuleEvaluationEngine.evaluate(any(), any())).thenReturn(IntegrityCheckResult.allow());
when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow());
broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
runJobInHandler();
ArgumentCaptor<AppInstallMetadata> metadataCaptor =
ArgumentCaptor.forClass(AppInstallMetadata.class);
Map<String, String> allowedInstallers = new HashMap<>();
ArgumentCaptor<Map<String, String>> allowedInstallersCaptor =
ArgumentCaptor.forClass(allowedInstallers.getClass());
verify(mRuleEvaluationEngine)
.evaluate(metadataCaptor.capture(), allowedInstallersCaptor.capture());
.evaluate(metadataCaptor.capture());
AppInstallMetadata appInstallMetadata = metadataCaptor.getValue();
allowedInstallers = allowedInstallersCaptor.getValue();
assertEquals(PACKAGE_NAME, appInstallMetadata.getPackageName());
assertThat(appInstallMetadata.getAppCertificates()).containsExactly(APP_CERT);
assertEquals(INSTALLER_SHA256, appInstallMetadata.getInstallerName());
@@ -289,9 +285,11 @@ public class AppIntegrityManagerServiceImplTest {
assertEquals(VERSION_CODE, appInstallMetadata.getVersionCode());
assertFalse(appInstallMetadata.isPreInstalled());
// These are hardcoded in the test apk android manifest
Map<String, String> allowedInstallers =
appInstallMetadata.getAllowedInstallersAndCertificates();
assertEquals(2, allowedInstallers.size());
assertEquals(PLAY_STORE_CERT, allowedInstallers.get(PLAY_STORE_PKG));
assertEquals(ADB_CERT, allowedInstallers.get(ADB_INSTALLER));
assertEquals(INSTALLER_CERTIFICATE_NOT_EVALUATED, allowedInstallers.get(ADB_INSTALLER));
}
@Test
@@ -303,7 +301,7 @@ public class AppIntegrityManagerServiceImplTest {
verify(mMockContext)
.registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
Intent intent = makeVerificationIntent();
when(mRuleEvaluationEngine.evaluate(any(), any())).thenReturn(IntegrityCheckResult.allow());
when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow());
broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
runJobInHandler();
@@ -321,7 +319,7 @@ public class AppIntegrityManagerServiceImplTest {
ArgumentCaptor.forClass(BroadcastReceiver.class);
verify(mMockContext)
.registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
when(mRuleEvaluationEngine.evaluate(any(), any()))
when(mRuleEvaluationEngine.evaluate(any()))
.thenReturn(
IntegrityCheckResult.deny(
Arrays.asList(
@@ -349,7 +347,7 @@ public class AppIntegrityManagerServiceImplTest {
verify(mMockContext)
.registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
Intent intent = makeVerificationIntent();
when(mRuleEvaluationEngine.evaluate(any(), any())).thenReturn(IntegrityCheckResult.allow());
when(mRuleEvaluationEngine.evaluate(any())).thenReturn(IntegrityCheckResult.allow());
broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);
runJobInHandler();
@@ -377,7 +375,7 @@ public class AppIntegrityManagerServiceImplTest {
verify(mMockContext, atLeastOnce())
.registerReceiver(broadcastReceiverCaptor.capture(), any(), any(), any());
Intent intent = makeVerificationIntent(TEST_FRAMEWORK_PACKAGE);
when(mRuleEvaluationEngine.evaluate(any(), any()))
when(mRuleEvaluationEngine.evaluate(any()))
.thenReturn(IntegrityCheckResult.deny(/* rule= */ null));
broadcastReceiverCaptor.getValue().onReceive(mMockContext, intent);

View File

@@ -22,6 +22,8 @@ import static org.mockito.ArgumentMatchers.any;
import static org.mockito.Mockito.when;
import android.content.integrity.AppInstallMetadata;
import android.content.integrity.IntegrityFormula;
import android.content.integrity.Rule;
import com.android.server.integrity.IntegrityFileManager;
import com.android.server.integrity.model.IntegrityCheckResult;
@@ -33,7 +35,6 @@ import org.junit.runners.JUnit4;
import org.mockito.Mock;
import org.mockito.MockitoAnnotations;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.Map;
@@ -60,13 +61,14 @@ public class RuleEvaluationEngineTest {
mEngine = new RuleEvaluationEngine(mIntegrityFileManager);
when(mIntegrityFileManager.readRules(any())).thenReturn(new ArrayList<>());
when(mIntegrityFileManager.readRules(any())).thenReturn(Collections.singletonList(new Rule(
IntegrityFormula.Installer.notAllowedByManifest(), Rule.DENY)));
when(mIntegrityFileManager.initialized()).thenReturn(true);
}
@Test
public void testAllowedInstallers_empty() {
Map<String, String> allowedInstallers = Collections.emptyMap();
AppInstallMetadata appInstallMetadata1 =
getAppInstallMetadataBuilder()
.setInstallerName(INSTALLER_1)
@@ -83,11 +85,11 @@ public class RuleEvaluationEngineTest {
.setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT))
.build();
assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect())
assertThat(mEngine.evaluate(appInstallMetadata1).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.ALLOW);
assertThat(mEngine.evaluate(appInstallMetadata2, allowedInstallers).getEffect())
assertThat(mEngine.evaluate(appInstallMetadata2).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.ALLOW);
assertThat(mEngine.evaluate(appInstallMetadata3, allowedInstallers).getEffect())
assertThat(mEngine.evaluate(appInstallMetadata3).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.ALLOW);
}
@@ -100,32 +102,36 @@ public class RuleEvaluationEngineTest {
getAppInstallMetadataBuilder()
.setInstallerName(INSTALLER_1)
.setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
.setAllowedInstallersAndCert(allowedInstallers)
.build();
assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect())
assertThat(mEngine.evaluate(appInstallMetadata1).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.ALLOW);
AppInstallMetadata appInstallMetadata2 =
getAppInstallMetadataBuilder()
.setInstallerName(RANDOM_INSTALLER)
.setAllowedInstallersAndCert(allowedInstallers)
.setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
.build();
assertThat(mEngine.evaluate(appInstallMetadata2, allowedInstallers).getEffect())
assertThat(mEngine.evaluate(appInstallMetadata2).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.DENY);
AppInstallMetadata appInstallMetadata3 =
getAppInstallMetadataBuilder()
.setInstallerName(INSTALLER_1)
.setAllowedInstallersAndCert(allowedInstallers)
.setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT))
.build();
assertThat(mEngine.evaluate(appInstallMetadata3, allowedInstallers).getEffect())
assertThat(mEngine.evaluate(appInstallMetadata3).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.DENY);
AppInstallMetadata appInstallMetadata4 =
getAppInstallMetadataBuilder()
.setInstallerName(INSTALLER_1)
.setAllowedInstallersAndCert(allowedInstallers)
.setInstallerCertificates(Collections.singletonList(RANDOM_INSTALLER_CERT))
.build();
assertThat(mEngine.evaluate(appInstallMetadata4, allowedInstallers).getEffect())
assertThat(mEngine.evaluate(appInstallMetadata4).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.DENY);
}
@@ -138,57 +144,37 @@ public class RuleEvaluationEngineTest {
AppInstallMetadata appInstallMetadata1 =
getAppInstallMetadataBuilder()
.setInstallerName(INSTALLER_1)
.setAllowedInstallersAndCert(allowedInstallers)
.setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
.build();
assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect())
assertThat(mEngine.evaluate(appInstallMetadata1).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.ALLOW);
AppInstallMetadata appInstallMetadata2 =
getAppInstallMetadataBuilder()
.setInstallerName(INSTALLER_2)
.setAllowedInstallersAndCert(allowedInstallers)
.setInstallerCertificates(Collections.singletonList(INSTALLER_2_CERT))
.build();
assertThat(mEngine.evaluate(appInstallMetadata2, allowedInstallers).getEffect())
assertThat(mEngine.evaluate(appInstallMetadata2).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.ALLOW);
AppInstallMetadata appInstallMetadata3 =
getAppInstallMetadataBuilder()
.setInstallerName(INSTALLER_1)
.setAllowedInstallersAndCert(allowedInstallers)
.setInstallerCertificates(Collections.singletonList(INSTALLER_2_CERT))
.build();
assertThat(mEngine.evaluate(appInstallMetadata3, allowedInstallers).getEffect())
assertThat(mEngine.evaluate(appInstallMetadata3).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.DENY);
AppInstallMetadata appInstallMetadata4 =
getAppInstallMetadataBuilder()
.setInstallerName(INSTALLER_2)
.setAllowedInstallersAndCert(allowedInstallers)
.setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
.build();
assertThat(mEngine.evaluate(appInstallMetadata4, allowedInstallers).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.DENY);
}
@Test
public void manifestBasedRuleEvaluationWorksEvenWhenIntegrityFilesAreUnavailable() {
when(mIntegrityFileManager.initialized()).thenReturn(false);
Map<String, String> allowedInstallers =
Collections.singletonMap(INSTALLER_1, INSTALLER_1_CERT);
AppInstallMetadata appInstallMetadata1 =
getAppInstallMetadataBuilder()
.setInstallerName(INSTALLER_1)
.setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
.build();
assertThat(mEngine.evaluate(appInstallMetadata1, allowedInstallers).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.ALLOW);
AppInstallMetadata appInstallMetadata2 =
getAppInstallMetadataBuilder()
.setInstallerName(RANDOM_INSTALLER)
.setInstallerCertificates(Collections.singletonList(INSTALLER_1_CERT))
.build();
assertThat(mEngine.evaluate(appInstallMetadata2, allowedInstallers).getEffect())
assertThat(mEngine.evaluate(appInstallMetadata4).getEffect())
.isEqualTo(IntegrityCheckResult.Effect.DENY);
}

View File

@@ -22,7 +22,7 @@
<uses-sdk android:minSdkVersion="14" android:targetSdkVersion="28" />
<application android:hasCode="false">
<meta-data android:name="allowed-installers" android:value="com.android.vending|play_store_cert,adb|"/>
<meta-data android:name="allowed-installers" android:value="com.android.vending|play_store_cert,adb"/>
</application>
</manifest>