Pass a VM secret to KeyMint from microdroid_manager

This secret will be used to protect the keyblobs so that only a VM that
gets the same secret will be able to use those blobs. It is held in a
system property so that it won't be lost should KeyMint happen to
restart and has SELinux rules to ensure only microdroid_manager can set
the value and only KeyMint can read the value.

Bug: 190578423
Test: atest MicrodroidHostTestCases
Change-Id: I675cc9d6e9942090a761b83a6b9456b5c9909747
diff --git a/microdroid/keymint/service.cpp b/microdroid/keymint/service.cpp
index 325e852..8e20f44 100644
--- a/microdroid/keymint/service.cpp
+++ b/microdroid/keymint/service.cpp
@@ -18,16 +18,84 @@
 
 #include <AndroidKeyMintDevice.h>
 #include <android-base/logging.h>
+#include <android-base/properties.h>
+#include <android-base/result.h>
 #include <android/binder_manager.h>
 #include <android/binder_process.h>
+#include <keymaster/android_keymaster_utils.h>
+#include <keymaster/mem.h>
 #include <keymaster/soft_keymaster_logger.h>
+#include <openssl/digest.h>
+#include <openssl/hkdf.h>
+#include <openssl/is_boringssl.h>
+#include <openssl/sha.h>
 
 #include "MicrodroidKeyMintDevice.h"
 
 using aidl::android::hardware::security::keymint::MicrodroidKeyMintDevice;
 using aidl::android::hardware::security::keymint::SecurityLevel;
 
