Test that BCCs look roughly the right shape
In the absence of a full BCC parser and despite the BCC not yet having
an application, make sure it roughly looks like the right sort of thing.
Make sure it's a valid CBOR array and has a reasonable looking number of
entries as a basic smoke test.
Bug: 218935426
Test: atest MicrodroidTests
Change-Id: Id28009c572e5a9b9cf02b9891fbe3314100d9aa4
diff --git a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
index 99c07bf..0913fe3 100644
--- a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
+++ b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
@@ -30,4 +30,7 @@
/* get the VM's attestation secret, this is _only_ done for testing. */
byte[] insecurelyExposeAttestationCdi();
+
+ /* get the VM's boot certificate chain (BCC). */
+ byte[] getBcc();
}
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index 0699e3d..818c05a 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -12,6 +12,7 @@
static_libs: [
"androidx.test.runner",
"androidx.test.ext.junit",
+ "cbor-java",
"com.android.microdroid.testservice-java",
"truth-prebuilt",
],
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 9995b44..c7bb011 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -47,10 +47,12 @@
import org.junit.runner.RunWith;
import org.junit.runners.Parameterized;
+import java.io.ByteArrayInputStream;
import java.io.File;
import java.io.IOException;
import java.io.RandomAccessFile;
import java.nio.file.Files;
+import java.util.List;
import java.util.OptionalLong;
import java.util.UUID;
import java.util.concurrent.CompletableFuture;
@@ -58,6 +60,12 @@
import java.util.concurrent.Executors;
import java.util.concurrent.TimeUnit;
+import co.nstant.in.cbor.CborDecoder;
+import co.nstant.in.cbor.CborException;
+import co.nstant.in.cbor.model.Array;
+import co.nstant.in.cbor.model.DataItem;
+import co.nstant.in.cbor.model.MajorType;
+
@RunWith(Parameterized.class)
public class MicrodroidTests {
@Rule public Timeout globalTimeout = Timeout.seconds(300);
@@ -369,6 +377,59 @@
assertThat(first_boot_cdis.cdiSeal).isEqualTo(second_boot_cdis.cdiSeal);
}
+ @Test
+ public void bccIsSuperficiallyWellFormed()
+ throws VirtualMachineException, InterruptedException, CborException {
+ assume()
+ .withMessage("Skip on Cuttlefish. b/195765441")
+ .that(android.os.Build.DEVICE)
+ .isNotEqualTo("vsoc_x86_64");
+
+ assume()
+ .withMessage("SKip on 5.4 kernel. b/218303240")
+ .that(KERNEL_VERSION)
+ .isNotEqualTo("5.4");
+
+ VirtualMachineConfig.Builder builder =
+ new VirtualMachineConfig.Builder(mInner.mContext, "assets/vm_config.json")
+ .protectedVm(mProtectedVm);
+ VirtualMachineConfig normalConfig = builder.debugLevel(DebugLevel.NONE).build();
+ mInner.mVm = mInner.mVmm.getOrCreate("bcc_vm", normalConfig);
+ final VmCdis vmCdis = new VmCdis();
+ final CompletableFuture<byte[]> bcc = new CompletableFuture<>();
+ final CompletableFuture<Exception> exception = new CompletableFuture<>();
+ VmEventListener listener =
+ new VmEventListener() {
+ @Override
+ public void onPayloadReady(VirtualMachine vm) {
+ try {
+ ITestService testService = ITestService.Stub.asInterface(
+ vm.connectToVsockServer(ITestService.SERVICE_PORT).get());
+ bcc.complete(testService.getBcc());
+ forceStop(vm);
+ } catch (Exception e) {
+ exception.complete(e);
+ }
+ }
+ };
+ listener.runToFinish(mInner.mVm);
+ byte[] bccBytes = bcc.getNow(null);
+ assertThat(exception.getNow(null)).isNull();
+ assertThat(bccBytes).isNotNull();
+
+ ByteArrayInputStream bais = new ByteArrayInputStream(bccBytes);
+ List<DataItem> dataItems = new CborDecoder(bais).decode();
+ assertThat(dataItems.size()).isEqualTo(1);
+ assertThat(dataItems.get(0).getMajorType()).isEqualTo(MajorType.ARRAY);
+ List<DataItem> rootArrayItems = ((Array) dataItems.get(0)).getDataItems();
+ assertThat(rootArrayItems.size()).isAtLeast(2); // Public key and one certificate
+ if (mProtectedVm) {
+ // When a true BCC is created, microdroid expects entries for at least: the root public
+ // key, pvmfw, u-boot, u-boot-env, microdroid, app payload and the service process.
+ assertThat(rootArrayItems.size()).isAtLeast(7);
+ }
+ }
+
private static final UUID MICRODROID_PARTITION_UUID =
UUID.fromString("cf9afe9a-0662-11ec-a329-c32663a09d75");
private static final long BLOCK_SIZE = 512;
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index 76df5be..89570c0 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -112,6 +112,23 @@
*out = {handover.cdiAttest.begin(), handover.cdiAttest.end()};
return ndk::ScopedAStatus::ok();
}
+
+ ndk::ScopedAStatus getBcc(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.bcc.data.begin(), handover.bcc.data.end()};
+ return ndk::ScopedAStatus::ok();
+ }
};
auto testService = ndk::SharedRefBase::make<TestService>();