diff --git a/Cargo.lock b/Cargo.lock index 2488f2429..ee520fe07 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" @@ -1742,6 +1813,7 @@ dependencies = [ "litebox_util_log", "num_enum", "once_cell", + "p384", "sha2", "spin 0.10.0", "thiserror", @@ -2116,6 +2188,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 +2323,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 +2538,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" @@ -2539,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/src/mshv/mod.rs b/litebox_platform_lvbs/src/mshv/mod.rs index 826daaa39..56a248356 100644 --- a/litebox_platform_lvbs/src/mshv/mod.rs +++ b/litebox_platform_lvbs/src/mshv/mod.rs @@ -134,6 +134,9 @@ pub const VSM_VTL_CALL_FUNC_ID_SET_PLATFORM_ROOT_KEY: 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; +// 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)] @@ -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..8e284f568 100644 --- a/litebox_platform_lvbs/src/mshv/vsm.rs +++ b/litebox_platform_lvbs/src/mshv/vsm.rs @@ -977,6 +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 => { + Err(VsmError::OperationNotSupported("Identity key generation")) + } VsmFunction::OpteeMessage => Err(VsmError::OperationNotSupported("OP-TEE communication")), }; match result { 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..04e3772ad --- /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.trunc(), + ) + .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,