From b9de7437a824a6ab29afc5a6c370c3cd1b9adcf3 Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Mon, 4 May 2026 18:48:40 +0000 Subject: [PATCH 1/6] generate identity signing key pair --- Cargo.lock | 113 +++++++++++++++++ dev_tests/src/ratchet.rs | 2 +- litebox_platform_lvbs/Cargo.toml | 2 + litebox_platform_lvbs/src/host/lvbs_impl.rs | 8 +- litebox_platform_lvbs/src/mshv/error.rs | 14 ++- litebox_platform_lvbs/src/mshv/mod.rs | 4 + litebox_platform_lvbs/src/mshv/vsm.rs | 133 ++++++++++++++++++++ 7 files changed, 269 insertions(+), 7 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 2488f2429..cf5d6ab28 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -145,6 +145,12 @@ version = "1.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "c08606f8c3cbf4ce6ec8e28fb0014a2c086708fe954eaa885384a6165172e7e8" +[[package]] +name = "base16ct" +version = "0.2.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" + [[package]] name = "base64" version = "0.22.1" @@ -487,6 +493,18 @@ version = "0.8.21" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d0a5c400df2834b80a4c3327b3aad3a4c4cd4de0629063962b03235697506a28" +[[package]] +name = "crypto-bigint" +version = "0.5.5" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "0dc92fb57ca44df6db8059111ab3af99a63d5d0f8375d9972e319a379c6bab76" +dependencies = [ + "generic-array", + "rand_core", + "subtle", + "zeroize", +] + [[package]] name = "crypto-common" version = "0.1.6" @@ -682,6 +700,19 @@ dependencies = [ "syn", ] +[[package]] +name = "ecdsa" +version = "0.16.9" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "ee27f32b5c5292967d2d4a9d7f1e0b0aed2c15daded5a60300e4abb9d8020bca" +dependencies = [ + "der", + "digest", + "elliptic-curve", + "rfc6979", + "signature", +] + [[package]] name = "either" version = "1.15.0" @@ -694,6 +725,24 @@ version = "0.8.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "55dd888a213fc57e957abf2aa305ee3e8a28dbe05687a251f33b637cd46b0070" +[[package]] +name = "elliptic-curve" +version = "0.13.8" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "b5e6043086bf7973472e0c7dff2142ea0b680d30e18d9cc40f267efbf222bd47" +dependencies = [ + "base16ct", + "crypto-bigint", + "digest", + "ff", + "generic-array", + "group", + "rand_core", + "sec1", + "subtle", + "zeroize", +] + [[package]] name = "encode_unicode" version = "1.0.0" @@ -776,6 +825,16 @@ version = "2.3.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "37909eebbb50d72f9059c3b6d82c0463f2ff062c9e95845c43a6c9c0355411be" +[[package]] +name = "ff" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "c0b50bfb653653f9ca9095b427bed08ab8d75a137839d9ad64eb11810d5b6393" +dependencies = [ + "rand_core", + "subtle", +] + [[package]] name = "filetime" version = "0.2.27" @@ -928,6 +987,7 @@ checksum = "85649ca51fd72272d7821adaf274ad91c288277713d9c18820d8499a7ff69e9a" dependencies = [ "typenum", "version_check", + "zeroize", ] [[package]] @@ -984,6 +1044,17 @@ dependencies = [ "regex-syntax", ] +[[package]] +name = "group" +version = "0.13.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f0f9ef7462f7c099f518d754361858f86d8a07af53ba9af0fe635bbccb151a63" +dependencies = [ + "ff", + "rand_core", + "subtle", +] + [[package]] name = "hash32" version = "0.3.1" @@ -1020,6 +1091,15 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" +[[package]] +name = "hkdf" +version = "0.12.4" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" +dependencies = [ + "hmac", +] + [[package]] name = "hmac" version = "0.12.1" @@ -1568,6 +1648,7 @@ dependencies = [ "digest", "elf", "hashbrown", + "hkdf", "libc", "litebox", "litebox_common_linux", @@ -1576,6 +1657,7 @@ dependencies = [ "num_enum", "object", "once_cell", + "p384", "rangemap", "raw-cpuid", "rsa", @@ -2116,6 +2198,18 @@ dependencies = [ "vcpkg", ] +[[package]] +name = "p384" +version = "0.13.1" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "fe42f1670a52a47d448f14b6a5c61dd78fce51856e68edaa38f7ae3a46b8d6b6" +dependencies = [ + "ecdsa", + "elliptic-curve", + "primeorder", + "sha2", +] + [[package]] name = "paste" version = "1.0.15" @@ -2239,6 +2333,15 @@ dependencies = [ "syn", ] +[[package]] +name = "primeorder" +version = "0.13.6" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "353e1ca18966c16d9deb1c69278edbc5f194139612772bd9537af60ac231e1e6" +dependencies = [ + "elliptic-curve", +] + [[package]] name = "proc-macro-error-attr2" version = "2.0.0" @@ -2445,6 +2548,16 @@ dependencies = [ "web-sys", ] +[[package]] +name = "rfc6979" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "f8dd2a808d456c4a54e300a23e9f5a67e122c3024119acbfd73e3bf664491cb2" +dependencies = [ + "hmac", + "subtle", +] + [[package]] name = "ringbuf" version = "0.4.8" diff --git a/dev_tests/src/ratchet.rs b/dev_tests/src/ratchet.rs index d5b76f069..71190b32d 100644 --- a/dev_tests/src/ratchet.rs +++ b/dev_tests/src/ratchet.rs @@ -37,7 +37,7 @@ fn ratchet_globals() -> Result<()> { ("litebox/", 9), ("litebox_platform_linux_kernel/", 6), ("litebox_platform_linux_userland/", 5), - ("litebox_platform_lvbs/", 24), + ("litebox_platform_lvbs/", 25), ("litebox_platform_multiplex/", 1), ("litebox_platform_windows_userland/", 8), ("litebox_runner_lvbs/", 6), diff --git a/litebox_platform_lvbs/Cargo.toml b/litebox_platform_lvbs/Cargo.toml index 0c628a346..b64e4441d 100644 --- a/litebox_platform_lvbs/Cargo.toml +++ b/litebox_platform_lvbs/Cargo.toml @@ -33,6 +33,8 @@ aligned-vec = { version = "0.6.4", default-features = false } raw-cpuid = "11.6.0" zerocopy = { version = "0.8", default-features = false, features = ["derive"] } zeroize = { version = "1.8", default-features = false } +hkdf = { version = "0.12.4", default-features = false } +p384 = { version = "0.13.1", default-features = false, features = ["arithmetic", "ecdsa"] } [target.'cfg(target_arch = "x86_64")'.dependencies] x86_64 = { version = "0.15.2", default-features = false, features = ["instructions"] } diff --git a/litebox_platform_lvbs/src/host/lvbs_impl.rs b/litebox_platform_lvbs/src/host/lvbs_impl.rs index a58ec66dd..075ba00dc 100644 --- a/litebox_platform_lvbs/src/host/lvbs_impl.rs +++ b/litebox_platform_lvbs/src/host/lvbs_impl.rs @@ -4,8 +4,8 @@ //! An implementation of [`HostInterface`] for LVBS use crate::{ - Errno, HostInterface, arch::ioport::serial_print_string, - host::per_cpu_variables::with_per_cpu_variables, + arch::ioport::serial_print_string, host::per_cpu_variables::with_per_cpu_variables, Errno, + HostInterface, }; use zeroize::Zeroizing; @@ -121,8 +121,8 @@ pub(crate) const PRK_LEN: usize = 32; static PRK_ONCE: spin::Once<[u8; PRK_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 -// through `DerivedKeyProvider` so PRK access stays in this module. +// Consumers should request derived key material through +// `DerivedKeyProvider` so PRK access stays centralized in this module. /// Sets the Platform Root Key (PRK) for this platform. /// diff --git a/litebox_platform_lvbs/src/mshv/error.rs b/litebox_platform_lvbs/src/mshv/error.rs index bd40d50b2..b6a63a5da 100644 --- a/litebox_platform_lvbs/src/mshv/error.rs +++ b/litebox_platform_lvbs/src/mshv/error.rs @@ -108,6 +108,13 @@ pub enum VsmError { #[error("{0} is not supported")] OperationNotSupported(&'static str), + // Key Management Errors + #[error("platform root key is not set")] + PlatformRootKeyNotSet, + + #[error("failed to generate identity signing key")] + IdentitySigningKeyGenerationFailed, + // VTL0 Memory Copy Errors #[error("failed to copy data from/to VTL0")] Vtl0CopyFailed, @@ -180,7 +187,8 @@ impl From for Errno { // Not found errors VsmError::SystemCertificatesNotFound | VsmError::KernelSymbolTableNotFound - | VsmError::PrecomputedPatchNotFound => Errno::ENOENT, + | VsmError::PrecomputedPatchNotFound + | VsmError::PlatformRootKeyNotSet => Errno::ENOENT, // Operation not permitted after end of boot VsmError::OperationAfterEndOfBoot(_) => Errno::EPERM, @@ -203,7 +211,9 @@ impl From for Errno { | VsmError::SymbolTableOutOfRange => Errno::ERANGE, // Init/hardware failures - I/O error - VsmError::ApInitFailed(_) | VsmError::HypercallFailed(_) => Errno::EIO, + VsmError::ApInitFailed(_) + | VsmError::HypercallFailed(_) + | VsmError::IdentitySigningKeyGenerationFailed => Errno::EIO, // True format/validation errors - invalid argument VsmError::AddressNotPageAligned diff --git a/litebox_platform_lvbs/src/mshv/mod.rs b/litebox_platform_lvbs/src/mshv/mod.rs index 826daaa39..ea96c20ad 100644 --- a/litebox_platform_lvbs/src/mshv/mod.rs +++ b/litebox_platform_lvbs/src/mshv/mod.rs @@ -131,6 +131,9 @@ 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 OP-TEE messages is subject to change pub const VSM_VTL_CALL_FUNC_ID_OPTEE_MESSAGE: u32 = 0x1_fff0; @@ -154,6 +157,7 @@ pub enum VsmFunction { 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, } pub const MSR_EFER: u32 = 0xc000_0080; diff --git a/litebox_platform_lvbs/src/mshv/vsm.rs b/litebox_platform_lvbs/src/mshv/vsm.rs index e2184c2f0..ae3ba2f4a 100644 --- a/litebox_platform_lvbs/src/mshv/vsm.rs +++ b/litebox_platform_lvbs/src/mshv/vsm.rs @@ -48,8 +48,12 @@ use core::{ sync::atomic::{AtomicBool, AtomicI64, Ordering}, }; use hashbrown::{HashMap, HashSet}; +use hkdf::Hkdf; +use litebox::platform::{DerivedKeyError, DerivedKeyProvider, KDFParams}; use litebox::utils::TruncateExt; use litebox_common_linux::errno::Errno; +use p384::elliptic_curve::sec1::ToEncodedPoint; +use sha2::Sha384; use spin::Once; use thiserror::Error; use x86_64::{ @@ -67,8 +71,21 @@ struct AlignedPage([u8; PAGE_SIZE]); // For now, we do not validate large kernel modules due to the VTL1's memory size limitation. const MODULE_VALIDATION_MAX_SIZE: usize = 64 * 1024 * 1024; +const IDENTITY_SIGNING_KEY_DERIVATION_INFO: &[u8] = b"litebox-lvbs-identity-signing-key-p384-v1"; +const IDENTITY_SIGNING_PRIVATE_KEY_LEN: usize = 48; +const IDENTITY_SIGNING_PUBLIC_KEY_LEN: usize = 97; + static CPU_ONLINE_MASK: Once> = Once::new(); +struct IdentitySigningKeyPair { + // Kept in VTL1 for future IDK_S signing operations; this VSM call only exports the public key. + #[allow(dead_code)] + secret_key: p384::SecretKey, + public_key: [u8; IDENTITY_SIGNING_PUBLIC_KEY_LEN], +} + +static IDENTITY_SIGNING_KEY_PAIR: Once = Once::new(); + pub(crate) fn init(is_bsp: bool) { assert!( !(is_bsp && mshv_vsm_configure_partition().is_err()), @@ -953,6 +970,94 @@ fn mshv_vsm_set_platform_root_key(key_pa: u64) -> Result { } } +/// VSM function for generating the identity signing key pair (IDK_S). +/// +/// - `public_key_pa`: Physical address (VTL0) where the uncompressed SEC1 P-384 +/// public key will be written. The corresponding private key is derived from the PRK +/// and never leaves VTL1. +/// +/// This function assumes that the caller prepares a buffer at the given physical +/// address (in a single or contiguous physical memory page(s)) whose length is equal to +/// or greater than `IDENTITY_SIGNING_PUBLIC_KEY_LEN`. +pub fn mshv_vsm_gen_identity_signing_key(public_key_pa: u64) -> Result { + if crate::platform_low().vtl0_kernel_info.check_end_of_boot() { + return Err(VsmError::OperationAfterEndOfBoot( + "generate identity signing key", + )); + } + + debug_serial_println!("VSM: Generate identity signing key"); + + let public_key_pa = + PhysAddr::try_new(public_key_pa).map_err(|_| VsmError::InvalidPhysicalAddress)?; + if public_key_pa.is_null() { + return Err(VsmError::InvalidInputAddress); + } + + let key_pair = IDENTITY_SIGNING_KEY_PAIR.try_call_once(derive_identity_signing_key_pair)?; + if unsafe { crate::platform_low().copy_slice_to_vtl0_phys(public_key_pa, &key_pair.public_key) } + { + Ok(0) + } else { + Err(VsmError::Vtl0CopyFailed) + } +} + +fn derive_identity_signing_key_pair() -> Result { + derive_identity_signing_key_pair_with(|context, output| { + crate::platform_low() + .derive_key( + Some(identity_signing_key_kdf), + KDFParams { context, output }, + ) + .map_err(|err| match err { + DerivedKeyError::ShimKDFRequired + | DerivedKeyError::UnsupportedRebootPersistentKey => { + VsmError::PlatformRootKeyNotSet + } + DerivedKeyError::ShimKDFError(err) => err, + }) + }) +} + +fn derive_identity_signing_key_pair_with( + derive_private_key: impl Fn(&[u8], &mut [u8]) -> Result<(), VsmError>, +) -> Result { + let mut derivation_info = [0u8; IDENTITY_SIGNING_KEY_DERIVATION_INFO.len() + 1]; + derivation_info[..IDENTITY_SIGNING_KEY_DERIVATION_INFO.len()] + .copy_from_slice(IDENTITY_SIGNING_KEY_DERIVATION_INFO); + let mut private_key_bytes = Zeroizing::new([0u8; IDENTITY_SIGNING_PRIVATE_KEY_LEN]); + + // HKDF output is uniformly random bytes, but P-384 private keys must be + // valid non-zero scalars smaller than the curve order. Retry with a new + // derivation label until the candidate is accepted. + for counter in u8::MIN..=u8::MAX { + derivation_info[IDENTITY_SIGNING_KEY_DERIVATION_INFO.len()] = counter; + derive_private_key(&derivation_info, &mut *private_key_bytes)?; + + let Ok(secret_key) = p384::SecretKey::from_slice(&*private_key_bytes) else { + continue; + }; + + let public_key = secret_key.public_key(); + let encoded_point = public_key.to_encoded_point(false); + let mut public_key_bytes = [0u8; IDENTITY_SIGNING_PUBLIC_KEY_LEN]; + public_key_bytes.copy_from_slice(encoded_point.as_bytes()); + return Ok(IdentitySigningKeyPair { + secret_key, + public_key: public_key_bytes, + }); + } + + Err(VsmError::IdentitySigningKeyGenerationFailed) +} + +fn identity_signing_key_kdf(root_key: &[u8], params: KDFParams<'_>) -> Result<(), VsmError> { + Hkdf::::new(None, root_key) + .expand(params.context, params.output) + .map_err(|_| VsmError::IdentitySigningKeyGenerationFailed) +} + /// VSM function dispatcher pub fn vsm_dispatch(func_id: VsmFunction, params: &[u64]) -> i64 { let result: Result = match func_id { @@ -977,6 +1082,7 @@ pub fn vsm_dispatch(func_id: VsmFunction, params: &[u64]) -> i64 { mshv_vsm_allocate_ringbuffer_memory(params[0], size) } VsmFunction::SetPlatformRootKey => mshv_vsm_set_platform_root_key(params[0]), + VsmFunction::GenerateIdentitySigningKey => mshv_vsm_gen_identity_signing_key(params[0]), VsmFunction::OpteeMessage => Err(VsmError::OperationNotSupported("OP-TEE communication")), }; match result { @@ -2093,3 +2199,30 @@ impl SymbolTable { Ok(0) } } + +#[cfg(test)] +mod tests { + use super::*; + + #[test] + fn identity_signing_key_pair_signs_and_verifies_message() { + use p384::ecdsa::{ + Signature, SigningKey, VerifyingKey, + signature::{Signer, Verifier}, + }; + + let prk = [0x5a; PRK_LEN]; + let message = b"IDK_S signing test message"; + + let key_pair = derive_identity_signing_key_pair_with(|context, output| { + identity_signing_key_kdf(&prk, KDFParams { context, output }) + }) + .unwrap(); + let signing_key = SigningKey::from(&key_pair.secret_key); + let verifying_key = VerifyingKey::from_sec1_bytes(&key_pair.public_key).unwrap(); + + let signature: Signature = signing_key.sign(message); + + verifying_key.verify(message, &signature).unwrap(); + } +} From a8dc37bc70bae9439cded2a5e636d022936eab91 Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Tue, 5 May 2026 03:37:04 +0000 Subject: [PATCH 2/6] drop hkdf --- Cargo.lock | 10 ---------- litebox_platform_lvbs/Cargo.toml | 1 - litebox_platform_lvbs/src/mshv/vsm.rs | 15 ++++++++++----- 3 files changed, 10 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index cf5d6ab28..a0a917960 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1091,15 +1091,6 @@ version = "0.5.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "2304e00983f87ffb38b55b444b5e3b60a884b5d30c0fca7d82fe33449bbe55ea" -[[package]] -name = "hkdf" -version = "0.12.4" -source = "registry+https://github.com/rust-lang/crates.io-index" -checksum = "7b5f8eb2ad728638ea2c7d47a21db23b7b58a72ed6a38256b8a1849f15fbbdf7" -dependencies = [ - "hmac", -] - [[package]] name = "hmac" version = "0.12.1" @@ -1648,7 +1639,6 @@ dependencies = [ "digest", "elf", "hashbrown", - "hkdf", "libc", "litebox", "litebox_common_linux", diff --git a/litebox_platform_lvbs/Cargo.toml b/litebox_platform_lvbs/Cargo.toml index b64e4441d..afd7ee0e3 100644 --- a/litebox_platform_lvbs/Cargo.toml +++ b/litebox_platform_lvbs/Cargo.toml @@ -33,7 +33,6 @@ aligned-vec = { version = "0.6.4", default-features = false } raw-cpuid = "11.6.0" zerocopy = { version = "0.8", default-features = false, features = ["derive"] } zeroize = { version = "1.8", default-features = false } -hkdf = { version = "0.12.4", default-features = false } p384 = { version = "0.13.1", default-features = false, features = ["arithmetic", "ecdsa"] } [target.'cfg(target_arch = "x86_64")'.dependencies] diff --git a/litebox_platform_lvbs/src/mshv/vsm.rs b/litebox_platform_lvbs/src/mshv/vsm.rs index ae3ba2f4a..c3d962398 100644 --- a/litebox_platform_lvbs/src/mshv/vsm.rs +++ b/litebox_platform_lvbs/src/mshv/vsm.rs @@ -48,12 +48,11 @@ use core::{ sync::atomic::{AtomicBool, AtomicI64, Ordering}, }; use hashbrown::{HashMap, HashSet}; -use hkdf::Hkdf; use litebox::platform::{DerivedKeyError, DerivedKeyProvider, KDFParams}; use litebox::utils::TruncateExt; use litebox_common_linux::errno::Errno; use p384::elliptic_curve::sec1::ToEncodedPoint; -use sha2::Sha384; +use sha2::{Digest, Sha384}; use spin::Once; use thiserror::Error; use x86_64::{ @@ -1053,9 +1052,15 @@ fn derive_identity_signing_key_pair_with( } fn identity_signing_key_kdf(root_key: &[u8], params: KDFParams<'_>) -> Result<(), VsmError> { - Hkdf::::new(None, root_key) - .expand(params.context, params.output) - .map_err(|_| VsmError::IdentitySigningKeyGenerationFailed) + let digest = Sha384::new() + .chain_update(root_key) + .chain_update(params.context) + .finalize(); + if params.output.len() != digest.len() { + return Err(VsmError::IdentitySigningKeyGenerationFailed); + } + params.output.copy_from_slice(&digest); + Ok(()) } /// VSM function dispatcher From fcf49fff9b409fe0566630ed0a855ccd9afc925c Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Tue, 5 May 2026 15:29:44 +0000 Subject: [PATCH 3/6] fix comments --- litebox_platform_lvbs/src/host/lvbs_impl.rs | 4 ++-- litebox_platform_lvbs/src/mshv/vsm.rs | 5 +++-- 2 files changed, 5 insertions(+), 4 deletions(-) diff --git a/litebox_platform_lvbs/src/host/lvbs_impl.rs b/litebox_platform_lvbs/src/host/lvbs_impl.rs index 075ba00dc..5a471d588 100644 --- a/litebox_platform_lvbs/src/host/lvbs_impl.rs +++ b/litebox_platform_lvbs/src/host/lvbs_impl.rs @@ -121,8 +121,8 @@ pub(crate) const PRK_LEN: usize = 32; static PRK_ONCE: spin::Once<[u8; PRK_LEN]> = spin::Once::new(); // Do not expose a raw PRK getter (i.e., no `get_platform_root_key`). -// Consumers should request derived key material through -// `DerivedKeyProvider` so PRK access stays centralized in this module. +// Consumers should provide key derivation function and context +// through `DerivedKeyProvider` so PRK access stays in this module. /// Sets the Platform Root Key (PRK) for this platform. /// diff --git a/litebox_platform_lvbs/src/mshv/vsm.rs b/litebox_platform_lvbs/src/mshv/vsm.rs index c3d962398..2d58ff7b8 100644 --- a/litebox_platform_lvbs/src/mshv/vsm.rs +++ b/litebox_platform_lvbs/src/mshv/vsm.rs @@ -969,9 +969,10 @@ fn mshv_vsm_set_platform_root_key(key_pa: u64) -> Result { } } -/// VSM function for generating the identity signing key pair (IDK_S). +/// This function generates an identity signing key pair (IDK_S) and returns the public +/// portion of it. /// -/// - `public_key_pa`: Physical address (VTL0) where the uncompressed SEC1 P-384 +/// - `public_key_pa`: Physical address (VTL0) where an uncompressed SEC1 P-384 /// public key will be written. The corresponding private key is derived from the PRK /// and never leaves VTL1. /// From 76e66daac67156cbd18fd0eb02bbc11926cfe0f1 Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Thu, 14 May 2026 16:09:29 +0000 Subject: [PATCH 4/6] refactoring --- dev_tests/src/ratchet.rs | 2 +- litebox_platform_lvbs/src/host/lvbs_impl.rs | 4 +- litebox_platform_lvbs/src/mshv/vsm.rs | 96 +++++++++++++-------- 3 files changed, 65 insertions(+), 37 deletions(-) diff --git a/dev_tests/src/ratchet.rs b/dev_tests/src/ratchet.rs index 71190b32d..d5b76f069 100644 --- a/dev_tests/src/ratchet.rs +++ b/dev_tests/src/ratchet.rs @@ -37,7 +37,7 @@ fn ratchet_globals() -> Result<()> { ("litebox/", 9), ("litebox_platform_linux_kernel/", 6), ("litebox_platform_linux_userland/", 5), - ("litebox_platform_lvbs/", 25), + ("litebox_platform_lvbs/", 24), ("litebox_platform_multiplex/", 1), ("litebox_platform_windows_userland/", 8), ("litebox_runner_lvbs/", 6), diff --git a/litebox_platform_lvbs/src/host/lvbs_impl.rs b/litebox_platform_lvbs/src/host/lvbs_impl.rs index 5a471d588..a58ec66dd 100644 --- a/litebox_platform_lvbs/src/host/lvbs_impl.rs +++ b/litebox_platform_lvbs/src/host/lvbs_impl.rs @@ -4,8 +4,8 @@ //! An implementation of [`HostInterface`] for LVBS use crate::{ - arch::ioport::serial_print_string, host::per_cpu_variables::with_per_cpu_variables, Errno, - HostInterface, + Errno, HostInterface, arch::ioport::serial_print_string, + host::per_cpu_variables::with_per_cpu_variables, }; use zeroize::Zeroizing; diff --git a/litebox_platform_lvbs/src/mshv/vsm.rs b/litebox_platform_lvbs/src/mshv/vsm.rs index 2d58ff7b8..98d374b47 100644 --- a/litebox_platform_lvbs/src/mshv/vsm.rs +++ b/litebox_platform_lvbs/src/mshv/vsm.rs @@ -51,7 +51,7 @@ use hashbrown::{HashMap, HashSet}; use litebox::platform::{DerivedKeyError, DerivedKeyProvider, KDFParams}; use litebox::utils::TruncateExt; use litebox_common_linux::errno::Errno; -use p384::elliptic_curve::sec1::ToEncodedPoint; +use p384::{NonZeroScalar, elliptic_curve::sec1::ToEncodedPoint}; use sha2::{Digest, Sha384}; use spin::Once; use thiserror::Error; @@ -61,7 +61,7 @@ use x86_64::{ }; use x509_cert::{Certificate, der::Decode}; use zerocopy::{FromBytes, FromZeros, Immutable, IntoBytes, KnownLayout}; -use zeroize::Zeroizing; +use zeroize::{Zeroize, Zeroizing}; #[derive(Copy, Clone, FromBytes, Immutable, KnownLayout)] #[repr(align(4096))] @@ -76,15 +76,6 @@ const IDENTITY_SIGNING_PUBLIC_KEY_LEN: usize = 97; static CPU_ONLINE_MASK: Once> = Once::new(); -struct IdentitySigningKeyPair { - // Kept in VTL1 for future IDK_S signing operations; this VSM call only exports the public key. - #[allow(dead_code)] - secret_key: p384::SecretKey, - public_key: [u8; IDENTITY_SIGNING_PUBLIC_KEY_LEN], -} - -static IDENTITY_SIGNING_KEY_PAIR: Once = Once::new(); - pub(crate) fn init(is_bsp: bool) { assert!( !(is_bsp && mshv_vsm_configure_partition().is_err()), @@ -994,17 +985,22 @@ pub fn mshv_vsm_gen_identity_signing_key(public_key_pa: u64) -> Result Result { - derive_identity_signing_key_pair_with(|context, output| { +fn derive_identity_signing_public_key() -> Result<[u8; IDENTITY_SIGNING_PUBLIC_KEY_LEN], VsmError> { + let private_key = derive_identity_signing_private_key()?; + identity_signing_public_key_from_private_key(&private_key) +} + +fn derive_identity_signing_private_key() +-> Result, VsmError> { + derive_identity_signing_private_key_with(|context, output| { crate::platform_low() .derive_key( Some(identity_signing_key_kdf), @@ -1020,9 +1016,9 @@ fn derive_identity_signing_key_pair() -> Result Result<(), VsmError>, -) -> Result { +) -> Result, VsmError> { let mut derivation_info = [0u8; IDENTITY_SIGNING_KEY_DERIVATION_INFO.len() + 1]; derivation_info[..IDENTITY_SIGNING_KEY_DERIVATION_INFO.len()] .copy_from_slice(IDENTITY_SIGNING_KEY_DERIVATION_INFO); @@ -1033,25 +1029,38 @@ fn derive_identity_signing_key_pair_with( // derivation label until the candidate is accepted. for counter in u8::MIN..=u8::MAX { derivation_info[IDENTITY_SIGNING_KEY_DERIVATION_INFO.len()] = counter; + private_key_bytes.zeroize(); derive_private_key(&derivation_info, &mut *private_key_bytes)?; - let Ok(secret_key) = p384::SecretKey::from_slice(&*private_key_bytes) else { - continue; - }; - - let public_key = secret_key.public_key(); - let encoded_point = public_key.to_encoded_point(false); - let mut public_key_bytes = [0u8; IDENTITY_SIGNING_PUBLIC_KEY_LEN]; - public_key_bytes.copy_from_slice(encoded_point.as_bytes()); - return Ok(IdentitySigningKeyPair { - secret_key, - public_key: public_key_bytes, - }); + if is_valid_identity_signing_private_key(&private_key_bytes) { + return Ok(private_key_bytes); + } } Err(VsmError::IdentitySigningKeyGenerationFailed) } +#[inline] +fn is_valid_identity_signing_private_key( + private_key: &[u8; IDENTITY_SIGNING_PRIVATE_KEY_LEN], +) -> bool { + NonZeroScalar::try_from(&private_key[..]).is_ok() +} + +fn identity_signing_public_key_from_private_key( + private_key: &[u8; IDENTITY_SIGNING_PRIVATE_KEY_LEN], +) -> Result<[u8; IDENTITY_SIGNING_PUBLIC_KEY_LEN], VsmError> { + let private_key_scalar = Zeroizing::new( + NonZeroScalar::try_from(&private_key[..]) + .map_err(|_| VsmError::IdentitySigningKeyGenerationFailed)?, + ); + let public_key = p384::PublicKey::from_secret_scalar(&private_key_scalar); + let encoded_point = public_key.to_encoded_point(false); + let mut public_key_bytes = [0u8; IDENTITY_SIGNING_PUBLIC_KEY_LEN]; + public_key_bytes.copy_from_slice(encoded_point.as_bytes()); + Ok(public_key_bytes) +} + fn identity_signing_key_kdf(root_key: &[u8], params: KDFParams<'_>) -> Result<(), VsmError> { let digest = Sha384::new() .chain_update(root_key) @@ -2211,7 +2220,7 @@ mod tests { use super::*; #[test] - fn identity_signing_key_pair_signs_and_verifies_message() { + fn identity_signing_private_key_signs_and_verifies_message() { use p384::ecdsa::{ Signature, SigningKey, VerifyingKey, signature::{Signer, Verifier}, @@ -2220,15 +2229,34 @@ mod tests { let prk = [0x5a; PRK_LEN]; let message = b"IDK_S signing test message"; - let key_pair = derive_identity_signing_key_pair_with(|context, output| { + let private_key = derive_identity_signing_private_key_with(|context, output| { identity_signing_key_kdf(&prk, KDFParams { context, output }) }) .unwrap(); - let signing_key = SigningKey::from(&key_pair.secret_key); - let verifying_key = VerifyingKey::from_sec1_bytes(&key_pair.public_key).unwrap(); + let signing_key = SigningKey::from_slice(&private_key[..]).unwrap(); + let public_key = identity_signing_public_key_from_private_key(&private_key).unwrap(); + let verifying_key = VerifyingKey::from_sec1_bytes(&public_key).unwrap(); let signature: Signature = signing_key.sign(message); verifying_key.verify(message, &signature).unwrap(); } + + #[test] + fn identity_signing_public_key_derivation_is_deterministic() { + let prk = [0x5a; PRK_LEN]; + + let first_private_key = derive_identity_signing_private_key_with(|context, output| { + identity_signing_key_kdf(&prk, KDFParams { context, output }) + }) + .unwrap(); + let second_private_key = derive_identity_signing_private_key_with(|context, output| { + identity_signing_key_kdf(&prk, KDFParams { context, output }) + }) + .unwrap(); + let first = identity_signing_public_key_from_private_key(&first_private_key).unwrap(); + let second = identity_signing_public_key_from_private_key(&second_private_key).unwrap(); + + assert_eq!(first, second); + } } From 6caf815cfd9ccc8cf9a84353a86d4688c5b7dd4e Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Mon, 18 May 2026 21:46:15 +0000 Subject: [PATCH 5/6] refactoring --- Cargo.lock | 15 ++- litebox_platform_lvbs/Cargo.toml | 1 - litebox_platform_lvbs/src/mshv/error.rs | 14 +- litebox_platform_lvbs/src/mshv/mod.rs | 6 +- litebox_platform_lvbs/src/mshv/vsm.rs | 172 +----------------------- litebox_runner_lvbs/src/lib.rs | 4 + litebox_shim_optee/Cargo.toml | 1 + litebox_shim_optee/src/idk.rs | 169 +++++++++++++++++++++++ litebox_shim_optee/src/lib.rs | 3 + 9 files changed, 200 insertions(+), 185 deletions(-) create mode 100644 litebox_shim_optee/src/idk.rs diff --git a/Cargo.lock b/Cargo.lock index a0a917960..ee520fe07 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1647,7 +1647,6 @@ dependencies = [ "num_enum", "object", "once_cell", - "p384", "rangemap", "raw-cpuid", "rsa", @@ -1814,6 +1813,7 @@ dependencies = [ "litebox_util_log", "num_enum", "once_cell", + "p384", "sha2", "spin 0.10.0", "thiserror", @@ -2642,6 +2642,19 @@ version = "1.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "94143f37725109f92c262ed2cf5e59bce7498c01bcc1502d7b9afe439a4e9f49" +[[package]] +name = "sec1" +version = "0.7.3" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "d3e97a565f76233a6003f9f5c54be1d9c5bdfa3eccfb189469f11ec4901c47dc" +dependencies = [ + "base16ct", + "der", + "generic-array", + "subtle", + "zeroize", +] + [[package]] name = "seccompiler" version = "0.5.0" diff --git a/litebox_platform_lvbs/Cargo.toml b/litebox_platform_lvbs/Cargo.toml index afd7ee0e3..0c628a346 100644 --- a/litebox_platform_lvbs/Cargo.toml +++ b/litebox_platform_lvbs/Cargo.toml @@ -33,7 +33,6 @@ aligned-vec = { version = "0.6.4", default-features = false } raw-cpuid = "11.6.0" zerocopy = { version = "0.8", default-features = false, features = ["derive"] } zeroize = { version = "1.8", default-features = false } -p384 = { version = "0.13.1", default-features = false, features = ["arithmetic", "ecdsa"] } [target.'cfg(target_arch = "x86_64")'.dependencies] x86_64 = { version = "0.15.2", default-features = false, features = ["instructions"] } diff --git a/litebox_platform_lvbs/src/mshv/error.rs b/litebox_platform_lvbs/src/mshv/error.rs index b6a63a5da..bd40d50b2 100644 --- a/litebox_platform_lvbs/src/mshv/error.rs +++ b/litebox_platform_lvbs/src/mshv/error.rs @@ -108,13 +108,6 @@ pub enum VsmError { #[error("{0} is not supported")] OperationNotSupported(&'static str), - // Key Management Errors - #[error("platform root key is not set")] - PlatformRootKeyNotSet, - - #[error("failed to generate identity signing key")] - IdentitySigningKeyGenerationFailed, - // VTL0 Memory Copy Errors #[error("failed to copy data from/to VTL0")] Vtl0CopyFailed, @@ -187,8 +180,7 @@ impl From for Errno { // Not found errors VsmError::SystemCertificatesNotFound | VsmError::KernelSymbolTableNotFound - | VsmError::PrecomputedPatchNotFound - | VsmError::PlatformRootKeyNotSet => Errno::ENOENT, + | VsmError::PrecomputedPatchNotFound => Errno::ENOENT, // Operation not permitted after end of boot VsmError::OperationAfterEndOfBoot(_) => Errno::EPERM, @@ -211,9 +203,7 @@ impl From for Errno { | VsmError::SymbolTableOutOfRange => Errno::ERANGE, // Init/hardware failures - I/O error - VsmError::ApInitFailed(_) - | VsmError::HypercallFailed(_) - | VsmError::IdentitySigningKeyGenerationFailed => Errno::EIO, + VsmError::ApInitFailed(_) | VsmError::HypercallFailed(_) => Errno::EIO, // True format/validation errors - invalid argument VsmError::AddressNotPageAligned diff --git a/litebox_platform_lvbs/src/mshv/mod.rs b/litebox_platform_lvbs/src/mshv/mod.rs index ea96c20ad..56a248356 100644 --- a/litebox_platform_lvbs/src/mshv/mod.rs +++ b/litebox_platform_lvbs/src/mshv/mod.rs @@ -131,12 +131,12 @@ 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 OP-TEE messages is subject to change pub const VSM_VTL_CALL_FUNC_ID_OPTEE_MESSAGE: u32 = 0x1_fff0; +// 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_fff1; + /// VSM Functions #[derive(Debug, PartialEq, TryFromPrimitive)] #[repr(u32)] diff --git a/litebox_platform_lvbs/src/mshv/vsm.rs b/litebox_platform_lvbs/src/mshv/vsm.rs index 98d374b47..8e284f568 100644 --- a/litebox_platform_lvbs/src/mshv/vsm.rs +++ b/litebox_platform_lvbs/src/mshv/vsm.rs @@ -48,11 +48,8 @@ use core::{ sync::atomic::{AtomicBool, AtomicI64, Ordering}, }; use hashbrown::{HashMap, HashSet}; -use litebox::platform::{DerivedKeyError, DerivedKeyProvider, KDFParams}; use litebox::utils::TruncateExt; use litebox_common_linux::errno::Errno; -use p384::{NonZeroScalar, elliptic_curve::sec1::ToEncodedPoint}; -use sha2::{Digest, Sha384}; use spin::Once; use thiserror::Error; use x86_64::{ @@ -61,7 +58,7 @@ use x86_64::{ }; use x509_cert::{Certificate, der::Decode}; use zerocopy::{FromBytes, FromZeros, Immutable, IntoBytes, KnownLayout}; -use zeroize::{Zeroize, Zeroizing}; +use zeroize::Zeroizing; #[derive(Copy, Clone, FromBytes, Immutable, KnownLayout)] #[repr(align(4096))] @@ -70,10 +67,6 @@ struct AlignedPage([u8; PAGE_SIZE]); // For now, we do not validate large kernel modules due to the VTL1's memory size limitation. const MODULE_VALIDATION_MAX_SIZE: usize = 64 * 1024 * 1024; -const IDENTITY_SIGNING_KEY_DERIVATION_INFO: &[u8] = b"litebox-lvbs-identity-signing-key-p384-v1"; -const IDENTITY_SIGNING_PRIVATE_KEY_LEN: usize = 48; -const IDENTITY_SIGNING_PUBLIC_KEY_LEN: usize = 97; - static CPU_ONLINE_MASK: Once> = Once::new(); pub(crate) fn init(is_bsp: bool) { @@ -960,119 +953,6 @@ fn mshv_vsm_set_platform_root_key(key_pa: u64) -> Result { } } -/// This function generates an identity signing key pair (IDK_S) and returns the public -/// portion of it. -/// -/// - `public_key_pa`: Physical address (VTL0) where an uncompressed SEC1 P-384 -/// public key will be written. The corresponding private key is derived from the PRK -/// and never leaves VTL1. -/// -/// This function assumes that the caller prepares a buffer at the given physical -/// address (in a single or contiguous physical memory page(s)) whose length is equal to -/// or greater than `IDENTITY_SIGNING_PUBLIC_KEY_LEN`. -pub fn mshv_vsm_gen_identity_signing_key(public_key_pa: u64) -> Result { - if crate::platform_low().vtl0_kernel_info.check_end_of_boot() { - return Err(VsmError::OperationAfterEndOfBoot( - "generate identity signing key", - )); - } - - debug_serial_println!("VSM: Generate identity signing key"); - - let public_key_pa = - PhysAddr::try_new(public_key_pa).map_err(|_| VsmError::InvalidPhysicalAddress)?; - if public_key_pa.is_null() { - return Err(VsmError::InvalidInputAddress); - } - - let public_key = derive_identity_signing_public_key()?; - if unsafe { crate::platform_low().copy_slice_to_vtl0_phys(public_key_pa, &public_key) } { - Ok(0) - } else { - Err(VsmError::Vtl0CopyFailed) - } -} - -fn derive_identity_signing_public_key() -> Result<[u8; IDENTITY_SIGNING_PUBLIC_KEY_LEN], VsmError> { - let private_key = derive_identity_signing_private_key()?; - identity_signing_public_key_from_private_key(&private_key) -} - -fn derive_identity_signing_private_key() --> Result, VsmError> { - derive_identity_signing_private_key_with(|context, output| { - crate::platform_low() - .derive_key( - Some(identity_signing_key_kdf), - KDFParams { context, output }, - ) - .map_err(|err| match err { - DerivedKeyError::ShimKDFRequired - | DerivedKeyError::UnsupportedRebootPersistentKey => { - VsmError::PlatformRootKeyNotSet - } - DerivedKeyError::ShimKDFError(err) => err, - }) - }) -} - -fn derive_identity_signing_private_key_with( - derive_private_key: impl Fn(&[u8], &mut [u8]) -> Result<(), VsmError>, -) -> Result, VsmError> { - let mut derivation_info = [0u8; IDENTITY_SIGNING_KEY_DERIVATION_INFO.len() + 1]; - derivation_info[..IDENTITY_SIGNING_KEY_DERIVATION_INFO.len()] - .copy_from_slice(IDENTITY_SIGNING_KEY_DERIVATION_INFO); - let mut private_key_bytes = Zeroizing::new([0u8; IDENTITY_SIGNING_PRIVATE_KEY_LEN]); - - // HKDF output is uniformly random bytes, but P-384 private keys must be - // valid non-zero scalars smaller than the curve order. Retry with a new - // derivation label until the candidate is accepted. - for counter in u8::MIN..=u8::MAX { - derivation_info[IDENTITY_SIGNING_KEY_DERIVATION_INFO.len()] = counter; - private_key_bytes.zeroize(); - derive_private_key(&derivation_info, &mut *private_key_bytes)?; - - if is_valid_identity_signing_private_key(&private_key_bytes) { - return Ok(private_key_bytes); - } - } - - Err(VsmError::IdentitySigningKeyGenerationFailed) -} - -#[inline] -fn is_valid_identity_signing_private_key( - private_key: &[u8; IDENTITY_SIGNING_PRIVATE_KEY_LEN], -) -> bool { - NonZeroScalar::try_from(&private_key[..]).is_ok() -} - -fn identity_signing_public_key_from_private_key( - private_key: &[u8; IDENTITY_SIGNING_PRIVATE_KEY_LEN], -) -> Result<[u8; IDENTITY_SIGNING_PUBLIC_KEY_LEN], VsmError> { - let private_key_scalar = Zeroizing::new( - NonZeroScalar::try_from(&private_key[..]) - .map_err(|_| VsmError::IdentitySigningKeyGenerationFailed)?, - ); - let public_key = p384::PublicKey::from_secret_scalar(&private_key_scalar); - let encoded_point = public_key.to_encoded_point(false); - let mut public_key_bytes = [0u8; IDENTITY_SIGNING_PUBLIC_KEY_LEN]; - public_key_bytes.copy_from_slice(encoded_point.as_bytes()); - Ok(public_key_bytes) -} - -fn identity_signing_key_kdf(root_key: &[u8], params: KDFParams<'_>) -> Result<(), VsmError> { - let digest = Sha384::new() - .chain_update(root_key) - .chain_update(params.context) - .finalize(); - if params.output.len() != digest.len() { - return Err(VsmError::IdentitySigningKeyGenerationFailed); - } - params.output.copy_from_slice(&digest); - Ok(()) -} - /// VSM function dispatcher pub fn vsm_dispatch(func_id: VsmFunction, params: &[u64]) -> i64 { let result: Result = match func_id { @@ -1097,7 +977,9 @@ pub fn vsm_dispatch(func_id: VsmFunction, params: &[u64]) -> i64 { mshv_vsm_allocate_ringbuffer_memory(params[0], size) } VsmFunction::SetPlatformRootKey => mshv_vsm_set_platform_root_key(params[0]), - VsmFunction::GenerateIdentitySigningKey => mshv_vsm_gen_identity_signing_key(params[0]), + VsmFunction::GenerateIdentitySigningKey => { + Err(VsmError::OperationNotSupported("Identity key generation")) + } VsmFunction::OpteeMessage => Err(VsmError::OperationNotSupported("OP-TEE communication")), }; match result { @@ -2214,49 +2096,3 @@ impl SymbolTable { Ok(0) } } - -#[cfg(test)] -mod tests { - use super::*; - - #[test] - fn identity_signing_private_key_signs_and_verifies_message() { - use p384::ecdsa::{ - Signature, SigningKey, VerifyingKey, - signature::{Signer, Verifier}, - }; - - let prk = [0x5a; PRK_LEN]; - let message = b"IDK_S signing test message"; - - let private_key = derive_identity_signing_private_key_with(|context, output| { - identity_signing_key_kdf(&prk, KDFParams { context, output }) - }) - .unwrap(); - let signing_key = SigningKey::from_slice(&private_key[..]).unwrap(); - let public_key = identity_signing_public_key_from_private_key(&private_key).unwrap(); - let verifying_key = VerifyingKey::from_sec1_bytes(&public_key).unwrap(); - - let signature: Signature = signing_key.sign(message); - - verifying_key.verify(message, &signature).unwrap(); - } - - #[test] - fn identity_signing_public_key_derivation_is_deterministic() { - let prk = [0x5a; PRK_LEN]; - - let first_private_key = derive_identity_signing_private_key_with(|context, output| { - identity_signing_key_kdf(&prk, KDFParams { context, output }) - }) - .unwrap(); - let second_private_key = derive_identity_signing_private_key_with(|context, output| { - identity_signing_key_kdf(&prk, KDFParams { context, output }) - }) - .unwrap(); - let first = identity_signing_public_key_from_private_key(&first_private_key).unwrap(); - let second = identity_signing_public_key_from_private_key(&second_private_key).unwrap(); - - assert_eq!(first, second); - } -} diff --git a/litebox_runner_lvbs/src/lib.rs b/litebox_runner_lvbs/src/lib.rs index 0ae319e59..d0397e1bc 100644 --- a/litebox_runner_lvbs/src/lib.rs +++ b/litebox_runner_lvbs/src/lib.rs @@ -257,6 +257,10 @@ 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]; + litebox_shim_optee::idk::generate_identity_signing_key(public_key_pa) + } _ => vsm_dispatch(func_id, ¶ms[1..]), } } diff --git a/litebox_shim_optee/Cargo.toml b/litebox_shim_optee/Cargo.toml index 36dcbaf05..8e6a88e02 100644 --- a/litebox_shim_optee/Cargo.toml +++ b/litebox_shim_optee/Cargo.toml @@ -22,6 +22,7 @@ spin = { version = "0.10.0", default-features = false, features = ["spin_mutex", thiserror = { version = "2.0.6", default-features = false } zerocopy = { version = "0.8", default-features = false, features = ["derive"] } zeroize = { version = "1.8", default-features = false, features = ["alloc"] } +p384 = { version = "0.13.1", default-features = false, features = ["arithmetic", "ecdsa"] } [features] default = ["platform_lvbs"] diff --git a/litebox_shim_optee/src/idk.rs b/litebox_shim_optee/src/idk.rs new file mode 100644 index 000000000..ce4692a0c --- /dev/null +++ b/litebox_shim_optee/src/idk.rs @@ -0,0 +1,169 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT license. + +use crate::NormalWorldMutPtr; +use litebox::{ + mm::linux::PAGE_SIZE, + platform::{DerivedKeyError, DerivedKeyProvider, KDFParams}, + utils::TruncateExt, +}; +use litebox_common_linux::errno::Errno; +use p384::{NonZeroScalar, elliptic_curve::sec1::ToEncodedPoint}; +use sha2::{Digest, Sha384}; +use zeroize::{Zeroize, Zeroizing}; + +const IDENTITY_SIGNING_KEY_DERIVATION_INFO: &[u8] = b"litebox-lvbs-identity-signing-key-p384-v1"; +const IDENTITY_SIGNING_PRIVATE_KEY_LEN: usize = 48; +const IDENTITY_SIGNING_PUBLIC_KEY_LEN: usize = 97; + +pub fn generate_identity_signing_key(public_key_pa: u64) -> i64 { + match generate_identity_signing_key_inner(public_key_pa) { + Ok(res) => res, + Err(e) => e.as_neg().into(), + } +} + +/// This function generates an identity signing key pair (IDK_S) and returns the public +/// portion of it. +/// +/// - `public_key_pa`: VTL0/Normal-world physical address where an uncompressed SEC1 P-384 +/// public key will be written. The corresponding private key is derived from the PRK +/// and never leaves VTL1/secure-world. +/// +/// This function assumes that the caller prepares a buffer at the given physical +/// address (in a single or contiguous physical memory page(s)) whose length is equal to +/// or greater than `IDENTITY_SIGNING_PUBLIC_KEY_LEN`. +fn generate_identity_signing_key_inner(public_key_pa: u64) -> Result { + let mut pubkey_ptr = + NormalWorldMutPtr::<[u8; IDENTITY_SIGNING_PUBLIC_KEY_LEN], PAGE_SIZE>::with_usize( + public_key_pa.truncate(), + ) + .map_err(|_| Errno::EINVAL)?; + + let public_key = derive_identity_signing_public_key()?; + unsafe { pubkey_ptr.write_at_offset(0, public_key) }.map_err(|_| Errno::EFAULT)?; + Ok(0) +} + +fn derive_identity_signing_public_key() -> Result<[u8; IDENTITY_SIGNING_PUBLIC_KEY_LEN], Errno> { + let private_key = derive_identity_signing_private_key()?; + identity_signing_public_key_from_private_key(&private_key) +} + +fn derive_identity_signing_private_key() +-> Result, Errno> { + derive_identity_signing_private_key_with(|context, output| { + litebox_platform_multiplex::platform() + .derive_key( + Some(identity_signing_key_kdf), + KDFParams { context, output }, + ) + .map_err(|err| match err { + DerivedKeyError::ShimKDFRequired + | DerivedKeyError::UnsupportedRebootPersistentKey => Errno::EINVAL, + DerivedKeyError::ShimKDFError(err) => err, + }) + }) +} + +fn derive_identity_signing_private_key_with( + derive_private_key: impl Fn(&[u8], &mut [u8]) -> Result<(), Errno>, +) -> Result, Errno> { + let mut derivation_info = [0u8; IDENTITY_SIGNING_KEY_DERIVATION_INFO.len() + 1]; + derivation_info[..IDENTITY_SIGNING_KEY_DERIVATION_INFO.len()] + .copy_from_slice(IDENTITY_SIGNING_KEY_DERIVATION_INFO); + let mut private_key_bytes = Zeroizing::new([0u8; IDENTITY_SIGNING_PRIVATE_KEY_LEN]); + + // HKDF output is uniformly random bytes, but P-384 private keys must be + // valid non-zero scalars smaller than the curve order. Retry with a new + // derivation label until the candidate is accepted. + for counter in u8::MIN..=u8::MAX { + derivation_info[IDENTITY_SIGNING_KEY_DERIVATION_INFO.len()] = counter; + private_key_bytes.zeroize(); + derive_private_key(&derivation_info, &mut *private_key_bytes)?; + + if is_valid_identity_signing_private_key(&private_key_bytes) { + return Ok(private_key_bytes); + } + } + + Err(Errno::EINVAL) +} + +#[inline] +fn is_valid_identity_signing_private_key( + private_key: &[u8; IDENTITY_SIGNING_PRIVATE_KEY_LEN], +) -> bool { + NonZeroScalar::try_from(&private_key[..]).is_ok() +} + +fn identity_signing_public_key_from_private_key( + private_key: &[u8; IDENTITY_SIGNING_PRIVATE_KEY_LEN], +) -> Result<[u8; IDENTITY_SIGNING_PUBLIC_KEY_LEN], Errno> { + let private_key_scalar = + Zeroizing::new(NonZeroScalar::try_from(&private_key[..]).map_err(|_| Errno::EINVAL)?); + let public_key = p384::PublicKey::from_secret_scalar(&private_key_scalar); + let encoded_point = public_key.to_encoded_point(false); + let mut public_key_bytes = [0u8; IDENTITY_SIGNING_PUBLIC_KEY_LEN]; + public_key_bytes.copy_from_slice(encoded_point.as_bytes()); + Ok(public_key_bytes) +} + +fn identity_signing_key_kdf(root_key: &[u8], params: KDFParams<'_>) -> Result<(), Errno> { + let digest = Sha384::new() + .chain_update(root_key) + .chain_update(params.context) + .finalize(); + if params.output.len() != digest.len() { + return Err(Errno::EINVAL); + } + params.output.copy_from_slice(&digest); + Ok(()) +} + +#[cfg(test)] +mod tests { + use super::*; + pub const PRK_LEN: usize = 32; + + #[test] + fn identity_signing_private_key_signs_and_verifies_message() { + use p384::ecdsa::{ + Signature, SigningKey, VerifyingKey, + signature::{Signer, Verifier}, + }; + + let prk = [0x5a; PRK_LEN]; + let message = b"IDK_S signing test message"; + + let private_key = derive_identity_signing_private_key_with(|context, output| { + identity_signing_key_kdf(&prk, KDFParams { context, output }) + }) + .unwrap(); + let signing_key = SigningKey::from_slice(&private_key[..]).unwrap(); + let public_key = identity_signing_public_key_from_private_key(&private_key).unwrap(); + let verifying_key = VerifyingKey::from_sec1_bytes(&public_key).unwrap(); + + let signature: Signature = signing_key.sign(message); + + verifying_key.verify(message, &signature).unwrap(); + } + + #[test] + fn identity_signing_public_key_derivation_is_deterministic() { + let prk = [0x5a; PRK_LEN]; + + let first_private_key = derive_identity_signing_private_key_with(|context, output| { + identity_signing_key_kdf(&prk, KDFParams { context, output }) + }) + .unwrap(); + let second_private_key = derive_identity_signing_private_key_with(|context, output| { + identity_signing_key_kdf(&prk, KDFParams { context, output }) + }) + .unwrap(); + let first = identity_signing_public_key_from_private_key(&first_private_key).unwrap(); + let second = identity_signing_public_key_from_private_key(&second_private_key).unwrap(); + + assert_eq!(first, second); + } +} diff --git a/litebox_shim_optee/src/lib.rs b/litebox_shim_optee/src/lib.rs index a91fe4a6e..17eff0356 100644 --- a/litebox_shim_optee/src/lib.rs +++ b/litebox_shim_optee/src/lib.rs @@ -36,6 +36,9 @@ pub(crate) mod syscalls; pub mod msg_handler; pub mod ptr; +#[cfg(feature = "platform_lvbs")] +pub mod idk; + // Re-export session management types for convenience pub use session::{ CreationReservation, MAX_TA_INSTANCES, SessionEntry, SessionManager, SessionMap, From 90d9bb3e40f14f131ac9b634c56ab4e5ea1dd98d Mon Sep 17 00:00:00 2001 From: Sangho Lee Date: Fri, 29 May 2026 02:37:36 +0000 Subject: [PATCH 6/6] rebase --- litebox_shim_optee/src/idk.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/litebox_shim_optee/src/idk.rs b/litebox_shim_optee/src/idk.rs index ce4692a0c..04e3772ad 100644 --- a/litebox_shim_optee/src/idk.rs +++ b/litebox_shim_optee/src/idk.rs @@ -36,7 +36,7 @@ pub fn generate_identity_signing_key(public_key_pa: u64) -> i64 { fn generate_identity_signing_key_inner(public_key_pa: u64) -> Result { let mut pubkey_ptr = NormalWorldMutPtr::<[u8; IDENTITY_SIGNING_PUBLIC_KEY_LEN], PAGE_SIZE>::with_usize( - public_key_pa.truncate(), + public_key_pa.trunc(), ) .map_err(|_| Errno::EINVAL)?;