Merge "Add last_update_seconds."
diff --git a/TEST_MAPPING b/TEST_MAPPING
index c4ee878..6338c82 100644
--- a/TEST_MAPPING
+++ b/TEST_MAPPING
@@ -14,6 +14,9 @@
     },
     {
       "name": "art_standalone_dexpreopt_tests"
+    },
+    {
+      "name": "compsvc_device_tests"
     }
   ],
   "postsubmit": [
diff --git a/binder_common/lazy_service.rs b/binder_common/lazy_service.rs
index a2b85db..9d605b6 100644
--- a/binder_common/lazy_service.rs
+++ b/binder_common/lazy_service.rs
@@ -17,7 +17,7 @@
 //! Rust API for lazy (aka dynamic) AIDL services.
 //! See https://source.android.com/devices/architecture/aidl/dynamic-aidl.
 
-use binder::public_api::force_lazy_services_persist;
+use binder::force_lazy_services_persist;
 use lazy_static::lazy_static;
 use std::sync::Mutex;
 
diff --git a/binder_common/lib.rs b/binder_common/lib.rs
index fa91f5a..fd81da5 100644
--- a/binder_common/lib.rs
+++ b/binder_common/lib.rs
@@ -20,7 +20,7 @@
 pub mod rpc_client;
 pub mod rpc_server;
 
-use binder::public_api::{ExceptionCode, Status};
+use binder::{ExceptionCode, Status};
 use std::ffi::CString;
 
 /// Constructs a new Binder error `Status` with the given `ExceptionCode` and message.
diff --git a/binder_common/rpc_client.rs b/binder_common/rpc_client.rs
index 262a689..1aabe84 100644
--- a/binder_common/rpc_client.rs
+++ b/binder_common/rpc_client.rs
@@ -16,14 +16,14 @@
 
 //! Helpers for implementing an RPC Binder client.
 
-use binder::public_api::{StatusCode, Strong};
 use binder::unstable_api::{new_spibinder, AIBinder};
+use binder::{StatusCode, Strong};
 
 /// Connects to a binder RPC server.
 pub fn connect_rpc_binder<T: binder::FromIBinder + ?Sized>(
     cid: u32,
     port: u32,
-) -> binder::Result<Strong<T>> {
+) -> Result<Strong<T>, StatusCode> {
     // SAFETY: AIBinder returned by RpcClient has correct reference count, and the ownership can be
     // safely taken by new_spibinder.
     let ibinder = unsafe {
diff --git a/binder_common/rpc_server.rs b/binder_common/rpc_server.rs
index 36075cf..5c9d2a0 100644
--- a/binder_common/rpc_server.rs
+++ b/binder_common/rpc_server.rs
@@ -16,8 +16,8 @@
 
 //! Helpers for implementing an RPC Binder server.
 
-use binder::public_api::SpIBinder;
 use binder::unstable_api::AsNative;
+use binder::SpIBinder;
 use std::os::raw;
 
 /// Run a binder RPC server, serving the supplied binder service implementation on the given vsock
diff --git a/compos/Android.bp b/compos/Android.bp
index 401f1c7..658f8bf 100644
--- a/compos/Android.bp
+++ b/compos/Android.bp
@@ -2,14 +2,12 @@
     default_applicable_licenses: ["Android-Apache-2.0"],
 }
 
-rust_binary {
-    name: "compsvc",
+rust_defaults {
+    name: "compsvc_defaults",
     srcs: ["src/compsvc_main.rs"],
     rustlibs: [
         "android.hardware.security.dice-V1-rust",
-        "android.hardware.security.keymint-V1-rust",
         "android.security.dice-rust",
-        "android.system.keystore2-V1-rust",
         "android.system.virtualmachineservice-rust",
         "authfs_aidl_interface-rust",
         "compos_aidl_interface-rust",
@@ -20,6 +18,7 @@
         "libbinder_rs",
         "libclap",
         "libcompos_common",
+        "libcompos_native_rust",
         "libenv_logger",
         "liblibc",
         "liblog_rust",
@@ -35,8 +34,20 @@
     prefer_rlib: true,
     shared_libs: [
         "libbinder_rpc_unstable",
+        "libcrypto",
     ],
+}
+
+rust_binary {
+    name: "compsvc",
+    defaults: ["compsvc_defaults"],
     apex_available: [
         "com.android.compos",
     ],
 }
+
+rust_test {
+    name: "compsvc_device_tests",
+    defaults: ["compsvc_defaults"],
+    test_suites: ["device-tests"],
+}
diff --git a/compos/aidl/com/android/compos/CompOsKeyData.aidl b/compos/aidl/com/android/compos/CompOsKeyData.aidl
index 381ec0d..dafb009 100644
--- a/compos/aidl/com/android/compos/CompOsKeyData.aidl
+++ b/compos/aidl/com/android/compos/CompOsKeyData.aidl
@@ -19,9 +19,10 @@
 /** {@hide} */
 parcelable CompOsKeyData {
     /**
-     * Self-signed certificate (X.509 DER) containing the public key.
+     * The public key, as a DER-encoded RSAPublicKey
+     * (https://datatracker.ietf.org/doc/html/rfc8017#appendix-A.1.1).
      */
-    byte[] certificate;
+    byte[] publicKey;
 
     /**
      * Opaque encrypted blob containing the private key and related metadata.
diff --git a/compos/aidl/com/android/compos/FdAnnotation.aidl b/compos/aidl/com/android/compos/FdAnnotation.aidl
deleted file mode 100644
index b910391..0000000
--- a/compos/aidl/com/android/compos/FdAnnotation.aidl
+++ /dev/null
@@ -1,32 +0,0 @@
-/*
- * 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.compos;
-
-/** {@hide} */
-parcelable FdAnnotation {
-    /**
-     * Input file descriptor numbers to be passed to the program.  This is currently assumed to be
-     * same as the file descriptor number used in the backend server.
-     */
-    int[] input_fds;
-
-    /**
-     * Output file descriptor numbers to be passed to the program.  This is currently assumed to be
-     * same as the file descriptor number used in the backend server.
-     */
-    int[] output_fds;
-}
diff --git a/compos/aidl/com/android/compos/ICompOsService.aidl b/compos/aidl/com/android/compos/ICompOsService.aidl
index b2b961a..cead5d0 100644
--- a/compos/aidl/com/android/compos/ICompOsService.aidl
+++ b/compos/aidl/com/android/compos/ICompOsService.aidl
@@ -17,7 +17,6 @@
 package com.android.compos;
 
 import com.android.compos.CompOsKeyData;
-import com.android.compos.FdAnnotation;
 
 /** {@hide} */
 interface ICompOsService {
@@ -51,21 +50,6 @@
             String zygoteArch, String systemServerCompilerFilter);
 
     /**
-     * Runs dexopt compilation encoded in the marshaled dexopt arguments.
-     *
-     * To keep ART indepdendantly updatable, the compilation arguments are not stabilized. As a
-     * result, the arguments are marshaled into byte array.  Upon received, the service asks ART to
-     * return relevant information (since ART is able to unmarshal its own encoding), in order to
-     * set up the execution context (mainly file descriptors for compiler input and output) then
-     * invokes the compiler.
-     *
-     * @param marshaledArguments The marshaled dexopt arguments.
-     * @param fd_annotation Additional file descriptor information of the execution.
-     * @return exit code
-     */
-    byte compile(in byte[] marshaledArguments, in FdAnnotation fd_annotation);
-
-    /**
      * Generate a new public/private key pair suitable for signing CompOs output files.
      *
      * @return a certificate for the public key and the encrypted private key
@@ -82,9 +66,4 @@
      * @return whether the inputs are valid and correspond to each other.
      */
     boolean verifySigningKey(in byte[] keyBlob, in byte[] publicKey);
-
-    /**
-     * Returns the DICE BCC for this instance of CompOS, allowing signatures to be verified.
-     */
-    byte[] getBootCertificateChain();
 }
diff --git a/compos/apex/composd.rc b/compos/apex/composd.rc
index bf34335..3e2efb1 100644
--- a/compos/apex/composd.rc
+++ b/compos/apex/composd.rc
@@ -17,6 +17,5 @@
     user root
     group system
     interface aidl android.system.composd
-    interface aidl android.system.composd.internal
     disabled
     oneshot
diff --git a/compos/common/binder.rs b/compos/common/binder.rs
index 6bd3957..ae857e0 100644
--- a/compos/common/binder.rs
+++ b/compos/common/binder.rs
@@ -17,7 +17,7 @@
 //! Helper for converting Error types to what Binder expects
 
 use anyhow::Result;
-use binder::public_api::{ExceptionCode, Result as BinderResult};
+use binder::{ExceptionCode, Result as BinderResult};
 use binder_common::new_binder_exception;
 use log::warn;
 use std::fmt::Debug;
diff --git a/compos/common/compos_client.rs b/compos/common/compos_client.rs
index b54a921..f4b3440 100644
--- a/compos/common/compos_client.rs
+++ b/compos/common/compos_client.rs
@@ -46,6 +46,9 @@
 use std::sync::{Arc, Condvar, Mutex};
 use std::thread;
 
+// Enough memory to complete odrefresh in the VM.
+const VM_MEMORY_MIB: i32 = 1024;
+
 /// This owns an instance of the CompOS VM.
 pub struct VmInstance {
     #[allow(dead_code)] // Keeps the VM alive even if we don`t touch it
@@ -119,9 +122,9 @@
             configPath: config_path.to_owned(),
             debugLevel: debug_level,
             extraIdsigs: vec![idsig_manifest_apk_fd],
+            memoryMib: VM_MEMORY_MIB,
             numCpus: parameters.cpus.map_or(1, NonZeroU32::get) as i32,
             cpuAffinity: parameters.cpu_set.clone(),
-            ..Default::default()
         });
 
         let vm = service
diff --git a/compos/compos_key_cmd/Android.bp b/compos/compos_key_cmd/Android.bp
index 9d5a490..d412f66 100644
--- a/compos/compos_key_cmd/Android.bp
+++ b/compos/compos_key_cmd/Android.bp
@@ -17,7 +17,6 @@
         "libbase",
         "libbinder_ndk",
         "libbinder_rpc_unstable",
-        "libcrypto",
         "libfsverity",
         "libprotobuf-cpp-lite",
     ],
diff --git a/compos/compos_key_cmd/compos_key_cmd.cpp b/compos/compos_key_cmd/compos_key_cmd.cpp
index 8ed07e6..07ff636 100644
--- a/compos/compos_key_cmd/compos_key_cmd.cpp
+++ b/compos/compos_key_cmd/compos_key_cmd.cpp
@@ -27,10 +27,6 @@
 #include <asm/byteorder.h>
 #include <libfsverity.h>
 #include <linux/fsverity.h>
-#include <openssl/evp.h>
-#include <openssl/mem.h>
-#include <openssl/sha.h>
-#include <openssl/x509.h>
 #include <stdio.h>
 #include <unistd.h>
 
@@ -73,6 +69,8 @@
 
 constexpr unsigned int kRpcPort = 6432;
 
+constexpr int kVmMemoryMib = 1024;
+
 constexpr const char* kConfigApkPath =
         "/apex/com.android.compos/app/CompOSPayloadApp/CompOSPayloadApp.apk";
 
@@ -279,7 +277,7 @@
         appConfig.configPath = mPreferStaged ? kPreferStagedConfigFilePath : kDefaultConfigFilePath;
         appConfig.debugLevel = mDebuggable ? VirtualMachineAppConfig::DebugLevel::FULL
                                            : VirtualMachineAppConfig::DebugLevel::NONE;
-        appConfig.memoryMib = 0; // Use default
+        appConfig.memoryMib = kVmMemoryMib;
 
         LOG(INFO) << "Starting VM";
         auto status = service->createVm(config, logFd, logFd, &mVm);
@@ -329,37 +327,6 @@
 
 } // namespace
 
-static Result<std::vector<uint8_t>> extractRsaPublicKey(
-        const std::vector<uint8_t>& der_certificate) {
-    auto data = der_certificate.data();
-    bssl::UniquePtr<X509> x509(d2i_X509(nullptr, &data, der_certificate.size()));
-    if (!x509) {
-        return Error() << "Failed to parse certificate";
-    }
-    if (data != der_certificate.data() + der_certificate.size()) {
-        return Error() << "Certificate has unexpected trailing data";
-    }
-
-    bssl::UniquePtr<EVP_PKEY> pkey(X509_get_pubkey(x509.get()));
-    if (EVP_PKEY_base_id(pkey.get()) != EVP_PKEY_RSA) {
-        return Error() << "Subject key is not RSA";
-    }
-    RSA* rsa = EVP_PKEY_get0_RSA(pkey.get());
-    if (!rsa) {
-        return Error() << "Failed to extract RSA key";
-    }
-
-    uint8_t* out = nullptr;
-    int size = i2d_RSAPublicKey(rsa, &out);
-    if (size < 0 || !out) {
-        return Error() << "Failed to convert to RSAPublicKey";
-    }
-
-    bssl::UniquePtr<uint8_t> buffer(out);
-    std::vector<uint8_t> result(out, out + size);
-    return result;
-}
-
 static Result<void> generate(TargetVm& vm, const std::string& blob_file,
                              const std::string& public_key_file) {
     auto cid = vm.resolveCid();
@@ -377,15 +344,11 @@
         return Error() << "Failed to generate key: " << status.getDescription();
     }
 
-    auto public_key = extractRsaPublicKey(key_data.certificate);
-    if (!public_key.ok()) {
-        return Error() << "Failed to extract public key from cert: " << public_key.error();
-    }
     if (!writeBytesToFile(key_data.keyBlob, blob_file)) {
         return Error() << "Failed to write keyBlob to " << blob_file;
     }
 
-    if (!writeBytesToFile(public_key.value(), public_key_file)) {
+    if (!writeBytesToFile(key_data.publicKey, public_key_file)) {
         return Error() << "Failed to write public key to " << public_key_file;
     }
 
diff --git a/compos/composd/Android.bp b/compos/composd/Android.bp
index 3190395..3b545e5 100644
--- a/compos/composd/Android.bp
+++ b/compos/composd/Android.bp
@@ -9,7 +9,6 @@
     prefer_rlib: true,
     rustlibs: [
         "android.system.composd-rust",
-        "android.system.composd.internal-rust",
         "android.system.virtualizationservice-rust",
         "compos_aidl_interface-rust",
         "libandroid_logger",
diff --git a/compos/composd/aidl/Android.bp b/compos/composd/aidl/Android.bp
index 376313b..56b0b60 100644
--- a/compos/composd/aidl/Android.bp
+++ b/compos/composd/aidl/Android.bp
@@ -19,19 +19,3 @@
         },
     },
 }
