Merge "Add Host-side test to check apex do not signed with well-known keys"
diff --git a/hostsidetests/appsecurity/OWNERS b/hostsidetests/appsecurity/OWNERS
index ea12403..159937f 100644
--- a/hostsidetests/appsecurity/OWNERS
+++ b/hostsidetests/appsecurity/OWNERS
@@ -2,6 +2,7 @@
toddke@google.com
per-file AccessSerialNumberTest.java = moltmann@google.com
per-file AdoptableHostTest.java = jsharkey@google.com
+per-file ApexSignatureVerificationTest.java = dariofreni@google.com
per-file ApplicationVisibilityTest.java = toddke@google.com
per-file AppSecurityTests.java = cbrubaker@google.com
per-file AuthBoundKeyTest.java = cbrubaker@google.com
diff --git a/hostsidetests/appsecurity/res/apexsigverify/README.md b/hostsidetests/appsecurity/res/apexsigverify/README.md
new file mode 100644
index 0000000..c3842e1
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/README.md
@@ -0,0 +1,24 @@
+# Cts Well known key test
+
+## AOSP Well known Key source path
+
+art/build/apex/com.android.runtime.avbpubkey
+external/conscrypt/apex/com.android.conscrypt.avbpubkey
+frameworks/av/apex/com.android.media.swcodec.avbpubkey
+frameworks/av/apex/com.android.media.avbpubkey
+system/apex/apexd/apexd_testdata/com.android.apex.test_package_2.avbpubkey
+system/apex/apexd/apexd_testdata/com.android.apex.test_package.no_inst_key.avbpubkey
+system/apex/apexd/apexd_testdata/com.android.apex.test_package.prepostinstall.fail.avbpubkey
+system/apex/apexd/apexd_testdata/com.android.apex.test_package.postinstall.avbpubkey
+system/apex/apexd/apexd_testdata/com.android.apex.test_package.preinstall.avbpubkey
+system/apex/apexd/apexd_testdata/com.android.apex.test_package.avbpubkey
+system/apex/tests/testdata/com.android.apex.test.avbpubkey
+system/apex/apexer/testdata/com.android.example.apex.avbpubkey
+system/apex/apexer/etc/com.android.support.apexer.avbpubkey
+system/timezone/apex/com.android.tzdata.avbpubkey
+system/netd/apex/com.android.resolv.avbpubkey
+system/core/rootdir/avb/s-gsi.avbpubkey
+system/core/rootdir/avb/q-gsi.avbpubkey
+system/core/rootdir/avb/r-gsi.avbpubkey
+
+## Updating public keys
\ No newline at end of file
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.test.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.test.avbpubkey
new file mode 100644
index 0000000..28bc8f7
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.test.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.test_package.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.test_package.avbpubkey
new file mode 100644
index 0000000..92767ea
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.test_package.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.test_package.no_inst_key.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.test_package.no_inst_key.avbpubkey
new file mode 100644
index 0000000..ea34cf3
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.test_package.no_inst_key.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.test_package.postinstall.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.test_package.postinstall.avbpubkey
new file mode 100644
index 0000000..97f26a2
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.test_package.postinstall.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.test_package.preinstall.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.test_package.preinstall.avbpubkey
new file mode 100644
index 0000000..f3593c4
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.test_package.preinstall.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.test_package.prepostinstall.fail.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.test_package.prepostinstall.fail.avbpubkey
new file mode 100644
index 0000000..fe7c4b3
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.test_package.prepostinstall.fail.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.test_package_2.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.test_package_2.avbpubkey
new file mode 100644
index 0000000..92767ea
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.apex.test_package_2.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.conscrypt.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.conscrypt.avbpubkey
new file mode 100644
index 0000000..5ce0fbd
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.conscrypt.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.example.apex.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.example.apex.avbpubkey
new file mode 100644
index 0000000..28bc8f7
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.example.apex.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.media.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.media.avbpubkey
new file mode 100644
index 0000000..c0c8fd3
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.media.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.resolv.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.resolv.avbpubkey
new file mode 100644
index 0000000..e0af34c
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.resolv.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.runtime.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.runtime.avbpubkey
new file mode 100644
index 0000000..b0ffc9b
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.runtime.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.support.apexer.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.support.apexer.avbpubkey
new file mode 100644
index 0000000..6113bba
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.support.apexer.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/com.android.tzdata.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/com.android.tzdata.avbpubkey
new file mode 100644
index 0000000..932091d
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/com.android.tzdata.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/q-gsi.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/q-gsi.avbpubkey
new file mode 100644
index 0000000..5ed7543
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/q-gsi.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/r-gsi.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/r-gsi.avbpubkey
new file mode 100644
index 0000000..2609b30
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/r-gsi.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/res/apexsigverify/s-gsi.avbpubkey b/hostsidetests/appsecurity/res/apexsigverify/s-gsi.avbpubkey
new file mode 100644
index 0000000..9065fb8
--- /dev/null
+++ b/hostsidetests/appsecurity/res/apexsigverify/s-gsi.avbpubkey
Binary files differ
diff --git a/hostsidetests/appsecurity/src/android/appsecurity/cts/ApexSignatureVerificationTest.java b/hostsidetests/appsecurity/src/android/appsecurity/cts/ApexSignatureVerificationTest.java
new file mode 100644
index 0000000..535899b
--- /dev/null
+++ b/hostsidetests/appsecurity/src/android/appsecurity/cts/ApexSignatureVerificationTest.java
@@ -0,0 +1,290 @@
+/*
+ * Copyright (C) 2019 The Android Open Source Project
+ *
+ * Licensed under the Apache License, Version 2.0 (the "License");
+ * you may not use this file except in compliance with the License.
+ * You may obtain a copy of the License at
+ *
+ * http://www.apache.org/licenses/LICENSE-2.0
+ *
+ * Unless required by applicable law or agreed to in writing, software
+ * distributed under the License is distributed on an "AS IS" BASIS,
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
+ * See the License for the specific language governing permissions and
+ * limitations under the License.
+ */
+
+package android.appsecurity.cts;
+
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.Truth.assertWithMessage;
+
+import com.android.tradefed.device.DeviceNotAvailableException;
+import com.android.tradefed.device.ITestDevice;
+import com.android.tradefed.testtype.DeviceJUnit4ClassRunner;
+import com.android.tradefed.testtype.junit4.BaseHostJUnit4Test;
+import com.android.tradefed.util.FileUtil;
+import com.android.tradefed.util.ZipUtil;
+
+import org.junit.AfterClass;
+import org.junit.Before;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TestRule;
+import org.junit.runner.Description;
+import org.junit.runner.RunWith;
+import org.junit.runners.model.Statement;
+
+import java.io.File;
+import java.io.IOException;
+import java.io.InputStream;
+import java.net.URISyntaxException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Enumeration;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.List;
+import java.util.Map;
+import java.util.Set;
+import java.util.regex.Pattern;
+import java.util.zip.ZipEntry;
+import java.util.zip.ZipException;
+import java.util.zip.ZipFile;
+
+/**
+ * Tests for APEX signature verification to ensure preloaded APEXes
+ * DO NOT signed with well-known keys.
+ */
+@RunWith(DeviceJUnit4ClassRunner.class)
+public class ApexSignatureVerificationTest extends BaseHostJUnit4Test {
+
+ private static final String TEST_BASE = "ApexSignatureVerificationTest";
+ private static final String TEST_APEX_SOURCE_DIR_PREFIX = "tests-apex_";
+ private static final String APEX_PUB_KEY_NAME = "apex_pubkey";
+
+ private static final Pattern WELL_KNOWN_PUBKEY_PATTERN = Pattern.compile(
+ "^apexsigverify\\/.*.avbpubkey");
+
+ private static boolean mHasTestFailure;
+
+ private static File mBasePath;
+ private static File mWellKnownKeyStorePath;
+ private static File mArchiveZip;
+
+ private static Map<String, String> mPreloadedApexPathMap = new HashMap<>();
+ private static Map<String, File> mLocalApexFileMap = new HashMap<>();
+ private static Map<String, File> mExtractedTestDirMap = new HashMap<>();
+ private static List<File> mWellKnownKeyFileList = new ArrayList<>();
+ private ITestDevice mDevice;
+
+ @Before
+ public void setUp() throws Exception {
+ mDevice = getDevice();
+ if (mBasePath == null && mWellKnownKeyStorePath == null
+ && mExtractedTestDirMap.size() == 0) {
+ mBasePath = FileUtil.createTempDir(TEST_BASE);
+ mBasePath.deleteOnExit();
+ mWellKnownKeyStorePath = FileUtil.createTempDir("wellknownsignatures", mBasePath);
+ mWellKnownKeyStorePath.deleteOnExit();
+ pullWellKnownSignatures();
+ getApexPackageList();
+ pullApexFiles();
+ extractApexFiles();
+ }
+ }
+
+ @AfterClass
+ public static void tearDownClass() throws IOException {
+ if (mArchiveZip == null && mHasTestFailure) {
+ // Archive all operation data and materials in host
+ // /tmp/ApexSignatureVerificationTest.zip
+ // in case the test result is not expected and need to debug.
+ mArchiveZip = ZipUtil.createZip(mBasePath, mBasePath.getName());
+ }
+ }
+
+ @Rule
+ public final OnFailureRule mDumpOnFailureRule = new OnFailureRule() {
+ @Override
+ protected void onTestFailure(Statement base, Description description, Throwable t) {
+ mHasTestFailure = true;
+ }
+ };
+
+ @Test
+ public void testApexIncludePubKey() {
+ for (Map.Entry<String, File> entry : mExtractedTestDirMap.entrySet()) {
+ final File pubKeyFile = FileUtil.findFile(entry.getValue(), APEX_PUB_KEY_NAME);
+
+ assertWithMessage("apex:" + entry.getKey() + " do not contain pubkey").that(
+ pubKeyFile.exists()).isTrue();
+ }
+ }
+
+ @Test
+ public void testApexPubKeyIsNotWellKnownKey() {
+ assertThat(mWellKnownKeyFileList).isNotNull();
+
+ for (Map.Entry<String, File> entry : mExtractedTestDirMap.entrySet()) {
+ final File pubKeyFile = FileUtil.findFile(entry.getValue(), APEX_PUB_KEY_NAME);
+ final Iterator it = mWellKnownKeyFileList.iterator();
+
+ assertThat(pubKeyFile).isNotNull();
+
+ while (it.hasNext()) {
+ final File wellKnownKey = (File) it.next();
+
+ try {
+ assertWithMessage("Well-known key:" + wellKnownKey.getName() + ", match apex:"
+ + entry.getKey()).that(
+ FileUtil.compareFileContents(pubKeyFile, wellKnownKey)).isFalse();
+ } catch (IOException e) {
+ throw new AssertionError("compareFileContents IOException" + e);
+ }
+ }
+ }
+ }
+
+ @Ignore
+ @Test
+ public void testApexPubKeyMatchPayloadImg() {
+ // TODO(b/142919428): Need more investigation to find a way verify apex_paylaod.img
+ // was signed by apex_pubkey
+ }
+
+ private void extractApexFiles() {
+ final String subFilesFilter = "\\w+.*";
+
+ try {
+ for (Map.Entry<String, File> entry : mLocalApexFileMap.entrySet()) {
+ final String testSrcDirPath = TEST_APEX_SOURCE_DIR_PREFIX + entry.getKey();
+ File apexDir = FileUtil.createTempDir(testSrcDirPath, mBasePath);
+ apexDir.deleteOnExit();
+ ZipUtil.extractZip(new ZipFile(entry.getValue()), apexDir);
+
+ assertThat(apexDir).isNotNull();
+
+ mExtractedTestDirMap.put(entry.getKey(), apexDir);
+
+ assertThat(FileUtil.findFiles(apexDir, subFilesFilter)).isNotNull();
+ }
+ } catch (IOException e) {
+ throw new AssertionError("extractApexFile IOException" + e);
+ }
+ }
+
+ private void getApexPackageList() {
+ Set<ITestDevice.ApexInfo> apexes;
+ try {
+ apexes = mDevice.getActiveApexes();
+ for (ITestDevice.ApexInfo ap : apexes) {
+ mPreloadedApexPathMap.put(ap.name, ap.sourceDir);
+ }
+
+ assertThat(mPreloadedApexPathMap.size()).isAtLeast(0);
+ } catch (DeviceNotAvailableException e) {
+ throw new AssertionError("getApexPackageList DeviceNotAvailableException" + e);
+ }
+ }
+
+ private static Collection<String> getResourcesFromJarFile(final File file,
+ final Pattern pattern) {
+ final ArrayList<String> candidateList = new ArrayList<>();
+ ZipFile zf;
+ try {
+ zf = new ZipFile(file);
+ assertThat(zf).isNotNull();
+ } catch (final ZipException e) {
+ throw new AssertionError("Query Jar file ZipException" + e);
+ } catch (final IOException e) {
+ throw new AssertionError("Query Jar file IOException" + e);
+ }
+ final Enumeration e = zf.entries();
+ while (e.hasMoreElements()) {
+ final ZipEntry ze = (ZipEntry) e.nextElement();
+ final String fileName = ze.getName();
+ final boolean isMatch = pattern.matcher(fileName).matches();
+ if (isMatch) {
+ candidateList.add(fileName);
+ }
+ }
+ try {
+ zf.close();
+ } catch (final IOException e1) {
+ }
+ return candidateList;
+ }
+
+ private void pullApexFiles() {
+ try {
+ for (Map.Entry<String, String> entry : mPreloadedApexPathMap.entrySet()) {
+ final File localTempFile = File.createTempFile(entry.getKey(), "", mBasePath);
+
+ assertThat(localTempFile).isNotNull();
+ assertThat(mDevice.pullFile(entry.getValue(), localTempFile)).isTrue();
+
+ mLocalApexFileMap.put(entry.getKey(), localTempFile);
+ }
+ } catch (DeviceNotAvailableException e) {
+ throw new AssertionError("pullApexFile DeviceNotAvailableException" + e);
+ } catch (IOException e) {
+ throw new AssertionError("pullApexFile IOException" + e);
+ }
+ }
+
+ private void pullWellKnownSignatures() {
+ final Collection<String> keyPath;
+
+ try {
+ File jarFile = new File(
+ this.getClass().getProtectionDomain().getCodeSource().getLocation().toURI());
+ keyPath = getResourcesFromJarFile(jarFile, WELL_KNOWN_PUBKEY_PATTERN);
+
+ assertThat(keyPath).isNotNull();
+ } catch (URISyntaxException e) {
+ throw new AssertionError("Iterate well-known key name from jar IOException" + e);
+ }
+
+ Iterator<String> keyIterator = keyPath.iterator();
+ while (keyIterator.hasNext()) {
+ final String tmpKeyPath = keyIterator.next();
+ final String keyFileName = tmpKeyPath.substring(tmpKeyPath.lastIndexOf("/"));
+ File outFile;
+ try (InputStream in = getClass().getResourceAsStream("/" + tmpKeyPath)) {
+ outFile = File.createTempFile(keyFileName, "", mWellKnownKeyStorePath);
+ mWellKnownKeyFileList.add(outFile);
+ FileUtil.writeToFile(in, outFile);
+ } catch (IOException e) {
+ throw new AssertionError("Copy well-known keys to tmp IOException" + e);
+ }
+ }
+ }
+
+ /**
+ * Custom JUnit4 rule that provides a callback upon test failures.
+ */
+ public abstract class OnFailureRule implements TestRule {
+ public OnFailureRule() {
+ }
+
+ @Override
+ public Statement apply(Statement base, Description description) {
+ return new Statement() {
+
+ @Override
+ public void evaluate() throws Throwable {
+ try {
+ base.evaluate();
+ } catch (Throwable t) {
+ onTestFailure(base, description, t);
+ throw t;
+ }
+ }
+ };
+ }
+
+ protected abstract void onTestFailure(Statement base, Description description, Throwable t);
+ }
+}