Merge "Extract source stamp during app install" into rvc-dev am: 759dbb7cf6 am: 08d82e4c1a

Change-Id: I5fa9a0af005922f2831de1aaa658a59dd7b3ccec
This commit is contained in:
Automerger Merge Worker
2020-03-05 00:05:06 +00:00
5 changed files with 199 additions and 59 deletions

View File

@@ -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#getStampCertificateHash()
* @see AppInstallMetadata#isStampPresent()
*/
@NonNull
public Builder setStampCertificateHash(@NonNull String stampCertificateHash) {
this.mStampCertificateHash = Objects.requireNonNull(stampCertificateHash);
public Builder setIsStampPresent(boolean isStampPresent) {
this.mIsStampPresent = isStampPresent;
return this;
}
/**
* Set whether the stamp embedded in the APK is verified or not.
*
* @see AppInstallMetadata#isStampVerified()
*/
@NonNull
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}.
*

View File

@@ -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) {

View File

@@ -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: "
@@ -323,7 +336,7 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
* Verify the UID and return the installer package name.
*
* @return the package name of the installer, or null if it cannot be determined or it is
* installed via adb.
* installed via adb.
*/
@Nullable
private String getInstallerPackageName(Intent intent) {
@@ -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;
@@ -633,9 +690,9 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub {
private boolean integrityCheckIncludesRuleProvider() {
return Settings.Global.getInt(
mContext.getContentResolver(),
Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER,
0)
mContext.getContentResolver(),
Settings.Global.INTEGRITY_CHECK_INCLUDES_RULE_PROVIDER,
0)
== 1;
}
}

View File

@@ -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();
@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;
@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,19 +160,28 @@ 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());
// setup mocks to prevent NPE
when(mMockContext.getPackageManager()).thenReturn(mSpyPackageManager);
when(mMockContext.getResources()).thenReturn(mMockResources);
when(mMockResources.getStringArray(anyInt())).thenReturn(new String[]{});
when(mMockResources.getStringArray(anyInt())).thenReturn(new String[] {});
when(mIntegrityFileManager.initialized()).thenReturn(true);
// These are needed to override the Settings.Global.get result.
when(mMockContext.getContentResolver()).thenReturn(mRealContext.getContentResolver());
@@ -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
@@ -445,7 +483,7 @@ public class AppIntegrityManagerServiceImplTest {
private void whitelistUsAsRuleProvider() {
Resources mockResources = mock(Resources.class);
when(mockResources.getStringArray(R.array.config_integrityRuleProviderPackages))
.thenReturn(new String[]{TEST_FRAMEWORK_PACKAGE});
.thenReturn(new String[] {TEST_FRAMEWORK_PACKAGE});
when(mMockContext.getResources()).thenReturn(mockResources);
}
@@ -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);
}
}