Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dev_tests/src/ratchet.rs
Original file line number Diff line number Diff line change
Expand Up @@ -37,7 +37,7 @@ fn ratchet_globals() -> Result<()> {
("litebox/", 9),
("litebox_platform_linux_kernel/", 6),
("litebox_platform_linux_userland/", 5),
("litebox_platform_lvbs/", 23),
("litebox_platform_lvbs/", 24),
("litebox_platform_multiplex/", 1),
("litebox_platform_windows_userland/", 8),
("litebox_runner_lvbs/", 6),
Expand Down
30 changes: 23 additions & 7 deletions litebox_common_lvbs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,6 +27,23 @@ pub const PAGE_SHIFT: usize = 12;
/// Length of the Platform Root Key in bytes.
pub const PRK_LEN: usize = 32;

/// Length of the CRNG seed.
pub const CRNG_SEED_LEN: usize = 32;

/// Max length of the IDK_S public key.
/// Currently, the key is an uncompressed SEC1 P-384 (97 bytes).
pub const IDKS_PUB_MAX_LEN: usize = 97;

#[derive(Clone, Copy, Debug, FromBytes, Immutable, KnownLayout)]
#[repr(C)]
pub struct ExchangeSecretsRequest {
pub prk: [u8; PRK_LEN],
pub idks_seed: [u8; CRNG_SEED_LEN],
pub key_alg_variant: u32,
pub idks_pub_len: u32,
pub idks_pub: [u8; IDKS_PUB_MAX_LEN],
}

/// Maximum number of CPU cores addressable through the VTL0 `cpu_online_mask`
/// ABI. Bounds how many bits of the mask VTL1 will honor when booting APs.
pub const MAX_CORES: usize = 128;
Expand All @@ -48,11 +65,8 @@ pub const VSM_VTL_CALL_FUNC_ID_KEXEC_VALIDATE: u32 = 0x1_ffea;
pub const VSM_VTL_CALL_FUNC_ID_PATCH_TEXT: u32 = 0x1_ffeb;
pub const VSM_VTL_CALL_FUNC_ID_ALLOCATE_RINGBUFFER_MEMORY: u32 = 0x1_ffec;

// This VSM function ID for setting the platform root key is subject to change
pub const VSM_VTL_CALL_FUNC_ID_SET_PLATFORM_ROOT_KEY: u32 = 0x1_ffed;

// This VSM function ID for generating the identity signing key is subject to change
pub const VSM_VTL_CALL_FUNC_ID_GENERATE_IDENTITY_SIGNING_KEY: u32 = 0x1_ffee;
// This VSM function ID for exchanging secrets is subject to change
pub const VSM_VTL_CALL_FUNC_ID_EXCHANGE_SECRETS: u32 = 0x1_ffed;

// This VSM function ID for OP-TEE messages is subject to change
pub const VSM_VTL_CALL_FUNC_ID_OPTEE_MESSAGE: u32 = 0x1_fff0;
Expand All @@ -76,8 +90,7 @@ pub enum VsmFunction {
PatchText = VSM_VTL_CALL_FUNC_ID_PATCH_TEXT,
OpteeMessage = VSM_VTL_CALL_FUNC_ID_OPTEE_MESSAGE,
AllocateRingbufferMemory = VSM_VTL_CALL_FUNC_ID_ALLOCATE_RINGBUFFER_MEMORY,
SetPlatformRootKey = VSM_VTL_CALL_FUNC_ID_SET_PLATFORM_ROOT_KEY,
GenerateIdentitySigningKey = VSM_VTL_CALL_FUNC_ID_GENERATE_IDENTITY_SIGNING_KEY,
ExchangeSecrets = VSM_VTL_CALL_FUNC_ID_EXCHANGE_SECRETS,
}

// `HV_STATUS_*` constants used as discriminants for `HypervCallError`.
Expand Down Expand Up @@ -970,6 +983,9 @@ pub trait Vtl1Gate {
/// Read the platform root key from VTL0 `key_pa` and store it in VTL1 state.
fn set_platform_root_key(&self, key_pa: u64) -> Result<(), VsmError>;

/// Read a CRNG seed from VTL0 `trusted_seed_pa` and store it in VTL1.
fn set_crng_seed(&self, trusted_seed_pa: u64) -> Result<(), VsmError>;

/// Close VTL1's window of trusting VTL0, making
/// [`Vtl0Gate::end_of_boot_reached`] report `true` from here on. One-way:
/// the window never reopens.
Expand Down
27 changes: 20 additions & 7 deletions litebox_platform_lvbs/src/host/lvbs_impl.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ use crate::{
host::per_cpu_variables::with_per_cpu_variables,
};
use digest::Digest;
use litebox_common_lvbs::PRK_LEN;
use litebox_common_lvbs::{CRNG_SEED_LEN, PRK_LEN};
use rand_core::{RngCore, SeedableRng};
use zeroize::Zeroizing;

Expand Down Expand Up @@ -112,7 +112,7 @@ impl litebox::platform::CrngProvider for LvbsLinuxKernel {
random
.get_or_insert_with(|| {
LvbsCrng::new(
PRK_ONCE.get().expect("Platform root key not initialized"),
CRNG_SEED_ONCE.get().expect("CRNG seed not set"),
rdrand_seed().expect("RDRAND unavailable during CRNG initialization"),
)
})
Expand All @@ -134,10 +134,10 @@ struct LvbsCrng {
}

impl LvbsCrng {
fn new(prk: &[u8; PRK_LEN], rdrand_seed: CrngSeed) -> Self {
fn new(seed: &[u8; CRNG_SEED_LEN], rdrand_seed: CrngSeed) -> Self {
Self {
random: rand_chacha::ChaCha20Rng::from_seed(crng_seed_from_prk_and_rdrand(
prk,
random: rand_chacha::ChaCha20Rng::from_seed(crng_seed_from_rot_and_rdrand(
seed,
rdrand_seed,
)),
bytes_until_reseed: CRNG_RESEED_INTERVAL_BYTES,
Expand Down Expand Up @@ -176,6 +176,7 @@ impl LvbsCrng {
}

static PRK_ONCE: spin::Once<[u8; PRK_LEN]> = spin::Once::new();
static CRNG_SEED_ONCE: spin::Once<[u8; CRNG_SEED_LEN]> = spin::Once::new();

// Do not expose a raw PRK getter (i.e., no `get_platform_root_key`).
// Consumers should provide key derivation function and context
Expand All @@ -193,6 +194,18 @@ pub(crate) fn set_platform_root_key(key: &[u8; PRK_LEN]) {
});
}

/// Sets the trusted CRNG seed for this platform.
///
/// This should be called once during platform initialization with a seed
/// derived from a hardware root of trust (e.g., a TPM).
pub(crate) fn set_crng_seed(seed: &[u8; CRNG_SEED_LEN]) {
CRNG_SEED_ONCE.call_once(|| {
let mut s = Zeroizing::new([0u8; CRNG_SEED_LEN]);
s.copy_from_slice(seed);
*s
});
}

impl litebox::platform::DerivedKeyProvider for LvbsLinuxKernel {
fn derive_key<E>(
&self,
Expand Down Expand Up @@ -231,10 +244,10 @@ fn rdrand_seed() -> Option<CrngSeed> {
Some(seed)
}

fn crng_seed_from_prk_and_rdrand(prk: &[u8; PRK_LEN], rdrand_seed: CrngSeed) -> CrngSeed {
fn crng_seed_from_rot_and_rdrand(seed: &[u8; CRNG_SEED_LEN], rdrand_seed: CrngSeed) -> CrngSeed {
sha2::Sha256::new()
.chain_update(b"litebox-lvbs-crng-seed-v1")
.chain_update(prk)
.chain_update(seed)
.chain_update(rdrand_seed)
.finalize()
.into()
Expand Down
2 changes: 1 addition & 1 deletion litebox_platform_lvbs/src/host/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -8,7 +8,7 @@ pub mod lvbs_impl;
pub mod per_cpu_variables;

pub use lvbs_impl::LvbsLinuxKernel;
pub(crate) use lvbs_impl::set_platform_root_key;
pub(crate) use lvbs_impl::{set_crng_seed, set_platform_root_key};

#[cfg(test)]
pub mod mock;
Expand Down
18 changes: 16 additions & 2 deletions litebox_platform_lvbs/src/mshv/vsm.rs
Original file line number Diff line number Diff line change
Expand Up @@ -27,8 +27,8 @@ use core::ops::Range;
use litebox::utils::TruncateExt;
use litebox_common_linux::vmap::PhysPageAddr;
use litebox_common_lvbs::{
FrameTxn, HypervCallError, MemAttr, PAGE_SHIFT, PAGE_SIZE, PRK_LEN, ReservationStatus,
VsmError, Vtl0Gate, Vtl0PrivilegedWrite, Vtl1Gate,
CRNG_SEED_LEN, FrameTxn, HypervCallError, MemAttr, PAGE_SHIFT, PAGE_SIZE, PRK_LEN,
ReservationStatus, VsmError, Vtl0Gate, Vtl0PrivilegedWrite, Vtl1Gate,
};
use rangemap::RangeSet;
use spin::{Once, rwlock::RwLock as SpinRwLock};
Expand Down Expand Up @@ -790,4 +790,18 @@ impl Vtl1Gate for LvbsVtl1Gate {
crate::host::set_platform_root_key(&keybuf);
Ok(())
}

fn set_crng_seed(&self, seed_pa: u64) -> Result<(), VsmError> {
if crate::platform_low().end_of_boot_reached() {
return Err(VsmError::OperationAfterEndOfBoot("set crng seed"));
}

let seed_pa = PhysAddr::try_new(seed_pa).map_err(|_| VsmError::InvalidPhysicalAddress)?;
let mut seed = Zeroizing::new([0u8; CRNG_SEED_LEN]);
LvbsVtl0Gate::mint()
.read_vtl0_contiguous(seed_pa.as_u64(), &mut *seed)
.map_err(|_| VsmError::Vtl0CopyFailed)?;
crate::host::set_crng_seed(&seed);
Ok(())
}
}
77 changes: 68 additions & 9 deletions litebox_runner_lvbs/src/lib.rs
Original file line number Diff line number Diff line change
Expand Up @@ -13,7 +13,9 @@ use litebox::{
utils::{ReinterpretSignedExt, TruncateExt},
};
use litebox_common_linux::errno::Errno;
use litebox_common_lvbs::{NUM_VTLCALL_PARAMS, VsmError, VsmFunction};
use litebox_common_lvbs::{
ExchangeSecretsRequest, NUM_VTLCALL_PARAMS, PRK_LEN, VsmError, VsmFunction,
};
use litebox_common_optee::{
OpteeMessageCommand, OpteeMsgArgs, OpteeRpcArgs, OpteeSmcArgs, OpteeSmcResult,
OpteeSmcReturnCode, TeeOrigin, TeeResult, UteeEntryFunc, UteeParams, optee_msg_args_total_size,
Expand Down Expand Up @@ -261,10 +263,62 @@ fn vtlcall_dispatch(params: &[u64; NUM_VTLCALL_PARAMS]) -> i64 {
let smc_args_pfn = params[1];
optee_smc_handler_entry(smc_args_pfn)
}
VsmFunction::GenerateIdentitySigningKey => {
let public_key_pa = params[1];
let key_alg = params[2];
litebox_shim_optee::idk::generate_identity_signing_key(public_key_pa, key_alg)
VsmFunction::ExchangeSecrets => {
let request_pa = params[1];
let Ok(request_ptr) =
NormalWorldConstPtr::<ExchangeSecretsRequest, PAGE_SIZE>::with_usize(
request_pa.trunc(),
)
else {
return Errno::EFAULT.as_neg().into();
};
let Ok(request) = request_ptr.read_at_offset(0) else {
return Errno::EFAULT.as_neg().into();
};

let return_code = vsm_dispatch(VsmFunction::ExchangeSecrets, &params[1..2]);
if return_code < 0 {
return return_code;
}

let public_key_pa = request_pa
.checked_add(core::mem::offset_of!(ExchangeSecretsRequest, idks_pub) as u64)
.ok_or(Errno::EINVAL);
let public_key_pa = match public_key_pa {
Ok(pa) => pa,
Err(error) => return error.as_neg().into(),
};

let return_code = litebox_shim_optee::idk::generate_identity_signing_key(
public_key_pa,
u64::from(request.key_alg_variant),
);
if return_code < 0 {
return return_code;
}

let public_key_len_pa = request_pa
.checked_add(core::mem::offset_of!(ExchangeSecretsRequest, idks_pub_len) as u64)
.ok_or(Errno::EINVAL);
let public_key_len_pa = match public_key_len_pa {
Ok(pa) => pa,
Err(error) => return error.as_neg().into(),
};
let Ok(public_key_len_ptr) =
NormalWorldMutPtr::<u32, PAGE_SIZE>::with_usize(public_key_len_pa.trunc())
else {
return Errno::EFAULT.as_neg().into();
};
let Ok(public_key_len) = u32::try_from(request.idks_pub.len()) else {
return Errno::EINVAL.as_neg().into();
};
if public_key_len_ptr
.write_at_offset(0, public_key_len)
.is_err()
{
return Errno::EFAULT.as_neg().into();
}
0
}
_ => vsm_dispatch(func_id, &params[1..]),
}
Expand Down Expand Up @@ -315,10 +369,15 @@ fn vsm_dispatch(func_id: VsmFunction, params: &[u64]) -> i64 {
VsmFunction::AllocateRingbufferMemory => {
heki.allocate_ringbuffer_memory(params[0], params[1])
}
VsmFunction::SetPlatformRootKey => vtl1.set_platform_root_key(params[0]).map(|()| 0),
VsmFunction::GenerateIdentitySigningKey => {
Err(VsmError::OperationNotSupported("Identity key generation"))
}
VsmFunction::ExchangeSecrets => vtl1
.set_platform_root_key(params[0])
.and_then(|()| {
params[0]
.checked_add(PRK_LEN as u64)
.ok_or(VsmError::IntegerOverflow)
})
.and_then(|crng_seed_pa| vtl1.set_crng_seed(crng_seed_pa))
.map(|()| 0),
VsmFunction::OpteeMessage => Err(VsmError::OperationNotSupported("OP-TEE communication")),
};
match result {
Expand Down