Add some truly end to end tests. am: da6a3d8ba9

Original change: https://android-review.googlesource.com/c/platform/packages/apps/RemoteProvisioner/+/1758274

Change-Id: Icef63f2baa0d02e78bdf9142f5a013472a4c6eca
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()));
+    }
+}