-
-aidl_interface {
-    name: "android.system.composd.internal",
-    srcs: ["android/system/composd/internal/*.aidl"],
-    imports: ["compos_aidl_interface"],
-    // TODO: Make this stable when the APEX becomes updatable.
-    unstable: true,
-    backend: {
-        rust: {
-            enabled: true,
-            apex_available: [
-                "com.android.compos",
-            ],
-        },
-    },
-}
diff --git a/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl b/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
index 0b5eec1..8156265 100644
--- a/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
+++ b/compos/composd/aidl/android/system/composd/IIsolatedCompilationService.aidl
@@ -42,10 +42,4 @@
      * a reference to the ICompilationTask until compilation completes or is cancelled.
      */
     ICompilationTask startTestCompile(ICompilationTaskCallback callback);
-
-    /**
-     * For testing.
-     * TODO(b/214233409): Remove
-     */
-    byte[] getBcc();
 }
diff --git a/compos/composd/aidl/android/system/composd/internal/ICompilationInternal.aidl b/compos/composd/aidl/android/system/composd/internal/ICompilationInternal.aidl
deleted file mode 100644
index a16a0a3..0000000
--- a/compos/composd/aidl/android/system/composd/internal/ICompilationInternal.aidl
+++ /dev/null
@@ -1,29 +0,0 @@
-/*
- * Copyright 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 android.system.composd.internal;
-
-import com.android.compos.FdAnnotation;
-
-interface ICompilationInternal {
-    /**
-     * Run dex2oat in the currently running instance of the CompOS VM. This is a simple proxy
-     * to ICompOsService#compile.
-     *
-     * This method can only be called from libcompos_client. If there is no currently running
-     * instance an error is returned.
-     */
-    byte compile(in byte[] marshaledArguments, in FdAnnotation fd_annotation);
-}
diff --git a/compos/composd/native/Android.bp b/compos/composd/native/Android.bp
index 135f4d4..ccd8651 100644
--- a/compos/composd/native/Android.bp
+++ b/compos/composd/native/Android.bp
@@ -8,40 +8,10 @@
     srcs: ["lib.rs"],
     rustlibs: [
         "libanyhow",
-        "libcxx",
         "liblibc",
     ],
-    static_libs: [
-        "libcomposd_native_cpp",
-    ],
     shared_libs: [
         "libartpalette-system",
-        "libcrypto",
     ],
     apex_available: ["com.android.compos"],
 }
-
-cc_library_static {
-    name: "libcomposd_native_cpp",
-    srcs: ["composd_native.cpp"],
-    shared_libs: ["libcrypto"],
-    generated_headers: ["composd_native_header"],
-    generated_sources: ["composd_native_code"],
-    apex_available: ["com.android.compos"],
-}
-
-genrule {
-    name: "composd_native_code",
-    tools: ["cxxbridge"],
-    cmd: "$(location cxxbridge) $(in) >> $(out)",
-    srcs: ["lib.rs"],
-    out: ["composd_native_cxx_generated.cc"],
-}
-
-genrule {
-    name: "composd_native_header",
-    tools: ["cxxbridge"],
-    cmd: "$(location cxxbridge) $(in) --header >> $(out)",
-    srcs: ["lib.rs"],
-    out: ["lib.rs.h"],
-}
diff --git a/compos/composd/native/composd_native.cpp b/compos/composd/native/composd_native.cpp
deleted file mode 100644
index ebed816..0000000
--- a/compos/composd/native/composd_native.cpp
+++ /dev/null
@@ -1,66 +0,0 @@
-/*
- * 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.
- */
-
-#include "composd_native.h"
-
-#include <openssl/evp.h>
-#include <openssl/mem.h>
-#include <openssl/sha.h>
-#include <openssl/x509.h>
-
-#include <algorithm>
-#include <iterator>
-
-using rust::Slice;
-using rust::String;
-
-namespace {
-KeyResult make_error(const char* message) {
-    return KeyResult{{}, message};
-}
-} // namespace
-
-KeyResult extract_rsa_public_key(rust::Slice<const uint8_t> der_certificate) {
-    auto data = der_certificate.data();
-    bssl::UniquePtr<X509> x509(d2i_X509(nullptr, &data, der_certificate.size()));
-    if (!x509) {
-        return make_error("Failed to parse certificate");
-    }
-    if (data != der_certificate.data() + der_certificate.size()) {
-        return make_error("Certificate has unexpected trailing data");
-    }
-
-    bssl::UniquePtr<EVP_PKEY> pkey(X509_get_pubkey(x509.get()));
-    if (EVP_PKEY_base_id(pkey.get()) != EVP_PKEY_RSA) {
-        return make_error("Subject key is not RSA");
-    }
-    RSA* rsa = EVP_PKEY_get0_RSA(pkey.get());
-    if (!rsa) {
-        return make_error("Failed to extract RSA key");
-    }
-
-    uint8_t* out = nullptr;
-    int size = i2d_RSAPublicKey(rsa, &out);
-    if (size < 0 || !out) {
-        return make_error("Failed to convert to RSAPublicKey");
-    }
-    bssl::UniquePtr<uint8_t> buffer(out);
-
-    KeyResult result;
-    result.key.reserve(size);
-    std::copy(out, out + size, std::back_inserter(result.key));
-    return result;
-}
diff --git a/compos/composd/native/lib.rs b/compos/composd/native/lib.rs
index cbec7fd..042eb2a 100644
--- a/compos/composd/native/lib.rs
+++ b/compos/composd/native/lib.rs
@@ -15,28 +15,6 @@
 //! Native helpers for composd.
 
 pub use art::*;
