Add some truly end to end tests.

This testing patch ensures that the app can properly fetch and provision
keys based on the production EEK. Additionally, it tests that the device
falls back to the default provisioned key in KM when there are no signed
remotely provisioned keys available, due to whatever problem may have
potentially occurred.

Bug: 189018262
Test: atest RemoteProvisionerUnitTests
Change-Id: I09b6e8d0e78c74e8f41c29f3b51ee1fdcac857fb
diff --git a/tests/unittests/src/com/android/remoteprovisioner/unittest/ServerToSystemTest.java b/tests/unittests/src/com/android/remoteprovisioner/unittest/ServerToSystemTest.java
new file mode 100644
index 0000000..e8ee610
--- /dev/null
+++ b/tests/unittests/src/com/android/remoteprovisioner/unittest/ServerToSystemTest.java
@@ -0,0 +1,178 @@
+/*
+ * Copyright (C) 2021 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 com.android.remoteprovisioner.unittest;
+
+import static android.hardware.security.keymint.SecurityLevel.TRUSTED_ENVIRONMENT;
+import static android.security.keystore.KeyProperties.KEY_ALGORITHM_EC;
+import static android.security.keystore.KeyProperties.PURPOSE_SIGN;
+
+import static org.junit.Assert.assertArrayEquals;
+import static org.junit.Assert.assertEquals;
+import static org.junit.Assert.assertFalse;
+import static org.junit.Assert.assertNotNull;
+import static org.junit.Assert.assertTrue;
+
+import android.content.Context;
+import android.os.ServiceManager;
+import android.security.keystore.KeyGenParameterSpec;
+import android.security.remoteprovisioning.AttestationPoolStatus;
+import android.security.remoteprovisioning.ImplInfo;
+import android.security.remoteprovisioning.IRemoteProvisioning;
+
+import androidx.test.core.app.ApplicationProvider;
+import androidx.test.runner.AndroidJUnit4;
+
+import com.android.remoteprovisioner.GeekResponse;
+import com.android.remoteprovisioner.Provisioner;
+import com.android.remoteprovisioner.ServerInterface;
+import com.android.remoteprovisioner.SettingsManager;
+
+import org.junit.After;
+import org.junit.Before;
+import org.junit.BeforeClass;
+import org.junit.Test;
+import org.junit.runner.RunWith;
+
+import java.security.KeyPairGenerator;
+import java.security.KeyStore;
+import java.security.cert.Certificate;
+import java.time.Duration;
+import java.util.Arrays;
+
+@RunWith(AndroidJUnit4.class)
+public class ServerToSystemTest {
+
+    private static final boolean IS_TEST_MODE = false;
+    private static final String SERVICE = "android.security.remoteprovisioning";
+
+    private static Context sContext;
+    private static IRemoteProvisioning sBinder;
+    private static int sCurve = 0;
+
+    private Duration mDuration;
+
+    private void assertPoolStatus(int total, int attested,
+                                  int unassigned, int expiring, Duration time) throws Exception {
+        AttestationPoolStatus pool = sBinder.getPoolStatus(time.toMillis(), TRUSTED_ENVIRONMENT);
+        assertEquals(total, pool.total);
+        assertEquals(attested, pool.attested);
+        assertEquals(unassigned, pool.unassigned);
+        assertEquals(expiring, pool.expiring);
+    }
+
+    private static Certificate[] generateKeyStoreKey(String alias) throws Exception {
+        KeyStore keyStore = KeyStore.getInstance("AndroidKeyStore");
+        keyStore.load(null);
+        KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_ALGORITHM_EC,
+                "AndroidKeyStore");
+        KeyGenParameterSpec spec = new KeyGenParameterSpec.Builder(alias, PURPOSE_SIGN)
+                .setAttestationChallenge("challenge".getBytes())
+                .build();
+        keyPairGenerator.initialize(spec);
+        keyPairGenerator.generateKeyPair();
+        Certificate[] certs = keyStore.getCertificateChain(spec.getKeystoreAlias());
+        keyStore.deleteEntry(alias);
+        return certs;
+    }
+
+    @BeforeClass
+    public static void init() throws Exception {
+        sContext = ApplicationProvider.getApplicationContext();
+        sBinder =
+              IRemoteProvisioning.Stub.asInterface(ServiceManager.getService(SERVICE));
+        assertNotNull(sBinder);
+        ImplInfo[] info = sBinder.getImplementationInfo();
+        for (int i = 0; i < info.length; i++) {
+            if (info[i].secLevel == TRUSTED_ENVIRONMENT) {
+                sCurve = info[i].supportedCurve;
+                break;
+            }
+        }
+    }
+
+    @Before
+    public void setUp() throws Exception {
+        SettingsManager.clearPreferences(sContext);
+        sBinder.deleteAllKeys();
+        mDuration = Duration.ofMillis(System.currentTimeMillis());
+    }
+
+    @After
+    public void tearDown() throws Exception {
+        SettingsManager.clearPreferences(sContext);
+        sBinder.deleteAllKeys();
+    }
+
+    @Test
+    public void testFullRoundTrip() throws Exception {
+        int numTestKeys = 1;
+        assertPoolStatus(0, 0, 0, 0, mDuration);
+        sBinder.generateKeyPair(IS_TEST_MODE, TRUSTED_ENVIRONMENT);
+        assertPoolStatus(numTestKeys, 0, 0, 0, mDuration);
+        GeekResponse geek = ServerInterface.fetchGeek(sContext);
+        assertNotNull(geek);
+        int numProvisioned =
+                Provisioner.provisionCerts(numTestKeys, TRUSTED_ENVIRONMENT,
+                                           geek.getGeekChain(sCurve), geek.getChallenge(), sBinder,
+                                           sContext);
+        assertEquals(numTestKeys, numProvisioned);
+        assertPoolStatus(numTestKeys, numTestKeys, numTestKeys, 0, mDuration);
+        // Certificate duration sent back from the server may change, however ~6 months should be
+        // pretty safe.
+        assertPoolStatus(numTestKeys, numTestKeys, numTestKeys,
+                         numTestKeys, mDuration.plusDays(180));
+    }
+
+    @Test
+    public void testFallback() throws Exception {
+        // Feed a fake URL into the device config to ensure that remote provisioning fails.
+        SettingsManager.setDeviceConfig(sContext, 2 /* extraKeys */, mDuration /* expiringBy */,
+                                        "Not even a URL" /* url */);
+        int numTestKeys = 1;
+        assertPoolStatus(0, 0, 0, 0, mDuration);
+        Certificate[] fallbackKeyCerts1 = generateKeyStoreKey("test1");
+
+        SettingsManager.clearPreferences(sContext);
+        sBinder.generateKeyPair(IS_TEST_MODE, TRUSTED_ENVIRONMENT);
+        GeekResponse geek = ServerInterface.fetchGeek(sContext);
+        int numProvisioned =
+                Provisioner.provisionCerts(numTestKeys, TRUSTED_ENVIRONMENT,
+                                           geek.getGeekChain(sCurve), geek.getChallenge(), sBinder,
+                                           sContext);
+        assertEquals(numTestKeys, numProvisioned);
+        assertPoolStatus(numTestKeys, numTestKeys, numTestKeys, 0, mDuration);
+        Certificate[] provisionedKeyCerts = generateKeyStoreKey("test2");
+        sBinder.deleteAllKeys();
+        sBinder.generateKeyPair(IS_TEST_MODE, TRUSTED_ENVIRONMENT);
+
+        SettingsManager.setDeviceConfig(sContext, 2 /* extraKeys */, mDuration /* expiringBy */,
+                                        "Not even a URL" /* url */);
+        // Even if there is an unsigned key hanging around, fallback should still occur.
+        Certificate[] fallbackKeyCerts2 = generateKeyStoreKey("test3");
+        assertEquals(1, SettingsManager.getFailureCounter(sContext));
+        assertTrue(fallbackKeyCerts1.length == fallbackKeyCerts2.length);
+        for (int i = 1; i < fallbackKeyCerts1.length; i++) {
+            assertArrayEquals("Cert: " + i, fallbackKeyCerts1[i].getEncoded(),
+                              fallbackKeyCerts2[i].getEncoded());
+        }
+        assertTrue(provisionedKeyCerts.length > 0);
+        // The root certificates should not match.
+        assertFalse("Provisioned and fallback attestation key root certificates match.",
+                    Arrays.equals(fallbackKeyCerts1[fallbackKeyCerts1.length - 1].getEncoded(),
+                              provisionedKeyCerts[provisionedKeyCerts.length - 1].getEncoded()));
+    }
+}