Smoke tests for VM instance secrets

Different instances should have different secrets but within an instance
it should get the same secrets. This should from the very start
of the VM and continue to be true after things have been loaded in the
VM. These tests checks that the microdroid payloads continue to follow
the expectations.

There is a slight chance for the test to fail by chance, but it is
cryptographically insignificant by design.

Bug: 214231981
Test: atest MicrodroidTestApp
Change-Id: I488c7370ad901a73a520bc3fa548d4b9b7959d19
diff --git a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
index 208d61f..f15036c 100644
--- a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
+++ b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
@@ -24,4 +24,7 @@
 
     /* read a system property. */
     String readProperty(String prop);
+
+    /* get the VM's stable secret. */
+    byte[] insecurelyExposeSecret();
 }
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index 4cca538..40d72fe 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -10,6 +10,7 @@
         "androidx.test.runner",
         "androidx.test.ext.junit",
         "com.android.microdroid.testservice-java",
+        "truth-prebuilt",
     ],
     libs: ["android.system.virtualmachine"],
     jni_libs: ["MicrodroidTestNativeLib"],
@@ -22,6 +23,7 @@
     name: "MicrodroidTestNativeLib",
     srcs: ["src/native/testbinary.cpp"],
     shared_libs: [
+        "android.security.dice-ndk",
         "android.system.virtualmachineservice-ndk",
         "com.android.microdroid.testservice-ndk",
         "libbase",
diff --git a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
index bd44a3c..803bdc6 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -15,14 +15,14 @@
  */
 package com.android.microdroid.test;
 
-import static org.hamcrest.core.Is.is;
-import static org.hamcrest.core.IsNot.not;
+import static com.google.common.truth.Truth.assertThat;
+import static com.google.common.truth.TruthJUnit.assume;
+
 import static org.junit.Assert.assertEquals;
 import static org.junit.Assert.assertFalse;
 import static org.junit.Assert.assertTrue;
 import static org.junit.Assert.fail;
 import static org.junit.Assume.assumeNoException;
-import static org.junit.Assume.assumeThat;
 
 import static java.nio.file.StandardCopyOption.REPLACE_EXISTING;
 
@@ -52,6 +52,7 @@
 import java.io.File;
 import java.io.IOException;
 import java.nio.file.Files;
+import java.util.concurrent.CompletableFuture;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
 import java.util.concurrent.Future;
@@ -212,8 +213,10 @@
     @Test
     public void changingDebugLevelInvalidatesVmIdentity()
             throws VirtualMachineException, InterruptedException, IOException {
-        assumeThat("Skip on Cuttlefish. b/195765441",
-                android.os.Build.DEVICE, is(not("vsoc_x86_64")));
+        assume()
+            .withMessage("Skip on Cuttlefish. b/195765441")
+            .that(android.os.Build.DEVICE)
+            .isNotEqualTo("vsoc_x86_64");
 
         VirtualMachineConfig.Builder builder =
                 new VirtualMachineConfig.Builder(mInner.mContext, "assets/vm_config.json");
@@ -269,4 +272,64 @@
                 };
         listener.runToFinish(mInner.mVm);
     }
