Merge "Propagate salt when resigning" into main
diff --git a/libs/dice/open_dice/Android.bp b/libs/dice/open_dice/Android.bp
index 2d0f52c..79d0b96 100644
--- a/libs/dice/open_dice/Android.bp
+++ b/libs/dice/open_dice/Android.bp
@@ -55,8 +55,10 @@
"libopen_dice_android",
],
visibility: [
+ "//hardware/interfaces/security/secretkeeper/aidl/vts",
"//packages/modules/Virtualization:__subpackages__",
"//system/authgraph/tests:__subpackages__",
+ "//system/secretkeeper/client:__subpackages__",
],
apex_available: [
"//apex_available:platform",
diff --git a/microdroid_manager/Android.bp b/microdroid_manager/Android.bp
index cb3b2aa..1696aae 100644
--- a/microdroid_manager/Android.bp
+++ b/microdroid_manager/Android.bp
@@ -29,6 +29,7 @@
"libclient_vm_csr",
"libciborium",
"libcoset",
+ "libdice_policy",
"libdiced_open_dice",
"libdiced_sample_inputs",
"libglob",
diff --git a/microdroid_manager/src/main.rs b/microdroid_manager/src/main.rs
index c94a937..8888feb 100644
--- a/microdroid_manager/src/main.rs
+++ b/microdroid_manager/src/main.rs
@@ -45,11 +45,14 @@
use microdroid_metadata::PayloadMetadata;
use microdroid_payload_config::{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;
use rustutils::system_properties;
use rustutils::system_properties::PropertyWatcher;
+use secretkeeper_comm::data_types::ID_SIZE;
use std::borrow::Cow::{Borrowed, Owned};
use std::env;
use std::ffi::CString;
@@ -281,8 +284,19 @@
// 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(dice_artifacts, service).context("Failed to create VM secrets")?;
+ VmSecret::new(vm_id, 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_payload_service.rs b/microdroid_manager/src/vm_payload_service.rs
index d3346d8..20a1b89 100644
--- a/microdroid_manager/src/vm_payload_service.rs
+++ b/microdroid_manager/src/vm_payload_service.rs
@@ -23,7 +23,6 @@
use avflog::LogResult;
use binder::{Interface, BinderFeatures, ExceptionCode, Strong, IntoBinderResult, Status};
use client_vm_csr::{generate_attestation_key_and_csr, ClientVmAttestationData};
-use diced_open_dice::DiceArtifacts;
use log::info;
use rpcbinder::RpcServer;
use crate::vm_secret::VmSecret;
@@ -57,7 +56,7 @@
fn getDiceAttestationChain(&self) -> binder::Result<Vec<u8>> {
self.check_restricted_apis_allowed()?;
- if let Some(bcc) = self.secret.dice().bcc() {
+ if let Some(bcc) = self.secret.dice_artifacts().bcc() {
Ok(bcc.to_vec())
} else {
Err(anyhow!("bcc is none")).or_binder_exception(ExceptionCode::ILLEGAL_STATE)
@@ -66,13 +65,13 @@
fn getDiceAttestationCdi(&self) -> binder::Result<Vec<u8>> {
self.check_restricted_apis_allowed()?;
- Ok(self.secret.dice().cdi_attest().to_vec())
+ Ok(self.secret.dice_artifacts().cdi_attest().to_vec())
}
fn requestAttestation(&self, challenge: &[u8]) -> binder::Result<AttestationResult> {
self.check_restricted_apis_allowed()?;
let ClientVmAttestationData { private_key, csr } =
- generate_attestation_key_and_csr(challenge, self.secret.dice())
+ generate_attestation_key_and_csr(challenge, self.secret.dice_artifacts())
.map_err(|e| {
Status::new_service_specific_error_str(
STATUS_FAILED_TO_PREPARE_CSR_AND_KEY,
diff --git a/microdroid_manager/src/vm_secret.rs b/microdroid_manager/src/vm_secret.rs
index 89c27c9..dea6af1 100644
--- a/microdroid_manager/src/vm_secret.rs
+++ b/microdroid_manager/src/vm_secret.rs
@@ -20,11 +20,13 @@
use secretkeeper_comm::data_types::request::Request;
use binder::{Strong};
use coset::CborSerializable;
+use dice_policy::{ConstraintSpec, ConstraintType, DicePolicy};
use diced_open_dice::{DiceArtifacts, OwnedDiceArtifacts};
use keystore2_crypto::ZVec;
use openssl::hkdf::hkdf;
use openssl::md::Md;
use openssl::sha;
+use secretkeeper_client::dice::OwnedDiceArtifactsWithExplicitKey;
use secretkeeper_client::SkSession;
use secretkeeper_comm::data_types::{Id, ID_SIZE, Secret, SECRET_SIZE};
use secretkeeper_comm::data_types::response::Response;
@@ -35,6 +37,10 @@
use zeroize::Zeroizing;
const ENCRYPTEDSTORE_KEY_IDENTIFIER: &str = "encryptedstore_key";
+const AUTHORITY_HASH: i64 = -4670549;
+const MODE: i64 = -4670551;
+const CONFIG_DESC: i64 = -4670548;
+const SECURITY_VERSION: i64 = -70005;
// Generated using hexdump -vn32 -e'14/1 "0x%02X, " 1 "\n"' /dev/urandom
const SALT_ENCRYPTED_STORE: &[u8] = &[
@@ -46,19 +52,6 @@
0x55, 0xF8, 0x08, 0x23, 0x81, 0x5F, 0xF5, 0x16, 0x20, 0x3E, 0xBE, 0xBA, 0xB7, 0xA8, 0x43, 0x92,
];
-// TODO(b/291213394): Remove this once policy is generated from dice_chain
-const HYPOTHETICAL_DICE_POLICY: [u8; 43] = [
- 0x83, 0x01, 0x81, 0x83, 0x01, 0x80, 0xA1, 0x01, 0x00, 0x82, 0x83, 0x01, 0x81, 0x01, 0x73, 0x74,
- 0x65, 0x73, 0x74, 0x69, 0x6E, 0x67, 0x5F, 0x64, 0x69, 0x63, 0x65, 0x5F, 0x70, 0x6F, 0x6C, 0x69,
- 0x63, 0x79, 0x83, 0x02, 0x82, 0x03, 0x18, 0x64, 0x19, 0xE9, 0x75,
-];
-// TODO(b/291213394): Differentiate the Id of nPVM based on 'salt'
-const ID_NP_VM: [u8; ID_SIZE] = [
- 0xF1, 0xB2, 0xED, 0x3B, 0xD1, 0xBD, 0xF0, 0x7D, 0xE1, 0xF0, 0x01, 0xFC, 0x61, 0x71, 0xD3, 0x42,
- 0xE5, 0x8A, 0xAF, 0x33, 0x6C, 0x11, 0xDC, 0xC8, 0x6F, 0xAE, 0x12, 0x5C, 0x26, 0x44, 0x6B, 0x86,
- 0xCC, 0x24, 0xFD, 0xBF, 0x91, 0x4A, 0x54, 0x84, 0xF9, 0x01, 0x59, 0x25, 0x70, 0x89, 0x38, 0x8D,
- 0x5E, 0xE6, 0x91, 0xDF, 0x68, 0x60, 0x69, 0x26, 0xBE, 0xFE, 0x79, 0x58, 0xF7, 0xEA, 0x81, 0x7D,
-];
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,
@@ -73,72 +66,75 @@
// with downgraded images will not have access to VM's secret.
// V2 secrets require hardware support - Secretkeeper HAL, which (among other things)
// is backed by tamper-evident storage, providing rollback protection to these secrets.
- V2 { dice: OwnedDiceArtifacts, skp_secret: ZVec },
+ V2 { dice_artifacts: OwnedDiceArtifactsWithExplicitKey, skp_secret: ZVec },
// V1 secrets are not protected against rollback of boot images.
// They are reliable only if rollback of images was prevented by verified boot ie,
// each stage (including pvmfw/Microdroid/Microdroid Manager) prevents downgrade of next
// stage. These are now legacy secrets & used only when Secretkeeper HAL is not supported
// by device.
- V1 { dice: OwnedDiceArtifacts },
-}
-
-fn get_id() -> [u8; ID_SIZE] {
- if super::is_strict_boot() {
- todo!("Id for protected VM is not implemented");
- } else {
- ID_NP_VM
- }
+ V1 { dice_artifacts: OwnedDiceArtifacts },
}
impl VmSecret {
pub fn new(
+ id: [u8; ID_SIZE],
dice_artifacts: OwnedDiceArtifacts,
vm_service: &Strong<dyn IVirtualMachineService>,
- ) -> Result<VmSecret> {
+ ) -> Result<Self> {
ensure!(dice_artifacts.bcc().is_some(), "Dice chain missing");
- if let Some(sk_service) = is_sk_supported(vm_service)? {
- let id = get_id();
- let mut skp_secret = Zeroizing::new([0u8; SECRET_SIZE]);
- if super::is_strict_boot() {
- if super::is_new_instance() {
- *skp_secret = rand::random();
- store_secret(sk_service.clone(), id, skp_secret.clone(), &dice_artifacts)?;
- } else {
- // Subsequent run of the pVM -> get the secret stored in Secretkeeper.
- *skp_secret = get_secret(sk_service.clone(), id, &dice_artifacts)?;
- }
+ let Some(sk_service) = is_sk_supported(vm_service)? else {
+ // 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)?;
+ let mut skp_secret = Zeroizing::new([0u8; SECRET_SIZE]);
+ if super::is_strict_boot() {
+ if super::is_new_instance() {
+ *skp_secret = rand::random();
+ store_secret(&mut session, id, skp_secret.clone(), 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(sk_service.clone(), id, SKP_SECRET_NP_VM.into(), &dice_artifacts)?;
- *skp_secret = get_secret(sk_service.clone(), id, &dice_artifacts)?;
+ // Subsequent run of the pVM -> get the secret stored in Secretkeeper.
+ *skp_secret = get_secret(&mut session, id, Some(policy))?;
}
- return Ok(Self::V2 {
- dice: dice_artifacts,
- skp_secret: ZVec::try_from(skp_secret.to_vec())?,
- });
+ } 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)?;
}
- // Use V1 secrets if Secretkeeper is not supported.
- Ok(Self::V1 { dice: dice_artifacts })
+ Ok(Self::V2 {
+ dice_artifacts: explicit_dice,
+ skp_secret: ZVec::try_from(skp_secret.to_vec())?,
+ })
}
- pub fn dice(&self) -> &OwnedDiceArtifacts {
+ pub fn dice_artifacts(&self) -> &dyn DiceArtifacts {
match self {
- Self::V2 { dice, .. } => dice,
- Self::V1 { dice } => dice,
+ Self::V2 { dice_artifacts, .. } => dice_artifacts,
+ Self::V1 { dice_artifacts } => dice_artifacts,
}
}
fn get_vm_secret(&self, salt: &[u8], identifier: &[u8], key: &mut [u8]) -> Result<()> {
match self {
- Self::V2 { dice, skp_secret } => {
+ Self::V2 { dice_artifacts, skp_secret } => {
let mut hasher = sha::Sha256::new();
- hasher.update(dice.cdi_seal());
+ hasher.update(dice_artifacts.cdi_seal());
hasher.update(skp_secret);
hkdf(key, Md::sha256(), &hasher.finish(), salt, identifier)?
}
- Self::V1 { dice } => hkdf(key, Md::sha256(), dice.cdi_seal(), salt, identifier)?,
+ Self::V1 { dice_artifacts } => {
+ hkdf(key, Md::sha256(), dice_artifacts.cdi_seal(), salt, identifier)?
+ }
}
Ok(())
}
@@ -154,24 +150,49 @@
}
}
+// Construct a sealing policy on the dice chain. VMs uses the following set of constraint for
+// protecting secrets against rollback of boot images.
+// 1. ExactMatch on AUTHORITY_HASH (Required ie, each DiceChainEntry must have it).
+// 2. ExactMatch on MODE (Required) - Secret should be inaccessible if any of the runtime
+// configuration changes. For ex, the secrets stored with a boot stage being in Normal mode
+// should be inaccessible when the same stage is booted in Debug mode.
+// 3. GreaterOrEqual on SECURITY_VERSION (Optional): The secrets will be accessible if version of
+// any image is greater or equal to the set version. This is an optional field, certain
+// components may chose to prevent booting of rollback images for ex, ABL is expected to provide
+// rollback protection of pvmfw. Such components may chose to not put SECURITY_VERSION in the
+// corresponding DiceChainEntry.
+// TODO(b/291219197) : Add constraints on Extra apks as well!
+fn sealing_policy(dice: &[u8]) -> Result<Vec<u8>, String> {
+ let constraint_spec = [
+ ConstraintSpec::new(
+ ConstraintType::ExactMatch,
+ vec![AUTHORITY_HASH],
+ /* Optional */ false,
+ ),
+ ConstraintSpec::new(ConstraintType::ExactMatch, vec![MODE], /* Optional */ false),
+ ConstraintSpec::new(
+ ConstraintType::GreaterOrEqual,
+ vec![CONFIG_DESC, SECURITY_VERSION],
+ /* Optional */ true,
+ ),
+ ];
+
+ DicePolicy::from_dice_chain(dice, &constraint_spec)?
+ .to_vec()
+ .map_err(|e| format!("DicePolicy construction failed {e:?}"))
+}
+
fn store_secret(
- secretkeeper: binder::Strong<dyn ISecretkeeper>,
+ session: &mut SkSession,
id: [u8; ID_SIZE],
secret: Zeroizing<[u8; SECRET_SIZE]>,
- _dice_chain: &OwnedDiceArtifacts,
+ sealing_policy: Vec<u8>,
) -> Result<()> {
- // Start a new secretkeeper session!
- let mut session = SkSession::new(secretkeeper).map_err(anyhow_err)?;
- let store_request = StoreSecretRequest {
- id: Id(id),
- secret: Secret(*secret),
- // TODO(b/291233371): Construct policy out of dice_chain.
- sealing_policy: HYPOTHETICAL_DICE_POLICY.to_vec(),
- };
+ let store_request = StoreSecretRequest { id: Id(id), secret: Secret(*secret), sealing_policy };
log::info!("Secretkeeper operation: {:?}", store_request);
let store_request = store_request.serialize_to_packet().to_vec().map_err(anyhow_err)?;
- let store_response = session.secret_management_request(&store_request).map_err(anyhow_err)?;
+ let store_response = session.secret_management_request(&store_request)?;
let store_response = ResponsePacket::from_slice(&store_response).map_err(anyhow_err)?;
let response_type = store_response.response_type().map_err(anyhow_err)?;
ensure!(
@@ -183,21 +204,14 @@
}
fn get_secret(
- secretkeeper: binder::Strong<dyn ISecretkeeper>,
+ session: &mut SkSession,
id: [u8; ID_SIZE],
- _dice_chain: &OwnedDiceArtifacts,
+ updated_sealing_policy: Option<Vec<u8>>,
) -> Result<[u8; SECRET_SIZE]> {
- // Start a new secretkeeper session!
- let mut session = SkSession::new(secretkeeper).map_err(anyhow_err)?;
- let get_request = GetSecretRequest {
- id: Id(id),
- // TODO(b/291233371): Construct policy out of dice_chain.
- updated_sealing_policy: None,
- };
+ let get_request = GetSecretRequest { id: Id(id), updated_sealing_policy };
log::info!("Secretkeeper operation: {:?}", get_request);
-
let get_request = get_request.serialize_to_packet().to_vec().map_err(anyhow_err)?;
- let get_response = session.secret_management_request(&get_request).map_err(anyhow_err)?;
+ let get_response = session.secret_management_request(&get_request)?;
let get_response = ResponsePacket::from_slice(&get_response).map_err(anyhow_err)?;
let response_type = get_response.response_type().map_err(anyhow_err)?;
ensure!(