+using android::base::Error;
+using android::base::GetProperty;
+using android::base::Result;
+
+using keymaster::KeymasterBlob;
+using keymaster::KeymasterKeyBlob;
+using keymaster::memset_s;
+
+namespace {
+
+template <typename T, class... Args>
+std::shared_ptr<T> addService(Args&&... args) {
+    std::shared_ptr<T> ser = ndk::SharedRefBase::make<T>(std::forward<Args>(args)...);
+    auto instanceName = std::string(T::descriptor) + "/default";
+    LOG(INFO) << "adding keymint service instance: " << instanceName;
+    binder_status_t status =
+            AServiceManager_addService(ser->asBinder().get(), instanceName.c_str());
+    CHECK(status == STATUS_OK);
+    return ser;
+}
+
+Result<KeymasterKeyBlob> getRootKey() {
+    const std::string prop = "ro.vmsecret.keymint";
+    const std::chrono::seconds timeout(15);
+    while (!android::base::WaitForPropertyCreation(prop, timeout)) {
+        LOG(WARNING) << "waited " << timeout.count() << "seconds for " << prop
+                     << ", still waiting...";
+    }
+
+    // In a small effort to avoid spreading the secret around too widely in
+    // memory, move the secert into a buffer that will wipe itself and clear
+    // the original string.
+    std::string secretProp = GetProperty(prop, "");
+    KeymasterBlob secret(reinterpret_cast<const uint8_t*>(secretProp.data()), secretProp.size());
+    memset_s(secretProp.data(), 0, secretProp.size());
+    if (secret.size() < 64u) return Error() << "secret is too small";
+
+    // Derive the root key from the secret to avoid getting locked into using
+    // the secret directly.
+    KeymasterKeyBlob rootKey(SHA512_DIGEST_LENGTH);
+    const uint8_t kRootKeyIkm[] = "keymint_root_key";
+    const uint8_t* kNoSalt = nullptr;
+    const size_t kNoSaltLen = 0;
+    if (!HKDF(rootKey.writable_data(), rootKey.size(), EVP_sha512(), (uint8_t*)secret.begin(),
+              secret.size(), kNoSalt, kNoSaltLen, kRootKeyIkm, sizeof(kRootKeyIkm))) {
+        return Error() << "Failed to derive a key";
+    }
+    if (rootKey.size() < 64u) return Error() << "root key is too small";
+
+    LOG(INFO) << "root key obtained";
+    return rootKey;
+}
+
+} // namespace
+
 int main() {
+    auto rootKey = getRootKey();
+    if (!rootKey.ok()) {
+        LOG(FATAL) << "Failed to get root key: " << rootKey.error();
+    }
+
     // Zero threads seems like a useless pool, but below we'll join this thread
     // to it, increasing the pool size to 1.
     ABinderProcess_setThreadPoolMaxThreadCount(0);
diff --git a/microdroid/sepolicy/system/private/domain.te b/microdroid/sepolicy/system/private/domain.te
index 54423ec..da811ed 100644
--- a/microdroid/sepolicy/system/private/domain.te
+++ b/microdroid/sepolicy/system/private/domain.te
@@ -218,7 +218,7 @@
 
 # workaround for supressing property accesses.
 # TODO: remove these
-set_prop(domain, property_type)
+set_prop(domain, property_type -vmsecret_keymint_prop)
 # auditallow { domain -init } property_type:property_service set;
 # auditallow { domain -init } property_type:file rw_file_perms;
 
diff --git a/microdroid/sepolicy/system/private/microdroid_manager.te b/microdroid/sepolicy/system/private/microdroid_manager.te
index 781a5e1..074024f 100644
--- a/microdroid/sepolicy/system/private/microdroid_manager.te
+++ b/microdroid/sepolicy/system/private/microdroid_manager.te
@@ -23,6 +23,9 @@
 # Let microdroid_manager kernel-log.
 allow microdroid_manager kmsg_device:chr_file w_file_perms;
 
+# Let microdroid_manager initialize the derived VM secrets.
+set_prop(microdroid_manager, vmsecret_keymint_prop);
+
 # Let microdroid_manager read a config file from /mnt/apk (fusefs)
 # TODO(b/188400186) remove the below two rules
 userdebug_or_eng(`
diff --git a/microdroid/sepolicy/system/private/property.te b/microdroid/sepolicy/system/private/property.te
new file mode 100644
index 0000000..d3d413e
--- /dev/null
+++ b/microdroid/sepolicy/system/private/property.te
@@ -0,0 +1,16 @@
+###
+### Neverallow rules
+###
+
+neverallow {
+  domain
+  -init
+  -microdroid_manager
+} vmsecret_keymint_prop:property_service set;
+
+neverallow {
+  domain
+  -init
+  -microdroid_manager
+  -hal_keymint_server
+} vmsecret_keymint_prop:file no_rw_file_perms;
diff --git a/microdroid/sepolicy/system/private/property_contexts b/microdroid/sepolicy/system/private/property_contexts
index deeb840..c8be9d9 100644
--- a/microdroid/sepolicy/system/private/property_contexts
+++ b/microdroid/sepolicy/system/private/property_contexts
@@ -50,6 +50,8 @@
 
 ro.build.fingerprint u:object_r:fingerprint_prop:s0 exact string
 
+ro.vmsecret.keymint u:object_r:vmsecret_keymint_prop:s0 exact string
+
 hwservicemanager.ready u:object_r:hwservicemanager_prop:s0 exact bool
 
 apexd.status u:object_r:apexd_prop:s0 exact enum starting activated ready
diff --git a/microdroid/sepolicy/system/public/property.te b/microdroid/sepolicy/system/public/property.te
index 2f3255b..f5dc758 100644
--- a/microdroid/sepolicy/system/public/property.te
+++ b/microdroid/sepolicy/system/public/property.te
@@ -30,6 +30,7 @@
 type shell_prop, property_type;
 type usb_control_prop, property_type;
 type vendor_default_prop, property_type;
+type vmsecret_keymint_prop, property_type;
 
 allow property_type tmpfs:filesystem associate;
 
diff --git a/microdroid/sepolicy/vendor/hal_keymint_default.te b/microdroid/sepolicy/vendor/hal_keymint_default.te
index 9ddd787..359ca60 100644
--- a/microdroid/sepolicy/vendor/hal_keymint_default.te
+++ b/microdroid/sepolicy/vendor/hal_keymint_default.te
@@ -9,3 +9,5 @@
 
 allow logd hal_keymint_default:dir search;
 allow logd hal_keymint_default:file { getattr open read };
+
+get_prop(hal_keymint_default, vmsecret_keymint_prop);
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index 15c439b..902b5da 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -12,6 +12,7 @@
         "libanyhow",
         "libkernlog",
         "libkeystore2_system_property-rust",
+        "liblibc",
         "liblog_rust",
         "libmicrodroid_metadata",
         "libmicrodroid_payload_config",
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index d88ba1a..1506142 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -19,7 +19,7 @@
 
 use anyhow::{anyhow, bail, Result};
 use keystore2_system_property::PropertyWatcher;
-use log::{error, info};
+use log::{error, info, warn};
 use microdroid_payload_config::{Task, TaskType, VmPayloadConfig};
 use std::fs::{self, File};
 use std::os::unix::io::{FromRawFd, IntoRawFd};
@@ -39,6 +39,11 @@
     if !metadata.payload_config_path.is_empty() {
         let config = load_config(Path::new(&metadata.payload_config_path))?;
 
+        let fake_secret = "This is a placeholder for a value that is derived from the images that are loaded in the VM.";
+        if let Err(err) = keystore2_system_property::write("ro.vmsecret.keymint", fake_secret) {
+            warn!("failed to set ro.vmsecret.keymint: {}", err);
+        }
+
         // TODO(jooyung): wait until sys.boot_completed?
         if let Some(main_task) = &config.task {
             exec_task(main_task).map_err(|e| {