-pub use crypto::*;
-
-#[cxx::bridge]
-mod crypto {
-    /// Contains either a key or a reason why the key could not be extracted.
-    struct KeyResult {
-        /// The extracted key. If empty, the attempt to extract the key failed.
-        key: Vec<u8>,
-        /// A description of what went wrong if the attempt failed.
-        error: String,
-    }
-
-    unsafe extern "C++" {
-        include!("composd_native.h");
-
-        // SAFETY: The C++ implementation manages its own memory, and does not retain or abuse
-        // the der_certificate reference. cxx handles the mapping of the return value.
-
-        /// Parse the supplied DER X.509 certificate and extract the subject's RsaPublicKey.
-        fn extract_rsa_public_key(der_certificate: &[u8]) -> KeyResult;
-    }
-}
 
 mod art {
     use anyhow::{anyhow, Result};
diff --git a/compos/composd/src/composd_main.rs b/compos/composd/src/composd_main.rs
index 6acf470..235c107 100644
--- a/compos/composd/src/composd_main.rs
+++ b/compos/composd/src/composd_main.rs
@@ -21,7 +21,6 @@
 mod fd_server_helper;
 mod instance_manager;
 mod instance_starter;
-mod internal_service;
 mod odrefresh_task;
 mod service;
 
@@ -47,10 +46,6 @@
     register_lazy_service("android.system.composd", composd_service.as_binder())
         .context("Registering composd service")?;
 
-    let internal_service = internal_service::new_binder();
-    register_lazy_service("android.system.composd.internal", internal_service.as_binder())
-        .context("Registering internal service")?;
-
     info!("Registered services, joining threadpool");
     ProcessState::join_thread_pool();
 
diff --git a/compos/composd/src/instance_starter.rs b/compos/composd/src/instance_starter.rs
index 4fed98a..a886584 100644
--- a/compos/composd/src/instance_starter.rs
+++ b/compos/composd/src/instance_starter.rs
@@ -138,13 +138,7 @@
 
         let key_data = service.generateSigningKey().context("Generating signing key")?;
         fs::write(&self.key_blob, &key_data.keyBlob).context("Writing key blob")?;
-
-        let key_result = composd_native::extract_rsa_public_key(&key_data.certificate);
-        let rsa_public_key = key_result.key;
-        if rsa_public_key.is_empty() {
-            bail!("Failed to extract public key from certificate: {}", key_result.error);
-        }
-        fs::write(&self.public_key, &rsa_public_key).context("Writing public key")?;
+        fs::write(&self.public_key, &key_data.publicKey).context("Writing public key")?;
 
         // Unlike when starting an existing instance, we don't need to verify the key, since we
         // just generated it and have it in memory.
diff --git a/compos/composd/src/internal_service.rs b/compos/composd/src/internal_service.rs
deleted file mode 100644
index 33935ef..0000000
--- a/compos/composd/src/internal_service.rs
+++ /dev/null
@@ -1,39 +0,0 @@
-/*
- * 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.
- */
-
-//! Implementation of ICompilationInternal, called from odrefresh during compilation.
-
-use android_system_composd_internal::aidl::android::system::composd::internal::ICompilationInternal::{
-    BnCompilationInternal, ICompilationInternal,
-};
-use android_system_composd::binder::{self, BinderFeatures, Interface, Strong};
-use binder_common::new_binder_service_specific_error;
-use compos_aidl_interface::aidl::com::android::compos::FdAnnotation::FdAnnotation;
-
-pub struct CompilationInternalService {}
-
-pub fn new_binder() -> Strong<dyn ICompilationInternal> {
-    let service = CompilationInternalService {};
-    BnCompilationInternal::new_binder(service, BinderFeatures::default())
-}
-
-impl Interface for CompilationInternalService {}
-
-impl ICompilationInternal for CompilationInternalService {
-    fn compile(&self, _marshaled: &[u8], _fd_annotation: &FdAnnotation) -> binder::Result<i8> {
-        Err(new_binder_service_specific_error(-1, "Not yet implemented"))
-    }
-}
diff --git a/compos/composd/src/service.rs b/compos/composd/src/service.rs
index cb52037..6cdcd85 100644
--- a/compos/composd/src/service.rs
+++ b/compos/composd/src/service.rs
@@ -61,12 +61,6 @@
         check_permissions()?;
         to_binder_result(self.do_start_test_compile(callback))
     }
-
-    // TODO(b/214233409): Remove
-    fn getBcc(&self) -> binder::Result<Vec<u8>> {
-        check_permissions()?;
-        to_binder_result(self.do_get_bcc())
-    }
 }
 
 impl IsolatedCompilationService {
@@ -94,11 +88,6 @@
 
         Ok(BnCompilationTask::new_binder(task, BinderFeatures::default()))
     }
-
-    fn do_get_bcc(&self) -> Result<Vec<u8>> {
-        let comp_os = self.instance_manager.start_test_instance().context("Starting CompOS")?;
-        comp_os.get_service().getBootCertificateChain().context("getBcc")
-    }
 }
 
 fn check_permissions() -> binder::Result<()> {
diff --git a/compos/composd_cmd/composd_cmd.rs b/compos/composd_cmd/composd_cmd.rs
index 9b41104..546c4af 100644
--- a/compos/composd_cmd/composd_cmd.rs
+++ b/compos/composd_cmd/composd_cmd.rs
@@ -29,8 +29,6 @@
 };
 use anyhow::{bail, Context, Result};
 use compos_common::timeouts::timeouts;
-use std::fs::File;
-use std::io::Write;
 use std::sync::{Arc, Condvar, Mutex};
 use std::time::Duration;
 
@@ -40,7 +38,7 @@
             .index(1)
             .takes_value(true)
             .required(true)
-            .possible_values(&["staged-apex-compile", "test-compile", "dice"]),
+            .possible_values(&["staged-apex-compile", "test-compile"]),
     );
     let args = app.get_matches();
     let command = args.value_of("command").unwrap();
@@ -50,7 +48,6 @@
     match command {
         "staged-apex-compile" => run_staged_apex_compile()?,
         "test-compile" => run_test_compile()?,
-        "dice" => write_dice()?,
         _ => panic!("Unexpected command {}", command),
     }
 
@@ -115,16 +112,6 @@
     run_async_compilation(|service, callback| service.startTestCompile(callback))
 }
 