+
+    private byte[] launchVmAndGetSecret(String instanceName)
+            throws VirtualMachineException, InterruptedException {
+        VirtualMachineConfig.Builder builder =
+                new VirtualMachineConfig.Builder(mInner.mContext, "assets/vm_config.json");
+        VirtualMachineConfig normalConfig = builder.debugLevel(DebugLevel.NONE).build();
+        mInner.mVm = mInner.mVmm.getOrCreate(instanceName, normalConfig);
+        final CompletableFuture<byte[]> secret = new CompletableFuture<>();
+        VmEventListener listener =
+                new VmEventListener() {
+                    @Override
+                    public void onPayloadReady(VirtualMachine vm) {
+                        try {
+                            ITestService testService = ITestService.Stub.asInterface(
+                                    vm.connectToVsockServer(ITestService.SERVICE_PORT).get());
+                            secret.complete(testService.insecurelyExposeSecret());
+                        } catch (Exception e) {
+                            fail("Exception while connecting to service: " + e.toString());
+                        }
+                        // TODO(b/208639280): remove this sleep. For now, we need to wait for a few
+                        // seconds so that crosvm can actually persist instance.img.
+                        try {
+                            Thread.sleep(30 * 1000);
+                        } catch (InterruptedException e) { }
+                        forceStop(vm);
+                    }
+                };
+        listener.runToFinish(mInner.mVm);
+        return secret.getNow(null);
+    }
+
+    @Test
+    public void instancesOfSameVmHaveDifferentSecrets()
+            throws VirtualMachineException, InterruptedException {
+        assume()
+            .withMessage("Skip on Cuttlefish. b/195765441")
+            .that(android.os.Build.DEVICE)
+            .isNotEqualTo("vsoc_x86_64");
+
+        byte[] vm_a_secret = launchVmAndGetSecret("test_vm_a");
+        byte[] vm_b_secret = launchVmAndGetSecret("test_vm_b");
+        assertThat(vm_a_secret).isNotNull();
+        assertThat(vm_b_secret).isNotNull();
+        assertThat(vm_a_secret).isNotEqualTo(vm_b_secret);
+    }
+
+    @Test
+    public void sameInstanceKeepsSameSecrets()
+            throws VirtualMachineException, InterruptedException {
+        assume()
+            .withMessage("Skip on Cuttlefish. b/195765441")
+            .that(android.os.Build.DEVICE)
+            .isNotEqualTo("vsoc_x86_64");
+
+        byte[] vm_secret_first_boot = launchVmAndGetSecret("test_vm");
+        byte[] vm_secret_second_boot = launchVmAndGetSecret("test_vm");
+        assertThat(vm_secret_first_boot).isNotNull();
+        assertThat(vm_secret_second_boot).isNotNull();
+        assertThat(vm_secret_first_boot).isEqualTo(vm_secret_second_boot);
+    }
 }
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index 301328a..417ff4a 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -13,6 +13,7 @@
  * See the License for the specific language governing permissions and
  * limitations under the License.
  */
+#include <aidl/android/security/dice/IDiceNode.h>
 #include <aidl/android/system/virtualmachineservice/IVirtualMachineService.h>
 #include <aidl/com/android/microdroid/testservice/BnTestService.h>
 #include <android-base/file.h>
@@ -32,6 +33,9 @@
 #include <binder_rpc_unstable.hpp>
 #include <string>
 
+using aidl::android::hardware::security::dice::BccHandover;
+using aidl::android::security::dice::IDiceNode;
+
 using aidl::android::system::virtualmachineservice::IVirtualMachineService;
 
 using android::base::ErrnoError;
@@ -74,6 +78,23 @@
 
             return ndk::ScopedAStatus::ok();
         }
+
+        ndk::ScopedAStatus insecurelyExposeSecret(std::vector<uint8_t>* out) override {
+            ndk::SpAIBinder binder(AServiceManager_getService("android.security.dice.IDiceNode"));
+            auto service = IDiceNode::fromBinder(binder);
+            if (service == nullptr) {
+                return ndk::ScopedAStatus::
+                        fromServiceSpecificErrorWithMessage(0, "Failed to find diced");
+            }
+            BccHandover handover;
+            auto deriveStatus = service->derive({}, &handover);
+            if (!deriveStatus.isOk()) {
+                return ndk::ScopedAStatus::fromServiceSpecificErrorWithMessage(0,
+                                                                               "Failed call diced");
+            }
+            *out = {handover.cdiSeal.begin(), handover.cdiSeal.end()};
+            return ndk::ScopedAStatus::ok();
+        }
     };
     auto testService = ndk::SharedRefBase::make<TestService>();