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| {