Merge changes from topic "compos_keygen"

* changes:
  Remove the ability to query CompOS BCC
  Migrate off keystore
  Key blob protection using AEAD
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/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/ICompOsService.aidl b/compos/aidl/com/android/compos/ICompOsService.aidl
index 39e9d61..cead5d0 100644
--- a/compos/aidl/com/android/compos/ICompOsService.aidl
+++ b/compos/aidl/com/android/compos/ICompOsService.aidl
@@ -66,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/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 4312a5a..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>
 
@@ -331,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();
@@ -379,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/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/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/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/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 b4af9b5..422f271 100644
--- a/compos/src/compsvc.rs
+++ b/compos/src/compsvc.rs
@@ -27,8 +27,7 @@
 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,
@@ -45,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()))
@@ -53,7 +52,7 @@
 
 struct CompOsService {
     odrefresh_path: PathBuf,
-    key_service: CompOsKeyService,
+    signing_key: SigningKey,
     key_blob: RwLock<Vec<u8>>,
 }
 
@@ -63,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,21 +107,17 @@
     }
 
     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)
+}