-fn write_dice() -> Result<()> {
-    let service = wait_for_interface::<dyn IIsolatedCompilationService>("android.system.composd")
-        .context("Failed to connect to composd service")?;
-
-    let bcc = service.getBcc()?;
-    let mut file =
-        File::create("/data/misc/apexdata/com.android.compos/bcc").context("Creating bcc file")?;
-    file.write_all(&bcc).context("Writing bcc")
-}
-
 fn run_async_compilation<F>(start_compile_fn: F) -> Result<()>
 where
     F: FnOnce(
diff --git a/compos/native/Android.bp b/compos/native/Android.bp
new file mode 100644
index 0000000..84e312a
--- /dev/null
+++ b/compos/native/Android.bp
@@ -0,0 +1,46 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_library {
+    name: "libcompos_native_rust",
+    crate_name: "compos_native",
+    srcs: ["lib.rs"],
+    rustlibs: [
+        "libanyhow",
+        "libcxx",
+        "liblibc",
+    ],
+    static_libs: [
+        "libcompos_native_cpp",
+    ],
+    shared_libs: [
+        "libcrypto",
+    ],
+    apex_available: ["com.android.compos"],
+}
+
+cc_library_static {
+    name: "libcompos_native_cpp",
+    srcs: ["compos_native.cpp"],
+    shared_libs: ["libcrypto"],
+    generated_headers: ["compos_native_header"],
+    generated_sources: ["compos_native_code"],
+    apex_available: ["com.android.compos"],
+}
+
+genrule {
+    name: "compos_native_code",
+    tools: ["cxxbridge"],
+    cmd: "$(location cxxbridge) $(in) >> $(out)",
+    srcs: ["lib.rs"],
+    out: ["compos_native_cxx_generated.cc"],
+}
+
+genrule {
+    name: "compos_native_header",
+    tools: ["cxxbridge"],
+    cmd: "$(location cxxbridge) $(in) --header >> $(out)",
+    srcs: ["lib.rs"],
+    out: ["lib.rs.h"],
+}
diff --git a/compos/native/compos_native.cpp b/compos/native/compos_native.cpp
new file mode 100644
index 0000000..f529421
--- /dev/null
+++ b/compos/native/compos_native.cpp
@@ -0,0 +1,112 @@
+/*
+ * 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.
+ */
+
+#include "compos_native.h"
+
+#include <openssl/bn.h>
+#include <openssl/mem.h>
+#include <openssl/nid.h>
+#include <openssl/rsa.h>
+#include <openssl/sha.h>
+
+#include <algorithm>
+#include <iterator>
+#include <vector>
+
+namespace {
+KeyResult make_key_error(const char* message) {
+    return KeyResult{{}, {}, message};
+}
+
+SignResult make_sign_error(const char* message) {
+    return SignResult{{}, message};
+}
+} // namespace
+
+constexpr int KEY_BITS = 2048;
+
+KeyResult generate_key_pair() {
+    bssl::UniquePtr<RSA> key_pair(RSA_new());
+
+    // This function specifies that the public exponent is always 65537, which is good because
+    // that's  what odsign is expecting.
+    if (!RSA_generate_key_fips(key_pair.get(), KEY_BITS, /*callback=*/nullptr)) {
+        return make_key_error("Failed to generate key pair");
+    }
+
+    KeyResult result;
+
+    uint8_t* out;
+    int size;
+    bssl::UniquePtr<uint8_t> out_owner;
+
+    // Extract public key as DER.
+    out = nullptr;
+    size = i2d_RSAPublicKey(key_pair.get(), &out);
+    if (size < 0 || !out) {
+        return make_key_error("Failed to get RSAPublicKey");
+    }
+    out_owner.reset(out);
+
+    result.public_key.reserve(size);
+    std::copy(out, out + size, std::back_inserter(result.public_key));
+    out_owner.reset();
+
+    // And ditto for the private key (which actually includes the public bits).
+    out = nullptr;
+    size = i2d_RSAPrivateKey(key_pair.get(), &out);
+    if (size < 0 || !out) {
+        return make_key_error("Failed to get RSAPrivateKey");
+    }
+    out_owner.reset(out);
+
+    result.private_key.reserve(size);
+    std::copy(out, out + size, std::back_inserter(result.private_key));
+    out_owner.reset();
+
+    return result;
+}
+
+SignResult sign(rust::Slice<const uint8_t> private_key, rust::Slice<const uint8_t> data) {
+    uint8_t digest[SHA256_DIGEST_LENGTH];
+    SHA256(data.data(), data.size(), digest);
+
+    const uint8_t* key_in = private_key.data();
+    bssl::UniquePtr<RSA> key(d2i_RSAPrivateKey(nullptr, &key_in, private_key.size()));
+    if (!key) {
+        return make_sign_error("Failed to load RSAPrivateKey");
+    }
+
+    // rust::Vec doesn't support resize, so we need our own buffer.
+    // The signature is always less than the modulus (public key), so
+    // will fit in KEY_BITS.
+
+    uint8_t signature[KEY_BITS / 8];
+    if (sizeof(signature) < RSA_size(key.get())) {
+        return make_sign_error("Signing key is too large");
+    }
+    unsigned signature_len = 0;
+
+    if (!RSA_sign(NID_sha256, digest, sizeof(digest), signature, &signature_len, key.get())) {
+        return make_sign_error("Failed to sign");
+    }
+
+    SignResult result;
+    result.signature.reserve(signature_len);
+    std::copy(signature, signature + signature_len, std::back_inserter(result.signature));
+
+    return result;
+}
diff --git a/compos/composd/native/composd_native.h b/compos/native/compos_native.h
similarity index 83%
rename from compos/composd/native/composd_native.h
rename to compos/native/compos_native.h
index 112ef73..6497c9b 100644
--- a/compos/composd/native/composd_native.h
+++ b/compos/native/compos_native.h
@@ -18,4 +18,6 @@
 
 #include "lib.rs.h"
 
-KeyResult extract_rsa_public_key(rust::Slice<const uint8_t> der_certificate);
+KeyResult generate_key_pair();
+
+SignResult sign(rust::Slice<const uint8_t> private_key, rust::Slice<const uint8_t> data);
diff --git a/compos/native/lib.rs b/compos/native/lib.rs
new file mode 100644
index 0000000..a5f0cc5
--- /dev/null
+++ b/compos/native/lib.rs
@@ -0,0 +1,59 @@
+// Copyright 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.
+
+//! Native helpers for CompOS.
+
+pub use crypto::*;
+
+#[cxx::bridge]
+mod crypto {
+    /// Contains either a key pair or a reason why the key could not be extracted.
+    struct KeyResult {
+        /// The DER-encoded RSAPublicKey
+        /// (https://datatracker.ietf.org/doc/html/rfc8017#appendix-A.1.1).
+        public_key: Vec<u8>,
+        /// The DER-encoded RSAPrivateKey
+        /// (https://datatracker.ietf.org/doc/html/rfc8017#appendix-A.1.2).
+        /// Note that this is unencrypted.
+        private_key: Vec<u8>,
+        /// A description of what went wrong if the attempt failed.
+        error: String,
+    }
+
+    /// Contains either a signature or a reason why signing failed.
+    struct SignResult {
+        /// The RSAES-PKCS1-v1_5 signature
+        /// (https://datatracker.ietf.org/doc/html/rfc3447#section-7.2).
+        signature: Vec<u8>,
+        /// A description of what went wrong if the attempt failed.
+        error: String,
+    }
+
+    unsafe extern "C++" {
+        include!("compos_native.h");
+
+        // SAFETY: The C++ implementation manages its own memory. cxx handles the mapping of the
+        // return value.
+
+        /// Generate a public/private key pair.
+        fn generate_key_pair() -> KeyResult;
+
+        // SAFETY: The C++ implementation manages its own memory, and does not retain or abuse
+        // the references passed to it. cxx handles the mapping of the return value.
+
+        /// Sign data using a SHA256 digest and RSAES-PKCS1-v1_5 using the given
+        /// DER-encoded RSAPrivateKey.
+        fn sign(private_key: &[u8], data: &[u8]) -> SignResult;
+    }
+}
diff --git a/compos/src/artifact_signer.rs b/compos/src/artifact_signer.rs
index a4b47d6..e1b0efa 100644
--- a/compos/src/artifact_signer.rs
+++ b/compos/src/artifact_signer.rs
@@ -19,8 +19,8 @@
 
 #![allow(dead_code)] // Will be used soon
 
-use crate::compos_key_service::Signer;
 use crate::fsverity;
+use crate::signing_key::Signer;
 use anyhow::{anyhow, Context, Result};
 use odsign_proto::odsign_info::OdsignInfo;
 use protobuf::Message;
diff --git a/compos/src/blob_encryption.rs b/compos/src/blob_encryption.rs
new file mode 100644
index 0000000..0db09ba
--- /dev/null
+++ b/compos/src/blob_encryption.rs
@@ -0,0 +1,118 @@
+/*
+ * Copyright 2022 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.
+ */
+
+//! Allows for data to be encrypted and authenticated (AEAD) with a key derived from some secret.
+//! The encrypted blob can be passed to the untrusted host without revealing the encrypted data
+//! but with the key the data can be retrieved as long as the blob has not been tampered with.
+
+use anyhow::{bail, Context, Result};
+use ring::{
+    aead::{Aad, LessSafeKey, Nonce, AES_256_GCM, NONCE_LEN},
+    hkdf::{Salt, HKDF_SHA256},
+    rand::{SecureRandom, SystemRandom},
+};
+
+// Non-secret input to the AEAD key derivation
+const KDF_INFO: &[u8] = b"CompOS blob sealing key";
+
+pub fn derive_aead_key(input_keying_material: &[u8]) -> Result<LessSafeKey> {
+    // Derive key using HKDF - see https://datatracker.ietf.org/doc/html/rfc5869#section-2
+    let salt = [];
+    let prk = Salt::new(HKDF_SHA256, &salt).extract(input_keying_material);
+    let okm = prk.expand(&[KDF_INFO], &AES_256_GCM).context("HKDF failed")?;
+    // LessSafeKey is only less safe in that it has less inherent protection against nonce
+    // reuse; we are safe because we use a new random nonce for each sealing operation.
+    // (See https://github.com/briansmith/ring/issues/899.)
+    Ok(LessSafeKey::new(okm.into()))
+}
+
+pub fn encrypt_bytes(key: LessSafeKey, bytes: &[u8]) -> Result<Vec<u8>> {
+    let mut output = Vec::with_capacity(bytes.len() + NONCE_LEN + key.algorithm().tag_len());
+
+    // Generate a unique nonce, since we may use the same key more than once, and put it at the
+    // start of the output blob.
+    let mut nonce_bytes = [0u8; NONCE_LEN];
+    SystemRandom::new().fill(&mut nonce_bytes).context("Failed to generate random nonce")?;
+    output.extend_from_slice(&nonce_bytes);
+
+    // Copy input to output then encrypt & seal it in place.
+    output.extend_from_slice(bytes);
+    let nonce = Nonce::assume_unique_for_key(nonce_bytes);
+    let tag = key
+        .seal_in_place_separate_tag(nonce, Aad::empty(), &mut output[NONCE_LEN..])
+        .context("Failed to seal blob")?;
+    output.extend_from_slice(tag.as_ref());
+
+    Ok(output)
+}
+
+pub fn decrypt_bytes(key: LessSafeKey, bytes: &[u8]) -> Result<Vec<u8>> {
+    if bytes.len() < NONCE_LEN + key.algorithm().tag_len() {
+        bail!("Encrypted blob is too small");
+    }
+
+    // We expect the nonce at the start followed by the sealed data (encrypted data +
+    // authentication tag).
+    let nonce = Nonce::try_assume_unique_for_key(&bytes[..NONCE_LEN]).unwrap();
+    let mut output = bytes[NONCE_LEN..].to_vec();
+
+    // Verify & decrypt the data in place
+    let unsealed_size =
+        key.open_in_place(nonce, Aad::empty(), &mut output).context("Failed to unseal blob")?.len();
+
+    // Remove the tag after the plaintext
+    output.truncate(unsealed_size);
+
+    Ok(output)
+}
+
+#[cfg(test)]
+mod tests {
+    use super::*;
+
+    #[test]
+    fn test_round_trip_data() -> Result<()> {
+        let input_keying_material = b"Key is derived from this";
+        let original_bytes = b"This is the secret data";
+
+        let key = derive_aead_key(input_keying_material)?;
+        let blob = encrypt_bytes(key, original_bytes)?;
+
+        let key = derive_aead_key(input_keying_material)?;
+        let decoded_bytes = decrypt_bytes(key, &blob)?;
+
+        assert_eq!(decoded_bytes, original_bytes);
+        Ok(())
+    }
+
+    #[test]
+    fn test_modified_data_detected() -> Result<()> {
+        let input_keying_material = b"Key is derived from this";
+        let original_bytes = b"This is the secret data";
+
+        let key = derive_aead_key(input_keying_material)?;
+        let mut blob = encrypt_bytes(key, original_bytes)?;
+
+        // Flip a bit.
+        blob[0] ^= 1;
+
+        let key = derive_aead_key(input_keying_material)?;
+        let decoded_bytes = decrypt_bytes(key, &blob);
+
+        assert!(decoded_bytes.is_err());
+        Ok(())
+    }
+}
diff --git a/compos/src/compilation.rs b/compos/src/compilation.rs
index 6db31b6..850a0a8 100644
--- a/compos/src/compilation.rs
+++ b/compos/src/compilation.rs
@@ -26,7 +26,7 @@
 use std::process::Command;
 
 use crate::artifact_signer::ArtifactSigner;
-use crate::compos_key_service::Signer;
+use crate::signing_key::Signer;
 use authfs_aidl_interface::aidl::com::android::virt::fs::{
     AuthFsConfig::{
         AuthFsConfig, InputDirFdAnnotation::InputDirFdAnnotation,
diff --git a/compos/src/compos_key_service.rs b/compos/src/compos_key_service.rs
deleted file mode 100644
index 086a162..0000000
--- a/compos/src/compos_key_service.rs
+++ /dev/null
@@ -1,148 +0,0 @@
-// Copyright 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.
-
-//! Provides a binder service for key generation & verification for CompOs. We assume we have
-//! access to Keystore in the VM, but not persistent storage; instead the host stores the key
-//! on our behalf via this service.
-
-use android_hardware_security_keymint::aidl::android::hardware::security::keymint::{
-    Algorithm::Algorithm, Digest::Digest, KeyParameter::KeyParameter,
-    KeyParameterValue::KeyParameterValue, KeyPurpose::KeyPurpose, PaddingMode::PaddingMode,
-    SecurityLevel::SecurityLevel, Tag::Tag,
-};
-use android_system_keystore2::aidl::android::system::keystore2::{
-    Domain::Domain, IKeystoreSecurityLevel::IKeystoreSecurityLevel,
-    IKeystoreService::IKeystoreService, KeyDescriptor::KeyDescriptor,
-};
-use android_system_keystore2::binder::{wait_for_interface, Strong};
-use anyhow::{anyhow, Context, Result};
-use compos_aidl_interface::aidl::com::android::compos::CompOsKeyData::CompOsKeyData;
-use ring::rand::{SecureRandom, SystemRandom};
-use ring::signature;
-use scopeguard::ScopeGuard;
-
-/// Keystore2 namespace ID, used for access control to keys. In a VM we can use the generic ID
-/// allocated for payloads. See microdroid's keystore2_key_contexts.
-const KEYSTORE_NAMESPACE: i64 = 140;
-
-const KEYSTORE_SERVICE_NAME: &str = "android.system.keystore2.IKeystoreService/default";
-const PURPOSE_SIGN: KeyParameter =
-    KeyParameter { tag: Tag::PURPOSE, value: KeyParameterValue::KeyPurpose(KeyPurpose::SIGN) };
-const ALGORITHM: KeyParameter =
-    KeyParameter { tag: Tag::ALGORITHM, value: KeyParameterValue::Algorithm(Algorithm::RSA) };
-const PADDING: KeyParameter = KeyParameter {
-    tag: Tag::PADDING,
-    value: KeyParameterValue::PaddingMode(PaddingMode::RSA_PKCS1_1_5_SIGN),
-};
-const DIGEST: KeyParameter =
-    KeyParameter { tag: Tag::DIGEST, value: KeyParameterValue::Digest(Digest::SHA_2_256) };
-const KEY_SIZE: KeyParameter =
-    KeyParameter { tag: Tag::KEY_SIZE, value: KeyParameterValue::Integer(2048) };
-const EXPONENT: KeyParameter =
-    KeyParameter { tag: Tag::RSA_PUBLIC_EXPONENT, value: KeyParameterValue::LongInteger(65537) };
-const NO_AUTH_REQUIRED: KeyParameter =
-    KeyParameter { tag: Tag::NO_AUTH_REQUIRED, value: KeyParameterValue::BoolValue(true) };
-
-const BLOB_KEY_DESCRIPTOR: KeyDescriptor =
-    KeyDescriptor { domain: Domain::BLOB, nspace: KEYSTORE_NAMESPACE, alias: None, blob: None };
-
-/// An internal service for CompOS key management.
-#[derive(Clone)]
-pub struct CompOsKeyService {
-    random: SystemRandom,
-    security_level: Strong<dyn IKeystoreSecurityLevel>,
-}
-
-impl CompOsKeyService {
-    pub fn new() -> Result<Self> {
-        let keystore_service = wait_for_interface::<dyn IKeystoreService>(KEYSTORE_SERVICE_NAME)
-            .context("No Keystore service")?;
-
-        Ok(CompOsKeyService {
-            random: SystemRandom::new(),
-            security_level: keystore_service
-                .getSecurityLevel(SecurityLevel::TRUSTED_ENVIRONMENT)
-                .context("Getting SecurityLevel failed")?,
-        })
-    }
-
-    pub fn generate(&self) -> Result<CompOsKeyData> {
-        let key_descriptor = BLOB_KEY_DESCRIPTOR;
-        let key_parameters =
-            [PURPOSE_SIGN, ALGORITHM, PADDING, DIGEST, KEY_SIZE, EXPONENT, NO_AUTH_REQUIRED];
-        let attestation_key = None;
-        let flags = 0;
-        let entropy = [];
-
-        let key_metadata = self
-            .security_level
-            .generateKey(&key_descriptor, attestation_key, &key_parameters, flags, &entropy)
-            .context("Generating key failed")?;
-
-        if let (Some(certificate), Some(blob)) = (key_metadata.certificate, key_metadata.key.blob) {
-            Ok(CompOsKeyData { certificate, keyBlob: blob })
-        } else {
-            Err(anyhow!("Missing cert or blob"))
-        }
-    }
-
-    pub fn verify(&self, key_blob: &[u8], public_key: &[u8]) -> Result<()> {
-        let mut data = [0u8; 32];
-        self.random.fill(&mut data).context("No random data")?;
-
-        let signature = self.new_signer(key_blob).sign(&data)?;
-
-        let public_key =
-            signature::UnparsedPublicKey::new(&signature::RSA_PKCS1_2048_8192_SHA256, public_key);
-        public_key.verify(&data, &signature).context("Signature verification failed")?;
-
-        Ok(())
-    }
-
-    pub fn new_signer(&self, key_blob: &[u8]) -> Signer {
-        Signer { key_blob: key_blob.to_vec(), security_level: self.security_level.clone() }
-    }
-}
-
-pub struct Signer {
-    key_blob: Vec<u8>,
-    security_level: Strong<dyn IKeystoreSecurityLevel>,
-}
-
-impl Signer {
-    pub fn sign(self, data: &[u8]) -> Result<Vec<u8>> {
-        let key_descriptor = KeyDescriptor { blob: Some(self.key_blob), ..BLOB_KEY_DESCRIPTOR };
-        let operation_parameters = [PURPOSE_SIGN, ALGORITHM, PADDING, DIGEST];
-        let forced = false;
-
-        let response = self
-            .security_level
-            .createOperation(&key_descriptor, &operation_parameters, forced)
-            .context("Creating key failed")?;
-        let operation = scopeguard::guard(
-            response.iOperation.ok_or_else(|| anyhow!("No operation created"))?,
-            |op| op.abort().unwrap_or_default(),
-        );
-
-        if response.operationChallenge.is_some() {
-            return Err(anyhow!("Key requires user authorization"));
-        }
-
-        let signature = operation.finish(Some(data), None).context("Signing failed")?;
-        // Operation has finished, we're no longer responsible for aborting it
-        ScopeGuard::into_inner(operation);
-
-        signature.ok_or_else(|| anyhow!("No signature returned"))
-    }
-}
diff --git a/compos/src/compsvc.rs b/compos/src/compsvc.rs
index 356cc7e..422f271 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -27,12 +27,10 @@
 use std::sync::RwLock;
 
 use crate::compilation::{odrefresh, OdrefreshContext};
-use crate::compos_key_service::{CompOsKeyService, Signer};
-use crate::dice::Dice;
+use crate::signing_key::{Signer, SigningKey};
 use authfs_aidl_interface::aidl::com::android::virt::fs::IAuthFsService::IAuthFsService;
 use compos_aidl_interface::aidl::com::android::compos::{
     CompOsKeyData::CompOsKeyData,
-    FdAnnotation::FdAnnotation,
     ICompOsService::{BnCompOsService, ICompOsService},
 };
 use compos_aidl_interface::binder::{
@@ -46,7 +44,7 @@
 pub fn new_binder() -> Result<Strong<dyn ICompOsService>> {
     let service = CompOsService {
         odrefresh_path: PathBuf::from(ODREFRESH_PATH),
-        key_service: CompOsKeyService::new()?,
+        signing_key: SigningKey::new()?,
         key_blob: RwLock::new(Vec::new()),
     };
     Ok(BnCompOsService::new_binder(service, BinderFeatures::default()))
@@ -54,7 +52,7 @@
 
 struct CompOsService {
     odrefresh_path: PathBuf,
-    key_service: CompOsKeyService,
+    signing_key: SigningKey,
     key_blob: RwLock<Vec<u8>>,
 }
 
@@ -64,14 +62,9 @@
         if key.is_empty() {
             Err(new_binder_exception(ExceptionCode::ILLEGAL_STATE, "Key is not initialized"))
         } else {
-            Ok(self.key_service.new_signer(key))
+            to_binder_result(self.signing_key.new_signer(key))
         }
     }
-
-    fn get_boot_certificate_chain(&self) -> Result<Vec<u8>> {
-        let dice = Dice::new()?;
-        dice.get_boot_certificate_chain()
-    }
 }
 
 impl Interface for CompOsService {}
@@ -113,26 +106,18 @@
         Ok(exit_code as i8)
     }
 
-    fn compile(&self, _marshaled: &[u8], _fd_annotation: &FdAnnotation) -> BinderResult<i8> {
-        Err(new_binder_exception(ExceptionCode::UNSUPPORTED_OPERATION, "Not yet implemented"))
-    }
-
     fn generateSigningKey(&self) -> BinderResult<CompOsKeyData> {
-        to_binder_result(self.key_service.generate())
+        to_binder_result(self.signing_key.generate())
     }
 
     fn verifySigningKey(&self, key_blob: &[u8], public_key: &[u8]) -> BinderResult<bool> {
-        Ok(if let Err(e) = self.key_service.verify(key_blob, public_key) {
+        Ok(if let Err(e) = self.signing_key.verify(key_blob, public_key) {
             warn!("Signing key verification failed: {:?}", e);
             false
         } else {
             true
         })
     }
-
-    fn getBootCertificateChain(&self) -> BinderResult<Vec<u8>> {
-        to_binder_result(self.get_boot_certificate_chain())
-    }
 }
 
 fn get_authfs_service() -> BinderResult<Strong<dyn IAuthFsService>> {
diff --git a/compos/src/compsvc_main.rs b/compos/src/compsvc_main.rs
index b4e3128..c2923f0 100644
--- a/compos/src/compsvc_main.rs
+++ b/compos/src/compsvc_main.rs
@@ -17,11 +17,12 @@
 //! A tool to start a standalone compsvc server that serves over RPC binder.
 
 mod artifact_signer;
+mod blob_encryption;
 mod compilation;
-mod compos_key_service;
 mod compsvc;
 mod dice;
 mod fsverity;
+mod signing_key;
 
 use android_system_virtualmachineservice::{
     aidl::android::system::virtualmachineservice::IVirtualMachineService::{
diff --git a/compos/src/dice.rs b/compos/src/dice.rs
index 22a7ee2..9f66b5e 100644
--- a/compos/src/dice.rs
+++ b/compos/src/dice.rs
@@ -14,7 +14,7 @@
  * limitations under the License.
  */
 
-//! Handles the use of DICE as the source of our unique signing key via diced / IDiceNode.
+//! Handles the use of DICE (via diced / IDiceNode) for accessing our VM's unique secret.
 
 use android_security_dice::aidl::android::security::dice::IDiceNode::IDiceNode;
 use android_security_dice::binder::{wait_for_interface, Strong};
@@ -31,12 +31,9 @@
         Ok(Self { node: dice_service })
     }
 
-    pub fn get_boot_certificate_chain(&self) -> Result<Vec<u8>> {
-        let input_values = []; // Get our BCC, not a child's
-        let bcc = self
-            .node
-            .getAttestationChain(&input_values)
-            .context("Getting attestation chain failed")?;
-        Ok(bcc.data)
+    pub fn get_sealing_cdi(&self) -> Result<Vec<u8>> {
+        let input_values = [];
+        let bcc_handover = self.node.derive(&input_values).context("Failed to retrieve CDI")?;
+        Ok(bcc_handover.cdiSeal.to_vec())
     }
 }
diff --git a/compos/src/signing_key.rs b/compos/src/signing_key.rs
new file mode 100644
index 0000000..175a11b
--- /dev/null
+++ b/compos/src/signing_key.rs
@@ -0,0 +1,97 @@
+/*
+ * Copyright 2022 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.
+ */
+
+//! RSA key pair generation, persistence (with the private key encrypted), verification and
+//! signing.
+
+#![allow(dead_code, unused_variables)]
+
+use crate::blob_encryption;
+use crate::dice::Dice;
+use anyhow::{bail, Context, Result};
+use compos_aidl_interface::aidl::com::android::compos::CompOsKeyData::CompOsKeyData;
+use ring::{
+    rand::{SecureRandom, SystemRandom},
+    signature,
+};
+
+pub struct SigningKey {
+    _unused: (), // Prevent construction other than by new()
+}
+
+impl SigningKey {
+    pub fn new() -> Result<Self> {
+        Ok(Self { _unused: () })
+    }
+
+    pub fn generate(&self) -> Result<CompOsKeyData> {
+        let key_result = compos_native::generate_key_pair();
+        if key_result.public_key.is_empty() || key_result.private_key.is_empty() {
+            bail!("Failed to generate key pair: {}", key_result.error);
+        }
+
+        let encrypted = encrypt_private_key(&Dice::new()?, &key_result.private_key)?;
+        Ok(CompOsKeyData { publicKey: key_result.public_key, keyBlob: encrypted })
+    }
+
+    pub fn verify(&self, key_blob: &[u8], public_key: &[u8]) -> Result<()> {
+        // We verify the private key by verifying the AEAD authentication tag in the signer.
+        // To verify the public key matches, we sign a block of random data with the private key,
+        // and then check that the signature matches the purported key.
+        let mut data = [0u8; 32]; // Size is fairly arbitrary.
+        SystemRandom::new().fill(&mut data).context("No random data")?;
+
+        let signature = self.new_signer(key_blob)?.sign(&data)?;
+
+        let public_key =
+            signature::UnparsedPublicKey::new(&signature::RSA_PKCS1_2048_8192_SHA256, public_key);
+        public_key.verify(&data, &signature).context("Signature verification failed")?;
+
+        Ok(())
+    }
+
+    pub fn new_signer(&self, key_blob: &[u8]) -> Result<Signer> {
+        Ok(Signer { key_blob: key_blob.to_owned(), dice: Dice::new()? })
+    }
+}
+
+pub struct Signer {
+    key_blob: Vec<u8>,
+    dice: Dice,
+}
+
+impl Signer {
+    pub fn sign(self, data: &[u8]) -> Result<Vec<u8>> {
+        let private_key = decrypt_private_key(&self.dice, &self.key_blob)?;
+        let sign_result = compos_native::sign(&private_key, data);
+        if sign_result.signature.is_empty() {
+            bail!("Failed to sign: {}", sign_result.error);
+        }
+        Ok(sign_result.signature)
+    }
+}
+
+fn encrypt_private_key(dice: &Dice, private_key: &[u8]) -> Result<Vec<u8>> {
+    let cdi = dice.get_sealing_cdi()?;
+    let aead_key = blob_encryption::derive_aead_key(&cdi)?;
+    blob_encryption::encrypt_bytes(aead_key, private_key)
+}
+
+fn decrypt_private_key(dice: &Dice, blob: &[u8]) -> Result<Vec<u8>> {
+    let cdi = dice.get_sealing_cdi()?;
+    let aead_key = blob_encryption::derive_aead_key(&cdi)?;
+    blob_encryption::decrypt_bytes(aead_key, blob)
+}
diff --git a/compos/tests/java/android/compos/test/ComposTestCase.java b/compos/tests/java/android/compos/test/ComposTestCase.java
index 1e439af..ff3baa2 100644
--- a/compos/tests/java/android/compos/test/ComposTestCase.java
+++ b/compos/tests/java/android/compos/test/ComposTestCase.java
@@ -181,8 +181,8 @@
             throws Exception {
         // Sort by filename (second column) to make comparison easier. Filter out compos.info and
         // compos.info.signature since it's only generated by CompOS.
-        // TODO(b/210473615): Remove irrelevant APEXes (i.e. those aren't contributing to the
-        // classpaths, thus not in the VM) from cache-info.xml.
+        // TODO(b/211458160): Remove cache-info.xml once we can plumb timestamp and isFactory of
+        // APEXes to the VM.
         return runner.run("cd " + path + "; find -type f -exec sha256sum {} \\;"
                 + "| grep -v cache-info.xml | grep -v compos.info"
                 + "| sort -k2");
diff --git a/microdroid/Android.bp b/microdroid/Android.bp
index 7f0b806..e078108 100644
--- a/microdroid/Android.bp
+++ b/microdroid/Android.bp
@@ -72,11 +72,11 @@
 
         "apexd",
         "debuggerd",
-        "diced",
+        "diced.microdroid",
         "keystore2_microdroid",
         "linker",
         "linkerconfig",
-        "servicemanager",
+        "servicemanager.microdroid",
         "tombstoned",
         "cgroups.json",
         "public.libraries.android.txt",
@@ -178,6 +178,7 @@
     partition_name: "vendor",
     use_avb: true,
     deps: [
+        "android.hardware.security.dice-service.microdroid",
         "android.hardware.security.keymint-service.microdroid",
         "microdroid_fstab",
         "microdroid_precompiled_sepolicy.plat_sepolicy_and_mapping.sha256",
diff --git a/microdroid/dice/Android.bp b/microdroid/dice/Android.bp
new file mode 100644
index 0000000..8026581
--- /dev/null
+++ b/microdroid/dice/Android.bp
@@ -0,0 +1,29 @@
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
+rust_binary {
+    name: "android.hardware.security.dice-service.microdroid",
+    srcs: ["service.rs"],
+    relative_install_path: "hw",
+    vendor: true,
+    prefer_rlib: true,
+    rustlibs: [
+        "android.hardware.security.dice-V1-rust",
+        "libandroid_logger",
+        "libanyhow",
+        "libbinder_rs",
+        "libbyteorder",
+        "libdiced_open_dice_cbor",
+        "libdiced_sample_inputs",
+        "libdiced_vendor",
+        "liblibc",
+        "liblog_rust",
+        "libserde",
+    ],
+    init_rc: ["android.hardware.security.dice-service.microdroid.rc"],
+    vintf_fragments: [
+        "android.hardware.security.dice-service.microdroid.xml",
+    ],
+    bootstrap: true,
+}
diff --git a/microdroid/dice/android.hardware.security.dice-service.microdroid.rc b/microdroid/dice/android.hardware.security.dice-service.microdroid.rc
new file mode 100644
index 0000000..162081e
--- /dev/null
+++ b/microdroid/dice/android.hardware.security.dice-service.microdroid.rc
@@ -0,0 +1,3 @@
+service vendor.dice-microdroid /vendor/bin/hw/android.hardware.security.dice-service.microdroid
+    class early_hal
+    user nobody
diff --git a/microdroid/dice/android.hardware.security.dice-service.microdroid.xml b/microdroid/dice/android.hardware.security.dice-service.microdroid.xml
new file mode 100644
index 0000000..cf6c482
--- /dev/null
+++ b/microdroid/dice/android.hardware.security.dice-service.microdroid.xml
@@ -0,0 +1,6 @@
+<manifest version="1.0" type="device">
+    <hal format="aidl">
+        <name>android.hardware.security.dice</name>
+        <fqname>IDiceDevice/default</fqname>
+    </hal>
+</manifest>
diff --git a/microdroid/dice/service.rs b/microdroid/dice/service.rs
new file mode 100644
index 0000000..3401654
--- /dev/null
+++ b/microdroid/dice/service.rs
@@ -0,0 +1,117 @@
+// Copyright 2022, 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.
+
+//! Main entry point for the microdroid IDiceDevice HAL implementation.
+
+use anyhow::Result;
+use diced::{
+    dice,
+    hal_node::{DiceArtifacts, DiceDevice, ResidentHal, UpdatableDiceArtifacts},
+};
+use serde::{Deserialize, Serialize};
+use std::panic;
+use std::sync::Arc;
+
+const DICE_HAL_SERVICE_NAME: &str = "android.hardware.security.dice.IDiceDevice/default";
+
+/// Artifacts that are kept in the process address space after the artifacts
+/// from the driver have been consumed.
+#[derive(Clone, Serialize, Deserialize)]
+struct RawArtifacts {
+    cdi_attest: [u8; dice::CDI_SIZE],
+    cdi_seal: [u8; dice::CDI_SIZE],
+    bcc: Vec<u8>,
+}
+
+impl DiceArtifacts for RawArtifacts {
+    fn cdi_attest(&self) -> &[u8; dice::CDI_SIZE] {
+        &self.cdi_attest
+    }
+    fn cdi_seal(&self) -> &[u8; dice::CDI_SIZE] {
+        &self.cdi_seal
+    }
+    fn bcc(&self) -> Vec<u8> {
+        // The BCC only contains public information so it's fine to copy.
+        self.bcc.clone()
+    }
+}
+
+#[derive(Clone, Serialize, Deserialize)]
+enum DriverArtifactManager {
+    Updated(RawArtifacts),
+}
+
+impl DriverArtifactManager {
+    fn new() -> Self {
+        // TODO(214231981): replace with true values passed by bootloader
+        let (cdi_attest, cdi_seal, bcc) = diced_sample_inputs::make_sample_bcc_and_cdis()
+            .expect("Failed to create sample dice artifacts.");
+        Self::Updated(RawArtifacts {
+            cdi_attest: cdi_attest[..].try_into().unwrap(),
+            cdi_seal: cdi_seal[..].try_into().unwrap(),
+            bcc,
+        })
+    }
+}
+
+impl UpdatableDiceArtifacts for DriverArtifactManager {
+    fn with_artifacts<F, T>(&self, f: F) -> Result<T>
+    where
+        F: FnOnce(&dyn DiceArtifacts) -> Result<T>,
+    {
+        match self {
+            Self::Updated(raw_artifacts) => f(raw_artifacts),
+        }
+    }
+    fn update(self, new_artifacts: &impl DiceArtifacts) -> Result<Self> {
+        Ok(Self::Updated(RawArtifacts {
+            cdi_attest: *new_artifacts.cdi_attest(),
+            cdi_seal: *new_artifacts.cdi_seal(),
+            bcc: new_artifacts.bcc(),
+        }))
+    }
+}
+
+fn main() {
+    android_logger::init_once(
+        android_logger::Config::default()
+            .with_tag("android.hardware.security.dice")
+            .with_min_level(log::Level::Debug),
+    );
+    // Redirect panic messages to logcat.
+    panic::set_hook(Box::new(|panic_info| {
+        log::error!("{}", panic_info);
+    }));
+
+    // Saying hi.
+    log::info!("android.hardware.security.dice is starting.");
+
+    let hal_impl = Arc::new(
+        unsafe {
+            // Safety: ResidentHal cannot be used in multi threaded processes.
+            // This service does not start a thread pool. The main thread is the only thread
+            // joining the thread pool, thereby keeping the process single threaded.
+            ResidentHal::new(DriverArtifactManager::new())
+        }
+        .expect("Failed to create ResidentHal implementation."),
+    );
+
+    let hal = DiceDevice::new_as_binder(hal_impl).expect("Failed to construct hal service.");
+
+    binder::add_service(DICE_HAL_SERVICE_NAME, hal.as_binder())
+        .expect("Failed to register IDiceDevice Service");
+
+    log::info!("Joining thread pool now.");
+    binder::ProcessState::join_thread_pool();
+}
diff --git a/microdroid/init.rc b/microdroid/init.rc
index 31c06d3..e76260e 100644
--- a/microdroid/init.rc
+++ b/microdroid/init.rc
@@ -17,25 +17,6 @@
 
     start ueventd
 
-    mkdir /mnt/apk 0755 system system
-    mkdir /mnt/extra-apk 0755 root root
-    # Microdroid_manager starts apkdmverity/zipfuse/apexd
-    start microdroid_manager
-
-    # restorecon so microdroid_manager can create subdirectories
-    restorecon /mnt/extra-apk
-
-    # Wait for apexd to finish activating APEXes before starting more processes.
-    wait_for_prop apexd.status activated
-    perform_apex_config
-
-    # Notify to microdroid_manager that perform_apex_config is done.
-    # Microdroid_manager shouldn't execute payload before this, because app
-    # payloads are not designed to run with bootstrap bionic
-    setprop apex_config.done true
-
-    setprop ro.debuggable ${ro.boot.microdroid.debuggable:-0}
-
 on init
     # Mount binderfs
     mkdir /dev/binderfs
@@ -78,18 +59,36 @@
     chmod 0664 /dev/cpuset/background/tasks
     chmod 0664 /dev/cpuset/system-background/tasks
 
-on init && property:ro.boot.logd.enabled=1
-    # Start logd before any other services run to ensure we capture all of their logs.
-    start logd
-
-on init
     start servicemanager
 
+    start vendor.dice-microdroid
+    start diced
+
+    mkdir /mnt/apk 0755 system system
+    mkdir /mnt/extra-apk 0755 root root
+    # Microdroid_manager starts apkdmverity/zipfuse/apexd
+    start microdroid_manager
+
+    # restorecon so microdroid_manager can create subdirectories
+    restorecon /mnt/extra-apk
+
+    # Wait for apexd to finish activating APEXes before starting more processes.
+    wait_for_prop apexd.status activated
+    perform_apex_config
+
+    # Notify to microdroid_manager that perform_apex_config is done.
+    # Microdroid_manager shouldn't execute payload before this, because app
+    # payloads are not designed to run with bootstrap bionic
+    setprop apex_config.done true
+
+    setprop ro.debuggable ${ro.boot.microdroid.debuggable:-0}
+
     # TODO(b/185767624): remove hidl after full keymint support
     start hwservicemanager
 
-    # TODO(b/214231981): start diced (and servicemanager) earlier than microdroid_manager.
-    start diced
+on init && property:ro.boot.logd.enabled=1
+    # Start logd before any other services run to ensure we capture all of their logs.
+    start logd
 
 on init && property:ro.boot.adb.enabled=1
     start adbd
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 23a61d9..f888b80 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -9,6 +9,7 @@
     edition: "2018",
     prefer_rlib: true,
     rustlibs: [
+        "android.security.dice-rust",
         "android.system.virtualizationservice-rust",
         "android.system.virtualmachineservice-rust",
         "libanyhow",
diff --git a/microdroid_manager/src/instance.rs b/microdroid_manager/src/instance.rs
index cb59e3b..1068792 100644
--- a/microdroid_manager/src/instance.rs
+++ b/microdroid_manager/src/instance.rs
@@ -33,7 +33,9 @@
 //! The payload of a partition is encrypted/signed by a key that is unique to the loader and to the
 //! VM as well. Failing to decrypt/authenticate a partition by a loader stops the boot process.
 
+use android_security_dice::aidl::android::security::dice::IDiceNode::IDiceNode;
 use anyhow::{anyhow, bail, Context, Result};
+use binder::wait_for_interface;
 use byteorder::{LittleEndian, ReadBytesExt, WriteBytesExt};
 use ring::aead::{Aad, Algorithm, LessSafeKey, Nonce, UnboundKey, AES_256_GCM};
 use ring::hkdf::{Salt, HKDF_SHA256};
@@ -131,7 +133,7 @@
 
         // Decrypt and authenticate the data (along with the header). The data is decrypted in
         // place. `open_in_place` returns slice to the decrypted part in the buffer.
-        let plaintext_len = get_key().open_in_place(nonce, Aad::from(&header), &mut data)?.len();
+        let plaintext_len = get_key()?.open_in_place(nonce, Aad::from(&header), &mut data)?.len();
         // Truncate to remove the tag
         data.truncate(plaintext_len);
 
@@ -174,7 +176,7 @@
 
         // Then encrypt and sign the data. The non-encrypted input data is copied to a vector
         // because it is encrypted in place, and also the tag is appended.
-        get_key().seal_in_place_append_tag(nonce, Aad::from(&header), &mut data)?;
+        get_key()?.seal_in_place_append_tag(nonce, Aad::from(&header), &mut data)?;
 
         // Persist the encrypted payload data
         self.file.write_all(&data)?;
@@ -276,17 +278,18 @@
 
 /// Returns the key that is used to encrypt the microdroid manager partition. It is derived from
 /// the sealing CDI of the previous stage, which is Android Boot Loader (ABL).
-fn get_key() -> ZeroOnDropKey {
-    // Sealing CDI from the previous stage. For now, this is hardcoded.
-    // TODO(jiyong): actually read this from the previous stage
-    const SEALING_CDI: [u8; 32] = [10; 32];
+fn get_key() -> Result<ZeroOnDropKey> {
+    // Sealing CDI from the previous stage.
+    let diced = wait_for_interface::<dyn IDiceNode>("android.security.dice.IDiceNode")
+        .context("IDiceNode service not found")?;
+    let bcc_handover = diced.derive(&[]).context("Failed to get BccHandover")?;
 
     // Derive a key from the Sealing CDI
     // Step 1 is extraction: https://datatracker.ietf.org/doc/html/rfc5869#section-2.2 where a
     // pseduo random key (PRK) is extracted from (Input Keying Material - IKM, which is secret) and
     // optional salt.
     let salt = Salt::new(HKDF_SHA256, &[]); // use 0 as salt
-    let prk = salt.extract(&SEALING_CDI); // Sealing CDI as IKM
+    let prk = salt.extract(&bcc_handover.cdiSeal); // Sealing CDI as IKM
 
     // Step 2 is expansion: https://datatracker.ietf.org/doc/html/rfc5869#section-2.3 where the PRK
     // (optionally with the `info` which gives contextual information) is expanded into the output
@@ -308,7 +311,7 @@
         ::std::ptr::write_volatile::<[u8; 32]>(&mut key, [0; 32]);
     }
 
-    ret
+    Ok(ret)
 }
 
 #[derive(Debug, Serialize, Deserialize, PartialEq)]
diff --git a/statslog_virtualization/Android.bp b/statslog_virtualization/Android.bp
index 846d12b..51a51a3 100644
--- a/statslog_virtualization/Android.bp
+++ b/statslog_virtualization/Android.bp
@@ -14,6 +14,10 @@
 // limitations under the License.
 
 // Autogenerate the class (and respective headers) with logging methods and constants
+package {
+    default_applicable_licenses: ["Android-Apache-2.0"],
+}
+
 genrule {
     name: "statslog_virtualization_header.rs",
     tools: ["stats-log-api-gen"],
diff --git a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
index cdcb2bd..208d61f 100644
--- a/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
+++ b/tests/aidl/com/android/microdroid/testservice/ITestService.aidl
@@ -21,4 +21,7 @@
 
     /* add two integers. */
     int addInteger(int a, int b);
+
+    /* read a system property. */
+    String readProperty(String prop);
 }
diff --git a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
index 69638d8..6aa7566 100644
--- a/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
+++ b/tests/hostside/java/android/virt/test/MicrodroidTestCase.java
@@ -97,15 +97,6 @@
         final String label = "u:object_r:system_file:s0";
         assertThat(runOnMicrodroid("ls", "-Z", testLib), is(label + " " + testLib));
 
-        // Check if the command in vm_config.json was executed by examining the side effect of the
-        // command
-        assertThat(runOnMicrodroid("getprop", "debug.microdroid.app.run"), is("true"));
-        assertThat(runOnMicrodroid("getprop", "debug.microdroid.app.sublib.run"), is("true"));
-
-        // Check that keystore was found by the payload. Wait until the property is set.
-        tryRunOnMicrodroid("watch -e \"getprop debug.microdroid.test.keystore | grep '^$'\"");
-        assertThat(runOnMicrodroid("getprop", "debug.microdroid.test.keystore"), is("PASS"));
-
         // Check that no denials have happened so far
         assertThat(runOnMicrodroid("logcat -d -e 'avc:[[:space:]]{1,2}denied'"), is(""));
 
diff --git a/tests/testapk/Android.bp b/tests/testapk/Android.bp
index 32c47dd..6cd16c2 100644
--- a/tests/testapk/Android.bp
+++ b/tests/testapk/Android.bp
@@ -9,6 +9,7 @@
     static_libs: [
         "androidx.test.runner",
         "androidx.test.ext.junit",
+        "com.android.microdroid.testservice-java",
     ],
     libs: ["android.system.virtualmachine"],
     jni_libs: ["MicrodroidTestNativeLib"],
@@ -29,6 +30,11 @@
         "libbinder_rpc_unstable",
         "MicrodroidTestNativeLibSub",
     ],
+    static_libs: [
+        "libfsverity_digests_proto_cc",
+        "liblog",
+        "libprotobuf-cpp-lite-ndk",
+    ],
 }
 
 cc_library_shared {
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 b03a915..032ecfd 100644
--- a/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
+++ b/tests/testapk/src/java/com/android/microdroid/test/MicrodroidTests.java
@@ -17,8 +17,10 @@
 
 import static org.hamcrest.core.Is.is;
 import static org.hamcrest.core.IsNot.not;
+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;
 
@@ -26,6 +28,7 @@
 
 import android.content.Context;
 import android.os.Build;
+import android.os.IBinder;
 import android.os.ParcelFileDescriptor;
 import android.system.virtualmachine.VirtualMachine;
 import android.system.virtualmachine.VirtualMachineCallback;
@@ -36,6 +39,8 @@
 
 import androidx.test.core.app.ApplicationProvider;
 
+import com.android.microdroid.testservice.ITestService;
+
 import org.junit.After;
 import org.junit.Before;
 import org.junit.Rule;
@@ -49,6 +54,7 @@
 import java.nio.file.Files;
 import java.util.concurrent.ExecutorService;
 import java.util.concurrent.Executors;
+import java.util.concurrent.Future;
 import java.util.concurrent.TimeUnit;
 
 @RunWith(JUnit4.class)
@@ -132,9 +138,10 @@
     private static final int MIN_MEM_X86_64 = 196;
 
     @Test
-    public void startAndStop() throws VirtualMachineException, InterruptedException {
+    public void connectToVmService() throws VirtualMachineException, InterruptedException {
         VirtualMachineConfig.Builder builder =
-                new VirtualMachineConfig.Builder(mInner.mContext, "assets/vm_config.json");
+                new VirtualMachineConfig.Builder(mInner.mContext,
+                        "assets/vm_config_extra_apk.json");
         if (Build.SUPPORTED_ABIS.length > 0) {
             String primaryAbi = Build.SUPPORTED_ABIS[0];
             switch(primaryAbi) {
@@ -148,24 +155,55 @@
         }
         VirtualMachineConfig config = builder.build();
 
-        mInner.mVm = mInner.mVmm.getOrCreate("test_vm", config);
+        mInner.mVm = mInner.mVmm.getOrCreate("test_vm_extra_apk", config);
         VmEventListener listener =
                 new VmEventListener() {
                     private boolean mPayloadReadyCalled = false;
                     private boolean mPayloadStartedCalled = false;
 
-                    @Override
-                    public void onPayloadStarted(VirtualMachine vm, ParcelFileDescriptor stream) {
-                        mPayloadStartedCalled = true;
+                    private void testVMService(Future<IBinder> service) {
+                        try {
+                            IBinder binder = service.get();
+
+                            ITestService testService = ITestService.Stub.asInterface(binder);
+                            assertEquals(
+                                    testService.addInteger(123, 456),
+                                    123 + 456);
+                            assertEquals(
+                                    testService.readProperty("debug.microdroid.app.run"),
+                                    "true");
+                            assertEquals(
+                                    testService.readProperty("debug.microdroid.app.sublib.run"),
+                                    "true");
+                            assertEquals(
+                                    testService.readProperty("debug.microdroid.test.keystore"),
+                                    "PASS");
+                            assertEquals(
+                                    testService.readProperty("debug.microdroid.test.extra_apk"),
+                                    "PASS");
+                        } catch (Exception e) {
+                            fail("Exception while testing service: " + e.toString());
+                        }
                     }
 
                     @Override
                     public void onPayloadReady(VirtualMachine vm) {
                         mPayloadReadyCalled = true;
+                        try {
+                            testVMService(vm.connectToVsockServer(ITestService.SERVICE_PORT));
+                        } catch (Exception e) {
+                            fail("Exception while connecting to service: " + e.toString());
+                        }
+
                         forceStop(vm);
                     }
 
                     @Override
+                    public void onPayloadStarted(VirtualMachine vm, ParcelFileDescriptor stream) {
+                        mPayloadStartedCalled = true;
+                    }
+
+                    @Override
                     public void onDied(VirtualMachine vm, @DeathReason int reason) {
                         assertTrue(mPayloadReadyCalled);
                         assertTrue(mPayloadStartedCalled);
diff --git a/tests/testapk/src/native/testbinary.cpp b/tests/testapk/src/native/testbinary.cpp
index f56b261..c748b2a 100644
--- a/tests/testapk/src/native/testbinary.cpp
+++ b/tests/testapk/src/native/testbinary.cpp
@@ -16,11 +16,14 @@
 #include <aidl/android/system/keystore2/IKeystoreService.h>
 #include <aidl/android/system/virtualmachineservice/IVirtualMachineService.h>
 #include <aidl/com/android/microdroid/testservice/BnTestService.h>
+#include <android-base/file.h>
+#include <android-base/properties.h>
 #include <android-base/result.h>
 #include <android-base/unique_fd.h>
 #include <android/binder_auto_utils.h>
 #include <android/binder_manager.h>
 #include <fcntl.h>
+#include <fsverity_digests.pb.h>
 #include <linux/vm_sockets.h>
 #include <stdint.h>
 #include <stdio.h>
@@ -29,6 +32,7 @@
 #include <unistd.h>
 
 #include <binder_rpc_unstable.hpp>
+#include <string>
 
 using aidl::android::hardware::security::keymint::Algorithm;
 using aidl::android::hardware::security::keymint::Digest;
@@ -191,8 +195,8 @@
         outcome << "PASS";
     } else {
         outcome << "FAIL: " << result.error();
-        // Pollute stdout with the error in case the property is truncated.
-        std::cout << "[" << name << "] test failed: " << result.error() << "\n";
+        // Pollute stderr with the error in case the property is truncated.
+        std::cerr << "[" << name << "] test failed: " << result.error() << "\n";
     }
     __system_property_set(property.c_str(), outcome.str().c_str());
     return result;
@@ -204,6 +208,17 @@
             *out = a + b;
             return ndk::ScopedAStatus::ok();
         }
+
+        ndk::ScopedAStatus readProperty(const std::string& prop, std::string* out) override {
+            *out = android::base::GetProperty(prop, "");
+            if (out->empty()) {
+                std::string msg = "cannot find property " + prop;
+                return ndk::ScopedAStatus::fromExceptionCodeWithMessage(EX_SERVICE_SPECIFIC,
+                                                                        msg.c_str());
+            }
+
+            return ndk::ScopedAStatus::ok();
+        }
     };
     auto testService = ndk::SharedRefBase::make<TestService>();
 
@@ -230,6 +245,21 @@
     return {};
 }
 
+Result<void> verify_apk() {
+    const char* path = "/mnt/extra-apk/0/assets/build_manifest.pb";
+
+    std::string str;
+    if (!android::base::ReadFileToString(path, &str)) {
+        return ErrnoError() << "failed to read build_manifest.pb";
+    }
+
+    if (!android::security::fsverity::FSVerityDigests().ParseFromString(str)) {
+        return Error() << "invalid build_manifest.pb";
+    }
+
+    return {};
+}
+
 } // Anonymous namespace
 
 extern "C" int android_native_main(int argc, char* argv[]) {
@@ -249,6 +279,9 @@
     testlib_sub();
     printf("\n");
 
+    // Extra apks may be missing; this is not a fatal error
+    report_test("extra_apk", verify_apk());
+
     __system_property_set("debug.microdroid.app.run", "true");
     if (!report_test("keystore", test_keystore()).ok()) return 1;