pVMs to use Sk for rollback protected secrets
To enable this, the patch does the following:
1. Get instance_id of the VM from guest DT. This is used as the SecretId
for Secretkeeper operations.
2. Get the public key of Secretkeeper from Guest DT, this is required
for session establishment with the expected Sk identity.
3. Disable Secretkeeper operations from non-protected VM. Rollback
protection is not a guarantee we offer non-protected VM under AVF
threat model. So far, non protected VM gave us good test coverage
because they were tested against nonsecure instance, but that is no
more required as we shift to the secure instance of Secretkeeper, nor
is it possible since non protected VMs on CF do have instance_id due
to limited support.
Test: atest MicrodroidTests#encryptedStorageIsPersistent on device with
default instance
Bug: 291213394
Change-Id: I303e8b9ec6e4fbca01864750a1a0f0bdd459a25c
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index 86284a5..0d67632 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -45,8 +45,6 @@
use microdroid_metadata::PayloadMetadata;
use microdroid_payload_config::{ApkConfig, OsConfig, Task, TaskType, VmPayloadConfig};
use nix::sys::signal::Signal;
-use openssl::hkdf::hkdf;
-use openssl::md::Md;
use payload::load_metadata;
use rpcbinder::RpcSession;
use rustutils::sockets::android_get_control_socket;
@@ -73,6 +71,8 @@
const AVF_DEBUG_POLICY_RAMDUMP: &str = "/proc/device-tree/avf/guest/common/ramdump";
const DEBUG_MICRODROID_NO_VERIFIED_BOOT: &str =
"/proc/device-tree/virtualization/guest/debug-microdroid,no-verified-boot";
+const SECRETKEEPER_KEY: &str = "/proc/device-tree/avf/secretkeeper_public_key";
+const INSTANCE_ID_PATH: &str = "/proc/device-tree/avf/untrusted/instance-id";
const ENCRYPTEDSTORE_BIN: &str = "/system/bin/encryptedstore";
const ZIPFUSE_BIN: &str = "/system/bin/zipfuse";
@@ -145,6 +145,23 @@
Ok(())
}
+/// The (host allocated) instance_id can be found at node /avf/untrusted/ in the device tree.
+fn get_instance_id() -> Result<Option<[u8; ID_SIZE]>> {
+ let path = Path::new(INSTANCE_ID_PATH);
+ let instance_id = if path.exists() {
+ Some(
+ fs::read(path)?
+ .try_into()
+ .map_err(|x: Vec<_>| anyhow!("Expected {ID_SIZE} bytes, found {:?}", x.len()))?,
+ )
+ } else {
+ // TODO(b/325094712): x86 support for Device tree in nested guest is limited/broken/
+ // untested. So instance_id will not be present in cuttlefish.
+ None
+ };
+ Ok(instance_id)
+}
+
fn main() -> Result<()> {
// If debuggable, print full backtrace to console log with stdio_to_kmsg
if is_debuggable()? {
@@ -284,19 +301,8 @@
// To minimize the exposure to untrusted data, derive dice profile as soon as possible.
info!("DICE derivation for payload");
let dice_artifacts = dice_derivation(dice, &instance_data, &payload_metadata)?;
- // TODO(b/291213394): This will be the Id for non-pVM only, instance_data.salt is all 0
- // for protected VM, implement a mechanism for pVM!
- let mut vm_id = [0u8; ID_SIZE];
- hkdf(
- &mut vm_id,
- Md::sha256(),
- &instance_data.salt,
- /* salt */ b"",
- /* info */ b"VM_ID",
- )
- .context("hkdf failed")?;
let vm_secret =
- VmSecret::new(vm_id, dice_artifacts, service).context("Failed to create VM secrets")?;
+ VmSecret::new(dice_artifacts, service).context("Failed to create VM secrets")?;
if cfg!(dice_changes) {
// Now that the DICE derivation is done, it's ok to allow payload code to run.
diff --git a/microdroid_manager/src/vm_secret.rs b/microdroid_manager/src/vm_secret.rs
index 7df7cc9..4ead211 100644
--- a/microdroid_manager/src/vm_secret.rs
+++ b/microdroid_manager/src/vm_secret.rs
@@ -19,7 +19,7 @@
use android_hardware_security_secretkeeper::aidl::android::hardware::security::secretkeeper::ISecretkeeper::ISecretkeeper;
use secretkeeper_comm::data_types::request::Request;
use binder::{Strong};
-use coset::CborSerializable;
+use coset::{CoseKey, CborSerializable, CborOrdering};
use dice_policy_builder::{CertIndex, ConstraintSpec, ConstraintType, policy_for_dice_chain, MissingAction, WILDCARD_FULL_ARRAY};
use diced_open_dice::{DiceArtifacts, OwnedDiceArtifacts};
use keystore2_crypto::ZVec;
@@ -34,6 +34,7 @@
use secretkeeper_comm::data_types::request_response_impl::{
StoreSecretRequest, GetSecretResponse, GetSecretRequest};
use secretkeeper_comm::data_types::error::SecretkeeperError;
+use std::fs;
use zeroize::Zeroizing;
const ENCRYPTEDSTORE_KEY_IDENTIFIER: &str = "encryptedstore_key";
@@ -58,11 +59,6 @@
0x55, 0xF8, 0x08, 0x23, 0x81, 0x5F, 0xF5, 0x16, 0x20, 0x3E, 0xBE, 0xBA, 0xB7, 0xA8, 0x43, 0x92,
];
-const SKP_SECRET_NP_VM: [u8; SECRET_SIZE] = [
- 0xA9, 0x89, 0x97, 0xFE, 0xAE, 0x97, 0x55, 0x4B, 0x32, 0x35, 0xF0, 0xE8, 0x93, 0xDA, 0xEA, 0x24,
- 0x06, 0xAC, 0x36, 0x8B, 0x3C, 0x95, 0x50, 0x16, 0x67, 0x71, 0x65, 0x26, 0xEB, 0xD0, 0xC3, 0x98,
-];
-
pub enum VmSecret {
// V2 secrets are derived from 2 independently secured secrets:
// 1. Secretkeeper protected secrets (skp secret).
@@ -81,9 +77,16 @@
V1 { dice_artifacts: OwnedDiceArtifacts },
}
+// For supporting V2 secrets, guest expects the public key to be present in the Linux device tree.
+fn get_secretkeeper_identity() -> Result<CoseKey> {
+ let key = fs::read(super::SECRETKEEPER_KEY)?;
+ let mut key = CoseKey::from_slice(&key)?;
+ key.canonicalize(CborOrdering::Lexicographic);
+ Ok(key)
+}
+
impl VmSecret {
pub fn new(
- id: [u8; ID_SIZE],
dice_artifacts: OwnedDiceArtifacts,
vm_service: &Strong<dyn IVirtualMachineService>,
) -> Result<Self> {
@@ -93,29 +96,27 @@
// Use V1 secrets if Secretkeeper is not supported.
return Ok(Self::V1 { dice_artifacts });
};
+
let explicit_dice =
OwnedDiceArtifactsWithExplicitKey::from_owned_artifacts(dice_artifacts)?;
- let explicit_dice_chain = explicit_dice
- .explicit_key_dice_chain()
- .ok_or(anyhow!("Missing explicit dice chain, this is unusual"))?;
- let policy = sealing_policy(explicit_dice_chain).map_err(anyhow_err)?;
-
- // Start a new session with Secretkeeper!
- let mut session = SkSession::new(sk_service, &explicit_dice, None)?;
+ // For pVM, skp_secret are stored in Secretkeeper. For non-protected it is all 0s.
let mut skp_secret = Zeroizing::new([0u8; SECRET_SIZE]);
if super::is_strict_boot() {
+ let mut session =
+ SkSession::new(sk_service, &explicit_dice, Some(get_secretkeeper_identity()?))?;
+ let id = super::get_instance_id()?.ok_or(anyhow!("Missing instance_id"))?;
+ let explicit_dice_chain = explicit_dice
+ .explicit_key_dice_chain()
+ .ok_or(anyhow!("Missing explicit dice chain, this is unusual"))?;
+ let policy = sealing_policy(explicit_dice_chain).map_err(anyhow_err)?;
if super::is_new_instance() {
+ // New instance -> create a secret & store in Secretkeeper.
*skp_secret = rand::random();
store_secret(&mut session, id, skp_secret.clone(), policy)?;
} else {
// Subsequent run of the pVM -> get the secret stored in Secretkeeper.
*skp_secret = get_secret(&mut session, id, Some(policy))?;
}
- } else {
- // TODO(b/291213394): Non protected VM don't need to use Secretkeeper, remove this
- // once we have sufficient testing on protected VM.
- store_secret(&mut session, id, SKP_SECRET_NP_VM.into(), policy)?;
- *skp_secret = get_secret(&mut session, id, None)?;
}
Ok(Self::V2 {
dice_artifacts: explicit_dice,
@@ -273,17 +274,13 @@
host: &Strong<dyn IVirtualMachineService>,
) -> Result<Option<Strong<dyn ISecretkeeper>>> {
let sk = if cfg!(llpvm_changes) {
- if super::is_strict_boot() {
- // TODO: For protected VM check for Secretkeeper authentication data in device tree.
- None
- } else {
- // For non-protected VM, believe what host claims.
- host.getSecretkeeper()
- // TODO rename this error!
- .map_err(|e| {
- super::MicrodroidError::FailedToConnectToVirtualizationService(e.to_string())
- })?
- }
+ host.getSecretkeeper()
+ // TODO rename this error!
+ .map_err(|e| {
+ super::MicrodroidError::FailedToConnectToVirtualizationService(format!(
+ "Failed to get Secretkeeper: {e:?}"
+ ))
+ })?
} else {
// LLPVM flag is disabled
None