blob: ec40b45deecaf09fca8b8c9c62d4906316791f55 [file] [log] [blame]
// Copyright 2023, The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//! Class for encapsulating & managing represent VM secrets.
use anyhow::{anyhow, ensure, Context, Result};
use android_system_virtualmachineservice::aidl::android::system::virtualmachineservice::IVirtualMachineService::IVirtualMachineService;
use android_hardware_security_secretkeeper::aidl::android::hardware::security::secretkeeper::ISecretkeeper::ISecretkeeper;
use secretkeeper_comm::data_types::request::Request;
use binder::{Strong};
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;
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;
use secretkeeper_comm::data_types::packet::{ResponsePacket, ResponseType};
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";
const AUTHORITY_HASH: i64 = -4670549;
const MODE: i64 = -4670551;
const CONFIG_DESC: i64 = -4670548;
const SECURITY_VERSION: i64 = -70005;
const SUBCOMPONENT_DESCRIPTORS: i64 = -71002;
const SUBCOMPONENT_SECURITY_VERSION: i64 = 2;
const SUBCOMPONENT_AUTHORITY_HASH: i64 = 4;
// Index of DiceChainEntry corresponding to Payload (relative to the end considering DICE Chain
// as an array)
const PAYLOAD_INDEX_FROM_END: usize = 0;
// Generated using hexdump -vn32 -e'14/1 "0x%02X, " 1 "\n"' /dev/urandom
const SALT_ENCRYPTED_STORE: &[u8] = &[
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,
];
const SALT_PAYLOAD_SERVICE: &[u8] = &[
0x8B, 0x0F, 0xF0, 0xD3, 0xB1, 0x69, 0x2B, 0x95, 0x84, 0x2C, 0x9E, 0x3C, 0x99, 0x56, 0x7A, 0x22,
0x55, 0xF8, 0x08, 0x23, 0x81, 0x5F, 0xF5, 0x16, 0x20, 0x3E, 0xBE, 0xBA, 0xB7, 0xA8, 0x43, 0x92,
];
pub enum VmSecret {
// V2 secrets are derived from 2 independently secured secrets:
// 1. Secretkeeper protected secrets (skp secret).
// 2. Dice Sealing CDIs (Similar to V1).
//
// These are protected against rollback of boot images i.e. VM instance rebooted
// 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_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_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(
dice_artifacts: OwnedDiceArtifacts,
vm_service: &Strong<dyn IVirtualMachineService>,
) -> Result<Self> {
ensure!(dice_artifacts.bcc().is_some(), "Dice chain missing");
if !crate::should_defer_rollback_protection() {
return Ok(Self::V1 { dice_artifacts });
}
let explicit_dice = OwnedDiceArtifactsWithExplicitKey::from_owned_artifacts(dice_artifacts)
.context("Failed to get Dice artifacts in explicit key format")?;
// 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 sk_service = get_secretkeeper_service(vm_service)?;
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(|e| anyhow!("Failed to build a sealing_policy: {e}"))?;
if let Some(secret) = get_secret(&mut session, id, Some(policy.clone()))? {
*skp_secret = secret;
} else {
log::warn!(
"No entry found in Secretkeeper for this VM instance, creating new secret."
);
*skp_secret = rand::random();
store_secret(&mut session, id, skp_secret.clone(), policy)?;
}
}
Ok(Self::V2 {
dice_artifacts: explicit_dice,
skp_secret: ZVec::try_from(skp_secret.to_vec())?,
})
}
pub fn dice_artifacts(&self) -> &dyn DiceArtifacts {
match self {
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_artifacts, skp_secret } => {
let mut hasher = sha::Sha256::new();
hasher.update(dice_artifacts.cdi_seal());
hasher.update(skp_secret);
hkdf(key, Md::sha256(), &hasher.finish(), salt, identifier)?
}
Self::V1 { dice_artifacts } => {
hkdf(key, Md::sha256(), dice_artifacts.cdi_seal(), salt, identifier)?
}
}
Ok(())
}
/// Derive sealing key for payload with following identifier.
pub fn derive_payload_sealing_key(&self, identifier: &[u8], key: &mut [u8]) -> Result<()> {
self.get_vm_secret(SALT_PAYLOAD_SERVICE, identifier, key)
}
/// Derive encryptedstore key. This uses hardcoded random salt & fixed identifier.
pub fn derive_encryptedstore_key(&self, key: &mut [u8]) -> Result<()> {
self.get_vm_secret(SALT_ENCRYPTED_STORE, ENCRYPTEDSTORE_KEY_IDENTIFIER.as_bytes(), key)
}
}
// 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.
// 4. For each Subcomponent on the last DiceChainEntry (which corresponds to VM payload, See
// microdroid_manager/src/vm_config.cddl):
// - GreaterOrEqual on SECURITY_VERSION (Required)
// - ExactMatch on AUTHORITY_HASH (Required).
fn sealing_policy(dice: &[u8]) -> Result<Vec<u8>, String> {
let constraint_spec = [
ConstraintSpec::new(
ConstraintType::ExactMatch,
vec![AUTHORITY_HASH],
MissingAction::Fail,
CertIndex::All,
),
ConstraintSpec::new(
ConstraintType::ExactMatch,
vec![MODE],
MissingAction::Fail,
CertIndex::All,
),
ConstraintSpec::new(
ConstraintType::GreaterOrEqual,
vec![CONFIG_DESC, SECURITY_VERSION],
MissingAction::Ignore,
CertIndex::All,
),
ConstraintSpec::new(
ConstraintType::GreaterOrEqual,
vec![
CONFIG_DESC,
SUBCOMPONENT_DESCRIPTORS,
WILDCARD_FULL_ARRAY,
SUBCOMPONENT_SECURITY_VERSION,
],
MissingAction::Fail,
CertIndex::FromEnd(PAYLOAD_INDEX_FROM_END),
),
ConstraintSpec::new(
ConstraintType::ExactMatch,
vec![
CONFIG_DESC,
SUBCOMPONENT_DESCRIPTORS,
WILDCARD_FULL_ARRAY,
SUBCOMPONENT_AUTHORITY_HASH,
],
MissingAction::Fail,
CertIndex::FromEnd(PAYLOAD_INDEX_FROM_END),
),
];
policy_for_dice_chain(dice, &constraint_spec)?
.to_vec()
.map_err(|e| format!("DicePolicy construction failed {e:?}"))
}
fn store_secret(
session: &mut SkSession,
id: [u8; ID_SIZE],
secret: Zeroizing<[u8; SECRET_SIZE]>,
sealing_policy: Vec<u8>,
) -> Result<()> {
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)?;
let store_response = ResponsePacket::from_slice(&store_response).map_err(anyhow_err)?;
let response_type = store_response.response_type().map_err(anyhow_err)?;
ensure!(
response_type == ResponseType::Success,
"Secretkeeper store failed with error: {:?}",
*SecretkeeperError::deserialize_from_packet(store_response).map_err(anyhow_err)?
);
Ok(())
}
fn get_secret(
session: &mut SkSession,
id: [u8; ID_SIZE],
updated_sealing_policy: Option<Vec<u8>>,
) -> Result<Option<[u8; SECRET_SIZE]>> {
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)?;
let get_response = ResponsePacket::from_slice(&get_response).map_err(anyhow_err)?;
let response_type = get_response.response_type().map_err(anyhow_err)?;
if response_type == ResponseType::Success {
let get_response =
*GetSecretResponse::deserialize_from_packet(get_response).map_err(anyhow_err)?;
Ok(Some(get_response.secret.0))
} else {
let error = SecretkeeperError::deserialize_from_packet(get_response).map_err(anyhow_err)?;
if *error == SecretkeeperError::EntryNotFound {
return Ok(None);
}
Err(anyhow!("Secretkeeper get failed: {error:?}"))
}
}
#[inline]
fn anyhow_err<E: core::fmt::Debug>(err: E) -> anyhow::Error {
anyhow!("{:?}", err)
}
fn get_secretkeeper_service(
host: &Strong<dyn IVirtualMachineService>,
) -> Result<Strong<dyn ISecretkeeper>> {
Ok(host
.getSecretkeeper()
// TODO rename this error!
.map_err(|e| {
super::MicrodroidError::FailedToConnectToVirtualizationService(format!(
"Failed to get Secretkeeper: {e:?}"
))
})?)
}