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);
+    }
+}