Microdroid: Map a dm-crypt dev on (virtio-blk)disk

1. microdroid_manager, on seeing a (named) block device dedicated for
   storage, will run encryptedstore binary.
2. The key derived for the encryption will be derived from the dice
   using the CDIs of *payload* as hashes.
3. encryptedstore binary will create the dm-crypt device using the
   libdm_rust library.

Note: The salt used for the key is deterministic but I randomly got from
/dev/urandom. This ensures the key & payload secret are different.

Test: Run bin/vm run-app using --storage & --storage-size flag
Test: Write into the crypt device & check persistence by running another
VM with same instance image.
Bug: 241541860

Change-Id: I11d00343a040935dd90a232fe7c5ab4e06b6d145
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 4b06b3e..44b4c01 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -23,6 +23,7 @@
         "libdiced_sample_inputs",
         "libdiced_utils",
         "libglob",
+        "libhex",
         "libitertools",
         "libkernlog",
         "libkeystore2_crypto_rust",
diff --git a/microdroid_manager/src/dice.rs b/microdroid_manager/src/dice.rs
index 3881db3..499835f 100644
--- a/microdroid_manager/src/dice.rs
+++ b/microdroid_manager/src/dice.rs
@@ -38,6 +38,16 @@
     pub bcc: Vec<u8>,
 }
 
+impl DiceContext {
+    pub fn get_sealing_key(&self, salt: &[u8], identifier: &[u8], keysize: u32) -> Result<ZVec> {
+        // Deterministically derive a key to use for sealing data based on salt. Use different salt
+        // for different keys.
+        let mut key = ZVec::new(keysize as usize)?;
+        hkdf(&mut key, Md::sha256(), &self.cdi_seal, salt, identifier)?;
+        Ok(key)
+    }
+}
+
 /// Artifacts that are mapped into the process address space from the driver.
 pub enum DiceDriver<'a> {
     Real {
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index c18dd26..cc4b9dc 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -80,6 +80,12 @@
 // SYNC WITH virtualizationservice/src/crosvm.rs
 const FAILURE_SERIAL_DEVICE: &str = "/dev/ttyS1";
 
+/// Identifier for the key used for encrypted store.
+const ENCRYPTEDSTORE_BACKING_DEVICE: &str = "/dev/block/by-name/encryptedstore";
+const ENCRYPTEDSTORE_BIN: &str = "/system/bin/encryptedstore";
+const ENCRYPTEDSTORE_KEY_IDENTIFIER: &str = "encryptedstore_key";
+const ENCRYPTEDSTORE_KEYSIZE: u32 = 64;
+
 #[derive(thiserror::Error, Debug)]
 enum MicrodroidError {
     #[error("Cannot connect to virtualization service: {0}")]
@@ -350,7 +356,15 @@
 
     // To minimize the exposure to untrusted data, derive dice profile as soon as possible.
     info!("DICE derivation for payload");
-    let dice = dice_derivation(dice, &verified_data, &payload_metadata)?;
+    let dice_context = dice_derivation(dice, &verified_data, &payload_metadata)?;
+
+    // Run encryptedstore binary to prepare the storage
+    let encryptedstore_child = if Path::new(ENCRYPTEDSTORE_BACKING_DEVICE).exists() {
+        info!("Preparing encryptedstore ...");
+        Some(prepare_encryptedstore(&dice_context).context("encryptedstore run")?)
+    } else {
+        None
+    };
 
     // Before reading a file from the APK, start zipfuse
     run_zipfuse(
@@ -404,7 +418,12 @@
     // Wait until zipfuse has mounted the APK so we can access the payload
     wait_for_property_true(APK_MOUNT_DONE_PROP).context("Failed waiting for APK mount done")?;
 
-    register_vm_payload_service(allow_restricted_apis, service.clone(), dice)?;
+    register_vm_payload_service(allow_restricted_apis, service.clone(), dice_context)?;
+
+    if let Some(mut child) = encryptedstore_child {
+        let exitcode = child.wait().context("Wait for encryptedstore child")?;
+        ensure!(exitcode.success(), "Unable to prepare encrypted storage. Exitcode={}", exitcode);
+    }
 
     system_properties::write("dev.bootcomplete", "1").context("set dev.bootcomplete")?;
     exec_task(task, service).context("Failed to run payload")
@@ -802,3 +821,27 @@
 fn to_hex_string(buf: &[u8]) -> String {
     buf.iter().map(|b| format!("{:02X}", b)).collect()
 }
+
+fn prepare_encryptedstore(dice: &DiceContext) -> Result<Child> {
+    // Use a fixed salt to scope the derivation to this API.
+    // Generated using hexdump -vn32 -e'14/1 "0x%02X, " 1 "\n"' /dev/urandom
+    // TODO(b/241541860) : Move this (& other salts) to a salt container, i.e. a global enum
+    let salt = [
+        0xFC, 0x1D, 0x35, 0x7B, 0x96, 0xF3, 0xEF, 0x17, 0x78, 0x7D, 0x70, 0xED, 0xEA, 0xFE, 0x1D,
+        0x6F, 0xB3, 0xF9, 0x40, 0xCE, 0xDD, 0x99, 0x40, 0xAA, 0xA7, 0x0E, 0x92, 0x73, 0x90, 0x86,
+        0x4A, 0x75,
+    ];
+    let key = dice.get_sealing_key(
+        &salt,
+        ENCRYPTEDSTORE_KEY_IDENTIFIER.as_bytes(),
+        ENCRYPTEDSTORE_KEYSIZE,
+    )?;
+
+    let mut cmd = Command::new(ENCRYPTEDSTORE_BIN);
+    cmd.arg("--blkdevice")
+        .arg(ENCRYPTEDSTORE_BACKING_DEVICE)
+        .arg("--key")
+        .arg(hex::encode(&*key))
+        .spawn()
+        .context("encryptedstore failed")
+}