From f6b1e8c3f637dcedce2cf1b11cdb3c32a7440c76 Mon Sep 17 00:00:00 2001 From: Khaled Abdelmohsen Date: Thu, 12 Mar 2020 22:14:49 +0000 Subject: [PATCH] Support multi apk stamp verification Bug: 148005911 Test: atest FrameworksCoreTests:SourceStampVerifierTest Change-Id: Iffab565a03ae57c469784baa12bec9cd130e69a1 --- .../android/util/apk/SourceStampVerifier.java | 19 ++++++++ .../util/apk/SourceStampVerifierTest.java | 44 +++++++++++++++++++ .../AppIntegrityManagerServiceImpl.java | 22 +++++++++- 3 files changed, 83 insertions(+), 2 deletions(-) diff --git a/core/java/android/util/apk/SourceStampVerifier.java b/core/java/android/util/apk/SourceStampVerifier.java index 70e4a51bc07a8..c78baf5b7c530 100644 --- a/core/java/android/util/apk/SourceStampVerifier.java +++ b/core/java/android/util/apk/SourceStampVerifier.java @@ -43,6 +43,7 @@ import java.security.NoSuchAlgorithmException; import java.security.PublicKey; import java.security.Signature; import java.security.SignatureException; +import java.security.cert.Certificate; import java.security.cert.CertificateException; import java.security.cert.CertificateFactory; import java.security.cert.X509Certificate; @@ -78,6 +79,24 @@ public abstract class SourceStampVerifier { /** Hidden constructor to prevent instantiation. */ private SourceStampVerifier() {} + /** Verifies SourceStamp present in a list of APKs. */ + public static SourceStampVerificationResult verify(List apkFiles) { + Certificate stampCertificate = null; + for (String apkFile : apkFiles) { + SourceStampVerificationResult sourceStampVerificationResult = verify(apkFile); + if (!sourceStampVerificationResult.isPresent() + || !sourceStampVerificationResult.isVerified()) { + return sourceStampVerificationResult; + } + if (stampCertificate != null + && !stampCertificate.equals(sourceStampVerificationResult.getCertificate())) { + return SourceStampVerificationResult.notVerified(); + } + stampCertificate = sourceStampVerificationResult.getCertificate(); + } + return SourceStampVerificationResult.verified(stampCertificate); + } + /** Verifies SourceStamp present in the provided APK. */ public static SourceStampVerificationResult verify(String apkFile) { try (RandomAccessFile apk = new RandomAccessFile(apkFile, "r")) { diff --git a/core/tests/coretests/src/android/util/apk/SourceStampVerifierTest.java b/core/tests/coretests/src/android/util/apk/SourceStampVerifierTest.java index 44f64079d8313..37b2817928aa6 100644 --- a/core/tests/coretests/src/android/util/apk/SourceStampVerifierTest.java +++ b/core/tests/coretests/src/android/util/apk/SourceStampVerifierTest.java @@ -37,6 +37,8 @@ import java.io.IOException; import java.io.InputStream; import java.nio.file.Files; import java.security.MessageDigest; +import java.util.ArrayList; +import java.util.List; import java.util.zip.ZipEntry; import java.util.zip.ZipFile; @@ -129,6 +131,48 @@ public class SourceStampVerifierTest { assertNull(result.getCertificate()); } + @Test + public void testSourceStamp_multiApk_validStamps() throws Exception { + File testApk1 = getApk("SourceStampVerifierTest/valid-stamp.apk"); + File testApk2 = getApk("SourceStampVerifierTest/valid-stamp.apk"); + ZipFile apkZipFile = new ZipFile(testApk1); + ZipEntry stampCertZipEntry = apkZipFile.getEntry("stamp-cert-sha256"); + int size = (int) stampCertZipEntry.getSize(); + byte[] expectedStampCertHash = new byte[size]; + try (InputStream inputStream = apkZipFile.getInputStream(stampCertZipEntry)) { + inputStream.read(expectedStampCertHash); + } + List apkFiles = new ArrayList<>(); + apkFiles.add(testApk1.getAbsolutePath()); + apkFiles.add(testApk2.getAbsolutePath()); + + SourceStampVerificationResult result = + SourceStampVerifier.verify(apkFiles); + + assertTrue(result.isPresent()); + assertTrue(result.isVerified()); + assertNotNull(result.getCertificate()); + byte[] actualStampCertHash = + MessageDigest.getInstance("SHA-256").digest(result.getCertificate().getEncoded()); + assertArrayEquals(expectedStampCertHash, actualStampCertHash); + } + + @Test + public void testSourceStamp_multiApk_invalidStamps() throws Exception { + File testApk1 = getApk("SourceStampVerifierTest/valid-stamp.apk"); + File testApk2 = getApk("SourceStampVerifierTest/stamp-apk-hash-mismatch.apk"); + List apkFiles = new ArrayList<>(); + apkFiles.add(testApk1.getAbsolutePath()); + apkFiles.add(testApk2.getAbsolutePath()); + + SourceStampVerificationResult result = + SourceStampVerifier.verify(apkFiles); + + assertTrue(result.isPresent()); + assertFalse(result.isVerified()); + assertNull(result.getCertificate()); + } + private File getApk(String apkPath) throws IOException { File testApk = File.createTempFile("SourceStampApk", ".apk"); try (InputStream inputStream = mContext.getAssets().open(apkPath)) { diff --git a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java index 6da0de13f6234..c2d1364c9a01d 100644 --- a/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java +++ b/services/core/java/com/android/server/integrity/AppIntegrityManagerServiceImpl.java @@ -69,8 +69,10 @@ import com.android.server.pm.parsing.pkg.ParsedPackage; import java.io.ByteArrayInputStream; import java.io.File; +import java.io.IOException; import java.io.InputStream; import java.nio.charset.StandardCharsets; +import java.nio.file.Files; import java.security.MessageDigest; import java.security.NoSuchAlgorithmException; import java.security.cert.CertificateEncodingException; @@ -85,6 +87,7 @@ import java.util.HashSet; import java.util.List; import java.util.Map; import java.util.Set; +import java.util.stream.Collectors; /** Implementation of {@link AppIntegrityManagerService}. */ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { @@ -467,8 +470,23 @@ public class AppIntegrityManagerServiceImpl extends IAppIntegrityManager.Stub { if (installationPath == null) { throw new IllegalArgumentException("Installation path is null, package not found"); } - SourceStampVerificationResult sourceStampVerificationResult = - SourceStampVerifier.verify(installationPath.getAbsolutePath()); + + SourceStampVerificationResult sourceStampVerificationResult; + if (installationPath.isDirectory()) { + try { + List apkFiles = + Files.list(installationPath.toPath()) + .map(path -> path.toAbsolutePath().toString()) + .collect(Collectors.toList()); + sourceStampVerificationResult = SourceStampVerifier.verify(apkFiles); + } catch (IOException e) { + throw new IllegalArgumentException("Could not read APK directory"); + } + } else { + sourceStampVerificationResult = + SourceStampVerifier.verify(installationPath.getAbsolutePath()); + } + appInstallMetadata.setIsStampPresent(sourceStampVerificationResult.isPresent()); appInstallMetadata.setIsStampVerified(sourceStampVerificationResult.isVerified()); // A verified stamp is set to be trusted.