pvmfw: Perform DICE derivation
Integrate DICE derivation into pvmfw and fail if the operation fails.
Note that we still need to get the salt ("hidden" DICE input) from the
instance.img or TRNG and pass the result to the next stage.
Bug: 256827715
Test: atest MicrodroidHostTests
Change-Id: Ibebaf526fd6055b9d05ce6017b560fb8814471e5
diff --git a/libs/dice/src/bcc.rs b/libs/dice/src/bcc.rs
index 849dfa0..6dc0cc3 100644
--- a/libs/dice/src/bcc.rs
+++ b/libs/dice/src/bcc.rs
@@ -16,19 +16,29 @@
//! Wrapper around dice/android/bcc.h.
+use core::ffi::CStr;
use core::mem;
use core::ptr;
+use open_dice_bcc_bindgen::BccConfigValues;
+use open_dice_bcc_bindgen::BccFormatConfigDescriptor;
+use open_dice_bcc_bindgen::BccHandoverMainFlow;
use open_dice_bcc_bindgen::BccHandoverParse;
+use open_dice_bcc_bindgen::DiceInputValues;
+use open_dice_bcc_bindgen::BCC_INPUT_COMPONENT_NAME;
+use open_dice_bcc_bindgen::BCC_INPUT_COMPONENT_VERSION;
+use open_dice_bcc_bindgen::BCC_INPUT_RESETTABLE;
use crate::check_call;
use crate::Cdi;
use crate::Error;
+use crate::InputValues;
use crate::Result;
/// Boot Chain Certificate handover format combining the BCC and CDIs in a single CBOR object.
#[derive(Clone, Debug)]
pub struct Handover<'a> {
+ buffer: &'a [u8],
/// Attestation CDI.
pub cdi_attest: &'a Cdi,
/// Sealing CDI.
@@ -75,8 +85,80 @@
Some(buffer.get(i..(i + bcc_size)).ok_or(Error::PlatformError)?)
};
- Ok(Self { cdi_attest, cdi_seal, bcc })
+ Ok(Self { buffer, cdi_attest, cdi_seal, bcc })
}
+
+ /// Executes the main BCC handover flow.
+ pub fn main_flow(&self, input_values: &InputValues, buffer: &mut [u8]) -> Result<usize> {
+ let context = ptr::null_mut();
+ let mut size: usize = 0;
+ // SAFETY - The function only reads `self.buffer`, writes to `buffer` within its bounds,
+ // reads `input_values` as a constant input and doesn't store any pointer.
+ check_call(unsafe {
+ BccHandoverMainFlow(
+ context,
+ self.buffer.as_ptr(),
+ self.buffer.len(),
+ input_values as *const _ as *const DiceInputValues,
+ buffer.len(),
+ buffer.as_mut_ptr(),
+ &mut size as *mut usize,
+ )
+ })?;
+
+ Ok(size)
+ }
+}
+
+/// Formats a configuration descriptor following the BCC's specification.
+///
+/// ```
+/// BccConfigDescriptor = {
+/// ? -70002 : tstr, ; Component name
+/// ? -70003 : int, ; Component version
+/// ? -70004 : null, ; Resettable
+/// }
+/// ```
+pub fn format_config_descriptor(
+ buffer: &mut [u8],
+ name: Option<&CStr>,
+ version: Option<u64>,
+ resettable: bool,
+) -> Result<usize> {
+ let mut inputs = 0;
+
+ if name.is_some() {
+ inputs |= BCC_INPUT_COMPONENT_NAME;
+ }
+
+ if version.is_some() {
+ inputs |= BCC_INPUT_COMPONENT_VERSION;
+ }
+
+ if resettable {
+ inputs |= BCC_INPUT_RESETTABLE;
+ }
+
+ let values = BccConfigValues {
+ inputs,
+ component_name: name.map_or(ptr::null(), |p| p.as_ptr()),
+ component_version: version.unwrap_or(0),
+ };
+
+ let mut buffer_size = 0;
+
+ // SAFETY - The function writes to the buffer, within the given bounds, and only reads the
+ // input values. It writes its result to buffer_size.
+ check_call(unsafe {
+ BccFormatConfigDescriptor(
+ &values as *const _,
+ buffer.len(),
+ buffer.as_mut_ptr(),
+ &mut buffer_size as *mut _,
+ )
+ })?;
+
+ Ok(buffer_size)
}
fn index_from_ptr(slice: &[u8], pointer: *const u8) -> Option<usize> {
diff --git a/libs/dice/src/lib.rs b/libs/dice/src/lib.rs
index 43d167f..9bbacc6 100644
--- a/libs/dice/src/lib.rs
+++ b/libs/dice/src/lib.rs
@@ -19,9 +19,19 @@
#![no_std]
use core::fmt;
+use core::mem;
+use core::ptr;
use core::result;
+use open_dice_cbor_bindgen::DiceConfigType_kDiceConfigTypeDescriptor as DICE_CONFIG_TYPE_DESCRIPTOR;
+use open_dice_cbor_bindgen::DiceConfigType_kDiceConfigTypeInline as DICE_CONFIG_TYPE_INLINE;
use open_dice_cbor_bindgen::DiceHash;
+use open_dice_cbor_bindgen::DiceInputValues;
+use open_dice_cbor_bindgen::DiceMode;
+use open_dice_cbor_bindgen::DiceMode_kDiceModeDebug as DICE_MODE_DEBUG;
+use open_dice_cbor_bindgen::DiceMode_kDiceModeMaintenance as DICE_MODE_MAINTENANCE;
+use open_dice_cbor_bindgen::DiceMode_kDiceModeNormal as DICE_MODE_NORMAL;
+use open_dice_cbor_bindgen::DiceMode_kDiceModeNotInitialized as DICE_MODE_NOT_INITIALIZED;
use open_dice_cbor_bindgen::DiceResult;
use open_dice_cbor_bindgen::DiceResult_kDiceResultBufferTooSmall as DICE_RESULT_BUFFER_TOO_SMALL;
use open_dice_cbor_bindgen::DiceResult_kDiceResultInvalidInput as DICE_RESULT_INVALID_INPUT;
@@ -32,11 +42,17 @@
const CDI_SIZE: usize = open_dice_cbor_bindgen::DICE_CDI_SIZE as usize;
const HASH_SIZE: usize = open_dice_cbor_bindgen::DICE_HASH_SIZE as usize;
+const HIDDEN_SIZE: usize = open_dice_cbor_bindgen::DICE_HIDDEN_SIZE as usize;
+const INLINE_CONFIG_SIZE: usize = open_dice_cbor_bindgen::DICE_INLINE_CONFIG_SIZE as usize;
/// Array type of CDIs.
pub type Cdi = [u8; CDI_SIZE];
/// Array type of hashes used by DICE.
pub type Hash = [u8; HASH_SIZE];
+/// Array type of additional input.
+pub type Hidden = [u8; HIDDEN_SIZE];
+/// Array type of inline configuration values.
+pub type InlineConfig = [u8; INLINE_CONFIG_SIZE];
/// Error type used by DICE.
pub enum Error {
@@ -74,6 +90,79 @@
}
}
+/// DICE mode values.
+#[derive(Clone, Copy, Debug)]
+pub enum Mode {
+ /// At least one security mechanism has not been configured. Also acts as a catch-all.
+ /// Invalid mode values should be treated like this mode.
+ NotInitialized = DICE_MODE_NOT_INITIALIZED as _,
+ /// Indicates the device is operating normally under secure configuration.
+ Normal = DICE_MODE_NORMAL as _,
+ /// Indicates at least one criteria for Normal mode is not met.
+ Debug = DICE_MODE_DEBUG as _,
+ /// Indicates a recovery or maintenance mode of some kind.
+ Maintenance = DICE_MODE_MAINTENANCE as _,
+}
+
+impl From<Mode> for DiceMode {
+ fn from(mode: Mode) -> Self {
+ mode as Self
+ }
+}
+
+/// DICE configuration input type.
+#[derive(Debug)]
+pub enum ConfigType<'a> {
+ /// Uses the formatted 64-byte configuration input value (See the Open Profile for DICE).
+ Inline(InlineConfig),
+ /// Uses the 64-byte hash of more configuration data.
+ Descriptor(&'a [u8]),
+}
+
+/// Set of DICE inputs.
+#[repr(transparent)]
+#[derive(Clone, Debug)]
+pub struct InputValues(DiceInputValues);
+
+impl InputValues {
+ /// Wrap the DICE inputs in a InputValues, expected by bcc::main_flow().
+ pub fn new(
+ code_hash: &Hash,
+ code_descriptor: Option<&[u8]>,
+ config: &ConfigType,
+ auth_hash: Option<&Hash>,
+ auth_descriptor: Option<&[u8]>,
+ mode: Mode,
+ hidden: Option<&Hidden>,
+ ) -> Self {
+ const ZEROED_INLINE_CONFIG: InlineConfig = [0; INLINE_CONFIG_SIZE];
+ let (config_type, config_value, config_descriptor) = match config {
+ ConfigType::Inline(value) => (DICE_CONFIG_TYPE_INLINE, *value, None),
+ ConfigType::Descriptor(desc) => {
+ (DICE_CONFIG_TYPE_DESCRIPTOR, ZEROED_INLINE_CONFIG, Some(*desc))
+ }
+ };
+ let (code_descriptor, code_descriptor_size) = as_raw_parts(code_descriptor);
+ let (config_descriptor, config_descriptor_size) = as_raw_parts(config_descriptor);
+ let (authority_descriptor, authority_descriptor_size) = as_raw_parts(auth_descriptor);
+
+ Self(DiceInputValues {
+ code_hash: *code_hash,
+ code_descriptor,
+ code_descriptor_size,
+ config_type,
+ config_value,
+ config_descriptor,
+ config_descriptor_size,
+ authority_hash: auth_hash.map_or([0; mem::size_of::<Hash>()], |h| *h),
+ authority_descriptor,
+ authority_descriptor_size,
+ mode: mode.into(),
+ hidden: hidden.map_or([0; mem::size_of::<Hidden>()], |h| *h),
+ })
+ }
+}
+
fn ctx() -> *mut core::ffi::c_void {
core::ptr::null_mut()
}
@@ -85,3 +174,10 @@
check_call(unsafe { DiceHash(ctx(), bytes.as_ptr(), bytes.len(), output.as_mut_ptr()) })?;
Ok(output)
}
+
+fn as_raw_parts<T: Sized>(s: Option<&[T]>) -> (*const T, usize) {
+ match s {
+ Some(s) => (s.as_ptr(), s.len()),
+ None => (ptr::null(), 0),
+ }
+}
diff --git a/pvmfw/src/dice.rs b/pvmfw/src/dice.rs
new file mode 100644
index 0000000..b322850
--- /dev/null
+++ b/pvmfw/src/dice.rs
@@ -0,0 +1,58 @@
+// Copyright 2022, 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.
+
+//! Support for DICE derivation and BCC generation.
+
+use core::ffi::CStr;
+
+use dice::bcc::format_config_descriptor;
+use dice::bcc::Handover;
+use dice::hash;
+use dice::ConfigType;
+use dice::InputValues;
+
+/// Derive the VM-specific secrets and certificate through DICE.
+pub fn derive_next_bcc(
+ bcc: &Handover,
+ next_bcc: &mut [u8],
+ code: &[u8],
+ debug_mode: bool,
+ authority: &[u8],
+) -> dice::Result<usize> {
+ let code_hash = hash(code)?;
+ let auth_hash = hash(authority)?;
+ let mode = if debug_mode { dice::Mode::Debug } else { dice::Mode::Normal };
+ let component_name = CStr::from_bytes_with_nul(b"vm_entry\0").unwrap();
+ let mut config_descriptor_buffer = [0; 128];
+ let config_descriptor_size = format_config_descriptor(
+ &mut config_descriptor_buffer,
+ Some(component_name),
+ None, // component_version
+ false, // resettable
+ )?;
+ let config = &config_descriptor_buffer[..config_descriptor_size];
+ let config = ConfigType::Descriptor(config);
+
+ let input_values = InputValues::new(
+ &code_hash,
+ None, // code_descriptor
+ &config,
+ Some(&auth_hash),
+ None, // auth_descriptor
+ mode,
+ None, // TODO(b/249723852): Get salt from instance.img (virtio-blk) and/or TRNG.
+ );
+
+ bcc.main_flow(&input_values, next_bcc)
+}
diff --git a/pvmfw/src/entry.rs b/pvmfw/src/entry.rs
index 1b35c79..52e7a22 100644
--- a/pvmfw/src/entry.rs
+++ b/pvmfw/src/entry.rs
@@ -48,6 +48,8 @@
InvalidRamdisk,
/// Failed to verify the payload.
PayloadVerificationError,
+ /// DICE layering process failed.
+ SecretDerivationError,
}
main!(start);
diff --git a/pvmfw/src/helpers.rs b/pvmfw/src/helpers.rs
index e8a20a8..ad980f5 100644
--- a/pvmfw/src/helpers.rs
+++ b/pvmfw/src/helpers.rs
@@ -20,6 +20,8 @@
pub const SIZE_4KB: usize = 4 << 10;
pub const SIZE_2MB: usize = 2 << 20;
+pub const GUEST_PAGE_SIZE: usize = SIZE_4KB;
+
/// Computes the largest multiple of the provided alignment smaller or equal to the address.
///
/// Note: the result is undefined if alignment isn't a power of two.
diff --git a/pvmfw/src/main.rs b/pvmfw/src/main.rs
index b0177bf..efd3e7c 100644
--- a/pvmfw/src/main.rs
+++ b/pvmfw/src/main.rs
@@ -21,6 +21,7 @@
mod avb;
mod config;
+mod dice;
mod entry;
mod exceptions;
mod fdt;
@@ -35,16 +36,20 @@
use crate::{
avb::PUBLIC_KEY,
+ dice::derive_next_bcc,
entry::RebootReason,
+ helpers::GUEST_PAGE_SIZE,
memory::MemoryTracker,
pci::{find_virtio_devices, map_mmio},
};
-use dice::bcc;
+use ::dice::bcc;
use fdtpci::{PciError, PciInfo};
use libfdt::Fdt;
use log::{debug, error, info, trace};
use pvmfw_avb::verify_payload;
+const NEXT_BCC_SIZE: usize = GUEST_PAGE_SIZE;
+
fn main(
fdt: &Fdt,
signed_kernel: &[u8],
@@ -77,6 +82,32 @@
RebootReason::PayloadVerificationError
})?;
+ let mut scratch_bcc = [0; NEXT_BCC_SIZE];
+ let next_bcc = &mut scratch_bcc; // TODO(b/256827715): Pass result BCC to next stage.
+ let debug_mode = false; // TODO(b/256148034): Derive the DICE mode from the received initrd.
+ const HASH_SIZE: usize = 64;
+ let mut hashes = [0; HASH_SIZE * 2]; // TODO(b/256148034): Extract AvbHashDescriptor digests.
+ hashes[..HASH_SIZE].copy_from_slice(&::dice::hash(signed_kernel).map_err(|_| {
+ error!("Failed to hash the kernel");
+ RebootReason::InternalError
+ })?);
+ // Note: Using signed_kernel currently makes the DICE code input depend on its VBMeta fields.
+ let code_hash = if let Some(rd) = ramdisk {
+ hashes[HASH_SIZE..].copy_from_slice(&::dice::hash(rd).map_err(|_| {
+ error!("Failed to hash the ramdisk");
+ RebootReason::InternalError
+ })?);
+ &hashes[..]
+ } else {
+ &hashes[..HASH_SIZE]
+ };
+ let next_bcc_size =
+ derive_next_bcc(bcc, next_bcc, code_hash, debug_mode, PUBLIC_KEY).map_err(|e| {
+ error!("Failed to derive next-stage DICE secrets: {e:?}");
+ RebootReason::SecretDerivationError
+ })?;
+ trace!("Next BCC: {:x?}", bcc::Handover::new(&next_bcc[..next_bcc_size]));
+
info!("Starting payload...");
Ok(())
}