From 15e60f34afd658329cf2feb3be33b46b213493df Mon Sep 17 00:00:00 2001 From: Fikret Can Date: Tue, 11 Feb 2025 20:45:41 -0800 Subject: [PATCH 01/16] Adding cmvutil: initial work --- Cargo.lock | 16 ++ Cargo.toml | 2 + vm/cvmutil/Cargo.toml | 24 +++ vm/cvmutil/src/main.rs | 268 +++++++++++++++++++++++++++++++ vm/devices/tpm/src/lib.rs | 6 +- vm/devices/tpm/src/tpm_helper.rs | 37 ++++- 6 files changed, 348 insertions(+), 5 deletions(-) create mode 100644 vm/cvmutil/Cargo.toml create mode 100644 vm/cvmutil/src/main.rs diff --git a/Cargo.lock b/Cargo.lock index cd499492ac..5b2b459783 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -990,6 +990,22 @@ dependencies = [ "tracing-subscriber", ] +[[package]] +name = "cvmutil" +version = "0.0.0" +dependencies = [ + "clap", + "getrandom", + "inspect", + "ms-tpm-20-ref", + "sha2", + "tpm", + "tpm_resources", + "tracing", + "tracing-subscriber", + "tracing_helpers", +] + [[package]] name = "debug_ptr" version = "0.0.0" diff --git a/Cargo.toml b/Cargo.toml index fa081b8457..2e146a3783 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -46,6 +46,7 @@ members = [ "vm/loader/igvmfilegen", "vm/vmgs/vmgs_lib", "vm/vmgs/vmgstool", + "vm/cvmutil", ] exclude = [ "xsync", @@ -352,6 +353,7 @@ vmgs = { path = "vm/vmgs/vmgs" } vmgs_broker = { path = "vm/vmgs/vmgs_broker" } vmgs_format = { path = "vm/vmgs/vmgs_format" } vmgs_resources = { path = "vm/vmgs/vmgs_resources" } +cvmutil = { path = "vm/cvmutil" } watchdog_core = { path = "vm/devices/watchdog/watchdog_core" } watchdog_vmgs_format = { path = "vm/devices/watchdog/watchdog_vmgs_format" } whp = { path = "vm/whp" } diff --git a/vm/cvmutil/Cargo.toml b/vm/cvmutil/Cargo.toml new file mode 100644 index 0000000000..ecd012e1f2 --- /dev/null +++ b/vm/cvmutil/Cargo.toml @@ -0,0 +1,24 @@ +# Copyright (c) Microsoft Corporation. +# Licensed under the MIT License. + +[package] +name = "cvmutil" +edition = "2021" +rust-version.workspace = true + +[features] + +[dependencies] +ms-tpm-20-ref = { version = "0.1", git = "https://github.com/microsoft/ms-tpm-20-ref-rs.git", branch = "main" } +inspect.workspace = true +getrandom.workspace = true +tracing.workspace = true +tracing_helpers.workspace = true +tracing-subscriber.workspace = true +tpm = { workspace = true, features = ["tpm"] } +tpm_resources.workspace = true +clap = { workspace = true, features = ["derive"] } +sha2.workspace = true + +[lints] +workspace = true diff --git a/vm/cvmutil/src/main.rs b/vm/cvmutil/src/main.rs new file mode 100644 index 0000000000..630f9cef3d --- /dev/null +++ b/vm/cvmutil/src/main.rs @@ -0,0 +1,268 @@ +use ms_tpm_20_ref; +use ms_tpm_20_ref::MsTpm20RefPlatform; +//use tpm::{self, tpm_helper}; +use tpm::tpm_helper::{self, TpmEngineHelper}; +//use ms_tpm_20_ref::PlatformCallbacks; +//use crate::Tpm; +// use inspect::InspectMut; +use ms_tpm_20_ref::DynResult; +use std::fs; +use std::io::Read; +use std::io::Seek; +use std::io::Write; +use std::time::Instant; +use tpm::TPM_RSA_SRK_HANDLE; +use tracing::Level; + +use sha2::{Digest, Sha256}; + +use clap::Parser; + +#[derive(Parser, Debug)] +#[clap(name = "cvmutil", about = "Tool to interact with vtmp blobs.")] +struct CmdArgs { + /// Creates a vTpm blob and stores to file. Example: ./cvmutil --createvtpmblob vTpm.blob + #[arg(long = "createvtpmblob", value_name = "path-to-blob-file")] + createvtpmblob: Option, + + /// Writes the SRK pub in TPM_2B format. Example: ./cvmutil --writeSrk vTpm.blob srk.pub + #[clap(long = "writeSrk", value_names = &["path-to-blob-file", "path-to-srk-out-file"], number_of_values = 2)] + write_srk: Option>, + + /// Prints the tpm key name. Example: CvmUtil.exe -printKeyName srk.pub + #[arg(long = "printKeyName", value_name = "path-to-srkPub")] + print_key_name: Option, +} + +fn main() { + // Initialize the logger. + tracing_subscriber::fmt() + .with_writer(std::io::stderr) + .log_internal_errors(true) + .with_max_level(Level::ERROR) + .init(); + + // Parse the command line arguments. + let args = CmdArgs::parse(); + + if let Some(path) = args.createvtpmblob { + println!("CreateVtpmBlob: {}", path); + let file = fs::File::create(path.clone()).expect("failed to create file"); + println!("Creating vTPM blob in file: {:?}", path); + let tpm_engine_helper = create_tpm_engine_helper(file); + create_vtpm_blob(tpm_engine_helper); + } else if let Some(paths) = args.write_srk { + if paths.len() == 2 { + let vtpm_blob_path = &paths[0]; + // Restore the TPM engine from the vTPM blob. + let vtpm_engine_helper = restore_tpm_engine_helper(vtpm_blob_path.to_string()); + let srk_out_path = &paths[1]; + println!( + "WriteSrk: blob file: {}, Srk out file: {}", + vtpm_blob_path, srk_out_path + ); + export_vtpm_srk_pub(vtpm_engine_helper, srk_out_path); + } else { + println!("Invalid number of arguments for --writeSrk. Expected 2 values."); + } + } else if let Some(srkpub_path) = args.print_key_name { + print_vtpm_srk_pub_key_name(srkpub_path); + } else { + println!("No command specified. Please re-run with --help for usage information."); + println!(); + } +} + +fn create_vtpm_blob(mut tpm_engine_helper: TpmEngineHelper) { + // Create a vTPM instance. + let result = tpm_engine_helper.initialize_tpm_engine(); + assert!(result.is_ok()); + + //let mut tpm_engine_helper = create_tpm_engine_helper(); + //restart_tpm_engine(&mut tpm_engine_helper, false, true); + + // Create a primary key: SRK + let auth_handle = tpm::tpm20proto::TPM20_RH_OWNER; + // DON'T have a template for SRK. Use EK template instead for now. + let result = tpm_helper::srk_pub_template(); + assert!(result.is_ok()); + let in_public = result.unwrap(); + let result = tpm_engine_helper.create_primary(auth_handle, in_public); + match result { + Ok(response) => { + println!("SRK handle: {:?}", response.object_handle); + assert_ne!(response.out_public.size.get(), 0); + println!("SRK public area: {:?}", response.out_public.public_area); + + // Write the SRK pub to a file. + //let srk_pub_file = Use the input. + let mut srk_pub_file = fs::File::create("srk_pub.bin").expect("failed to create file"); + let srk_pub = response.out_public.public_area.serialize(); + let srk_pub = srk_pub.as_slice(); + srk_pub_file + .write_all(&srk_pub) + .expect("failed to write to file"); + + // Do I need to do evict control here?? + // Evict the SRK handle. + let result = tpm_engine_helper.evict_control( + tpm::tpm20proto::TPM20_RH_OWNER, + response.object_handle, + TPM_RSA_SRK_HANDLE, + ); + assert!(result.is_ok()); + } + Err(e) => { + println!("Error in create_primary: {:?}", e); + } + } + //assert!(result.is_ok()); + + // Save the state of the TPM engine. + tpm_engine_helper.tpm_engine.save_state(); +} + +fn export_vtpm_srk_pub(mut tpm_engine_helper: TpmEngineHelper, srk_out_path: &str) { + // Create a vTPM instance. + println!("Initializing TPM engine."); + let result = tpm_engine_helper.initialize_tpm_engine(); + assert!(result.is_ok()); + print!("TPM engine initialized."); + + // Extract SRK primary key public area. + let result = tpm_engine_helper.read_public(TPM_RSA_SRK_HANDLE); + match result { + Ok(response) => { + println!("SRK public area: {:?}", response.out_public.public_area); + + // Write the SRK pub to a file. + //let srk_pub_file = Use the input. + let mut srk_pub_file = fs::File::create(srk_out_path).expect("failed to create file"); + let srk_pub = response.out_public.public_area.serialize(); + let srk_pub = srk_pub.as_slice(); + srk_pub_file + .write_all(&srk_pub) + .expect("failed to write to file"); + } + Err(e) => { + println!("Error in read_public: {:?}", e); + } + } +} + +/// Print the SRK public key name. +fn print_vtpm_srk_pub_key_name(srkpub_path: String) { + let mut srk_pub_file = fs::OpenOptions::new() + .write(false) + .read(true) + .open(srkpub_path) + .expect("failed to open file"); + + let mut srkpub_content_buf = Vec::new(); + srk_pub_file + .read_to_end(&mut srkpub_content_buf) + .expect("failed to read file"); + + // Deserialize the srkpub to a public area. + + let public_key = tpm::tpm20proto::protocol::Tpm2bPublic::deserialize(&srkpub_content_buf) + .expect("failed to deserialize srkpub"); + let public_area = public_key.public_area; + // Compute SHA256 hash of the public area + let mut hasher = Sha256::new(); + hasher.update(public_area.serialize()); + let result = hasher.finalize(); + println!("Printing key properties.\n"); + println!("SHA256 Hash:"); + print!(" "); + for i in 0..result.len() { + print!("{:02X} ", result[i]); + } + println!(); + + //println!("Key name: {:?}\n", result); + //let algorithm_id = public_area.name_alg; + + println!("Operation completed successfully.\n"); + +} + +struct TestPlatformCallbacks { + file: fs::File, + time: Instant, +} + +impl ms_tpm_20_ref::PlatformCallbacks for TestPlatformCallbacks { + fn commit_nv_state(&mut self, state: &[u8]) -> DynResult<()> { + tracing::info!("commit_nv_state: {:?}", state); + tracing::info!("committing nv state with len {}", state.len()); + self.file.rewind()?; + self.file.write_all(state)?; + Ok(()) + } + + fn get_crypt_random(&mut self, buf: &mut [u8]) -> DynResult { + getrandom::getrandom(buf).expect("rng failure"); + + Ok(buf.len()) + } + + fn monotonic_timer(&mut self) -> std::time::Duration { + self.time.elapsed() + } + + fn get_unique_value(&self) -> &'static [u8] { + b"vtpm test" + } +} + +/// Create a new TPM engine with blank state. +pub fn create_tpm_engine_helper(file: fs::File) -> TpmEngineHelper { + let result = MsTpm20RefPlatform::initialize( + Box::new(TestPlatformCallbacks { + file, + time: Instant::now(), + }), + ms_tpm_20_ref::InitKind::ColdInit, + ); + assert!(result.is_ok()); + + let tpm_engine: MsTpm20RefPlatform = result.unwrap(); + + TpmEngineHelper { + tpm_engine, + reply_buffer: [0u8; 4096], + } +} + +/// Restore a TPM engine helper from vtpm blob. +pub fn restore_tpm_engine_helper(vtpm_blob_path: String) -> TpmEngineHelper { + let mut vtpm_blob_file = fs::OpenOptions::new() + .write(true) + .read(true) + .open(vtpm_blob_path) + .expect("failed to open file"); + + let mut vtpm_content_buf = Vec::new(); + vtpm_blob_file + .read_to_end(&mut vtpm_content_buf) + .expect("failed to read file"); + + let result = MsTpm20RefPlatform::initialize( + Box::new(TestPlatformCallbacks { + file: vtpm_blob_file, + time: Instant::now(), + }), + ms_tpm_20_ref::InitKind::ColdInitWithPersistentState { + nvmem_blob: vtpm_content_buf.into(), + }, + ); + assert!(result.is_ok()); + + let tpm_engine: MsTpm20RefPlatform = result.unwrap(); + + TpmEngineHelper { + tpm_engine, + reply_buffer: [0u8; 4096], + } +} diff --git a/vm/devices/tpm/src/lib.rs b/vm/devices/tpm/src/lib.rs index 82933ed738..64cc2d262a 100644 --- a/vm/devices/tpm/src/lib.rs +++ b/vm/devices/tpm/src/lib.rs @@ -16,8 +16,8 @@ pub mod ak_cert; pub mod logger; mod recover; pub mod resolver; -mod tpm20proto; -mod tpm_helper; +pub mod tpm20proto; +pub mod tpm_helper; use self::io_port_interface::PpiOperation; use self::io_port_interface::TpmIoCommand; @@ -86,7 +86,7 @@ const RSA_2K_MODULUS_BITS: u16 = 2048; const RSA_2K_MODULUS_SIZE: usize = (RSA_2K_MODULUS_BITS / 8) as usize; const RSA_2K_EXPONENT_SIZE: usize = 3; -const TPM_RSA_SRK_HANDLE: ReservedHandle = ReservedHandle::new(TPM20_HT_PERSISTENT, 0x01); +pub const TPM_RSA_SRK_HANDLE: ReservedHandle = ReservedHandle::new(TPM20_HT_PERSISTENT, 0x01); const TPM_AZURE_AIK_HANDLE: ReservedHandle = ReservedHandle::new(TPM20_HT_PERSISTENT, 0x03); const TPM_GUEST_SECRET_HANDLE: ReservedHandle = ReservedHandle::new(TPM20_HT_PERSISTENT, 0x04); diff --git a/vm/devices/tpm/src/tpm_helper.rs b/vm/devices/tpm/src/tpm_helper.rs index d4906bc0d1..76a10cb0b7 100644 --- a/vm/devices/tpm/src/tpm_helper.rs +++ b/vm/devices/tpm/src/tpm_helper.rs @@ -1871,6 +1871,39 @@ pub fn ek_pub_template() -> Result { Ok(in_public) } +/// Returns the public template for SRK. +/// TODO: review the properties. +pub fn srk_pub_template() -> Result { + let symmetric = TpmtSymDefObject::new(AlgIdEnum::AES.into(), Some(128), Some(AlgIdEnum::CFB.into())); + + //let scheme = TpmtRsaScheme::new(AlgIdEnum::RSA.into(), Some(AlgIdEnum::SHA256.into())); + // Define the RSA scheme as TPM2_ALG_NULL for general use + let scheme = TpmtRsaScheme::new(AlgIdEnum::NULL.into(), None); + + let rsa_params = TpmsRsaParams::new(symmetric, scheme, crate::RSA_2K_MODULUS_BITS, 0); + + let object_attributes = TpmaObjectBits::new() + .with_fixed_tpm(true) + .with_fixed_parent(true) + .with_sensitive_data_origin(true) + .with_user_with_auth(true) + .with_no_da(true) + .with_restricted(true) + .with_decrypt(true); + + let in_public = TpmtPublic::new( + AlgIdEnum::RSA.into(), + AlgIdEnum::SHA256.into(), + object_attributes, + &[], + rsa_params, + &[0u8; crate::RSA_2K_MODULUS_SIZE], + ) + .map_err(TpmHelperUtilityError::InvalidInputParameter)?; + + Ok(in_public) +} + /// Helper function for converting `Tpm2bPublic` to `TpmRsa2kPublic`. fn export_rsa_public(public: &Tpm2bPublic) -> Result { if public.public_area.parameters.exponent.get() != 0 { @@ -1897,7 +1930,7 @@ fn export_rsa_public(public: &Tpm2bPublic) -> Result TpmEngineHelper { + pub fn create_tpm_engine_helper() -> TpmEngineHelper { let result = MsTpm20RefPlatform::initialize( Box::new(TestPlatformCallbacks { blob: vec![], From 7c7bda6f29e68ce1cd6f2bc4316ff6c0e9d2d545 Mon Sep 17 00:00:00 2001 From: Fikret Can Date: Mon, 24 Feb 2025 11:49:16 -0800 Subject: [PATCH 02/16] Update print srk key properties command --- Cargo.lock | 3 +- vm/cvmutil/Cargo.toml | 1 + vm/cvmutil/src/main.rs | 57 ++++++++++++++++++++++++++------ vm/devices/tpm/src/tpm20proto.rs | 2 +- vm/devices/tpm/src/tpm_helper.rs | 2 +- 5 files changed, 51 insertions(+), 14 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 5b2b459783..cef94c76e5 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -994,8 +994,9 @@ dependencies = [ name = "cvmutil" version = "0.0.0" dependencies = [ + "base64 0.22.1", "clap", - "getrandom", + "getrandom 0.3.3", "inspect", "ms-tpm-20-ref", "sha2", diff --git a/vm/cvmutil/Cargo.toml b/vm/cvmutil/Cargo.toml index ecd012e1f2..3a333856e7 100644 --- a/vm/cvmutil/Cargo.toml +++ b/vm/cvmutil/Cargo.toml @@ -19,6 +19,7 @@ tpm = { workspace = true, features = ["tpm"] } tpm_resources.workspace = true clap = { workspace = true, features = ["derive"] } sha2.workspace = true +base64.workspace = true [lints] workspace = true diff --git a/vm/cvmutil/src/main.rs b/vm/cvmutil/src/main.rs index 630f9cef3d..e1dfb205b7 100644 --- a/vm/cvmutil/src/main.rs +++ b/vm/cvmutil/src/main.rs @@ -1,19 +1,22 @@ use ms_tpm_20_ref; use ms_tpm_20_ref::MsTpm20RefPlatform; //use tpm::{self, tpm_helper}; +use tpm::tpm20proto::protocol::TpmtPublic; +use tpm::tpm20proto::AlgId; use tpm::tpm_helper::{self, TpmEngineHelper}; //use ms_tpm_20_ref::PlatformCallbacks; //use crate::Tpm; // use inspect::InspectMut; use ms_tpm_20_ref::DynResult; -use std::fs; use std::io::Read; use std::io::Seek; use std::io::Write; use std::time::Instant; +use std::{fs, vec}; use tpm::TPM_RSA_SRK_HANDLE; use tracing::Level; +use base64::Engine; use sha2::{Digest, Sha256}; use clap::Parser; @@ -165,26 +168,58 @@ fn print_vtpm_srk_pub_key_name(srkpub_path: String) { // Deserialize the srkpub to a public area. - let public_key = tpm::tpm20proto::protocol::Tpm2bPublic::deserialize(&srkpub_content_buf) - .expect("failed to deserialize srkpub"); - let public_area = public_key.public_area; + let public_key = + TpmtPublic::deserialize(&srkpub_content_buf).expect("failed to deserialize srkpub"); + let public_area: TpmtPublic = public_key.into(); // Compute SHA256 hash of the public area let mut hasher = Sha256::new(); hasher.update(public_area.serialize()); - let result = hasher.finalize(); + let public_area_hash = hasher.finalize(); + + // Compute the key name + let rsa_key = public_area.unique; + // Compute SHA256 hash of the public key + let mut hasher = Sha256::new(); + hasher.update(rsa_key.buffer); + let rsa_key_hash = hasher.finalize(); + println!("Printing key properties.\n"); println!("SHA256 Hash:"); print!(" "); - for i in 0..result.len() { - print!("{:02X} ", result[i]); + for i in 0..rsa_key_hash.len() { + if i % 16 == 0 { + println!(); + print!(" "); + } + print!("{:02X} ", rsa_key_hash[i]); } println!(); - //println!("Key name: {:?}\n", result); - //let algorithm_id = public_area.name_alg; + // Compute the key name + let algorithm_id = public_area.name_alg; + let mut output_key = vec![0u8; size_of::() + public_area_hash.len()]; + output_key[0] = (algorithm_id.0.get() >> 8) as u8; + output_key[1] = (algorithm_id.0.get() & 0xFF) as u8; + for i in 0..public_area_hash.len() { + output_key[i + 2] = public_area_hash[i]; + } + + let base64_key = base64::engine::general_purpose::STANDARD.encode(&output_key); + println!("Key name:\n"); + println!(" {}", base64_key); - println!("Operation completed successfully.\n"); - + // Print RSA bytes in hex to be able to compare with tpm2_readpublic -c 0x81000001 + println!("\nRSA key bytes:"); + print!(" "); + for i in 0..tpm_helper::RSA_2K_MODULUS_SIZE { + if i % 32 == 0 { + println!(); + print!(" "); + } + print!("{:02X}", rsa_key.buffer[i]); + } + println!(); + println!("\nOperation completed successfully.\n"); } struct TestPlatformCallbacks { diff --git a/vm/devices/tpm/src/tpm20proto.rs b/vm/devices/tpm/src/tpm20proto.rs index 6f8c47a2ca..39d45a1db8 100644 --- a/vm/devices/tpm/src/tpm20proto.rs +++ b/vm/devices/tpm/src/tpm20proto.rs @@ -1374,7 +1374,7 @@ pub mod protocol { #[derive(Debug, Copy, Clone, FromBytes, IntoBytes, Immutable, KnownLayout)] pub struct TpmtPublic { my_type: AlgId, - name_alg: AlgId, + pub name_alg: AlgId, pub object_attributes: TpmaObject, auth_policy: Tpm2bBuffer, // `TPMS_RSA_PARAMS` diff --git a/vm/devices/tpm/src/tpm_helper.rs b/vm/devices/tpm/src/tpm_helper.rs index 76a10cb0b7..e88fe4d7d7 100644 --- a/vm/devices/tpm/src/tpm_helper.rs +++ b/vm/devices/tpm/src/tpm_helper.rs @@ -60,7 +60,7 @@ const MAX_NV_INDEX_SIZE: u16 = 4096; const MAX_ATTESTATION_INDEX_SIZE: u16 = 2600; const RSA_2K_MODULUS_BITS: u16 = 2048; -const RSA_2K_MODULUS_SIZE: usize = (RSA_2K_MODULUS_BITS / 8) as usize; +pub const RSA_2K_MODULUS_SIZE: usize = (RSA_2K_MODULUS_BITS / 8) as usize; const RSA_2K_EXPONENT_SIZE: usize = 3; /// TPM command debug information used by error logs. From 5a7da0fe95c5cb5c65ec58b0edbc7607d72a900e Mon Sep 17 00:00:00 2001 From: Fikret Can Date: Mon, 3 Mar 2025 16:18:21 -0800 Subject: [PATCH 03/16] Add create provisioning secret key - step 1 --- Cargo.lock | 2 + vm/cvmutil/Cargo.toml | 2 + vm/cvmutil/src/main.rs | 288 +++++++++++++++++++++++++++---- vm/cvmutil/src/marshal.rs | 168 ++++++++++++++++++ vm/devices/tpm/src/tpm20proto.rs | 4 +- 5 files changed, 431 insertions(+), 33 deletions(-) create mode 100644 vm/cvmutil/src/marshal.rs diff --git a/Cargo.lock b/Cargo.lock index cef94c76e5..6624b42f22 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -999,12 +999,14 @@ dependencies = [ "getrandom 0.3.3", "inspect", "ms-tpm-20-ref", + "openssl", "sha2", "tpm", "tpm_resources", "tracing", "tracing-subscriber", "tracing_helpers", + "zerocopy 0.8.25", ] [[package]] diff --git a/vm/cvmutil/Cargo.toml b/vm/cvmutil/Cargo.toml index 3a333856e7..892a9bf45c 100644 --- a/vm/cvmutil/Cargo.toml +++ b/vm/cvmutil/Cargo.toml @@ -17,9 +17,11 @@ tracing_helpers.workspace = true tracing-subscriber.workspace = true tpm = { workspace = true, features = ["tpm"] } tpm_resources.workspace = true +zerocopy.workspace = true clap = { workspace = true, features = ["derive"] } sha2.workspace = true base64.workspace = true +openssl.workspace = true [lints] workspace = true diff --git a/vm/cvmutil/src/main.rs b/vm/cvmutil/src/main.rs index e1dfb205b7..c5a59e4a73 100644 --- a/vm/cvmutil/src/main.rs +++ b/vm/cvmutil/src/main.rs @@ -1,9 +1,15 @@ use ms_tpm_20_ref; use ms_tpm_20_ref::MsTpm20RefPlatform; //use tpm::{self, tpm_helper}; -use tpm::tpm20proto::protocol::TpmtPublic; -use tpm::tpm20proto::AlgId; +use tpm::tpm20proto::protocol::{ + Tpm2bBuffer, Tpm2bPublic, TpmsRsaParams, TpmtPublic, TpmtRsaScheme, TpmtSymDefObject, +}; +use tpm::tpm20proto::{AlgId, AlgIdEnum, TpmaObjectBits}; use tpm::tpm_helper::{self, TpmEngineHelper}; +use tpm::TPM_RSA_SRK_HANDLE; +mod marshal; +use marshal::TpmtSensitive; +use zerocopy::FromZeroes; //use ms_tpm_20_ref::PlatformCallbacks; //use crate::Tpm; // use inspect::InspectMut; @@ -12,11 +18,17 @@ use std::io::Read; use std::io::Seek; use std::io::Write; use std::time::Instant; -use std::{fs, vec}; -use tpm::TPM_RSA_SRK_HANDLE; +use std::{fs, fs::File, vec}; use tracing::Level; use base64::Engine; +//use openssl::bn::BigNum; +use openssl::ec::EcGroup; +use openssl::ec::EcKey; +//use openssl::ec::PointConversionForm; +use openssl::nid::Nid; +use openssl::pkey::PKey; +use openssl::rsa::Rsa; use sha2::{Digest, Sha256}; use clap::Parser; @@ -25,16 +37,28 @@ use clap::Parser; #[clap(name = "cvmutil", about = "Tool to interact with vtmp blobs.")] struct CmdArgs { /// Creates a vTpm blob and stores to file. Example: ./cvmutil --createvtpmblob vTpm.blob - #[arg(long = "createvtpmblob", value_name = "path-to-blob-file")] + #[arg( + short = 'c', + long = "createvtpmblob", + value_name = "path-to-blob-file", + number_of_values = 1 + )] createvtpmblob: Option, - /// Writes the SRK pub in TPM_2B format. Example: ./cvmutil --writeSrk vTpm.blob srk.pub - #[clap(long = "writeSrk", value_names = &["path-to-blob-file", "path-to-srk-out-file"], number_of_values = 2)] + #[clap(short='w', long = "writeSrk", value_names = &["path-to-blob-file", "path-to-srk-out-file"], long_help="Write the SRK public key in TPM2B format.\n./cvmutil --writeSrk vTpm.blob srk.pub")] write_srk: Option>, - /// Prints the tpm key name. Example: CvmUtil.exe -printKeyName srk.pub - #[arg(long = "printKeyName", value_name = "path-to-srkPub")] + #[arg(short = 'p', long = "printKeyName", value_name = "path-to-srkPub", long_help="Print the TPM key name \n./cvmutil --printKeyName srk.pub")] print_key_name: Option, + + #[arg(short = 's', long = "createRandomKeyInTpm2ImportBlobFormat", value_names = &["algorithm", "publicKey", "output-file"], long_help="Create random RSA/ECC key in Tpm2 import blob format:TPM2B_PUBLIC || TP2B_PRIVATE || TP2B_ENCRYPTED_SEED \n./cvmutil --createRandomKeyInTpm2ImportBlobFormat rsa rsa_pub.der marshalled_import_blob.tpm2b")] + create_random_key_in_tpm2_import_blob_format: Option>, + + #[arg(short = 'd', long = "printDER", value_name = "path-to-pubKey-der", long_help="Print info about DER key \n./cvmutil --printDER rsa_pub.der")] + print_pub_key_der: Option, + + #[arg(short = 't', long = "printTPM2B", value_name = "path-to-privKey-tpm2b", long_help="Print info about TPM2B import file: TPM2B_PUBLIC || TP2B_PRIVATE || TP2B_ENCRYPTED_SEED. \n./cvmutil --printTPM2B marshalled_import_blob.tpm2b")] + print_priv_key_tpm2b: Option, } fn main() { @@ -50,7 +74,7 @@ fn main() { if let Some(path) = args.createvtpmblob { println!("CreateVtpmBlob: {}", path); - let file = fs::File::create(path.clone()).expect("failed to create file"); + let file = File::create(path.clone()).expect("failed to create file"); println!("Creating vTPM blob in file: {:?}", path); let tpm_engine_helper = create_tpm_engine_helper(file); create_vtpm_blob(tpm_engine_helper); @@ -68,8 +92,25 @@ fn main() { } else { println!("Invalid number of arguments for --writeSrk. Expected 2 values."); } + } else if let Some(args) = args.create_random_key_in_tpm2_import_blob_format { + if args.len() == 3 { + let algorithm = &args[0]; + let public_key_file = &args[1]; + let private_key_tpm2b_file = &args[2]; + create_random_key_in_tpm2_import_blob_format( + algorithm, + public_key_file, + private_key_tpm2b_file, + ); + } else { + println!("Invalid number of arguments for --createRandomKeyInTpm2ImportBlobFormat. Expected 3 values."); + } } else if let Some(srkpub_path) = args.print_key_name { print_vtpm_srk_pub_key_name(srkpub_path); + } else if let Some(pub_der_path) = args.print_pub_key_der { + print_pub_key_der(pub_der_path); + } else if let Some(priv_tpm2b_path) = args.print_priv_key_tpm2b { + print_tpm2bimport_content(priv_tpm2b_path); } else { println!("No command specified. Please re-run with --help for usage information."); println!(); @@ -99,7 +140,7 @@ fn create_vtpm_blob(mut tpm_engine_helper: TpmEngineHelper) { // Write the SRK pub to a file. //let srk_pub_file = Use the input. - let mut srk_pub_file = fs::File::create("srk_pub.bin").expect("failed to create file"); + let mut srk_pub_file = File::create("srk_pub.bin").expect("failed to create file"); let srk_pub = response.out_public.public_area.serialize(); let srk_pub = srk_pub.as_slice(); srk_pub_file @@ -114,6 +155,9 @@ fn create_vtpm_blob(mut tpm_engine_helper: TpmEngineHelper) { TPM_RSA_SRK_HANDLE, ); assert!(result.is_ok()); + + // TODO: Do we need to change_seed() or the creation assigns ones? + } Err(e) => { println!("Error in create_primary: {:?}", e); @@ -140,7 +184,7 @@ fn export_vtpm_srk_pub(mut tpm_engine_helper: TpmEngineHelper, srk_out_path: &st // Write the SRK pub to a file. //let srk_pub_file = Use the input. - let mut srk_pub_file = fs::File::create(srk_out_path).expect("failed to create file"); + let mut srk_pub_file = File::create(srk_out_path).expect("failed to create file"); let srk_pub = response.out_public.public_area.serialize(); let srk_pub = srk_pub.as_slice(); srk_pub_file @@ -178,22 +222,11 @@ fn print_vtpm_srk_pub_key_name(srkpub_path: String) { // Compute the key name let rsa_key = public_area.unique; - // Compute SHA256 hash of the public key - let mut hasher = Sha256::new(); - hasher.update(rsa_key.buffer); - let rsa_key_hash = hasher.finalize(); - println!("Printing key properties.\n"); - println!("SHA256 Hash:"); - print!(" "); - for i in 0..rsa_key_hash.len() { - if i % 16 == 0 { - println!(); - print!(" "); - } - print!("{:02X} ", rsa_key_hash[i]); - } - println!(); + println!("Public key type: {:?}", public_area.r#type); + println!("Public hash alg: {:?}", public_area.name_alg); + println!("Public key size in bits: {:?}", public_area.parameters.key_bits); + print_sha256_hash(rsa_key.buffer.as_slice()); // Compute the key name let algorithm_id = public_area.name_alg; @@ -212,18 +245,211 @@ fn print_vtpm_srk_pub_key_name(srkpub_path: String) { println!("\nRSA key bytes:"); print!(" "); for i in 0..tpm_helper::RSA_2K_MODULUS_SIZE { - if i % 32 == 0 { + // if i % 16 == 0 { + // println!(); + // print!(" "); + // } + print!("{:02x}", rsa_key.buffer[i]); + } + println!(); + println!("\nOperation completed successfully.\n"); +} + +/// Create random RSA or ECC key. Export the public public key to a file and private key in TPM2B format. +fn create_random_key_in_tpm2_import_blob_format( + algorithm: &String, + public_key_file: &String, + private_key_tpm2b_file: &String, +) { + match algorithm.to_lowercase().as_str() { + "rsa" => { + // Generate RSA 2048-bit key + let rsa = Rsa::generate(2048).unwrap(); + let pkey = PKey::from_rsa(rsa).unwrap(); + println!("RSA 2048-bit key generated."); + + // Export the public key to a file in pem format + let public_key_der = pkey.public_key_to_der().unwrap(); + let mut pub_file = File::create(public_key_file).unwrap(); + pub_file.write_all(&public_key_der).unwrap(); + println!("RSA public key saved to {public_key_file}."); + print_sha256_hash(public_key_der.as_slice()); + + // Convert the private key to TPM2B format + let tpm2_import_blob = get_key_in_tpm2_import_format_rsa(&pkey); + + // Save the TPM2B private key to a file + let mut priv_file = File::create(private_key_tpm2b_file).unwrap(); + priv_file.write_all(&tpm2_import_blob).unwrap(); + println!("RSA private key in TPM2B import format saved to {private_key_tpm2b_file}."); + let private_key_der = pkey.private_key_to_der().unwrap(); + print_sha256_hash(&private_key_der.as_slice()); + } + "ecc" => { + // Create a random ECC P-256 key using openssl-sys crate. + let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap(); + let ec_key = EcKey::generate(&group).unwrap(); + let pkey = PKey::from_ec_key(ec_key).unwrap(); + println!("ECC P-256 key generated."); + + // Export the public key to a file + let public_key_pem = pkey.public_key_to_pem().unwrap(); + let mut pub_file = File::create(public_key_file).unwrap(); + pub_file.write_all(&public_key_pem).unwrap(); + println!("ECC public key saved to {public_key_file}."); + + // Convert the private key to TPM2B format + // TODO: define the ECC version for TPM2B format + let tpm2_import_blob = get_key_in_tpm2_import_format_rsa(&pkey); + + // Save the TPM2B private key to a file + let mut priv_file = File::create(private_key_tpm2b_file).unwrap(); + priv_file.write_all(&tpm2_import_blob).unwrap(); + + println!("ECC private key in TPM2B import format saved to {private_key_tpm2b_file}."); + } + _ => { + println!("Invalid algorithm. Supported algorithms are rsa and ecc."); + return; + } + } +} + +// Convert the private key to TPM2B format +fn get_key_in_tpm2_import_format_rsa(priv_key: &PKey) -> Vec { + let rsa = priv_key.rsa().unwrap(); + + let key_bits: u16 = rsa.size() as u16 * 8; // 2048; + println!("Key bits: {:?}", key_bits); + let exponent = 0; + let auth_policy = [0; 0]; + let symmetric_def = TpmtSymDefObject::new(AlgIdEnum::NULL.into(), None, None); + let rsa_scheme = TpmtRsaScheme::new(AlgIdEnum::NULL.into(), None); + + // Create a TPM2B_PUBLIC structure + let tpmt_public_area = TpmtPublic::new( + AlgIdEnum::RSA.into(), + AlgIdEnum::SHA256.into(), + TpmaObjectBits::new() + .with_user_with_auth(true) + .with_decrypt(true), + &auth_policy, + TpmsRsaParams::new(symmetric_def, rsa_scheme, key_bits, exponent), + &Tpm2bBuffer::new(rsa.n().to_vec().as_slice()) + .unwrap() + .buffer, + ) + .unwrap(); + let tpm2b_public = Tpm2bPublic::new(tpmt_public_area); + + // Create a TPM2B_PRIVATE structure + // let cb_public_exp = rsa.e().to_vec().len(); + // let cb_modulus = rsa.n().to_vec().len(); + let priv_prime1 = Tpm2bBuffer::new(rsa.p().unwrap().to_vec().as_slice()).unwrap(); + let rsa_sensitive = TpmtSensitive { + sensitive_type: tpmt_public_area.r#type, + auth_value: Tpm2bBuffer::new_zeroed(), + seed_value: Tpm2bBuffer::new_zeroed(), + sensitive: priv_prime1, + }; + //let source_slice = &soft_rsa_key_blob[offset..offset + cb_prime1]; + //rsa_sensitive.sensitive.buffer[..cb_prime1].copy_from_slice(source_slice); + let marshaled_tpmt_sensitive = marshal::tpmt_sensitive_marshal(&rsa_sensitive).unwrap(); + let tpm2b_private = Tpm2bBuffer::new(marshaled_tpmt_sensitive.as_slice()).unwrap(); + + // Marshal the TPM2B_PUBLIC and TPM2B_PRIVATE structures into TP2B_IMPORT format + // TPM2B_IMPORT = TPM2B_PUBLIC || TPM2B_PRIVATE || TPM2B_ENCRYPTED_SEED + let tpm2b_import = marshal::marshal_tpm2b_import(&tpm2b_public, &tpm2b_private).unwrap(); + + tpm2b_import.to_vec() +} + +/// Print info about public key in DER format. +fn print_pub_key_der(pub_key_der_path: String) { + let mut pub_key_file = fs::OpenOptions::new() + .write(false) + .read(true) + .open(pub_key_der_path) + .expect("failed to open file"); + + let mut pub_key_content_buf = Vec::new(); + pub_key_file + .read_to_end(&mut pub_key_content_buf) + .expect("failed to read file"); + + // Deserialize the pub der to a rsa public key. + let rsa = + Rsa::public_key_from_der(&pub_key_content_buf).expect("failed to deserialize pub der"); + let pkey = PKey::from_rsa(rsa).unwrap(); + + // Print the key type and size + println!("Key type: {:?}", pkey.id()); + println!("Key size: {:?}", pkey.bits()); + print_sha256_hash(pkey.public_key_to_der().unwrap().as_slice()); + + println!("\nOperation completed successfully.\n"); +} + +/// Print SHA256 hash of the data. +fn print_sha256_hash(data: &[u8]) { + let mut hasher = Sha256::new(); + hasher.update(data); + let hash = hasher.finalize(); + println!("SHA256 hash:"); + print!(" "); + for i in 0..hash.len() { + if i % 16 == 0 { println!(); print!(" "); } - print!("{:02X}", rsa_key.buffer[i]); + print!("{:02X} ", hash[i]); } println!(); +} + +/// Print info about private key in TPM2B format. +/// Tpm2ImportFormat is TPM2B_PUBLIC || TPM2B_PRIVATE || TPM2B_ENCRYPTED_SEED +fn print_tpm2bimport_content(tpm2b_import_file_path: String) { + let mut tpm2b_import_file = fs::OpenOptions::new() + .write(false) + .read(true) + .open(tpm2b_import_file_path) + .expect("failed to open file"); + + let mut tpm2b_import_content = Vec::new(); + tpm2b_import_file + .read_to_end(&mut tpm2b_import_content) + .expect("failed to read file"); + println!("TPM2B import file size: {:?}", tpm2b_import_content.len()); + + // Reverse the operations in get_key_in_tpm2_import_format_rsa + // Deserialize the tpm2b import to a Tpm2bPublic and Tpm2bBuffer. + let tpm2b_public = Tpm2bPublic::deserialize(&tpm2b_import_content) + .expect("failed to deserialize tpm2b public"); + println!("TPM2B public size: {:?}", tpm2b_public.size); + println!("TPM2B public type: {:?}", tpm2b_public.public_area.r#type); + let tpm2b_public_size = u16::from_be(tpm2b_public.size.into()) as usize; + println!("TPM2B public size: {:?}", tpm2b_public_size); + let tpm2b_private = Tpm2bBuffer::deserialize(&tpm2b_import_content[tpm2b_public_size..]) + .expect("failed to deserialize tpm2b private"); + println!("TPM2B private size: {:?}", tpm2b_private.size); + + // // Deserialize the priv key der to a rsa private key from the TPM2B format. + // // Ignore first 2 bytes of the TPM2B format. + // // Read length of the key from the next 2 bytes in BE byte order. + // let len = + // u16::from_be_bytes([priv_key_tpm2b_content_buf[2], priv_key_tpm2b_content_buf[3]]) as usize; + // println!("Length of the tpm2b : {:?}", len); + // // Read the key from the rest of the buffer. + // let rsa = Rsa::private_key_from_der(&priv_key_tpm2b_content_buf[4..4 + len]) + // .expect("failed to deserialize priv key tpm2b"); + //let pkey = PKey::from_rsa(rsa).unwrap(); + println!("\nOperation completed successfully.\n"); } struct TestPlatformCallbacks { - file: fs::File, + file: File, time: Instant, } @@ -252,7 +478,7 @@ impl ms_tpm_20_ref::PlatformCallbacks for TestPlatformCallbacks { } /// Create a new TPM engine with blank state. -pub fn create_tpm_engine_helper(file: fs::File) -> TpmEngineHelper { +pub fn create_tpm_engine_helper(file: File) -> TpmEngineHelper { let result = MsTpm20RefPlatform::initialize( Box::new(TestPlatformCallbacks { file, diff --git a/vm/cvmutil/src/marshal.rs b/vm/cvmutil/src/marshal.rs new file mode 100644 index 0000000000..90a2bc9fe9 --- /dev/null +++ b/vm/cvmutil/src/marshal.rs @@ -0,0 +1,168 @@ +// This is a good source: https://msazure.visualstudio.com/One/_git/Azure-Compute-Move?path=%2Fsrc%2FServices%2Fsecurity%2FAzureDiskEncryption%2FConfidentialVMUtil%2Flib%2FMarshal.cpp&_a=contents&version=GBmaster + +use tpm::tpm20proto::protocol::{Tpm2bBuffer, Tpm2bPublic}; +use tpm::tpm20proto::AlgId; + +//use std::mem::{size_of, ManuallyDrop}; + +use zerocopy::AsBytes; + +// struct Tpm2b { +// size: u16, +// buffer: [u8; 1], +// } + +// Table 67 -- TPMT_HA Structure +// const SHA_1_OUTPUT_SIZE_BYTES: usize = 20; +// const SHA_256_OUTPUT_SIZE_BYTES: usize = 32; +// const SM3_256_DIGEST_SIZE: usize = 32; +// const SHA_384_OUTPUT_SIZE_BYTES: usize = 48; +// const SHA_512_OUTPUT_SIZE_BYTES: usize = 64; + +// union TpmuHash { +// sha1: [u8; SHA_1_OUTPUT_SIZE_BYTES], +// sha256: [u8; SHA_256_OUTPUT_SIZE_BYTES], +// sha384: [u8; SHA_384_OUTPUT_SIZE_BYTES], +// sha512: [u8; SHA_512_OUTPUT_SIZE_BYTES], +// sm3_256: [u8; SM3_256_DIGEST_SIZE], +// } + +// pub struct TpmtHash { +// hash_alg: AlgId, +// digest: TpmuHash, +// } + +// Table 68 -- TPM2B_DIGEST Structure +// struct Tpm2bDigest { +// size: u16, +// buffer: [u8; size_of::()], +// } + +// Table 69 -- TPM2B_DATA Structure +// struct Data2b { +// size: u16, +// buffer: [u8; 64], +// } +// union Tpm2bData { +// t: ManuallyDrop, +// b: ManuallyDrop, +// } + +// Table 71 -- TPM2B_AUTH Types +// type Tpm2bAuth = Tpm2bDigest; + + +// Table 172 -- TPMT_SIGNATURE Structure +// struct TpmtSignature { +// sig_alg: AlgId, +// signature: Tpm2bBuffer, +// } + +/* +// Table 174 -- TPM2B_ENCRYPTED_SECRET Structure +#[repr(C)] +struct EncryptedSecret2b { + size: u16, + secret: [u8; + size_of::() + ], +} + +#[repr(C)] +union TpmuEncryptedSecret { + sym: EncryptedSecret2b, + asym: EncryptedSecret2b, +} +*/ + +#[repr(C)] +pub struct Tpm2bPrivate { + size: u16, + buffer: Vec, // Adjust the size as needed +} + +impl Default for Tpm2bPrivate { + fn default() -> Self { + Tpm2bPrivate { + size: 0, + buffer: Vec::new(), + } + } +} + +// Table 187 -- TPMT_SENSITIVE Structure +#[repr(C)] +pub struct TpmtSensitive { + /// TPMI_ALG_PUBLIC + pub sensitive_type: AlgId, + /// `TPM2B_AUTH` + pub auth_value: Tpm2bBuffer, + /// `TPM2B_DIGEST` + pub seed_value: Tpm2bBuffer, + /// `TPM2B_PRIVATE_KEY_RSA` + pub sensitive: Tpm2bBuffer, +} + +// impl TpmtSensitive { +// fn serialize(&self) -> Vec { +// let mut buffer = Vec::new(); + +// buffer.extend_from_slice(self.sensitive_type.as_bytes()); +// buffer.extend_from_slice(&self.auth_value.serialize()); +// buffer.extend_from_slice(&self.seed_value.serialize()); +// buffer.extend_from_slice(&self.sensitive.serialize()); + +// buffer +// } +// } + +/// Marshals the `TpmtSensitive` structure into a buffer. +pub fn tpmt_sensitive_marshal( + source: &TpmtSensitive, +) -> Result, std::io::Error> { + let mut buffer = Vec::new(); + buffer.extend_from_slice(&source.sensitive_type.as_bytes()); + + Ok(buffer) +} + +/* +pub fn tpmi_alg_public_marshal( + value: &AlgId, + buffer: &mut &mut [u8], + size: &mut i32, +) -> u16 { + // Implement the marshaling logic here + 0 +} + +pub fn tpm2b_auth_marshal(value: &Tpm2bAuth, buffer: &mut &mut [u8], size: &mut i32) -> u16 { + // Implement the marshaling logic here + 0 +} + +pub fn tpm2b_digest_marshal(value: &Tpm2bDi, buffer: &mut &mut [u8], size: &mut i32) -> u16 { + // Implement the marshaling logic here + 0 +} + +pub fn tpmu_sensitive_composite_marshal( + value: &TPMU_SENSITIVE_COMPOSITE, + buffer: &mut &mut [u8], + size: &mut i32, + sensitive_type: u32, +) -> u16 { + // Implement the marshaling logic here + 0 +} +*/ + +pub fn marshal_tpm2b_import( + tpm2b_public: &Tpm2bPublic, + tpm2b_private: &Tpm2bBuffer, +) -> Result, String> { + let mut buffer = Vec::new(); + buffer.extend_from_slice(&tpm2b_public.serialize()); + buffer.extend_from_slice(&tpm2b_private.serialize()); + Ok(buffer) +} \ No newline at end of file diff --git a/vm/devices/tpm/src/tpm20proto.rs b/vm/devices/tpm/src/tpm20proto.rs index 39d45a1db8..71142b91f9 100644 --- a/vm/devices/tpm/src/tpm20proto.rs +++ b/vm/devices/tpm/src/tpm20proto.rs @@ -1290,7 +1290,7 @@ pub mod protocol { pub struct TpmsRsaParams { symmetric: TpmtSymDefObject, scheme: TpmtRsaScheme, - key_bits: u16_be, + pub key_bits: u16_be, pub exponent: u32_be, } @@ -1373,7 +1373,7 @@ pub mod protocol { #[repr(C)] #[derive(Debug, Copy, Clone, FromBytes, IntoBytes, Immutable, KnownLayout)] pub struct TpmtPublic { - my_type: AlgId, + pub my_type: AlgId, pub name_alg: AlgId, pub object_attributes: TpmaObject, auth_policy: Tpm2bBuffer, From 24509ef851574a614d08e1aa4aa7f218a5410e1a Mon Sep 17 00:00:00 2001 From: Fikret Can Date: Fri, 14 Mar 2025 15:19:29 -0700 Subject: [PATCH 04/16] Serialize srk pub correctly --- vm/cvmutil/src/main.rs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/cvmutil/src/main.rs b/vm/cvmutil/src/main.rs index c5a59e4a73..93418aed0a 100644 --- a/vm/cvmutil/src/main.rs +++ b/vm/cvmutil/src/main.rs @@ -185,7 +185,7 @@ fn export_vtpm_srk_pub(mut tpm_engine_helper: TpmEngineHelper, srk_out_path: &st // Write the SRK pub to a file. //let srk_pub_file = Use the input. let mut srk_pub_file = File::create(srk_out_path).expect("failed to create file"); - let srk_pub = response.out_public.public_area.serialize(); + let srk_pub = response.out_public.serialize(); let srk_pub = srk_pub.as_slice(); srk_pub_file .write_all(&srk_pub) From d293e770d21a5bb42cf05737dc5eff2ea1b75c98 Mon Sep 17 00:00:00 2001 From: Fikret Can Date: Wed, 9 Apr 2025 01:38:04 -0700 Subject: [PATCH 05/16] Export rsa public key in der pkcs1 format --- vm/cvmutil/src/main.rs | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/vm/cvmutil/src/main.rs b/vm/cvmutil/src/main.rs index 93418aed0a..dda09f82d2 100644 --- a/vm/cvmutil/src/main.rs +++ b/vm/cvmutil/src/main.rs @@ -211,7 +211,6 @@ fn print_vtpm_srk_pub_key_name(srkpub_path: String) { .expect("failed to read file"); // Deserialize the srkpub to a public area. - let public_key = TpmtPublic::deserialize(&srkpub_content_buf).expect("failed to deserialize srkpub"); let public_area: TpmtPublic = public_key.into(); @@ -265,11 +264,11 @@ fn create_random_key_in_tpm2_import_blob_format( "rsa" => { // Generate RSA 2048-bit key let rsa = Rsa::generate(2048).unwrap(); + let public_key_der = rsa.public_key_to_der_pkcs1().unwrap(); let pkey = PKey::from_rsa(rsa).unwrap(); println!("RSA 2048-bit key generated."); // Export the public key to a file in pem format - let public_key_der = pkey.public_key_to_der().unwrap(); let mut pub_file = File::create(public_key_file).unwrap(); pub_file.write_all(&public_key_der).unwrap(); println!("RSA public key saved to {public_key_file}."); From 1a6c9b5ef6e59ef73b180fe874556468ee5a59cc Mon Sep 17 00:00:00 2001 From: Fikret Can Date: Mon, 19 May 2025 11:10:39 -0700 Subject: [PATCH 06/16] Cvmutil: fix rsa exponent. Remove srk_pub.bin during vtpm creation --- vm/cvmutil/src/main.rs | 13 ++----------- vm/devices/tpm/src/tpm_helper.rs | 8 ++++++-- 2 files changed, 8 insertions(+), 13 deletions(-) diff --git a/vm/cvmutil/src/main.rs b/vm/cvmutil/src/main.rs index dda09f82d2..f6932f17d7 100644 --- a/vm/cvmutil/src/main.rs +++ b/vm/cvmutil/src/main.rs @@ -138,15 +138,6 @@ fn create_vtpm_blob(mut tpm_engine_helper: TpmEngineHelper) { assert_ne!(response.out_public.size.get(), 0); println!("SRK public area: {:?}", response.out_public.public_area); - // Write the SRK pub to a file. - //let srk_pub_file = Use the input. - let mut srk_pub_file = File::create("srk_pub.bin").expect("failed to create file"); - let srk_pub = response.out_public.public_area.serialize(); - let srk_pub = srk_pub.as_slice(); - srk_pub_file - .write_all(&srk_pub) - .expect("failed to write to file"); - // Do I need to do evict control here?? // Evict the SRK handle. let result = tpm_engine_helper.evict_control( @@ -212,8 +203,8 @@ fn print_vtpm_srk_pub_key_name(srkpub_path: String) { // Deserialize the srkpub to a public area. let public_key = - TpmtPublic::deserialize(&srkpub_content_buf).expect("failed to deserialize srkpub"); - let public_area: TpmtPublic = public_key.into(); + Tpm2bPublic::deserialize(&srkpub_content_buf).expect("failed to deserialize srkpub"); + let public_area: TpmtPublic = public_key.public_area.into(); // Compute SHA256 hash of the public area let mut hasher = Sha256::new(); hasher.update(public_area.serialize()); diff --git a/vm/devices/tpm/src/tpm_helper.rs b/vm/devices/tpm/src/tpm_helper.rs index e88fe4d7d7..1b51a79b2b 100644 --- a/vm/devices/tpm/src/tpm_helper.rs +++ b/vm/devices/tpm/src/tpm_helper.rs @@ -1874,13 +1874,17 @@ pub fn ek_pub_template() -> Result { /// Returns the public template for SRK. /// TODO: review the properties. pub fn srk_pub_template() -> Result { - let symmetric = TpmtSymDefObject::new(AlgIdEnum::AES.into(), Some(128), Some(AlgIdEnum::CFB.into())); + let symmetric = TpmtSymDefObject::new( + AlgIdEnum::AES.into(), + Some(128), + Some(AlgIdEnum::CFB.into()), + ); //let scheme = TpmtRsaScheme::new(AlgIdEnum::RSA.into(), Some(AlgIdEnum::SHA256.into())); // Define the RSA scheme as TPM2_ALG_NULL for general use let scheme = TpmtRsaScheme::new(AlgIdEnum::NULL.into(), None); - let rsa_params = TpmsRsaParams::new(symmetric, scheme, crate::RSA_2K_MODULUS_BITS, 0); + let rsa_params = TpmsRsaParams::new(symmetric, scheme, crate::RSA_2K_MODULUS_BITS, 65537); let object_attributes = TpmaObjectBits::new() .with_fixed_tpm(true) From 183c071757f25f00abe6b9bf9133d6ef1f8343db Mon Sep 17 00:00:00 2001 From: Fikret Can Date: Thu, 3 Jul 2025 14:24:57 -0700 Subject: [PATCH 07/16] Make the first byte as 1 in unique data. This is to fix Ubuntu disk unlock failure --- vm/devices/tpm/src/tpm_helper.rs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/vm/devices/tpm/src/tpm_helper.rs b/vm/devices/tpm/src/tpm_helper.rs index 1b51a79b2b..d8eaaec51d 100644 --- a/vm/devices/tpm/src/tpm_helper.rs +++ b/vm/devices/tpm/src/tpm_helper.rs @@ -1895,13 +1895,15 @@ pub fn srk_pub_template() -> Result { .with_restricted(true) .with_decrypt(true); + let mut unique: [u8; 102] = [0; 102]; // Initialize the unique field with zeros + unique[0] = 0x01; // Set the first byte to 1 to indicate a valid unique value, required for Ubuntu. let in_public = TpmtPublic::new( AlgIdEnum::RSA.into(), AlgIdEnum::SHA256.into(), object_attributes, &[], rsa_params, - &[0u8; crate::RSA_2K_MODULUS_SIZE], + unique.as_ref(), ) .map_err(TpmHelperUtilityError::InvalidInputParameter)?; From ba385891cd6babc3d7f2f63af80f69068068cdd8 Mon Sep 17 00:00:00 2001 From: Fikret Can Date: Sun, 14 Sep 2025 02:20:15 -0700 Subject: [PATCH 08/16] Rebase. Fix bugs in cvmutil for create random rsa key --- vm/cvmutil/src/main.rs | 537 +++++++++++++++++++++---------- vm/cvmutil/src/marshal.rs | 183 +++-------- vm/devices/tpm/src/tpm_helper.rs | 7 +- 3 files changed, 406 insertions(+), 321 deletions(-) diff --git a/vm/cvmutil/src/main.rs b/vm/cvmutil/src/main.rs index f6932f17d7..0c35961f20 100644 --- a/vm/cvmutil/src/main.rs +++ b/vm/cvmutil/src/main.rs @@ -1,6 +1,7 @@ +//! CvmUtil is a tool to create and manage vTPM blobs. +//! vTPM blobs are used to provide TPM functionality to trusted and confidential VMs. use ms_tpm_20_ref; use ms_tpm_20_ref::MsTpm20RefPlatform; -//use tpm::{self, tpm_helper}; use tpm::tpm20proto::protocol::{ Tpm2bBuffer, Tpm2bPublic, TpmsRsaParams, TpmtPublic, TpmtRsaScheme, TpmtSymDefObject, }; @@ -9,23 +10,16 @@ use tpm::tpm_helper::{self, TpmEngineHelper}; use tpm::TPM_RSA_SRK_HANDLE; mod marshal; use marshal::TpmtSensitive; -use zerocopy::FromZeroes; -//use ms_tpm_20_ref::PlatformCallbacks; -//use crate::Tpm; -// use inspect::InspectMut; +use zerocopy::FromZeros; +use std::sync::{Arc, Mutex}; use ms_tpm_20_ref::DynResult; use std::io::Read; -use std::io::Seek; use std::io::Write; use std::time::Instant; use std::{fs, fs::File, vec}; -use tracing::Level; - use base64::Engine; -//use openssl::bn::BigNum; use openssl::ec::EcGroup; use openssl::ec::EcKey; -//use openssl::ec::PointConversionForm; use openssl::nid::Nid; use openssl::pkey::PKey; use openssl::rsa::Rsa; @@ -36,6 +30,10 @@ use clap::Parser; #[derive(Parser, Debug)] #[clap(name = "cvmutil", about = "Tool to interact with vtmp blobs.")] struct CmdArgs { + /// Enable verbose logging (trace level) + #[arg(short = 'v', long = "verbose")] + verbose: bool, + /// Creates a vTpm blob and stores to file. Example: ./cvmutil --createvtpmblob vTpm.blob #[arg( short = 'c', @@ -51,7 +49,7 @@ struct CmdArgs { #[arg(short = 'p', long = "printKeyName", value_name = "path-to-srkPub", long_help="Print the TPM key name \n./cvmutil --printKeyName srk.pub")] print_key_name: Option, - #[arg(short = 's', long = "createRandomKeyInTpm2ImportBlobFormat", value_names = &["algorithm", "publicKey", "output-file"], long_help="Create random RSA/ECC key in Tpm2 import blob format:TPM2B_PUBLIC || TP2B_PRIVATE || TP2B_ENCRYPTED_SEED \n./cvmutil --createRandomKeyInTpm2ImportBlobFormat rsa rsa_pub.der marshalled_import_blob.tpm2b")] + #[arg(short = 's', long = "createRandomKeyInTpm2ImportBlobFormat", value_names = &["algorithm", "publicKey", "output-file"], long_help="Create random RSA/ECC key in Tpm2 import blob format:TPM2B_PUBLIC || TP2B_PRIVATE || TP2B_ENCRYPTED_SEED \n./cvmutil --createRandomKeyInTpm2ImportBlobFormat rsa rsa_pub.der rsa_priv_marshalled.tpm2b")] create_random_key_in_tpm2_import_blob_format: Option>, #[arg(short = 'd', long = "printDER", value_name = "path-to-pubKey-der", long_help="Print info about DER key \n./cvmutil --printDER rsa_pub.der")] @@ -59,38 +57,69 @@ struct CmdArgs { #[arg(short = 't', long = "printTPM2B", value_name = "path-to-privKey-tpm2b", long_help="Print info about TPM2B import file: TPM2B_PUBLIC || TP2B_PRIVATE || TP2B_ENCRYPTED_SEED. \n./cvmutil --printTPM2B marshalled_import_blob.tpm2b")] print_priv_key_tpm2b: Option, + + #[arg(short = 'i', long = "testTPM2BImportKeys", value_names = &["path-to-pubKey-der", "path-to-privKey-tpm2b"], long_help="Import the public in DER and private in TPM2B format. Make sure they form a keypair. \n./cvmutil --testTPM2BImportKeys rsa_pub.der marshalled_import_blob.tpm2b")] + test_tpm2b_import_keys: Option>, } +/// Main entry point for cvmutil. fn main() { - // Initialize the logger. + // Parse the command line arguments. + let args = CmdArgs::parse(); + tracing_subscriber::fmt() .with_writer(std::io::stderr) .log_internal_errors(true) - .with_max_level(Level::ERROR) + .with_max_level(if args.verbose { + tracing::Level::TRACE + } else { + tracing::Level::INFO + }) .init(); - // Parse the command line arguments. - let args = CmdArgs::parse(); - if let Some(path) = args.createvtpmblob { - println!("CreateVtpmBlob: {}", path); - let file = File::create(path.clone()).expect("failed to create file"); - println!("Creating vTPM blob in file: {:?}", path); - let tpm_engine_helper = create_tpm_engine_helper(file); - create_vtpm_blob(tpm_engine_helper); + // Create a vTPM instance. + tracing::info!("Creating vTPM blob and saving to file: {}", path); + let (mut tpm_engine_helper, nv_blob_accessor) = create_tpm_engine_helper(); + let result = tpm_engine_helper.initialize_tpm_engine(); + assert!(result.is_ok()); + + // Create vTPM in memory and save state to a file. + let state = create_vtpm_blob(tpm_engine_helper, nv_blob_accessor); + tracing::info!("vTPM blob size: {}", state.len()); + + // if the vtpm file exists, delet it and create a new one + if std::path::Path::new(&path).exists() { + tracing::info!("vTPM file already exists. Deleting the existing file and creating a new one."); + fs::remove_file(&path).expect("failed to delete existing vtpm file"); + } + fs::write(&path, state.as_slice()).expect("Failed to write vtpm state to blob file"); + tracing::info!("vTPM blob created and saved to file: {}", path); + } else if let Some(paths) = args.write_srk { if paths.len() == 2 { let vtpm_blob_path = &paths[0]; + // Read the vtpm file content. + let vtpm_blob_content = fs::read(vtpm_blob_path) + .expect("failed to read vtpm blob file"); // Restore the TPM engine from the vTPM blob. - let vtpm_engine_helper = restore_tpm_engine_helper(vtpm_blob_path.to_string()); + let (mut vtpm_engine_helper, _nv_blob_accessor) = create_tpm_engine_helper(); + + let result = vtpm_engine_helper.tpm_engine.reset(Some(&vtpm_blob_content)); + assert!(result.is_ok()); + + let result = vtpm_engine_helper.initialize_tpm_engine(); + assert!(result.is_ok()); + tracing::info!("TPM engine initialized from blob file."); + let srk_out_path = &paths[1]; - println!( + tracing::info!( "WriteSrk: blob file: {}, Srk out file: {}", vtpm_blob_path, srk_out_path ); export_vtpm_srk_pub(vtpm_engine_helper, srk_out_path); } else { - println!("Invalid number of arguments for --writeSrk. Expected 2 values."); + tracing::error!("Invalid number of arguments for --writeSrk. Expected 2 values."); } } else if let Some(args) = args.create_random_key_in_tpm2_import_blob_format { if args.len() == 3 { @@ -103,7 +132,7 @@ fn main() { private_key_tpm2b_file, ); } else { - println!("Invalid number of arguments for --createRandomKeyInTpm2ImportBlobFormat. Expected 3 values."); + tracing::error!("Invalid number of arguments for --createRandomKeyInTpm2ImportBlobFormat. Expected 3 values."); } } else if let Some(srkpub_path) = args.print_key_name { print_vtpm_srk_pub_key_name(srkpub_path); @@ -111,34 +140,35 @@ fn main() { print_pub_key_der(pub_der_path); } else if let Some(priv_tpm2b_path) = args.print_priv_key_tpm2b { print_tpm2bimport_content(priv_tpm2b_path); + } else if let Some(key_files) = args.test_tpm2b_import_keys { + if key_files.len() == 2 { + let public_key_file = &key_files[0]; + let private_key_file = &key_files[1]; + test_import_tpm2b_keys(public_key_file, private_key_file); + } else { + tracing::error!("Invalid number of arguments for --testTPM2BImportKeys. Expected 2 values."); + } } else { - println!("No command specified. Please re-run with --help for usage information."); - println!(); + tracing::error!("No command specified. Please re-run with --help for usage information."); } } -fn create_vtpm_blob(mut tpm_engine_helper: TpmEngineHelper) { +/// Create vtpm and return its state as a byte vector. +fn create_vtpm_blob(mut tpm_engine_helper: TpmEngineHelper, nvm_state_blob: Arc>>) -> Vec { // Create a vTPM instance. - let result = tpm_engine_helper.initialize_tpm_engine(); - assert!(result.is_ok()); - - //let mut tpm_engine_helper = create_tpm_engine_helper(); - //restart_tpm_engine(&mut tpm_engine_helper, false, true); - + tracing::info!("Initializing TPM engine."); // Create a primary key: SRK let auth_handle = tpm::tpm20proto::TPM20_RH_OWNER; - // DON'T have a template for SRK. Use EK template instead for now. let result = tpm_helper::srk_pub_template(); assert!(result.is_ok()); - let in_public = result.unwrap(); - let result = tpm_engine_helper.create_primary(auth_handle, in_public); + let srk_in_public = result.unwrap(); + let result = tpm_engine_helper.create_primary(auth_handle, srk_in_public); match result { Ok(response) => { - println!("SRK handle: {:?}", response.object_handle); + tracing::info!("SRK handle: {:?}", response.object_handle); assert_ne!(response.out_public.size.get(), 0); - println!("SRK public area: {:?}", response.out_public.public_area); + tracing::trace!("SRK public area: {:?}", response.out_public.public_area); - // Do I need to do evict control here?? // Evict the SRK handle. let result = tpm_engine_helper.evict_control( tpm::tpm20proto::TPM20_RH_OWNER, @@ -146,32 +176,53 @@ fn create_vtpm_blob(mut tpm_engine_helper: TpmEngineHelper) { TPM_RSA_SRK_HANDLE, ); assert!(result.is_ok()); + } + Err(e) => { + tracing::error!("Error in create_primary: {:?}", e); + } + } - // TODO: Do we need to change_seed() or the creation assigns ones? - + // DEBUG: retrieve the SRK and print its SHA256 hash + let result = tpm_engine_helper.read_public(TPM_RSA_SRK_HANDLE); + match result { + Ok(response) => { + let mut hasher = Sha256::new(); + hasher.update(response.out_public.public_area.serialize()); + let public_area_hash = hasher.finalize(); + tracing::trace!("SRK public area SHA256 hash: {:x}", public_area_hash); } Err(e) => { - println!("Error in create_primary: {:?}", e); + tracing::error!("Error in read_public: {:?}", e); } } - //assert!(result.is_ok()); - // Save the state of the TPM engine. - tpm_engine_helper.tpm_engine.save_state(); + // Get the nv state of the TPM. + let nv_blob = nvm_state_blob.lock().unwrap().clone(); + tracing::trace!("Retrieved NV blob size: {}", nv_blob.len()); + nv_blob.to_vec() } +/// Export the vTPM SRK public key to a file in TPM2B format. fn export_vtpm_srk_pub(mut tpm_engine_helper: TpmEngineHelper, srk_out_path: &str) { - // Create a vTPM instance. - println!("Initializing TPM engine."); - let result = tpm_engine_helper.initialize_tpm_engine(); - assert!(result.is_ok()); - print!("TPM engine initialized."); + // Debug: Check if the SRK handle exists + tracing::trace!("Checking if SRK handle exists..."); + let find_result = tpm_engine_helper.find_object(TPM_RSA_SRK_HANDLE); + match find_result { + Ok(Some(_handle)) => tracing::trace!("SRK handle found"), + //Ok(Some(handle)) => println!("SRK handle found: {:?}", handle), + Ok(None) => { + tracing::trace!("SRK handle NOT found! Need to create it."); + // The SRK doesn't exist, so we need to create it + //recreate_srk(&mut tmp_engine_helper); + } + Err(e) => tracing::error!("Error finding SRK handle: {:?}", e), + } // Extract SRK primary key public area. let result = tpm_engine_helper.read_public(TPM_RSA_SRK_HANDLE); - match result { + match result { Ok(response) => { - println!("SRK public area: {:?}", response.out_public.public_area); + tracing::trace!("SRK public area: {:?}", response.out_public.public_area); // Write the SRK pub to a file. //let srk_pub_file = Use the input. @@ -181,9 +232,14 @@ fn export_vtpm_srk_pub(mut tpm_engine_helper: TpmEngineHelper, srk_out_path: &st srk_pub_file .write_all(&srk_pub) .expect("failed to write to file"); + // Compute SHA256 hash of the public area + let mut hasher = Sha256::new(); + hasher.update(response.out_public.public_area.serialize()); + let public_area_hash = hasher.finalize(); + tracing::trace!("SRK public area SHA256 hash: {:x} is written to file {}", public_area_hash, srk_out_path); } Err(e) => { - println!("Error in read_public: {:?}", e); + tracing::error!("Error in read_public: {:?}", e); } } } @@ -212,10 +268,10 @@ fn print_vtpm_srk_pub_key_name(srkpub_path: String) { // Compute the key name let rsa_key = public_area.unique; - println!("Printing key properties.\n"); - println!("Public key type: {:?}", public_area.r#type); - println!("Public hash alg: {:?}", public_area.name_alg); - println!("Public key size in bits: {:?}", public_area.parameters.key_bits); + tracing::trace!("Printing key properties.\n"); + tracing::trace!("Public key type: {:?}", public_area.my_type); + tracing::trace!("Public hash alg: {:?}", public_area.name_alg); + tracing::trace!("Public key size in bits: {:?}", public_area.parameters.key_bits); print_sha256_hash(rsa_key.buffer.as_slice()); // Compute the key name @@ -228,21 +284,16 @@ fn print_vtpm_srk_pub_key_name(srkpub_path: String) { } let base64_key = base64::engine::general_purpose::STANDARD.encode(&output_key); - println!("Key name:\n"); - println!(" {}", base64_key); + tracing::trace!("Key name:\n"); + tracing::trace!(" {}", base64_key); - // Print RSA bytes in hex to be able to compare with tpm2_readpublic -c 0x81000001 - println!("\nRSA key bytes:"); - print!(" "); + // DEBUG: Print RSA bytes in hex to be able to compare with tpm2_readpublic -c 0x81000001 + tracing::trace!("\nRSA key bytes:"); + tracing::trace!(" "); for i in 0..tpm_helper::RSA_2K_MODULUS_SIZE { - // if i % 16 == 0 { - // println!(); - // print!(" "); - // } - print!("{:02x}", rsa_key.buffer[i]); + tracing::trace!("{:02x}", rsa_key.buffer[i]); } - println!(); - println!("\nOperation completed successfully.\n"); + tracing::info!("\nOperation completed successfully.\n"); } /// Create random RSA or ECC key. Export the public public key to a file and private key in TPM2B format. @@ -255,14 +306,19 @@ fn create_random_key_in_tpm2_import_blob_format( "rsa" => { // Generate RSA 2048-bit key let rsa = Rsa::generate(2048).unwrap(); + let modulus_bytes = rsa.n().to_vec(); + tracing::trace!("RSA modulus size: {} bytes", modulus_bytes.len()); + let modulus_buffer = Tpm2bBuffer::new(modulus_bytes.as_slice()).unwrap(); + tracing::trace!("Tpm2bBuffer modulus size field: {} bytes", modulus_buffer.size.get()); + let public_key_der = rsa.public_key_to_der_pkcs1().unwrap(); let pkey = PKey::from_rsa(rsa).unwrap(); - println!("RSA 2048-bit key generated."); + tracing::info!("RSA 2048-bit key generated."); // Export the public key to a file in pem format let mut pub_file = File::create(public_key_file).unwrap(); pub_file.write_all(&public_key_der).unwrap(); - println!("RSA public key saved to {public_key_file}."); + tracing::info!("RSA public key is saved to {public_key_file} in DER PKCS1 format."); print_sha256_hash(public_key_der.as_slice()); // Convert the private key to TPM2B format @@ -271,8 +327,9 @@ fn create_random_key_in_tpm2_import_blob_format( // Save the TPM2B private key to a file let mut priv_file = File::create(private_key_tpm2b_file).unwrap(); priv_file.write_all(&tpm2_import_blob).unwrap(); - println!("RSA private key in TPM2B import format saved to {private_key_tpm2b_file}."); + tracing::info!("RSA private key is saved to {private_key_tpm2b_file} in TPM2B import format."); let private_key_der = pkey.private_key_to_der().unwrap(); + print_sha256_hash(&private_key_der.as_slice()); } "ecc" => { @@ -280,13 +337,13 @@ fn create_random_key_in_tpm2_import_blob_format( let group = EcGroup::from_curve_name(Nid::X9_62_PRIME256V1).unwrap(); let ec_key = EcKey::generate(&group).unwrap(); let pkey = PKey::from_ec_key(ec_key).unwrap(); - println!("ECC P-256 key generated."); + tracing::info!("ECC P-256 key generated."); // Export the public key to a file let public_key_pem = pkey.public_key_to_pem().unwrap(); let mut pub_file = File::create(public_key_file).unwrap(); pub_file.write_all(&public_key_pem).unwrap(); - println!("ECC public key saved to {public_key_file}."); + tracing::info!("ECC public key saved to {public_key_file}."); // Convert the private key to TPM2B format // TODO: define the ECC version for TPM2B format @@ -296,10 +353,10 @@ fn create_random_key_in_tpm2_import_blob_format( let mut priv_file = File::create(private_key_tpm2b_file).unwrap(); priv_file.write_all(&tpm2_import_blob).unwrap(); - println!("ECC private key in TPM2B import format saved to {private_key_tpm2b_file}."); + tracing::info!("ECC private key in TPM2B import format saved to {private_key_tpm2b_file}."); } _ => { - println!("Invalid algorithm. Supported algorithms are rsa and ecc."); + tracing::error!("Invalid algorithm. Supported algorithms are rsa and ecc."); return; } } @@ -310,8 +367,8 @@ fn get_key_in_tpm2_import_format_rsa(priv_key: &PKey) -> let rsa = priv_key.rsa().unwrap(); let key_bits: u16 = rsa.size() as u16 * 8; // 2048; - println!("Key bits: {:?}", key_bits); - let exponent = 0; + tracing::trace!("Key bits: {:?}", key_bits); + let exponent = 0; // Use 0 to indicate default exponent (65537) let auth_policy = [0; 0]; let symmetric_def = TpmtSymDefObject::new(AlgIdEnum::NULL.into(), None, None); let rsa_scheme = TpmtRsaScheme::new(AlgIdEnum::NULL.into(), None); @@ -325,33 +382,67 @@ fn get_key_in_tpm2_import_format_rsa(priv_key: &PKey) -> .with_decrypt(true), &auth_policy, TpmsRsaParams::new(symmetric_def, rsa_scheme, key_bits, exponent), - &Tpm2bBuffer::new(rsa.n().to_vec().as_slice()) - .unwrap() - .buffer, + &rsa.n().to_vec(), ) .unwrap(); + let tpm2b_public = Tpm2bPublic::new(tpmt_public_area); + // Debug: Check TPM2B_PUBLIC size breakdown + tracing::trace!("TPM2B_PUBLIC size {} bytes", tpm2b_public.size.get()); + tracing::trace!("TPM2B_PUBLIC serialized size: {} bytes", tpm2b_public.serialize().len()); // Create a TPM2B_PRIVATE structure - // let cb_public_exp = rsa.e().to_vec().len(); - // let cb_modulus = rsa.n().to_vec().len(); - let priv_prime1 = Tpm2bBuffer::new(rsa.p().unwrap().to_vec().as_slice()).unwrap(); - let rsa_sensitive = TpmtSensitive { - sensitive_type: tpmt_public_area.r#type, - auth_value: Tpm2bBuffer::new_zeroed(), - seed_value: Tpm2bBuffer::new_zeroed(), - sensitive: priv_prime1, + // For RSA import format, use the first prime factor (p), not the private exponent (d) + let prime1_bytes = rsa.p().unwrap().to_vec(); + tracing::trace!("RSA prime1 (p) size: {} bytes", prime1_bytes.len()); + let sensitive_rsa = Tpm2bBuffer::new(&prime1_bytes).unwrap(); + + let tpmt_sensitive = TpmtSensitive { + sensitive_type: tpmt_public_area.my_type, // TPM_ALG_RSA + auth_value: Tpm2bBuffer::new_zeroed(), // Empty auth value + seed_value: Tpm2bBuffer::new_zeroed(), // Empty seed value + sensitive: sensitive_rsa, }; - //let source_slice = &soft_rsa_key_blob[offset..offset + cb_prime1]; - //rsa_sensitive.sensitive.buffer[..cb_prime1].copy_from_slice(source_slice); - let marshaled_tpmt_sensitive = marshal::tpmt_sensitive_marshal(&rsa_sensitive).unwrap(); - let tpm2b_private = Tpm2bBuffer::new(marshaled_tpmt_sensitive.as_slice()).unwrap(); - // Marshal the TPM2B_PUBLIC and TPM2B_PRIVATE structures into TP2B_IMPORT format - // TPM2B_IMPORT = TPM2B_PUBLIC || TPM2B_PRIVATE || TPM2B_ENCRYPTED_SEED - let tpm2b_import = marshal::marshal_tpm2b_import(&tpm2b_public, &tpm2b_private).unwrap(); + let marshaled_tpmt_sensitive = marshal::tpmt_sensitive_marshal(&tpmt_sensitive).unwrap(); + let marshaled_size = marshaled_tpmt_sensitive.len() as u16; - tpm2b_import.to_vec() + // Create TPM2B_PRIVATE structure: size + marshaled_data + let mut tpm2b_private_buffer = Vec::new(); + + // Add the TPM2B size field (total size of the buffer excluding this size field) + tpm2b_private_buffer.extend_from_slice(&marshaled_size.to_be_bytes()); + + // Add the marshaled sensitive data + tpm2b_private_buffer.extend_from_slice(&marshaled_tpmt_sensitive); + + tracing::trace!("TPM2B_PRIVATE total buffer size: {} bytes", tpm2b_private_buffer.len()); + tracing::trace!(" - Size field: 2 bytes"); + tracing::trace!(" - Marshaled sensitive data: {} bytes", marshaled_tpmt_sensitive.len()); + tracing::trace!(" - sensitive_type: 2 bytes"); + tracing::trace!(" - auth_value: {} bytes (size + data)", 2 + tpmt_sensitive.auth_value.size.get()); + tracing::trace!(" - seed_value: {} bytes (size + data)", 2 + tpmt_sensitive.seed_value.size.get()); + tracing::trace!(" - sensitive (RSA private exp): {} bytes (size + data)", 2 + tpmt_sensitive.sensitive.size.get()); + + // Create the final import blob: TPM2B_PUBLIC || TPM2B_PRIVATE || TPM2B_ENCRYPTED_SECRET + let mut final_import_blob = Vec::new(); + + // Add TPM2B_PUBLIC + let serialized_public = tpm2b_public.serialize(); + final_import_blob.extend_from_slice(&serialized_public); + + // Add TPM2B_PRIVATE + final_import_blob.extend_from_slice(&tpm2b_private_buffer); + + // Add TPM2B_ENCRYPTED_SECRET (empty - just 2 bytes of zeros for size) + final_import_blob.extend_from_slice(&[0u8, 0u8]); + + tracing::trace!("Final TPM2B import format size: {} bytes", final_import_blob.len()); + tracing::trace!(" - TPM2B_PUBLIC: {} bytes", serialized_public.len()); + tracing::trace!(" - TPM2B_PRIVATE: {} bytes", tpm2b_private_buffer.len()); + tracing::trace!(" - TPM2B_ENCRYPTED_SECRET: 2 bytes (empty)"); + + final_import_blob } /// Print info about public key in DER format. @@ -373,11 +464,11 @@ fn print_pub_key_der(pub_key_der_path: String) { let pkey = PKey::from_rsa(rsa).unwrap(); // Print the key type and size - println!("Key type: {:?}", pkey.id()); - println!("Key size: {:?}", pkey.bits()); + tracing::trace!("Key type: {:?}", pkey.id()); + tracing::trace!("Key size: {:?}", pkey.bits()); print_sha256_hash(pkey.public_key_to_der().unwrap().as_slice()); - println!("\nOperation completed successfully.\n"); + tracing::info!("\nOperation completed successfully.\n"); } /// Print SHA256 hash of the data. @@ -385,16 +476,11 @@ fn print_sha256_hash(data: &[u8]) { let mut hasher = Sha256::new(); hasher.update(data); let hash = hasher.finalize(); - println!("SHA256 hash:"); - print!(" "); + let mut hash_str = String::new(); for i in 0..hash.len() { - if i % 16 == 0 { - println!(); - print!(" "); - } - print!("{:02X} ", hash[i]); + hash_str.push_str(&format!("{:02X}", hash[i])); } - println!(); + tracing::trace!("SHA256 hash: {}\n", hash_str); } /// Print info about private key in TPM2B format. @@ -410,50 +496,185 @@ fn print_tpm2bimport_content(tpm2b_import_file_path: String) { tpm2b_import_file .read_to_end(&mut tpm2b_import_content) .expect("failed to read file"); - println!("TPM2B import file size: {:?}", tpm2b_import_content.len()); + tracing::trace!("TPM2B import file size: {:?}", tpm2b_import_content.len()); // Reverse the operations in get_key_in_tpm2_import_format_rsa // Deserialize the tpm2b import to a Tpm2bPublic and Tpm2bBuffer. let tpm2b_public = Tpm2bPublic::deserialize(&tpm2b_import_content) .expect("failed to deserialize tpm2b public"); - println!("TPM2B public size: {:?}", tpm2b_public.size); - println!("TPM2B public type: {:?}", tpm2b_public.public_area.r#type); + tracing::trace!("TPM2B public size: {:?}", tpm2b_public.size); + tracing::trace!("TPM2B public type: {:?}", tpm2b_public.public_area.my_type); let tpm2b_public_size = u16::from_be(tpm2b_public.size.into()) as usize; - println!("TPM2B public size: {:?}", tpm2b_public_size); + tracing::trace!("TPM2B public size: {:?}", tpm2b_public_size); let tpm2b_private = Tpm2bBuffer::deserialize(&tpm2b_import_content[tpm2b_public_size..]) .expect("failed to deserialize tpm2b private"); - println!("TPM2B private size: {:?}", tpm2b_private.size); + tracing::trace!("TPM2B private size: {:?}", tpm2b_private.size); + + tracing::info!("\nOperation completed successfully.\n"); +} + +/// Test importing TPM2B format keys by reading and validating them +fn test_import_tpm2b_keys(public_key_file: &str, private_key_file: &str) { + tracing::info!("Testing TPM2B key import..."); + tracing::info!("Public key file: {}", public_key_file); + tracing::info!("Private key file: {}", private_key_file); + + // Read the public key file + let mut pub_key_file = fs::OpenOptions::new() + .read(true) + .open(public_key_file) + .expect("Failed to open public key file"); + + let mut pub_key_content = Vec::new(); + pub_key_file.read_to_end(&mut pub_key_content) + .expect("Failed to read public key file"); + + tracing::info!("Public key file size: {} bytes", pub_key_content.len()); + + // Try to determine the format and parse accordingly + // First, try DER format (most likely for .pub files from your tool) + let rsa_public_opt = if let Ok(rsa) = Rsa::public_key_from_der_pkcs1(&pub_key_content) { + tracing::info!("Successfully parsed as PKCS1 DER format"); + Some(rsa) + } else if let Ok(rsa) = Rsa::public_key_from_der(&pub_key_content) { + tracing::info!("Successfully parsed as standard DER format"); + Some(rsa) + } else { + tracing::info!("Failed to parse as DER formats, trying TPM2B format..."); + None + }; + + if let Some(rsa_public) = rsa_public_opt { + tracing::info!("RSA public key successfully parsed:"); + tracing::info!(" Key size: {} bits", rsa_public.size() * 8); + tracing::info!(" Modulus size: {} bytes", rsa_public.n().to_vec().len()); + tracing::info!(" Exponent size: {} bytes", rsa_public.e().to_vec().len()); + + // Continue with DER format validation + validate_der_format_keys(&rsa_public, private_key_file); + } else { + // Try TPM2B format as last resort + tracing::error!("Failed to parse public as DER formats..."); + return; + } +} + +/// Validate keys when public key is in DER format +fn validate_der_format_keys(rsa_public: &Rsa, private_key_file: &str) { + // Read the private key file (TPM2B import format) + let mut priv_key_file = fs::OpenOptions::new() + .read(true) + .open(private_key_file) + .expect("Failed to open private key file"); + + let mut priv_key_content = Vec::new(); + priv_key_file.read_to_end(&mut priv_key_content) + .expect("Failed to read private key file"); + + tracing::info!("Private key file size: {} bytes", priv_key_content.len()); + + // Parse the TPM2B import format: TPM2B_PUBLIC || TPM2B_PRIVATE || TPM2B_ENCRYPTED_SEED + + // 1. Parse TPM2B_PUBLIC + let tmp2b_public = Tpm2bPublic::deserialize(&priv_key_content) + .expect("Failed to deserialize TPM2B_PUBLIC"); + + let public_size = tmp2b_public.size.get() as usize + 2; // +2 for size field + tracing::info!("TPM2B_PUBLIC parsed:"); + tracing::info!(" Size: {} bytes", public_size); + tracing::info!(" Algorithm: {:?}", tmp2b_public.public_area.my_type); + tracing::info!(" Name algorithm: {:?}", tmp2b_public.public_area.name_alg); + tracing::info!(" Key bits: {:?}", tmp2b_public.public_area.parameters.key_bits); + + // 2. Parse TPM2B_PRIVATE + let remaining_data = &priv_key_content[public_size..]; + let tmp2b_private = Tpm2bBuffer::deserialize(remaining_data) + .expect("Failed to deserialize TPM2B_PRIVATE"); + + let private_size = tmp2b_private.size.get() as usize + 2; // +2 for size field + tracing::info!("TPM2B_PRIVATE parsed:"); + tracing::info!(" Size: {} bytes", private_size); + tracing::info!(" Data size: {} bytes", tmp2b_private.size.get()); + + // 3. Parse TPM2B_ENCRYPTED_SECRET (should be empty - 2 zero bytes) + let encrypted_seed_data = &remaining_data[private_size..]; + if encrypted_seed_data.len() >= 2 { + let encrypted_seed_size = u16::from_be_bytes([encrypted_seed_data[0], encrypted_seed_data[1]]); + tracing::info!("TPM2B_ENCRYPTED_SECRET parsed:"); + tracing::info!(" Size: {} bytes (should be 0)", encrypted_seed_size); + + if encrypted_seed_size == 0 { + tracing::info!("Encrypted seed is empty as expected"); + } else { + tracing::warn!("Encrypted seed is not empty"); + } + } + + // Validation: Compare the modulus from the DER public key with the TPM2B public key + let der_modulus = rsa_public.n().to_vec(); + let tpm2b_modulus: &[u8; 256] = &tmp2b_public.public_area.unique.buffer[0..256].try_into().expect("Modulus size mismatch"); + + tracing::info!("Validation:"); + tracing::info!(" DER modulus size: {} bytes", der_modulus.len()); + tracing::info!(" TPM2B modulus size: {} bytes", tpm2b_modulus.len()); + + if der_modulus == *tpm2b_modulus { + tracing::info!(" Modulus values match between DER and TPM2B formats"); + } else { + tracing::error!(" Modulus values do NOT match"); + tracing::error!(" First 16 bytes of DER modulus: {:02X?}", &der_modulus[..16.min(der_modulus.len())]); + tracing::error!(" First 16 bytes of TPM2B modulus: {:02X?}", &tpm2b_modulus[..16.min(tpm2b_modulus.len())]); + } + + // Calculate expected total size + let expected_total = public_size + private_size + 2; // +2 for encrypted seed + tracing::info!("Size breakdown:"); + tracing::info!(" TPM2B_PUBLIC: {} bytes", public_size); + tracing::info!(" TPM2B_PRIVATE: {} bytes", private_size); + tracing::info!(" TPM2B_ENCRYPTED_SECRET: 2 bytes"); + tracing::info!(" Expected total: {} bytes", expected_total); + tracing::info!(" Actual file size: {} bytes", priv_key_content.len()); + + if expected_total == priv_key_content.len() { + tracing::info!("File size matches expected TPM2B import format"); + } else { + tracing::error!("File size does NOT match expected format"); + } - // // Deserialize the priv key der to a rsa private key from the TPM2B format. - // // Ignore first 2 bytes of the TPM2B format. - // // Read length of the key from the next 2 bytes in BE byte order. - // let len = - // u16::from_be_bytes([priv_key_tpm2b_content_buf[2], priv_key_tpm2b_content_buf[3]]) as usize; - // println!("Length of the tpm2b : {:?}", len); - // // Read the key from the rest of the buffer. - // let rsa = Rsa::private_key_from_der(&priv_key_tpm2b_content_buf[4..4 + len]) - // .expect("failed to deserialize priv key tpm2b"); - //let pkey = PKey::from_rsa(rsa).unwrap(); - - println!("\nOperation completed successfully.\n"); + tracing::info!("DER pub and TPM2B priv key validation completed successfully!"); } struct TestPlatformCallbacks { - file: File, + blob: Vec, time: Instant, + // Add shared access to the blob + shared_blob: Arc>>, +} + +impl TestPlatformCallbacks { + fn new() -> (Self, Arc>>) { + let shared_blob = Arc::new(Mutex::new(Vec::new())); + let callbacks = TestPlatformCallbacks { + blob: vec![], + time: Instant::now(), + shared_blob: shared_blob.clone(), + }; + (callbacks, shared_blob) + } } impl ms_tpm_20_ref::PlatformCallbacks for TestPlatformCallbacks { fn commit_nv_state(&mut self, state: &[u8]) -> DynResult<()> { - tracing::info!("commit_nv_state: {:?}", state); - tracing::info!("committing nv state with len {}", state.len()); - self.file.rewind()?; - self.file.write_all(state)?; + tracing::trace!("committing nv state with len {}", state.len()); + self.blob = state.to_vec(); + // Also update the shared blob + *self.shared_blob.lock().unwrap() = state.to_vec(); + Ok(()) } fn get_crypt_random(&mut self, buf: &mut [u8]) -> DynResult { - getrandom::getrandom(buf).expect("rng failure"); + getrandom::fill(buf).expect("rng failure"); Ok(buf.len()) } @@ -463,57 +684,27 @@ impl ms_tpm_20_ref::PlatformCallbacks for TestPlatformCallbacks { } fn get_unique_value(&self) -> &'static [u8] { - b"vtpm test" + b"CPS team cvmutil" } } -/// Create a new TPM engine with blank state. -pub fn create_tpm_engine_helper(file: File) -> TpmEngineHelper { - let result = MsTpm20RefPlatform::initialize( - Box::new(TestPlatformCallbacks { - file, - time: Instant::now(), - }), - ms_tpm_20_ref::InitKind::ColdInit, - ); - assert!(result.is_ok()); +/// Create a new TPM engine with blank state and return the helper and NV state blob. +pub fn create_tpm_engine_helper() -> (TpmEngineHelper, Arc>>) { - let tpm_engine: MsTpm20RefPlatform = result.unwrap(); - - TpmEngineHelper { - tpm_engine, - reply_buffer: [0u8; 4096], - } -} - -/// Restore a TPM engine helper from vtpm blob. -pub fn restore_tpm_engine_helper(vtpm_blob_path: String) -> TpmEngineHelper { - let mut vtpm_blob_file = fs::OpenOptions::new() - .write(true) - .read(true) - .open(vtpm_blob_path) - .expect("failed to open file"); - - let mut vtpm_content_buf = Vec::new(); - vtpm_blob_file - .read_to_end(&mut vtpm_content_buf) - .expect("failed to read file"); + let (callbacks, nv_blob_accessor) = TestPlatformCallbacks::new(); let result = MsTpm20RefPlatform::initialize( - Box::new(TestPlatformCallbacks { - file: vtpm_blob_file, - time: Instant::now(), - }), - ms_tpm_20_ref::InitKind::ColdInitWithPersistentState { - nvmem_blob: vtpm_content_buf.into(), - }, + Box::new(callbacks), + ms_tpm_20_ref::InitKind::ColdInit, ); assert!(result.is_ok()); let tpm_engine: MsTpm20RefPlatform = result.unwrap(); - TpmEngineHelper { + let tpm_helper = TpmEngineHelper { tpm_engine, reply_buffer: [0u8; 4096], - } -} + }; + + (tpm_helper, nv_blob_accessor) +} \ No newline at end of file diff --git a/vm/cvmutil/src/marshal.rs b/vm/cvmutil/src/marshal.rs index 90a2bc9fe9..eb3156b9b5 100644 --- a/vm/cvmutil/src/marshal.rs +++ b/vm/cvmutil/src/marshal.rs @@ -1,94 +1,9 @@ -// This is a good source: https://msazure.visualstudio.com/One/_git/Azure-Compute-Move?path=%2Fsrc%2FServices%2Fsecurity%2FAzureDiskEncryption%2FConfidentialVMUtil%2Flib%2FMarshal.cpp&_a=contents&version=GBmaster +///! Marshal TPM structures: selected ones only for our use case in cvmutil. +///! TPM reference documents such as TPM-Rev-2.0-Part-2-Structures-01.38.pdf is a good source. -use tpm::tpm20proto::protocol::{Tpm2bBuffer, Tpm2bPublic}; +use tpm::tpm20proto::protocol::Tpm2bBuffer; use tpm::tpm20proto::AlgId; - -//use std::mem::{size_of, ManuallyDrop}; - -use zerocopy::AsBytes; - -// struct Tpm2b { -// size: u16, -// buffer: [u8; 1], -// } - -// Table 67 -- TPMT_HA Structure -// const SHA_1_OUTPUT_SIZE_BYTES: usize = 20; -// const SHA_256_OUTPUT_SIZE_BYTES: usize = 32; -// const SM3_256_DIGEST_SIZE: usize = 32; -// const SHA_384_OUTPUT_SIZE_BYTES: usize = 48; -// const SHA_512_OUTPUT_SIZE_BYTES: usize = 64; - -// union TpmuHash { -// sha1: [u8; SHA_1_OUTPUT_SIZE_BYTES], -// sha256: [u8; SHA_256_OUTPUT_SIZE_BYTES], -// sha384: [u8; SHA_384_OUTPUT_SIZE_BYTES], -// sha512: [u8; SHA_512_OUTPUT_SIZE_BYTES], -// sm3_256: [u8; SM3_256_DIGEST_SIZE], -// } - -// pub struct TpmtHash { -// hash_alg: AlgId, -// digest: TpmuHash, -// } - -// Table 68 -- TPM2B_DIGEST Structure -// struct Tpm2bDigest { -// size: u16, -// buffer: [u8; size_of::()], -// } - -// Table 69 -- TPM2B_DATA Structure -// struct Data2b { -// size: u16, -// buffer: [u8; 64], -// } -// union Tpm2bData { -// t: ManuallyDrop, -// b: ManuallyDrop, -// } - -// Table 71 -- TPM2B_AUTH Types -// type Tpm2bAuth = Tpm2bDigest; - - -// Table 172 -- TPMT_SIGNATURE Structure -// struct TpmtSignature { -// sig_alg: AlgId, -// signature: Tpm2bBuffer, -// } - -/* -// Table 174 -- TPM2B_ENCRYPTED_SECRET Structure -#[repr(C)] -struct EncryptedSecret2b { - size: u16, - secret: [u8; - size_of::() - ], -} - -#[repr(C)] -union TpmuEncryptedSecret { - sym: EncryptedSecret2b, - asym: EncryptedSecret2b, -} -*/ - -#[repr(C)] -pub struct Tpm2bPrivate { - size: u16, - buffer: Vec, // Adjust the size as needed -} - -impl Default for Tpm2bPrivate { - fn default() -> Self { - Tpm2bPrivate { - size: 0, - buffer: Vec::new(), - } - } -} +use zerocopy::IntoBytes; // Table 187 -- TPMT_SENSITIVE Structure #[repr(C)] @@ -103,66 +18,44 @@ pub struct TpmtSensitive { pub sensitive: Tpm2bBuffer, } -// impl TpmtSensitive { -// fn serialize(&self) -> Vec { -// let mut buffer = Vec::new(); - -// buffer.extend_from_slice(self.sensitive_type.as_bytes()); -// buffer.extend_from_slice(&self.auth_value.serialize()); -// buffer.extend_from_slice(&self.seed_value.serialize()); -// buffer.extend_from_slice(&self.sensitive.serialize()); - -// buffer -// } -// } - /// Marshals the `TpmtSensitive` structure into a buffer. pub fn tpmt_sensitive_marshal( source: &TpmtSensitive, ) -> Result, std::io::Error> { let mut buffer = Vec::new(); - buffer.extend_from_slice(&source.sensitive_type.as_bytes()); - - Ok(buffer) -} - -/* -pub fn tpmi_alg_public_marshal( - value: &AlgId, - buffer: &mut &mut [u8], - size: &mut i32, -) -> u16 { - // Implement the marshaling logic here - 0 -} - -pub fn tpm2b_auth_marshal(value: &Tpm2bAuth, buffer: &mut &mut [u8], size: &mut i32) -> u16 { - // Implement the marshaling logic here - 0 -} - -pub fn tpm2b_digest_marshal(value: &Tpm2bDi, buffer: &mut &mut [u8], size: &mut i32) -> u16 { - // Implement the marshaling logic here - 0 -} - -pub fn tpmu_sensitive_composite_marshal( - value: &TPMU_SENSITIVE_COMPOSITE, - buffer: &mut &mut [u8], - size: &mut i32, - sensitive_type: u32, -) -> u16 { - // Implement the marshaling logic here - 0 -} -*/ - -pub fn marshal_tpm2b_import( - tpm2b_public: &Tpm2bPublic, - tpm2b_private: &Tpm2bBuffer, -) -> Result, String> { - let mut buffer = Vec::new(); - buffer.extend_from_slice(&tpm2b_public.serialize()); - buffer.extend_from_slice(&tpm2b_private.serialize()); + + // Marshal sensitive_type (TPMI_ALG_PUBLIC) - 2 bytes + let sensitive_type_bytes = source.sensitive_type.as_bytes(); + tracing::trace!("Marshaling sensitive_type: {} bytes = {:02X?}", + sensitive_type_bytes.len(), sensitive_type_bytes); + buffer.extend_from_slice(&sensitive_type_bytes); + + // Marshal auth_value (TPM2B_AUTH) - size + data + let auth_value_bytes = source.auth_value.serialize(); + tracing::trace!("Marshaling auth_value: {} bytes = {:02X?}", + auth_value_bytes.len(), + if auth_value_bytes.len() <= 8 { &auth_value_bytes[..] } else { &auth_value_bytes[..8] }); + buffer.extend_from_slice(&auth_value_bytes); + + // Marshal seed_value (TPM2B_DIGEST) - size + data + let seed_value_bytes = source.seed_value.serialize(); + tracing::trace!("Marshaling seed_value: {} bytes = {:02X?}", + seed_value_bytes.len(), + if seed_value_bytes.len() <= 8 { &seed_value_bytes[..] } else { &seed_value_bytes[..8] }); + buffer.extend_from_slice(&seed_value_bytes); + + // Marshal sensitive (TPMU_SENSITIVE_COMPOSITE) for RSA + // Based on C++ TPM2B_PRIVATE_KEY_RSA_Marshal, this should be: + // 1. uint16_t size (of the buffer data) + // 2. byte array data (the actual prime data) + let sensitive_bytes = source.sensitive.serialize(); + tracing::trace!("Marshaling sensitive: {} bytes = {:02X?}", + sensitive_bytes.len(), + if sensitive_bytes.len() <= 8 { &sensitive_bytes[..] } else { &sensitive_bytes[..8] }); + let data_size = sensitive_bytes.len() as u16; + buffer.extend_from_slice(&data_size.to_be_bytes()); + buffer.extend_from_slice(&sensitive_bytes); + + tracing::trace!("Total marshaled TPMT_SENSITIVE: {} bytes", buffer.len()); Ok(buffer) } \ No newline at end of file diff --git a/vm/devices/tpm/src/tpm_helper.rs b/vm/devices/tpm/src/tpm_helper.rs index d8eaaec51d..3002d93244 100644 --- a/vm/devices/tpm/src/tpm_helper.rs +++ b/vm/devices/tpm/src/tpm_helper.rs @@ -1872,7 +1872,6 @@ pub fn ek_pub_template() -> Result { } /// Returns the public template for SRK. -/// TODO: review the properties. pub fn srk_pub_template() -> Result { let symmetric = TpmtSymDefObject::new( AlgIdEnum::AES.into(), @@ -1895,8 +1894,10 @@ pub fn srk_pub_template() -> Result { .with_restricted(true) .with_decrypt(true); - let mut unique: [u8; 102] = [0; 102]; // Initialize the unique field with zeros - unique[0] = 0x01; // Set the first byte to 1 to indicate a valid unique value, required for Ubuntu. + //let mut unique: [u8; 102] = [0; 102]; // Initialize the unique field with zeros + //unique[0] = 0x01; // Set the first byte to 1 to indicate a valid unique value, required for Ubuntu. + let unique = &[0u8; crate::RSA_2K_MODULUS_SIZE]; + let in_public = TpmtPublic::new( AlgIdEnum::RSA.into(), AlgIdEnum::SHA256.into(), From f40ee88f5ab43e65fcc3022599e4083ad1fed56a Mon Sep 17 00:00:00 2001 From: Fikret Can Date: Thu, 18 Sep 2025 12:16:53 -0700 Subject: [PATCH 09/16] Upgrading to rust 2024 edition. Some reformatting --- vm/cvmutil/Cargo.toml | 3 +- vm/cvmutil/src/main.rs | 282 +++++++++++++++++++++---------- vm/cvmutil/src/marshal.rs | 64 ++++--- vm/devices/tpm/src/tpm_helper.rs | 3 +- 4 files changed, 236 insertions(+), 116 deletions(-) diff --git a/vm/cvmutil/Cargo.toml b/vm/cvmutil/Cargo.toml index 892a9bf45c..2ead8703e1 100644 --- a/vm/cvmutil/Cargo.toml +++ b/vm/cvmutil/Cargo.toml @@ -3,13 +3,12 @@ [package] name = "cvmutil" -edition = "2021" rust-version.workspace = true [features] [dependencies] -ms-tpm-20-ref = { version = "0.1", git = "https://github.com/microsoft/ms-tpm-20-ref-rs.git", branch = "main" } +ms-tpm-20-ref.workspace = true inspect.workspace = true getrandom.workspace = true tracing.workspace = true diff --git a/vm/cvmutil/src/main.rs b/vm/cvmutil/src/main.rs index 0c35961f20..71c32adfbc 100644 --- a/vm/cvmutil/src/main.rs +++ b/vm/cvmutil/src/main.rs @@ -1,29 +1,37 @@ //! CvmUtil is a tool to create and manage vTPM blobs. //! vTPM blobs are used to provide TPM functionality to trusted and confidential VMs. -use ms_tpm_20_ref; +extern crate base64; +extern crate clap; +extern crate ms_tpm_20_ref; +extern crate openssl; +extern crate sha2; +extern crate tpm; +extern crate zerocopy; + use ms_tpm_20_ref::MsTpm20RefPlatform; +use tpm::TPM_RSA_SRK_HANDLE; +use tpm::tpm_helper::{self, TpmEngineHelper}; use tpm::tpm20proto::protocol::{ Tpm2bBuffer, Tpm2bPublic, TpmsRsaParams, TpmtPublic, TpmtRsaScheme, TpmtSymDefObject, }; use tpm::tpm20proto::{AlgId, AlgIdEnum, TpmaObjectBits}; -use tpm::tpm_helper::{self, TpmEngineHelper}; -use tpm::TPM_RSA_SRK_HANDLE; mod marshal; +use base64::Engine; use marshal::TpmtSensitive; -use zerocopy::FromZeros; -use std::sync::{Arc, Mutex}; use ms_tpm_20_ref::DynResult; -use std::io::Read; -use std::io::Write; -use std::time::Instant; -use std::{fs, fs::File, vec}; -use base64::Engine; use openssl::ec::EcGroup; use openssl::ec::EcKey; use openssl::nid::Nid; use openssl::pkey::PKey; use openssl::rsa::Rsa; use sha2::{Digest, Sha256}; +use std::convert::TryInto; +use std::io::Read; +use std::io::Write; +use std::sync::{Arc, Mutex}; +use std::time::Instant; +use std::{fs, fs::File, vec}; +use zerocopy::FromZeros; use clap::Parser; @@ -43,22 +51,59 @@ struct CmdArgs { )] createvtpmblob: Option, - #[clap(short='w', long = "writeSrk", value_names = &["path-to-blob-file", "path-to-srk-out-file"], long_help="Write the SRK public key in TPM2B format.\n./cvmutil --writeSrk vTpm.blob srk.pub")] + /// Write the SRK public key in TPM2B format. Example: ./cvmutil --writeSrk vTpm.blob srk.pub + #[arg( + short = 'w', + long = "writeSrk", + value_names = &["path-to-blob-file", "path-to-srk-out-file"], + long_help = "Write the SRK public key in TPM2B format.\n./cvmutil --writeSrk vTpm.blob srk.pub" + )] write_srk: Option>, - #[arg(short = 'p', long = "printKeyName", value_name = "path-to-srkPub", long_help="Print the TPM key name \n./cvmutil --printKeyName srk.pub")] + /// Print the TPM key name of the SRK public key file. Example: ./cvmutil --printKeyName srk.pub + #[arg( + short = 'p', + long = "printKeyName", + value_name = "path-to-srkPub", + long_help = "Print the TPM key name \n./cvmutil --printKeyName srk.pub" + )] print_key_name: Option, - #[arg(short = 's', long = "createRandomKeyInTpm2ImportBlobFormat", value_names = &["algorithm", "publicKey", "output-file"], long_help="Create random RSA/ECC key in Tpm2 import blob format:TPM2B_PUBLIC || TP2B_PRIVATE || TP2B_ENCRYPTED_SEED \n./cvmutil --createRandomKeyInTpm2ImportBlobFormat rsa rsa_pub.der rsa_priv_marshalled.tpm2b")] + /// Create random RSA/ECC key in Tpm2 import blob format:TPM2B_PUBLIC || TP2B_PRIVATE || TP2B_ENCRYPTED_SEED + /// Example: ./cvmutil --createRandomKeyInTpm2ImportBlobFormat rsa rsa_pub.der rsa_priv_marshalled.tpm2b + #[arg( + short = 's', + long = "createRandomKeyInTpm2ImportBlobFormat", + value_names = &["algorithm", "publicKey", "output-file"], + long_help = "Create random RSA/ECC key in Tpm2 import blob format:TPM2B_PUBLIC || TP2B_PRIVATE || TP2B_ENCRYPTED_SEED \n./cvmutil --createRandomKeyInTpm2ImportBlobFormat rsa rsa_pub.der rsa_priv_marshalled.tpm2b" + )] create_random_key_in_tpm2_import_blob_format: Option>, - #[arg(short = 'd', long = "printDER", value_name = "path-to-pubKey-der", long_help="Print info about DER key \n./cvmutil --printDER rsa_pub.der")] + /// Print info about public key in DER format. Example: ./cvmutil --printDER rsa_pub.der + #[arg( + short = 'd', + long = "printDER", + value_name = "path-to-pubKey-der", + long_help = "Print info about DER key \n./cvmutil --printDER rsa_pub.der" + )] print_pub_key_der: Option, - #[arg(short = 't', long = "printTPM2B", value_name = "path-to-privKey-tpm2b", long_help="Print info about TPM2B import file: TPM2B_PUBLIC || TP2B_PRIVATE || TP2B_ENCRYPTED_SEED. \n./cvmutil --printTPM2B marshalled_import_blob.tpm2b")] + /// Print info about private key in TPM2B format: TPM2B_PUBLIC || TP2B_PRIVATE || TP2B_ENCRYPTED_SEED + #[arg( + short = 't', + long = "printTPM2B", + value_name = "path-to-privKey-tpm2b", + long_help = "Print info about TPM2B import file: TPM2B_PUBLIC || TP2B_PRIVATE || TP2B_ENCRYPTED_SEED. \n./cvmutil --printTPM2B marshalled_import_blob.tpm2b" + )] print_priv_key_tpm2b: Option, - #[arg(short = 'i', long = "testTPM2BImportKeys", value_names = &["path-to-pubKey-der", "path-to-privKey-tpm2b"], long_help="Import the public in DER and private in TPM2B format. Make sure they form a keypair. \n./cvmutil --testTPM2BImportKeys rsa_pub.der marshalled_import_blob.tpm2b")] + /// Test importing public key in DER format and private key in TPM2B format. Make sure they form a keypair. + #[arg( + short = 'i', + long = "testTPM2BImportKeys", + value_names = &["path-to-pubKey-der", "path-to-privKey-tpm2b"], + long_help = "Import the public in DER and private in TPM2B format. Make sure they form a keypair. \n./cvmutil --testTPM2BImportKeys rsa_pub.der marshalled_import_blob.tpm2b" + )] test_tpm2b_import_keys: Option>, } @@ -67,6 +112,7 @@ fn main() { // Parse the command line arguments. let args = CmdArgs::parse(); + // Initialize tracing subscriber for logging. tracing_subscriber::fmt() .with_writer(std::io::stderr) .log_internal_errors(true) @@ -90,22 +136,25 @@ fn main() { // if the vtpm file exists, delet it and create a new one if std::path::Path::new(&path).exists() { - tracing::info!("vTPM file already exists. Deleting the existing file and creating a new one."); + tracing::info!( + "vTPM file already exists. Deleting the existing file and creating a new one." + ); fs::remove_file(&path).expect("failed to delete existing vtpm file"); } fs::write(&path, state.as_slice()).expect("Failed to write vtpm state to blob file"); tracing::info!("vTPM blob created and saved to file: {}", path); - } else if let Some(paths) = args.write_srk { if paths.len() == 2 { let vtpm_blob_path = &paths[0]; // Read the vtpm file content. - let vtpm_blob_content = fs::read(vtpm_blob_path) - .expect("failed to read vtpm blob file"); + let vtpm_blob_content = + fs::read(vtpm_blob_path).expect("failed to read vtpm blob file"); // Restore the TPM engine from the vTPM blob. let (mut vtpm_engine_helper, _nv_blob_accessor) = create_tpm_engine_helper(); - let result = vtpm_engine_helper.tpm_engine.reset(Some(&vtpm_blob_content)); + let result = vtpm_engine_helper + .tpm_engine + .reset(Some(&vtpm_blob_content)); assert!(result.is_ok()); let result = vtpm_engine_helper.initialize_tpm_engine(); @@ -115,7 +164,8 @@ fn main() { let srk_out_path = &paths[1]; tracing::info!( "WriteSrk: blob file: {}, Srk out file: {}", - vtpm_blob_path, srk_out_path + vtpm_blob_path, + srk_out_path ); export_vtpm_srk_pub(vtpm_engine_helper, srk_out_path); } else { @@ -132,7 +182,9 @@ fn main() { private_key_tpm2b_file, ); } else { - tracing::error!("Invalid number of arguments for --createRandomKeyInTpm2ImportBlobFormat. Expected 3 values."); + tracing::error!( + "Invalid number of arguments for --createRandomKeyInTpm2ImportBlobFormat. Expected 3 values." + ); } } else if let Some(srkpub_path) = args.print_key_name { print_vtpm_srk_pub_key_name(srkpub_path); @@ -146,7 +198,9 @@ fn main() { let private_key_file = &key_files[1]; test_import_tpm2b_keys(public_key_file, private_key_file); } else { - tracing::error!("Invalid number of arguments for --testTPM2BImportKeys. Expected 2 values."); + tracing::error!( + "Invalid number of arguments for --testTPM2BImportKeys. Expected 2 values." + ); } } else { tracing::error!("No command specified. Please re-run with --help for usage information."); @@ -154,7 +208,10 @@ fn main() { } /// Create vtpm and return its state as a byte vector. -fn create_vtpm_blob(mut tpm_engine_helper: TpmEngineHelper, nvm_state_blob: Arc>>) -> Vec { +fn create_vtpm_blob( + mut tpm_engine_helper: TpmEngineHelper, + nvm_state_blob: Arc>>, +) -> Vec { // Create a vTPM instance. tracing::info!("Initializing TPM engine."); // Create a primary key: SRK @@ -220,12 +277,11 @@ fn export_vtpm_srk_pub(mut tpm_engine_helper: TpmEngineHelper, srk_out_path: &st // Extract SRK primary key public area. let result = tpm_engine_helper.read_public(TPM_RSA_SRK_HANDLE); - match result { + match result { Ok(response) => { tracing::trace!("SRK public area: {:?}", response.out_public.public_area); // Write the SRK pub to a file. - //let srk_pub_file = Use the input. let mut srk_pub_file = File::create(srk_out_path).expect("failed to create file"); let srk_pub = response.out_public.serialize(); let srk_pub = srk_pub.as_slice(); @@ -236,7 +292,11 @@ fn export_vtpm_srk_pub(mut tpm_engine_helper: TpmEngineHelper, srk_out_path: &st let mut hasher = Sha256::new(); hasher.update(response.out_public.public_area.serialize()); let public_area_hash = hasher.finalize(); - tracing::trace!("SRK public area SHA256 hash: {:x} is written to file {}", public_area_hash, srk_out_path); + tracing::trace!( + "SRK public area SHA256 hash: {:x} is written to file {}", + public_area_hash, + srk_out_path + ); } Err(e) => { tracing::error!("Error in read_public: {:?}", e); @@ -271,7 +331,10 @@ fn print_vtpm_srk_pub_key_name(srkpub_path: String) { tracing::trace!("Printing key properties.\n"); tracing::trace!("Public key type: {:?}", public_area.my_type); tracing::trace!("Public hash alg: {:?}", public_area.name_alg); - tracing::trace!("Public key size in bits: {:?}", public_area.parameters.key_bits); + tracing::trace!( + "Public key size in bits: {:?}", + public_area.parameters.key_bits + ); print_sha256_hash(rsa_key.buffer.as_slice()); // Compute the key name @@ -309,7 +372,10 @@ fn create_random_key_in_tpm2_import_blob_format( let modulus_bytes = rsa.n().to_vec(); tracing::trace!("RSA modulus size: {} bytes", modulus_bytes.len()); let modulus_buffer = Tpm2bBuffer::new(modulus_bytes.as_slice()).unwrap(); - tracing::trace!("Tpm2bBuffer modulus size field: {} bytes", modulus_buffer.size.get()); + tracing::trace!( + "Tpm2bBuffer modulus size field: {} bytes", + modulus_buffer.size.get() + ); let public_key_der = rsa.public_key_to_der_pkcs1().unwrap(); let pkey = PKey::from_rsa(rsa).unwrap(); @@ -327,7 +393,9 @@ fn create_random_key_in_tpm2_import_blob_format( // Save the TPM2B private key to a file let mut priv_file = File::create(private_key_tpm2b_file).unwrap(); priv_file.write_all(&tpm2_import_blob).unwrap(); - tracing::info!("RSA private key is saved to {private_key_tpm2b_file} in TPM2B import format."); + tracing::info!( + "RSA private key is saved to {private_key_tpm2b_file} in TPM2B import format." + ); let private_key_der = pkey.private_key_to_der().unwrap(); print_sha256_hash(&private_key_der.as_slice()); @@ -353,7 +421,9 @@ fn create_random_key_in_tpm2_import_blob_format( let mut priv_file = File::create(private_key_tpm2b_file).unwrap(); priv_file.write_all(&tpm2_import_blob).unwrap(); - tracing::info!("ECC private key in TPM2B import format saved to {private_key_tpm2b_file}."); + tracing::info!( + "ECC private key in TPM2B import format saved to {private_key_tpm2b_file}." + ); } _ => { tracing::error!("Invalid algorithm. Supported algorithms are rsa and ecc."); @@ -378,8 +448,8 @@ fn get_key_in_tpm2_import_format_rsa(priv_key: &PKey) -> AlgIdEnum::RSA.into(), AlgIdEnum::SHA256.into(), TpmaObjectBits::new() - .with_user_with_auth(true) - .with_decrypt(true), + .with_user_with_auth(true) + .with_decrypt(true), &auth_policy, TpmsRsaParams::new(symmetric_def, rsa_scheme, key_bits, exponent), &rsa.n().to_vec(), @@ -389,7 +459,10 @@ fn get_key_in_tpm2_import_format_rsa(priv_key: &PKey) -> let tpm2b_public = Tpm2bPublic::new(tpmt_public_area); // Debug: Check TPM2B_PUBLIC size breakdown tracing::trace!("TPM2B_PUBLIC size {} bytes", tpm2b_public.size.get()); - tracing::trace!("TPM2B_PUBLIC serialized size: {} bytes", tpm2b_public.serialize().len()); + tracing::trace!( + "TPM2B_PUBLIC serialized size: {} bytes", + tpm2b_public.serialize().len() + ); // Create a TPM2B_PRIVATE structure // For RSA import format, use the first prime factor (p), not the private exponent (d) @@ -398,9 +471,9 @@ fn get_key_in_tpm2_import_format_rsa(priv_key: &PKey) -> let sensitive_rsa = Tpm2bBuffer::new(&prime1_bytes).unwrap(); let tpmt_sensitive = TpmtSensitive { - sensitive_type: tpmt_public_area.my_type, // TPM_ALG_RSA - auth_value: Tpm2bBuffer::new_zeroed(), // Empty auth value - seed_value: Tpm2bBuffer::new_zeroed(), // Empty seed value + sensitive_type: tpmt_public_area.my_type, // TPM_ALG_RSA + auth_value: Tpm2bBuffer::new_zeroed(), // Empty auth value + seed_value: Tpm2bBuffer::new_zeroed(), // Empty seed value sensitive: sensitive_rsa, }; @@ -409,39 +482,57 @@ fn get_key_in_tpm2_import_format_rsa(priv_key: &PKey) -> // Create TPM2B_PRIVATE structure: size + marshaled_data let mut tpm2b_private_buffer = Vec::new(); - + // Add the TPM2B size field (total size of the buffer excluding this size field) tpm2b_private_buffer.extend_from_slice(&marshaled_size.to_be_bytes()); - + // Add the marshaled sensitive data tpm2b_private_buffer.extend_from_slice(&marshaled_tpmt_sensitive); - tracing::trace!("TPM2B_PRIVATE total buffer size: {} bytes", tpm2b_private_buffer.len()); + tracing::trace!( + "TPM2B_PRIVATE total buffer size: {} bytes", + tpm2b_private_buffer.len() + ); tracing::trace!(" - Size field: 2 bytes"); - tracing::trace!(" - Marshaled sensitive data: {} bytes", marshaled_tpmt_sensitive.len()); + tracing::trace!( + " - Marshaled sensitive data: {} bytes", + marshaled_tpmt_sensitive.len() + ); tracing::trace!(" - sensitive_type: 2 bytes"); - tracing::trace!(" - auth_value: {} bytes (size + data)", 2 + tpmt_sensitive.auth_value.size.get()); - tracing::trace!(" - seed_value: {} bytes (size + data)", 2 + tpmt_sensitive.seed_value.size.get()); - tracing::trace!(" - sensitive (RSA private exp): {} bytes (size + data)", 2 + tpmt_sensitive.sensitive.size.get()); + tracing::trace!( + " - auth_value: {} bytes (size + data)", + 2 + tpmt_sensitive.auth_value.size.get() + ); + tracing::trace!( + " - seed_value: {} bytes (size + data)", + 2 + tpmt_sensitive.seed_value.size.get() + ); + tracing::trace!( + " - sensitive (RSA private exp): {} bytes (size + data)", + 2 + tpmt_sensitive.sensitive.size.get() + ); // Create the final import blob: TPM2B_PUBLIC || TPM2B_PRIVATE || TPM2B_ENCRYPTED_SECRET let mut final_import_blob = Vec::new(); - + // Add TPM2B_PUBLIC let serialized_public = tpm2b_public.serialize(); final_import_blob.extend_from_slice(&serialized_public); - - // Add TPM2B_PRIVATE + + // Add TPM2B_PRIVATE final_import_blob.extend_from_slice(&tpm2b_private_buffer); - + // Add TPM2B_ENCRYPTED_SECRET (empty - just 2 bytes of zeros for size) final_import_blob.extend_from_slice(&[0u8, 0u8]); - - tracing::trace!("Final TPM2B import format size: {} bytes", final_import_blob.len()); + + tracing::trace!( + "Final TPM2B import format size: {} bytes", + final_import_blob.len() + ); tracing::trace!(" - TPM2B_PUBLIC: {} bytes", serialized_public.len()); tracing::trace!(" - TPM2B_PRIVATE: {} bytes", tpm2b_private_buffer.len()); tracing::trace!(" - TPM2B_ENCRYPTED_SECRET: 2 bytes (empty)"); - + final_import_blob } @@ -518,19 +609,20 @@ fn test_import_tpm2b_keys(public_key_file: &str, private_key_file: &str) { tracing::info!("Testing TPM2B key import..."); tracing::info!("Public key file: {}", public_key_file); tracing::info!("Private key file: {}", private_key_file); - + // Read the public key file let mut pub_key_file = fs::OpenOptions::new() .read(true) .open(public_key_file) .expect("Failed to open public key file"); - + let mut pub_key_content = Vec::new(); - pub_key_file.read_to_end(&mut pub_key_content) + pub_key_file + .read_to_end(&mut pub_key_content) .expect("Failed to read public key file"); - + tracing::info!("Public key file size: {} bytes", pub_key_content.len()); - + // Try to determine the format and parse accordingly // First, try DER format (most likely for .pub files from your tool) let rsa_public_opt = if let Ok(rsa) = Rsa::public_key_from_der_pkcs1(&pub_key_content) { @@ -543,13 +635,13 @@ fn test_import_tpm2b_keys(public_key_file: &str, private_key_file: &str) { tracing::info!("Failed to parse as DER formats, trying TPM2B format..."); None }; - + if let Some(rsa_public) = rsa_public_opt { tracing::info!("RSA public key successfully parsed:"); tracing::info!(" Key size: {} bits", rsa_public.size() * 8); tracing::info!(" Modulus size: {} bytes", rsa_public.n().to_vec().len()); tracing::info!(" Exponent size: {} bytes", rsa_public.e().to_vec().len()); - + // Continue with DER format validation validate_der_format_keys(&rsa_public, private_key_file); } else { @@ -566,66 +658,79 @@ fn validate_der_format_keys(rsa_public: &Rsa, private_key .read(true) .open(private_key_file) .expect("Failed to open private key file"); - + let mut priv_key_content = Vec::new(); - priv_key_file.read_to_end(&mut priv_key_content) + priv_key_file + .read_to_end(&mut priv_key_content) .expect("Failed to read private key file"); - + tracing::info!("Private key file size: {} bytes", priv_key_content.len()); - + // Parse the TPM2B import format: TPM2B_PUBLIC || TPM2B_PRIVATE || TPM2B_ENCRYPTED_SEED - + // 1. Parse TPM2B_PUBLIC - let tmp2b_public = Tpm2bPublic::deserialize(&priv_key_content) - .expect("Failed to deserialize TPM2B_PUBLIC"); - + let tmp2b_public = + Tpm2bPublic::deserialize(&priv_key_content).expect("Failed to deserialize TPM2B_PUBLIC"); + let public_size = tmp2b_public.size.get() as usize + 2; // +2 for size field tracing::info!("TPM2B_PUBLIC parsed:"); tracing::info!(" Size: {} bytes", public_size); tracing::info!(" Algorithm: {:?}", tmp2b_public.public_area.my_type); tracing::info!(" Name algorithm: {:?}", tmp2b_public.public_area.name_alg); - tracing::info!(" Key bits: {:?}", tmp2b_public.public_area.parameters.key_bits); - + tracing::info!( + " Key bits: {:?}", + tmp2b_public.public_area.parameters.key_bits + ); + // 2. Parse TPM2B_PRIVATE let remaining_data = &priv_key_content[public_size..]; - let tmp2b_private = Tpm2bBuffer::deserialize(remaining_data) - .expect("Failed to deserialize TPM2B_PRIVATE"); - + let tmp2b_private = + Tpm2bBuffer::deserialize(remaining_data).expect("Failed to deserialize TPM2B_PRIVATE"); + let private_size = tmp2b_private.size.get() as usize + 2; // +2 for size field tracing::info!("TPM2B_PRIVATE parsed:"); tracing::info!(" Size: {} bytes", private_size); tracing::info!(" Data size: {} bytes", tmp2b_private.size.get()); - + // 3. Parse TPM2B_ENCRYPTED_SECRET (should be empty - 2 zero bytes) let encrypted_seed_data = &remaining_data[private_size..]; if encrypted_seed_data.len() >= 2 { - let encrypted_seed_size = u16::from_be_bytes([encrypted_seed_data[0], encrypted_seed_data[1]]); + let encrypted_seed_size = + u16::from_be_bytes([encrypted_seed_data[0], encrypted_seed_data[1]]); tracing::info!("TPM2B_ENCRYPTED_SECRET parsed:"); tracing::info!(" Size: {} bytes (should be 0)", encrypted_seed_size); - + if encrypted_seed_size == 0 { tracing::info!("Encrypted seed is empty as expected"); } else { tracing::warn!("Encrypted seed is not empty"); } } - + // Validation: Compare the modulus from the DER public key with the TPM2B public key let der_modulus = rsa_public.n().to_vec(); - let tpm2b_modulus: &[u8; 256] = &tmp2b_public.public_area.unique.buffer[0..256].try_into().expect("Modulus size mismatch"); - + let tpm2b_modulus: &[u8; 256] = tmp2b_public.public_area.unique.buffer[0..256] + .try_into() + .expect("Modulus size mismatch"); + tracing::info!("Validation:"); tracing::info!(" DER modulus size: {} bytes", der_modulus.len()); tracing::info!(" TPM2B modulus size: {} bytes", tpm2b_modulus.len()); - + if der_modulus == *tpm2b_modulus { tracing::info!(" Modulus values match between DER and TPM2B formats"); } else { tracing::error!(" Modulus values do NOT match"); - tracing::error!(" First 16 bytes of DER modulus: {:02X?}", &der_modulus[..16.min(der_modulus.len())]); - tracing::error!(" First 16 bytes of TPM2B modulus: {:02X?}", &tpm2b_modulus[..16.min(tpm2b_modulus.len())]); + tracing::error!( + " First 16 bytes of DER modulus: {:02X?}", + &der_modulus[..16.min(der_modulus.len())] + ); + tracing::error!( + " First 16 bytes of TPM2B modulus: {:02X?}", + &tpm2b_modulus[..16.min(tpm2b_modulus.len())] + ); } - + // Calculate expected total size let expected_total = public_size + private_size + 2; // +2 for encrypted seed tracing::info!("Size breakdown:"); @@ -634,13 +739,13 @@ fn validate_der_format_keys(rsa_public: &Rsa, private_key tracing::info!(" TPM2B_ENCRYPTED_SECRET: 2 bytes"); tracing::info!(" Expected total: {} bytes", expected_total); tracing::info!(" Actual file size: {} bytes", priv_key_content.len()); - + if expected_total == priv_key_content.len() { tracing::info!("File size matches expected TPM2B import format"); } else { tracing::error!("File size does NOT match expected format"); } - + tracing::info!("DER pub and TPM2B priv key validation completed successfully!"); } @@ -690,13 +795,10 @@ impl ms_tpm_20_ref::PlatformCallbacks for TestPlatformCallbacks { /// Create a new TPM engine with blank state and return the helper and NV state blob. pub fn create_tpm_engine_helper() -> (TpmEngineHelper, Arc>>) { - let (callbacks, nv_blob_accessor) = TestPlatformCallbacks::new(); - let result = MsTpm20RefPlatform::initialize( - Box::new(callbacks), - ms_tpm_20_ref::InitKind::ColdInit, - ); + let result = + MsTpm20RefPlatform::initialize(Box::new(callbacks), ms_tpm_20_ref::InitKind::ColdInit); assert!(result.is_ok()); let tpm_engine: MsTpm20RefPlatform = result.unwrap(); @@ -707,4 +809,4 @@ pub fn create_tpm_engine_helper() -> (TpmEngineHelper, Arc>>) { }; (tpm_helper, nv_blob_accessor) -} \ No newline at end of file +} diff --git a/vm/cvmutil/src/marshal.rs b/vm/cvmutil/src/marshal.rs index eb3156b9b5..8806f88be1 100644 --- a/vm/cvmutil/src/marshal.rs +++ b/vm/cvmutil/src/marshal.rs @@ -1,8 +1,7 @@ +use tpm::tpm20proto::AlgId; ///! Marshal TPM structures: selected ones only for our use case in cvmutil. ///! TPM reference documents such as TPM-Rev-2.0-Part-2-Structures-01.38.pdf is a good source. - use tpm::tpm20proto::protocol::Tpm2bBuffer; -use tpm::tpm20proto::AlgId; use zerocopy::IntoBytes; // Table 187 -- TPMT_SENSITIVE Structure @@ -19,43 +18,62 @@ pub struct TpmtSensitive { } /// Marshals the `TpmtSensitive` structure into a buffer. -pub fn tpmt_sensitive_marshal( - source: &TpmtSensitive, -) -> Result, std::io::Error> { +pub fn tpmt_sensitive_marshal(source: &TpmtSensitive) -> Result, std::io::Error> { let mut buffer = Vec::new(); - + // Marshal sensitive_type (TPMI_ALG_PUBLIC) - 2 bytes let sensitive_type_bytes = source.sensitive_type.as_bytes(); - tracing::trace!("Marshaling sensitive_type: {} bytes = {:02X?}", - sensitive_type_bytes.len(), sensitive_type_bytes); + tracing::trace!( + "Marshaling sensitive_type: {} bytes = {:02X?}", + sensitive_type_bytes.len(), + sensitive_type_bytes + ); buffer.extend_from_slice(&sensitive_type_bytes); - + // Marshal auth_value (TPM2B_AUTH) - size + data let auth_value_bytes = source.auth_value.serialize(); - tracing::trace!("Marshaling auth_value: {} bytes = {:02X?}", - auth_value_bytes.len(), - if auth_value_bytes.len() <= 8 { &auth_value_bytes[..] } else { &auth_value_bytes[..8] }); + tracing::trace!( + "Marshaling auth_value: {} bytes = {:02X?}", + auth_value_bytes.len(), + if auth_value_bytes.len() <= 8 { + &auth_value_bytes[..] + } else { + &auth_value_bytes[..8] + } + ); buffer.extend_from_slice(&auth_value_bytes); - - // Marshal seed_value (TPM2B_DIGEST) - size + data + + // Marshal seed_value (TPM2B_DIGEST) - size + data let seed_value_bytes = source.seed_value.serialize(); - tracing::trace!("Marshaling seed_value: {} bytes = {:02X?}", - seed_value_bytes.len(), - if seed_value_bytes.len() <= 8 { &seed_value_bytes[..] } else { &seed_value_bytes[..8] }); + tracing::trace!( + "Marshaling seed_value: {} bytes = {:02X?}", + seed_value_bytes.len(), + if seed_value_bytes.len() <= 8 { + &seed_value_bytes[..] + } else { + &seed_value_bytes[..8] + } + ); buffer.extend_from_slice(&seed_value_bytes); - + // Marshal sensitive (TPMU_SENSITIVE_COMPOSITE) for RSA // Based on C++ TPM2B_PRIVATE_KEY_RSA_Marshal, this should be: // 1. uint16_t size (of the buffer data) // 2. byte array data (the actual prime data) let sensitive_bytes = source.sensitive.serialize(); - tracing::trace!("Marshaling sensitive: {} bytes = {:02X?}", - sensitive_bytes.len(), - if sensitive_bytes.len() <= 8 { &sensitive_bytes[..] } else { &sensitive_bytes[..8] }); + tracing::trace!( + "Marshaling sensitive: {} bytes = {:02X?}", + sensitive_bytes.len(), + if sensitive_bytes.len() <= 8 { + &sensitive_bytes[..] + } else { + &sensitive_bytes[..8] + } + ); let data_size = sensitive_bytes.len() as u16; buffer.extend_from_slice(&data_size.to_be_bytes()); buffer.extend_from_slice(&sensitive_bytes); - + tracing::trace!("Total marshaled TPMT_SENSITIVE: {} bytes", buffer.len()); Ok(buffer) -} \ No newline at end of file +} diff --git a/vm/devices/tpm/src/tpm_helper.rs b/vm/devices/tpm/src/tpm_helper.rs index 3002d93244..a16ac59421 100644 --- a/vm/devices/tpm/src/tpm_helper.rs +++ b/vm/devices/tpm/src/tpm_helper.rs @@ -1896,7 +1896,8 @@ pub fn srk_pub_template() -> Result { //let mut unique: [u8; 102] = [0; 102]; // Initialize the unique field with zeros //unique[0] = 0x01; // Set the first byte to 1 to indicate a valid unique value, required for Ubuntu. - let unique = &[0u8; crate::RSA_2K_MODULUS_SIZE]; + let mut unique = [0u8; crate::RSA_2K_MODULUS_SIZE]; + unique[0] = 0x01; // Set the first byte to 1 to indicate a valid unique value, required for Ubuntu. let in_public = TpmtPublic::new( AlgIdEnum::RSA.into(), From f6413eff3d4a848400e49567ef06af01c876667e Mon Sep 17 00:00:00 2001 From: Fikret Can Date: Thu, 18 Sep 2025 12:19:54 -0700 Subject: [PATCH 10/16] Upgrading to rust edition matching workspace --- vm/cvmutil/Cargo.toml | 1 + 1 file changed, 1 insertion(+) diff --git a/vm/cvmutil/Cargo.toml b/vm/cvmutil/Cargo.toml index 2ead8703e1..1c7b0fd5ec 100644 --- a/vm/cvmutil/Cargo.toml +++ b/vm/cvmutil/Cargo.toml @@ -3,6 +3,7 @@ [package] name = "cvmutil" +edition.workspace = true rust-version.workspace = true [features] From bbd3300aab0f9f7d75f4aff1024376d299c50f9a Mon Sep 17 00:00:00 2001 From: Fikret Can Date: Thu, 18 Sep 2025 12:22:33 -0700 Subject: [PATCH 11/16] Remove unnecessary crates --- vm/cvmutil/src/main.rs | 8 -------- 1 file changed, 8 deletions(-) diff --git a/vm/cvmutil/src/main.rs b/vm/cvmutil/src/main.rs index 71c32adfbc..1484d465f6 100644 --- a/vm/cvmutil/src/main.rs +++ b/vm/cvmutil/src/main.rs @@ -1,13 +1,5 @@ //! CvmUtil is a tool to create and manage vTPM blobs. //! vTPM blobs are used to provide TPM functionality to trusted and confidential VMs. -extern crate base64; -extern crate clap; -extern crate ms_tpm_20_ref; -extern crate openssl; -extern crate sha2; -extern crate tpm; -extern crate zerocopy; - use ms_tpm_20_ref::MsTpm20RefPlatform; use tpm::TPM_RSA_SRK_HANDLE; use tpm::tpm_helper::{self, TpmEngineHelper}; From 2b772649d4edba3219cec70910922e2fa0e16dd3 Mon Sep 17 00:00:00 2001 From: Fikret Can Date: Mon, 29 Sep 2025 14:13:25 -0700 Subject: [PATCH 12/16] CvmUtil: add options for offline sealing/online unsealing. Also, add TPM simulator for online unsealing --- Cargo.lock | 1 + vm/cvmutil/Cargo.toml | 3 + vm/cvmutil/src/main.rs | 1413 ++++++++++++++++++++++++++-- vm/cvmutil/src/marshal.rs | 415 +++++++- vm/cvmutil/src/vtpm_helper.rs | 73 ++ vm/cvmutil/src/vtpm_sock_server.rs | 495 ++++++++++ vm/devices/tpm/src/tpm20proto.rs | 12 +- vm/devices/tpm/src/tpm_helper.rs | 18 +- 8 files changed, 2345 insertions(+), 85 deletions(-) create mode 100644 vm/cvmutil/src/vtpm_helper.rs create mode 100644 vm/cvmutil/src/vtpm_sock_server.rs diff --git a/Cargo.lock b/Cargo.lock index 6624b42f22..90c8b76ddb 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1001,6 +1001,7 @@ dependencies = [ "ms-tpm-20-ref", "openssl", "sha2", + "tempfile", "tpm", "tpm_resources", "tracing", diff --git a/vm/cvmutil/Cargo.toml b/vm/cvmutil/Cargo.toml index 1c7b0fd5ec..f702548653 100644 --- a/vm/cvmutil/Cargo.toml +++ b/vm/cvmutil/Cargo.toml @@ -23,5 +23,8 @@ sha2.workspace = true base64.workspace = true openssl.workspace = true +[dev-dependencies] +tempfile.workspace = true + [lints] workspace = true diff --git a/vm/cvmutil/src/main.rs b/vm/cvmutil/src/main.rs index 1484d465f6..62190f9963 100644 --- a/vm/cvmutil/src/main.rs +++ b/vm/cvmutil/src/main.rs @@ -1,4 +1,7 @@ -//! CvmUtil is a tool to create and manage vTPM blobs. +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +//! The module includes the CvmUtil, which is a tool to create and manage vTPM blobs. //! vTPM blobs are used to provide TPM functionality to trusted and confidential VMs. use ms_tpm_20_ref::MsTpm20RefPlatform; use tpm::TPM_RSA_SRK_HANDLE; @@ -8,9 +11,10 @@ use tpm::tpm20proto::protocol::{ }; use tpm::tpm20proto::{AlgId, AlgIdEnum, TpmaObjectBits}; mod marshal; +mod vtpm_helper; +mod vtpm_sock_server; use base64::Engine; use marshal::TpmtSensitive; -use ms_tpm_20_ref::DynResult; use openssl::ec::EcGroup; use openssl::ec::EcKey; use openssl::nid::Nid; @@ -21,14 +25,14 @@ use std::convert::TryInto; use std::io::Read; use std::io::Write; use std::sync::{Arc, Mutex}; -use std::time::Instant; use std::{fs, fs::File, vec}; use zerocopy::FromZeros; +use crate::vtpm_helper::create_tpm_engine_helper; use clap::Parser; #[derive(Parser, Debug)] -#[clap(name = "cvmutil", about = "Tool to interact with vtmp blobs.")] +#[clap(name = "cvmutil", about = "Tool to interact with vTPM blobs.")] struct CmdArgs { /// Enable verbose logging (trace level) #[arg(short = 'v', long = "verbose")] @@ -47,11 +51,28 @@ struct CmdArgs { #[arg( short = 'w', long = "writeSrk", - value_names = &["path-to-blob-file", "path-to-srk-out-file"], + value_names = &["path-to-vtpm-blob-file", "path-to-srk-out-file"], long_help = "Write the SRK public key in TPM2B format.\n./cvmutil --writeSrk vTpm.blob srk.pub" )] write_srk: Option>, + /// Write the SRK template to file in Ubuntu-compatible format. Example: ./cvmutil --writeSrkTemplate tpm2-srk.tmpl + #[arg( + long = "writeSrkTemplate", + value_name = "path-to-template-file", + long_help = "Write the SRK template to file in Ubuntu-compatible format.\n./cvmutil --writeSrkTemplate tpm2-srk.tmpl" + )] + write_srk_template: Option, + + /// Recreate SRK from vTPM blob to verify deterministic generation. Example: ./cvmutil --recreate-srk vTpm.blob + #[arg( + short = 'r', + long = "recreate-srk", + value_name = "path-to-vtpm-blob-file", + long_help = "Recreate SRK from vTPM blob to verify deterministic generation.\nThis will undefine the existing SRK and recreate it to verify the seeds produce the same key.\n./cvmutil --recreate-srk vTpm.blob" + )] + recreate_srk: Option, + /// Print the TPM key name of the SRK public key file. Example: ./cvmutil --printKeyName srk.pub #[arg( short = 'p', @@ -61,6 +82,24 @@ struct CmdArgs { )] print_key_name: Option, + /// Seal data to SRK public key. Example: ./cvmutil --seal srk.pub input.txt output.bin + #[arg( + long = "seal", + value_names = &["path-to-srk-pub", "input-file", "output-file"], + number_of_values = 3, + long_help = "Seal data to SRK public key for testing.\n./cvmutil --seal srk.pub input.txt output.bin" + )] + seal: Option>, + + /// Unseal data from sealed blob using vTPM. Example: ./cvmutil --unseal vtpm.blob sealed.bin output.txt + #[arg( + long = "unseal", + value_names = &["path-to-vtpm-blob", "sealed-file", "output-file"], + number_of_values = 3, + long_help = "Unseal data from sealed blob using vTPM for testing.\n./cvmutil --unseal vtpm.blob sealed.bin output.txt" + )] + unseal: Option>, + /// Create random RSA/ECC key in Tpm2 import blob format:TPM2B_PUBLIC || TP2B_PRIVATE || TP2B_ENCRYPTED_SEED /// Example: ./cvmutil --createRandomKeyInTpm2ImportBlobFormat rsa rsa_pub.der rsa_priv_marshalled.tpm2b #[arg( @@ -97,6 +136,31 @@ struct CmdArgs { long_help = "Import the public in DER and private in TPM2B format. Make sure they form a keypair. \n./cvmutil --testTPM2BImportKeys rsa_pub.der marshalled_import_blob.tpm2b" )] test_tpm2b_import_keys: Option>, + + /// Import a sealed key blob that matches the tpmKeyData structure into an existing vTPM. Example: ./cvmutil --tpmimport /boot/efi/device/fde/cloudimg-rootfs.sealed-key + #[arg( + long = "tpmimport", + value_names = &["path-to-vtpm-blob-file", "path-to-sealed-key-file"], + long_help = "Import a sealed key blob that matches the tpmKeyData structure into an existing vTPM blob.\nThis loads the vTPM blob, imports the sealed key object into the TPM's storage hierarchy, and saves the updated vTPM state.\n./cvmutil --tpmimport vtpm.blob /boot/efi/device/fde/cloudimg-rootfs.sealed-key" + )] + tpm_import: Option>, + + /// Export a TPM key as a sealed key blob compatible with Canonical's format + #[arg( + long = "tpmkeyexport", + value_names = &["path-to-vtpm-blob-file", "key-handle-or-persistent-handle", "path-to-sealed-key-output-file"], + long_help = "Export a TPM key from vTPM blob as a sealed key file compatible with Canonical's cloudimg-rootfs.sealed-key format.\nThis reads a key from the vTPM and exports it in the format expected by Ubuntu's sealed key system.\n./cvmutil --tpmkeyexport vtpm.blob 0x81000001 cloudimg-rootfs.sealed-key" + )] + tpm_key_export: Option>, + + /// Start a TPM socket server using a vTPM blob as backing state + #[arg( + long = "socket-server", + value_names = &["path-to-vtpm-blob-file", "host:port"], + number_of_values = 2, + long_help = "Start a TPM socket server using vTPM blob as backing state.\nProvides a socket-based TPM interface compatible with tpm2-tools and go-tpm2.\n./cvmutil --socket-server vtpm.blob localhost:2321" + )] + socket_server: Option>, } /// Main entry point for cvmutil. @@ -163,6 +227,46 @@ fn main() { } else { tracing::error!("Invalid number of arguments for --writeSrk. Expected 2 values."); } + } else if let Some(vtpm_blob_path) = args.recreate_srk { + tracing::info!("Recreating SRK from vTPM blob: {}", vtpm_blob_path); + recreate_srk_test(&vtpm_blob_path); + } else if let Some(template_path) = args.write_srk_template { + tracing::info!("Writing SRK template to file: {}", template_path); + write_srk_template(&template_path); + } else if let Some(seal_args) = args.seal { + if seal_args.len() == 3 { + let srk_pub_path = &seal_args[0]; + let input_file = &seal_args[1]; + let output_file = &seal_args[2]; + tracing::info!( + "Sealing data: {} -> {} using SRK: {}", + input_file, + output_file, + srk_pub_path + ); + seal_data_to_srk(srk_pub_path, input_file, output_file); + } else { + tracing::error!( + "Invalid number of arguments for --seal. Expected 3 values: srk-pub-file input-file output-file" + ); + } + } else if let Some(unseal_args) = args.unseal { + if unseal_args.len() == 3 { + let vtpm_blob_path = &unseal_args[0]; + let sealed_file = &unseal_args[1]; + let output_file = &unseal_args[2]; + tracing::info!( + "Unsealing data: {} -> {} using vTPM: {}", + sealed_file, + output_file, + vtpm_blob_path + ); + unseal_data_from_vtpm(vtpm_blob_path, sealed_file, output_file); + } else { + tracing::error!( + "Invalid number of arguments for --unseal. Expected 3 values: vtmp-blob-file sealed-file output-file" + ); + } } else if let Some(args) = args.create_random_key_in_tpm2_import_blob_format { if args.len() == 3 { let algorithm = &args[0]; @@ -194,6 +298,70 @@ fn main() { "Invalid number of arguments for --testTPM2BImportKeys. Expected 2 values." ); } + } else if let Some(import_args) = args.tpm_import { + if import_args.len() == 2 { + let vtpm_blob_path = &import_args[0]; + let sealed_key_path = &import_args[1]; + tracing::info!( + "Importing sealed key {} into vTPM blob {}", + sealed_key_path, + vtpm_blob_path + ); + import_sealed_key_blob_into_vtpm(vtpm_blob_path, sealed_key_path); + } else { + tracing::error!( + "Invalid number of arguments for --tpmimport. Expected 2 values: vtpm-blob-file sealed-key-file" + ); + } + } else if let Some(export_args) = args.tpm_key_export { + if export_args.len() == 3 { + let vtpm_blob_path = &export_args[0]; + let sealed_key_output_path = &export_args[1]; + + tracing::info!( + "Creating new key for sealed key export to: {}", + sealed_key_output_path + ); + tracing::info!("Loading vTPM blob from: {}", vtpm_blob_path); + tracing::info!("Output sealed key file: {}", sealed_key_output_path); + + // Read the vtpm file content. + let vtpm_blob_content = + fs::read(vtpm_blob_path).expect("failed to read vtpm blob file"); + // Restore the TPM engine from the vTPM blob. + let (mut vtpm_engine_helper, _nv_blob_accessor) = create_tpm_engine_helper(); + + let result = vtpm_engine_helper + .tpm_engine + .reset(Some(&vtpm_blob_content)); + assert!(result.is_ok()); + + let result = vtpm_engine_helper.initialize_tpm_engine(); + assert!(result.is_ok()); + tracing::info!("TPM engine initialized from blob file."); + + // Instead of exporting existing key, create new one + export_new_key_as_sealed_blob(&mut vtpm_engine_helper, sealed_key_output_path); + } else { + tracing::error!( + "Invalid number of arguments for --tpmkeyexport. Expected 3 values: vtpm-blob-file key-handle sealed-key-output-file" + ); + } + } else if let Some(socket_args) = args.socket_server { + if socket_args.len() == 2 { + let vtpm_blob_path = &socket_args[0]; + let bind_addr = &socket_args[1]; + tracing::info!( + "Starting TPM socket server: {} -> {}", + vtpm_blob_path, + bind_addr + ); + vtpm_sock_server::start_tpm_socket_server(vtpm_blob_path, bind_addr); + } else { + tracing::error!( + "Invalid arguments for --socket-server. Expected: vtpm-blob-file host:port" + ); + } } else { tracing::error!("No command specified. Please re-run with --help for usage information."); } @@ -205,7 +373,11 @@ fn create_vtpm_blob( nvm_state_blob: Arc>>, ) -> Vec { // Create a vTPM instance. - tracing::info!("Initializing TPM engine."); + tracing::info!("Initializing TPM engine with deterministic ColdInit for Ubuntu compatibility."); + + // NOTE: We do NOT call refresh_tpm_seeds() as that would randomize the seeds. + // Ubuntu expects the TPM to use the initial deterministic seeds from ColdInit. + // Create a primary key: SRK let auth_handle = tpm::tpm20proto::TPM20_RH_OWNER; let result = tpm_helper::srk_pub_template(); @@ -231,7 +403,7 @@ fn create_vtpm_blob( } } - // DEBUG: retrieve the SRK and print its SHA256 hash + // DEBUG: retrieve the SRK and print its SHA256 hash and name let result = tpm_engine_helper.read_public(TPM_RSA_SRK_HANDLE); match result { Ok(response) => { @@ -239,6 +411,19 @@ fn create_vtpm_blob( hasher.update(response.out_public.public_area.serialize()); let public_area_hash = hasher.finalize(); tracing::trace!("SRK public area SHA256 hash: {:x}", public_area_hash); + + // Calculate and print the SRK name (algorithm ID + hash) + let algorithm_id = response.out_public.public_area.name_alg; + let mut srk_name = vec![0u8; 2 + public_area_hash.len()]; + srk_name[0] = (algorithm_id.0.get() >> 8) as u8; + srk_name[1] = (algorithm_id.0.get() & 0xFF) as u8; + srk_name[2..].copy_from_slice(&public_area_hash); + + let srk_name_hex = srk_name + .iter() + .map(|b| format!("{:02x}", b)) + .collect::(); + tracing::info!("Generated SRK name: {}", srk_name_hex); } Err(e) => { tracing::error!("Error in read_public: {:?}", e); @@ -262,7 +447,7 @@ fn export_vtpm_srk_pub(mut tpm_engine_helper: TpmEngineHelper, srk_out_path: &st Ok(None) => { tracing::trace!("SRK handle NOT found! Need to create it."); // The SRK doesn't exist, so we need to create it - //recreate_srk(&mut tmp_engine_helper); + //recreate_srk(&mut tpm_engine_helper); } Err(e) => tracing::error!("Error finding SRK handle: {:?}", e), } @@ -275,11 +460,15 @@ fn export_vtpm_srk_pub(mut tpm_engine_helper: TpmEngineHelper, srk_out_path: &st // Write the SRK pub to a file. let mut srk_pub_file = File::create(srk_out_path).expect("failed to create file"); + + // Use the full TPM2B_PUBLIC serialization to match Windows C++ GetSrkPub + // Windows returns the raw publicArea from ReadPublic.m_pOutPublic->Get(), + // which is the serialized TPM2B_PUBLIC structure let srk_pub = response.out_public.serialize(); - let srk_pub = srk_pub.as_slice(); srk_pub_file .write_all(&srk_pub) .expect("failed to write to file"); + // Compute SHA256 hash of the public area let mut hasher = Sha256::new(); hasher.update(response.out_public.public_area.serialize()); @@ -296,6 +485,636 @@ fn export_vtpm_srk_pub(mut tpm_engine_helper: TpmEngineHelper, srk_out_path: &st } } +/// Recreate SRK from vTPM blob to verify deterministic generation. +/// This function will: +/// 1. Load the vTPM blob and read the current SRK +/// 2. Undefine (remove) the persistent SRK +/// 3. Recreate the SRK using the same seeds +/// 4. Compare the old and new SRK to verify they match +fn recreate_srk_test(vtpm_blob_path: &str) { + tracing::info!("Starting SRK recreation test..."); + + // Read the vTPM blob file + let vtpm_blob_content = fs::read(vtpm_blob_path).expect("Failed to read vTPM blob file"); + + tracing::info!("vTPM blob size: {} bytes", vtpm_blob_content.len()); + + // Create TPM engine helper and restore from blob + let (mut tpm_engine_helper, _nv_blob_accessor) = create_tpm_engine_helper(); + + let result = tpm_engine_helper.tpm_engine.reset(Some(&vtpm_blob_content)); + assert!(result.is_ok(), "Failed to reset TPM engine from blob"); + + let result = tpm_engine_helper.initialize_tpm_engine(); + assert!(result.is_ok(), "Failed to initialize TPM engine"); + + tracing::info!("TPM engine initialized from blob"); + + // IMPORTANT: Use StartupType::State instead of initialize_tpm_engine() to preserve TPM state + // tracing::info!("Starting TPM with State preservation..."); + // let result = tpm_engine_helper.startup(tpm::tpm20proto::protocol::StartupType::State); + // assert!(result.is_ok(), "Failed to startup TPM with state preservation"); + + // Perform self-test but don't reinitialize the seeds/state + // let result = tpm_engine_helper.self_test(true); + // assert!(result.is_ok(), "Failed to perform TPM self-test"); + //tracing::info!("TPM engine initialized from blob with state preservation"); + + // Step 1: Read the original SRK + tracing::info!("Step 1: Reading original SRK..."); + let original_srk = tpm_engine_helper + .read_public(TPM_RSA_SRK_HANDLE) + .expect("Failed to read original SRK - SRK might not exist in this blob"); + + // Calculate and log the original SRK name + let mut original_hasher = Sha256::new(); + original_hasher.update(original_srk.out_public.public_area.serialize()); + let original_public_area_hash = original_hasher.finalize(); + + let algorithm_id = original_srk.out_public.public_area.name_alg; + let mut original_srk_name = vec![0u8; 2 + original_public_area_hash.len()]; + original_srk_name[0] = (algorithm_id.0.get() >> 8) as u8; + original_srk_name[1] = (algorithm_id.0.get() & 0xFF) as u8; + original_srk_name[2..].copy_from_slice(&original_public_area_hash); + + let original_srk_name_hex = original_srk_name + .iter() + .map(|b| format!("{:02x}", b)) + .collect::(); + tracing::info!("Original SRK name: {}", original_srk_name_hex); + tracing::info!( + "Original SRK public area size: {} bytes", + original_srk.out_public.size.get() + ); + + // Step 2: Undefine (remove) the persistent SRK + tracing::info!("Step 2: Undefining persistent SRK..."); + + let result = tpm_engine_helper.evict_control( + tpm::tpm20proto::TPM20_RH_OWNER, // auth_handle + TPM_RSA_SRK_HANDLE, // object_handle (persistent handle to remove) + TPM_RSA_SRK_HANDLE, // persistent_handle (same as object_handle for removal) + ); + + match result { + Ok(()) => { + tracing::info!("Successfully undefined persistent SRK"); + } + Err(e) => { + tracing::error!("Failed to undefine persistent SRK: {:?}", e); + panic!("Cannot proceed with test - failed to undefine SRK"); + } + } + + // Verify SRK is no longer present + let find_result = tpm_engine_helper.find_object(TPM_RSA_SRK_HANDLE); + match find_result { + Ok(Some(_)) => { + tracing::error!("SRK still exists after evict_control - this should not happen!"); + panic!("SRK was not properly undefined"); + } + Ok(None) => { + tracing::info!("Confirmed: SRK no longer exists in persistent storage"); + } + Err(e) => { + tracing::warn!( + "Error checking SRK existence (this might be expected): {:?}", + e + ); + } + } + + // Step 3: Recreate the SRK using the same method as create_vtpm_blob + tracing::info!("Step 3: Recreating SRK..."); + + let auth_handle = tpm::tpm20proto::TPM20_RH_OWNER; + let srk_template = tpm_helper::srk_pub_template().expect("Failed to create SRK template"); + + let create_result = tpm_engine_helper.create_primary(auth_handle, srk_template); + let new_object_handle = match create_result { + Ok(response) => { + tracing::info!( + "SRK recreated with temporary handle: {:?}", + response.object_handle + ); + assert_ne!( + response.out_public.size.get(), + 0, + "New SRK public area should not be empty" + ); + + // Calculate the new SRK name for comparison + let mut new_hasher = Sha256::new(); + new_hasher.update(response.out_public.public_area.serialize()); + let new_public_area_hash = new_hasher.finalize(); + + let mut new_srk_name = vec![0u8; 2 + new_public_area_hash.len()]; + new_srk_name[0] = (algorithm_id.0.get() >> 8) as u8; + new_srk_name[1] = (algorithm_id.0.get() & 0xFF) as u8; + new_srk_name[2..].copy_from_slice(&new_public_area_hash); + + let new_srk_name_hex = new_srk_name + .iter() + .map(|b| format!("{:02x}", b)) + .collect::(); + tracing::info!("New SRK name: {}", new_srk_name_hex); + + // Step 4: Compare the original and new SRK + tracing::info!("Step 4: Comparing original and new SRK..."); + + if original_srk_name == new_srk_name { + tracing::info!("SUCCESS: SRK names match exactly!"); + tracing::info!( + "This confirms that the TPM seeds are deterministic and produce identical keys" + ); + } else { + tracing::error!("FAILURE: SRK names do NOT match!"); + tracing::error!("Original: {}", original_srk_name_hex); + tracing::error!("New: {}", new_srk_name_hex); + tracing::error!( + "This indicates the TPM seeds have changed or are not deterministic" + ); + } + + // Also compare the public areas byte-by-byte for additional verification + let original_public_bytes = original_srk.out_public.public_area.serialize(); + let new_public_bytes = response.out_public.public_area.serialize(); + + if original_public_bytes == new_public_bytes { + tracing::info!("Public areas are identical (byte-for-byte match)"); + } else { + tracing::error!("Public areas differ!"); + tracing::trace!( + " Original public area: {} bytes", + original_public_bytes.len() + ); + tracing::trace!(" New public area: {} bytes", new_public_bytes.len()); + + // Show first few bytes that differ for debugging + let min_len = original_public_bytes.len().min(new_public_bytes.len()); + for i in 0..min_len { + if original_public_bytes[i] != new_public_bytes[i] { + tracing::trace!( + "First difference at byte {}: original=0x{:02x}, new=0x{:02x}", + i, + original_public_bytes[i], + new_public_bytes[i] + ); + break; + } + } + } + + response.object_handle + } + Err(e) => { + tracing::error!("Failed to recreate SRK: {:?}", e); + panic!("Cannot complete test - failed to recreate SRK"); + } + }; + + // Step 5: Make the new SRK persistent again (restore the blob to its original state) + tracing::info!("Step 5: Making new SRK persistent..."); + let result = tpm_engine_helper.evict_control( + tpm::tpm20proto::TPM20_RH_OWNER, + new_object_handle, + TPM_RSA_SRK_HANDLE, + ); + + match result { + Ok(()) => { + tracing::info!( + "Successfully made new SRK persistent at handle 0x{:08x}", + TPM_RSA_SRK_HANDLE.0.get() + ); + } + Err(e) => { + tracing::error!("Failed to make new SRK persistent: {:?}", e); + // This is not critical for the test, but good to restore state + } + } + + // Final verification: read the persistent SRK to confirm it's accessible + let final_srk_result = tpm_engine_helper.read_public(TPM_RSA_SRK_HANDLE); + match final_srk_result { + Ok(final_srk) => { + let mut final_hasher = Sha256::new(); + final_hasher.update(final_srk.out_public.public_area.serialize()); + let final_public_area_hash = final_hasher.finalize(); + + let mut final_srk_name = vec![0u8; 2 + final_public_area_hash.len()]; + final_srk_name[0] = (algorithm_id.0.get() >> 8) as u8; + final_srk_name[1] = (algorithm_id.0.get() & 0xFF) as u8; + final_srk_name[2..].copy_from_slice(&final_public_area_hash); + + let final_srk_name_hex = final_srk_name + .iter() + .map(|b| format!("{:02x}", b)) + .collect::(); + tracing::info!("Final persistent SRK name: {}", final_srk_name_hex); + + if final_srk_name == original_srk_name { + tracing::info!( + "Persistent SRK matches original - blob state restored successfully" + ); + } else { + tracing::warn!( + "Persistent SRK differs from original - blob state may have changed" + ); + } + } + Err(e) => { + tracing::warn!("Could not read final persistent SRK: {:?}", e); + } + } + + tracing::info!("SRK recreation test completed successfully!"); +} + +/// Write the SRK template to file in Ubuntu-compatible format. +/// This creates the same template format that Ubuntu's canonical-encrypt-cloud-image expects. +fn write_srk_template(template_path: &str) { + tracing::info!("Generating SRK template for Ubuntu compatibility..."); + + // Get the SRK template using the same function used for TPM initialization + let srk_template = tpm_helper::srk_pub_template().expect("Failed to create SRK template"); + + // Convert to Tpm2bPublic format (same as what gets stored in TPM) + let tpm2b_public = Tpm2bPublic::new(srk_template); + + // Serialize the template in the format Ubuntu expects + // Ubuntu uses go-tpm2's mu.Sized() format which is: size(2 bytes) + data + let serialized_template = tpm2b_public.serialize(); + + // Write to file + let mut template_file = File::create(template_path).expect("Failed to create template file"); + template_file + .write_all(&serialized_template) + .expect("Failed to write template to file"); + + tracing::info!( + "SRK template written to {} ({} bytes)", + template_path, + serialized_template.len() + ); + + // Debug: Print template properties for verification + tracing::trace!("SRK Template Properties:"); + tracing::trace!(" Type: {:?}", tpm2b_public.public_area.my_type); + tracing::trace!(" Name Algorithm: {:?}", tpm2b_public.public_area.name_alg); + tracing::trace!( + " Attributes: {:?}", + tpm2b_public.public_area.object_attributes + ); + tracing::trace!( + " Key Bits: {:?}", + tpm2b_public.public_area.parameters.key_bits + ); + tracing::trace!( + " Symmetric Algorithm: {:?}", + tpm2b_public.public_area.parameters.symmetric.algorithm + ); + tracing::trace!( + " Symmetric Key Bits: {:?}", + tpm2b_public.public_area.parameters.symmetric.key_bits + ); + tracing::trace!( + " Symmetric Mode: {:?}", + tpm2b_public.public_area.parameters.symmetric.mode + ); + + // Compute and display hash for verification + let mut hasher = Sha256::new(); + hasher.update(&serialized_template); + let template_hash = hasher.finalize(); + tracing::trace!("Template SHA256: {:x}", template_hash); + + tracing::info!("SRK template generation completed successfully."); +} + +/// Seal data to SRK using TPM-standard format compatible with Ubuntu secboot. +fn seal_data_to_srk(srk_pub_path: &str, input_file: &str, output_file: &str) { + use marshal::{AfSplitData, CURRENT_METADATA_VERSION, KEY_DATA_HEADER}; + use std::fs; + + tracing::info!("Creating TPM-standard sealed key compatible with Ubuntu secboot"); + tracing::info!("Reading input data from: {}", input_file); + let input_data = fs::read(input_file).expect("failed to read input file"); + tracing::info!("Input data size: {} bytes", input_data.len()); + + // Create minimal TPM structures for a sealed data object + // We'll create a simple keyedobject that contains the sealed data + + // 1. Create a minimal TPM2B_PRIVATE containing our data + let key_private = Tpm2bBuffer::new(&input_data).expect("input data too large for TPM2B buffer"); + + // 2. Create a minimal TPM2B_PUBLIC for the sealed object + // Use the SRK public key template but mark it as a data object + let srk_template = tpm_helper::srk_pub_template().expect("failed to create SRK template"); + let mut sealed_template = srk_template; + + // Modify to be a sealed data object instead of a key + sealed_template.object_attributes = TpmaObjectBits::new() + .with_user_with_auth(true) + .with_no_da(true) + .with_decrypt(true) + .into(); + + // Set unique field to indicate this contains sealed data + sealed_template.unique.buffer[0] = 0xDA; // "DATA" marker + sealed_template.unique.buffer[1] = 0x7A; + sealed_template.unique.buffer[2] = input_data.len() as u8; + sealed_template.unique.buffer[3] = (input_data.len() >> 8) as u8; + + let key_public = Tpm2bPublic::new(sealed_template); + + // 3. Create empty TPM2B_ENCRYPTED_SECRET + let import_sym_seed = Tpm2bBuffer::new_zeroed(); + + // 4. Set auth_mode_hint + let auth_mode_hint: u8 = 0; + + tracing::info!("Created TPM structures:"); + tracing::info!(" TPM2B_PRIVATE size: {} bytes", key_private.payload_size()); + tracing::info!(" TPM2B_PUBLIC size: {} bytes", key_public.payload_size()); + tracing::info!( + " TPM2B_ENCRYPTED_SECRET size: {} bytes", + import_sym_seed.payload_size() + ); + + // 5. Marshal in the order expected by secboot: PRIVATE || PUBLIC || auth_mode_hint || ENCRYPTED_SECRET + let mut tpm_data = Vec::new(); + tpm_data.extend_from_slice(&key_private.serialize()); + tpm_data.extend_from_slice(&key_public.serialize()); + tpm_data.push(auth_mode_hint); + tpm_data.extend_from_slice(&import_sym_seed.serialize()); + + tracing::info!("Marshaled TPM data: {} bytes", tpm_data.len()); + + // 6. Create AF split data using marshal.rs implementation + let af_split_data = AfSplitData::create(&tpm_data); + + // 7. Create the final sealed key file + let mut sealed_blob = Vec::new(); + + // Header: "USK$" magic (big endian) + sealed_blob.extend_from_slice(&KEY_DATA_HEADER.to_be_bytes()); + + // Version: 2 (big endian) + sealed_blob.extend_from_slice(&CURRENT_METADATA_VERSION.to_be_bytes()); + + // AF Split data (using marshal.rs serialization) + sealed_blob.extend_from_slice(&af_split_data.to_bytes()); + + tracing::info!( + "Writing Ubuntu secboot compatible sealed key to: {} ({} bytes)", + output_file, + sealed_blob.len() + ); + fs::write(output_file, sealed_blob).expect("failed to write sealed data file"); + + tracing::info!("TPM-standard sealing completed successfully"); + tracing::info!("Test with: ./test-key --debug {}", output_file); +} + +/// Unseal data from TPM-standard sealed blob using vTPM. +fn unseal_data_from_vtpm(vtpm_blob_path: &str, sealed_file: &str, output_file: &str) { + use marshal::{CURRENT_METADATA_VERSION, KEY_DATA_HEADER}; + use std::fs; + + tracing::info!("Unsealing TPM-standard sealed key compatible with Ubuntu secboot"); + tracing::info!("Reading sealed data from: {}", sealed_file); + let sealed_blob = fs::read(sealed_file).expect("failed to read sealed file"); + + // Parse the Ubuntu secboot format + if sealed_blob.len() < 8 { + // Magic(4) + Version(4) minimum + panic!("Sealed file too small for header"); + } + + // Parse header + let magic = u32::from_be_bytes([ + sealed_blob[0], + sealed_blob[1], + sealed_blob[2], + sealed_blob[3], + ]); + if magic != KEY_DATA_HEADER { + panic!( + "Invalid sealed file format: expected USK$ magic, got 0x{:08x}", + magic + ); + } + + let version = u32::from_be_bytes([ + sealed_blob[4], + sealed_blob[5], + sealed_blob[6], + sealed_blob[7], + ]); + if version != CURRENT_METADATA_VERSION { + panic!( + "Unsupported sealed file version: {} (expected version {})", + version, CURRENT_METADATA_VERSION + ); + } + + tracing::info!("Sealed key format validated: USK$ version {}", version); + + // Use the marshal::AfSplitData::from_bytes() method to parse the AF split data + // The AF split data starts at offset 8 (after the header) + let af_split_data = + marshal::AfSplitData::from_bytes(&sealed_blob[8..]).expect("failed to parse AF split data"); + + tracing::info!( + "AF Split data parsed: {} stripes, hash_alg=0x{:04x}, size={} bytes", + af_split_data.stripes, + af_split_data.hash_alg, + af_split_data.size + ); + + // Merge the AF split data to recover the original TPM structures + tracing::info!("Merging AF split data to recover TPM structures..."); + let merged_data = af_split_data + .merge() + .expect("failed to merge AF split data"); + tracing::info!( + "AF split merge successful: recovered {} bytes", + merged_data.len() + ); + + // Debug: print first few bytes of merged data + if merged_data.len() >= 16 { + tracing::debug!("First 16 bytes of merged data: {:02x?}", &merged_data[..16]); + } else { + tracing::debug!( + "First {} bytes of merged data: {:02x?}", + merged_data.len(), + &merged_data + ); + } + + // Parse the merged TPM data: TPM2B_PRIVATE || TPM2B_PUBLIC || auth_mode_hint || TPM2B_ENCRYPTED_SECRET + let mut offset = 0; + + // Parse TPM2B_PRIVATE + if merged_data.len() < offset + 2 { + panic!("Merged data too short for TPM2B_PRIVATE header"); + } + + // Check the size field of TPM2B_PRIVATE + let private_size = u16::from_be_bytes([merged_data[offset], merged_data[offset + 1]]); + tracing::debug!("TPM2B_PRIVATE size field: {} bytes", private_size); + + if merged_data.len() < offset + 2 + private_size as usize { + panic!( + "Merged data too short for TPM2B_PRIVATE: need {} bytes, have {} bytes", + offset + 2 + private_size as usize, + merged_data.len() + ); + } + + let key_private = Tpm2bBuffer::deserialize(&merged_data[offset..]); + let key_private = match key_private { + Some(buffer) => buffer, + None => { + tracing::error!("Failed to deserialize TPM2B_PRIVATE"); + tracing::error!( + "Data at offset {}: {:02x?}", + offset, + &merged_data[offset..offset.min(merged_data.len()).min(offset + 20)] + ); + panic!("failed to deserialize TPM2B_PRIVATE"); + } + }; + + offset += key_private.payload_size(); + tracing::info!("Parsed TPM2B_PRIVATE: {} bytes", key_private.payload_size()); + + // Parse TPM2B_PUBLIC + if merged_data.len() < offset + 2 { + panic!("Merged data too short for TPM2B_PUBLIC header"); + } + + let public_size = u16::from_be_bytes([merged_data[offset], merged_data[offset + 1]]); + tracing::debug!("TPM2B_PUBLIC size field: {} bytes", public_size); + + if merged_data.len() < offset + 2 + public_size as usize { + panic!( + "Merged data too short for TPM2B_PUBLIC: need {} bytes, have {} bytes", + offset + 2 + public_size as usize, + merged_data.len() + ); + } + + let key_public = Tpm2bPublic::deserialize(&merged_data[offset..]); + let key_public = match key_public { + Some(public) => public, + None => { + tracing::error!("Failed to deserialize TPM2B_PUBLIC"); + tracing::error!( + "Data at offset {}: {:02x?}", + offset, + &merged_data[offset..offset.min(merged_data.len()).min(offset + 20)] + ); + panic!("failed to deserialize TPM2B_PUBLIC"); + } + }; + + offset += key_public.payload_size(); + tracing::info!("Parsed TPM2B_PUBLIC: {} bytes", key_public.payload_size()); + + // Parse auth_mode_hint + if merged_data.len() < offset + 1 { + panic!("Merged data too short for auth_mode_hint"); + } + + let auth_mode_hint = merged_data[offset]; + offset += 1; + + tracing::info!("Parsed auth_mode_hint: {}", auth_mode_hint); + + // Parse TPM2B_ENCRYPTED_SECRET + if merged_data.len() < offset + 2 { + panic!("Merged data too short for TPM2B_ENCRYPTED_SECRET"); + } + + let import_sym_seed = Tpm2bBuffer::deserialize(&merged_data[offset..]); + let import_sym_seed = match import_sym_seed { + Some(buffer) => buffer, + None => { + tracing::error!("Failed to deserialize TPM2B_ENCRYPTED_SECRET"); + tracing::error!( + "Data at offset {}: {:02x?}", + offset, + &merged_data[offset..offset.min(merged_data.len()).min(offset + 20)] + ); + panic!("failed to deserialize TPM2B_ENCRYPTED_SECRET"); + } + }; + + offset += import_sym_seed.payload_size(); + tracing::info!( + "Parsed TPM2B_ENCRYPTED_SECRET: {} bytes", + import_sym_seed.payload_size() + ); + tracing::info!("Successfully parsed all TPM structures from sealed key"); + + // Load vTPM blob and initialize TPM engine + tracing::info!("Loading vTPM blob from: {}", vtpm_blob_path); + let vtpm_blob_content = fs::read(vtpm_blob_path).expect("failed to read vTPM blob file"); + + let (mut tpm_engine_helper, _nv_blob_accessor) = create_tpm_engine_helper(); + let result = tpm_engine_helper.tpm_engine.reset(Some(&vtpm_blob_content)); + if let Err(e) = result { + panic!("Failed to restore vTPM from blob: {:?}", e); + } + + // Initialize the TPM engine (required after reset) + tracing::info!("Initializing TPM engine..."); + let result = tpm_engine_helper.initialize_tpm_engine(); + if let Err(e) = result { + panic!("Failed to initialize TPM engine: {:?}", e); + } + + // The TPM2B_PRIVATE contains our original sealed data + // In our implementation, we stored the data directly in the TPM2B_PRIVATE buffer + let sealed_data_size = key_private.size.get() as usize; + if sealed_data_size == 0 { + panic!("No data found in sealed key"); + } + + let sealed_data = &key_private.buffer[0..sealed_data_size]; + + // Check if this looks like our sealed object by examining the unique field in the public key + let unique_marker = &key_public.public_area.unique.buffer[0..4]; + if unique_marker[0] == 0xDA && unique_marker[1] == 0x7A { + // This is our sealed data format + let expected_data_size = (unique_marker[2] as usize) | ((unique_marker[3] as usize) << 8); + tracing::info!( + "Detected sealed data object, expected size: {} bytes", + expected_data_size + ); + + if sealed_data_size != expected_data_size { + tracing::warn!( + "Data size mismatch: stored {} bytes, expected {} bytes", + sealed_data_size, + expected_data_size + ); + } + } + + tracing::info!("Extracted original data: {} bytes", sealed_data.len()); + + // Write the unsealed data + tracing::info!("Writing unsealed data to: {}", output_file); + fs::write(output_file, sealed_data).expect("failed to write unsealed data file"); + + tracing::info!("TPM-standard unsealing completed successfully"); + tracing::info!("Original data has been recovered from the sealed key"); +} + /// Print the SRK public key name. fn print_vtpm_srk_pub_key_name(srkpub_path: String) { let mut srk_pub_file = fs::OpenOptions::new() @@ -327,7 +1146,7 @@ fn print_vtpm_srk_pub_key_name(srkpub_path: String) { "Public key size in bits: {:?}", public_area.parameters.key_bits ); - print_sha256_hash(rsa_key.buffer.as_slice()); + print_sha256_hash(public_area.serialize().as_slice()); // Compute the key name let algorithm_id = public_area.name_alg; @@ -339,15 +1158,14 @@ fn print_vtpm_srk_pub_key_name(srkpub_path: String) { } let base64_key = base64::engine::general_purpose::STANDARD.encode(&output_key); - tracing::trace!("Key name:\n"); - tracing::trace!(" {}", base64_key); + tracing::info!("Key name: {}", base64_key); // DEBUG: Print RSA bytes in hex to be able to compare with tpm2_readpublic -c 0x81000001 - tracing::trace!("\nRSA key bytes:"); - tracing::trace!(" "); + let mut rsa_pub_str = String::new(); for i in 0..tpm_helper::RSA_2K_MODULUS_SIZE { - tracing::trace!("{:02x}", rsa_key.buffer[i]); + rsa_pub_str.push_str(&format!("{:02x}", rsa_key.buffer[i])); } + tracing::trace!("RSA key bytes: {}", rsa_pub_str); tracing::info!("\nOperation completed successfully.\n"); } @@ -661,28 +1479,28 @@ fn validate_der_format_keys(rsa_public: &Rsa, private_key // Parse the TPM2B import format: TPM2B_PUBLIC || TPM2B_PRIVATE || TPM2B_ENCRYPTED_SEED // 1. Parse TPM2B_PUBLIC - let tmp2b_public = + let tpm2b_public = Tpm2bPublic::deserialize(&priv_key_content).expect("Failed to deserialize TPM2B_PUBLIC"); - let public_size = tmp2b_public.size.get() as usize + 2; // +2 for size field + let public_size = tpm2b_public.size.get() as usize + 2; // +2 for size field tracing::info!("TPM2B_PUBLIC parsed:"); tracing::info!(" Size: {} bytes", public_size); - tracing::info!(" Algorithm: {:?}", tmp2b_public.public_area.my_type); - tracing::info!(" Name algorithm: {:?}", tmp2b_public.public_area.name_alg); + tracing::info!(" Algorithm: {:?}", tpm2b_public.public_area.my_type); + tracing::info!(" Name algorithm: {:?}", tpm2b_public.public_area.name_alg); tracing::info!( " Key bits: {:?}", - tmp2b_public.public_area.parameters.key_bits + tpm2b_public.public_area.parameters.key_bits ); // 2. Parse TPM2B_PRIVATE let remaining_data = &priv_key_content[public_size..]; - let tmp2b_private = + let tpm2b_private = Tpm2bBuffer::deserialize(remaining_data).expect("Failed to deserialize TPM2B_PRIVATE"); - let private_size = tmp2b_private.size.get() as usize + 2; // +2 for size field + let private_size = tpm2b_private.size.get() as usize + 2; // +2 for size field tracing::info!("TPM2B_PRIVATE parsed:"); tracing::info!(" Size: {} bytes", private_size); - tracing::info!(" Data size: {} bytes", tmp2b_private.size.get()); + tracing::info!(" Data size: {} bytes", tpm2b_private.size.get()); // 3. Parse TPM2B_ENCRYPTED_SECRET (should be empty - 2 zero bytes) let encrypted_seed_data = &remaining_data[private_size..]; @@ -701,7 +1519,7 @@ fn validate_der_format_keys(rsa_public: &Rsa, private_key // Validation: Compare the modulus from the DER public key with the TPM2B public key let der_modulus = rsa_public.n().to_vec(); - let tpm2b_modulus: &[u8; 256] = tmp2b_public.public_area.unique.buffer[0..256] + let tpm2b_modulus: &[u8; 256] = tpm2b_public.public_area.unique.buffer[0..256] .try_into() .expect("Modulus size mismatch"); @@ -741,64 +1559,529 @@ fn validate_der_format_keys(rsa_public: &Rsa, private_key tracing::info!("DER pub and TPM2B priv key validation completed successfully!"); } -struct TestPlatformCallbacks { - blob: Vec, - time: Instant, - // Add shared access to the blob - shared_blob: Arc>>, -} +/// Import a sealed key blob into an existing vTPM blob file +fn import_sealed_key_blob_into_vtpm(vtpm_blob_path: &str, sealed_key_path: &str) { + tracing::info!("Loading vTPM blob from: {}", vtpm_blob_path); + tracing::info!("Reading sealed key file: {}", sealed_key_path); -impl TestPlatformCallbacks { - fn new() -> (Self, Arc>>) { - let shared_blob = Arc::new(Mutex::new(Vec::new())); - let callbacks = TestPlatformCallbacks { - blob: vec![], - time: Instant::now(), - shared_blob: shared_blob.clone(), - }; - (callbacks, shared_blob) + // Read the vTPM blob file + let vtpm_blob_content = match fs::read(vtpm_blob_path) { + Ok(data) => data, + Err(e) => { + tracing::error!("Failed to read vTPM blob file {}: {}", vtpm_blob_path, e); + return; + } + }; + + tracing::info!("vTPM blob size: {} bytes", vtpm_blob_content.len()); + + // Read the sealed key file + let sealed_key_data = match fs::read(sealed_key_path) { + Ok(data) => data, + Err(e) => { + tracing::error!("Failed to read sealed key file {}: {}", sealed_key_path, e); + return; + } + }; + + tracing::info!("Sealed key file size: {} bytes", sealed_key_data.len()); + + // Parse the sealed key data + let tpm_key_data = match marshal::TpmKeyData::from_bytes(&sealed_key_data) { + Ok(data) => data, + Err(e) => { + tracing::error!("Failed to parse sealed key data: {}", e); + return; + } + }; + + tracing::info!("Successfully parsed sealed key data:"); + tracing::info!(" Version: {}", tpm_key_data.version); + tracing::info!(" Auth mode hint: {}", tpm_key_data.auth_mode_hint); + tracing::info!( + " Key private size: {} bytes", + tpm_key_data.key_private.payload_size() + ); + tracing::info!( + " Key public size: {} bytes", + tpm_key_data.key_public.payload_size() + ); + tracing::info!( + " Import sym seed size: {} bytes", + tpm_key_data.import_sym_seed.payload_size() + ); + + // Create TPM engine helper and restore from blob + let (mut tpm_engine_helper, nv_blob_accessor) = create_tpm_engine_helper(); + + let result = tpm_engine_helper.tpm_engine.reset(Some(&vtpm_blob_content)); + if let Err(e) = result { + tracing::error!("Failed to reset TPM engine from blob: {:?}", e); + return; + } + + let result = tpm_engine_helper.initialize_tpm_engine(); + if let Err(e) = result { + tracing::error!("Failed to initialize TPM engine: {:?}", e); + return; } -} -impl ms_tpm_20_ref::PlatformCallbacks for TestPlatformCallbacks { - fn commit_nv_state(&mut self, state: &[u8]) -> DynResult<()> { - tracing::trace!("committing nv state with len {}", state.len()); - self.blob = state.to_vec(); - // Also update the shared blob - *self.shared_blob.lock().unwrap() = state.to_vec(); + tracing::info!("TPM engine initialized from blob"); - Ok(()) + // Check if SRK exists (required as parent for import) + if tpm_engine_helper + .find_object(TPM_RSA_SRK_HANDLE) + .unwrap_or(None) + .is_none() + { + tracing::error!("Storage Root Key (SRK) not found in vTPM blob - cannot import sealed key"); + tracing::info!("The vTPM blob may be invalid or not properly initialized"); + return; } - fn get_crypt_random(&mut self, buf: &mut [u8]) -> DynResult { - getrandom::fill(buf).expect("rng failure"); + tracing::info!("SRK found in vTPM - proceeding with sealed key import"); + + // Extract the import blob format from the sealed key data + let import_blob = tpm_key_data.to_import_blob(); + + // Check if we need to import or can load directly + if import_blob.in_sym_seed.size.get() > 0 { + tracing::info!( + "Key has import symmetric seed ({} bytes) - importing into TPM storage hierarchy", + import_blob.in_sym_seed.size.get() + ); + + // Import the key under the SRK + let import_reply = match tpm_engine_helper.import( + TPM_RSA_SRK_HANDLE, + &import_blob.object_public, + &import_blob.duplicate, + &import_blob.in_sym_seed, + ) { + Ok(reply) => { + tracing::info!("Successfully imported sealed key into vTPM"); + reply + } + Err(e) => { + tracing::error!("Failed to import sealed key object into vTPM: {:?}", e); + tracing::error!("This could indicate:"); + tracing::error!(" - Bad sealed key object"); + tracing::error!(" - Invalid symmetric seed"); + tracing::error!(" - TPM owner changed"); + tracing::error!(" - Wrong TPM (key was sealed to different vTPM)"); + return; + } + }; + + // Load the imported key to verify it works + let load_reply = match tpm_engine_helper.load( + TPM_RSA_SRK_HANDLE, + &import_reply.out_private, + &import_blob.object_public, + ) { + Ok(reply) => { + tracing::info!( + "Successfully loaded imported sealed key (temporary handle: {:?})", + reply.object_handle + ); + reply + } + Err(e) => { + tracing::error!("Failed to load imported sealed key: {:?}", e); + return; + } + }; - Ok(buf.len()) + // Verify we can access the key + match tpm_engine_helper.read_public(load_reply.object_handle) { + Ok(read_reply) => { + tracing::info!( + "Verified key access - public area size: {} bytes", + read_reply.out_public.size.get() + ); + tracing::info!( + "Key algorithm: {:?}", + read_reply.out_public.public_area.my_type + ); + } + Err(e) => { + tracing::warn!("Could not read public area of loaded key: {:?}", e); + } + } + + // Clean up the temporary handle + if let Err(e) = tpm_engine_helper.flush_context(load_reply.object_handle) { + tracing::warn!("Failed to flush temporary key handle: {:?}", e); + } else { + tracing::info!("Cleaned up temporary key handle"); + } + } else { + tracing::info!("Key does not require import - attempting to load directly"); + + // Try to load directly under SRK + match tpm_engine_helper.load( + TPM_RSA_SRK_HANDLE, + &import_blob.duplicate, + &import_blob.object_public, + ) { + Ok(load_reply) => { + tracing::info!( + "Successfully loaded sealed key directly (handle: {:?})", + load_reply.object_handle + ); + + // Clean up + if let Err(e) = tpm_engine_helper.flush_context(load_reply.object_handle) { + tracing::warn!("Failed to flush temporary key handle: {:?}", e); + } else { + tracing::info!("Cleaned up temporary key handle"); + } + } + Err(e) => { + tracing::error!("Failed to load sealed key directly: {:?}", e); + return; + } + } } - fn monotonic_timer(&mut self) -> std::time::Duration { - self.time.elapsed() + // Save the updated vTPM state back to the blob file + let updated_blob = nv_blob_accessor.lock().unwrap().clone(); + + // Create backup of original blob + let backup_path = format!("{}.backup", vtpm_blob_path); + if let Err(e) = fs::copy(vtpm_blob_path, &backup_path) { + tracing::warn!("Failed to create backup at {}: {}", backup_path, e); + } else { + tracing::info!("Created backup of original vTPM blob at: {}", backup_path); } - fn get_unique_value(&self) -> &'static [u8] { - b"CPS team cvmutil" + // Write updated blob + if let Err(e) = fs::write(vtpm_blob_path, &updated_blob) { + tracing::error!( + "Failed to write updated vTPM blob to {}: {}", + vtpm_blob_path, + e + ); + tracing::error!("Original blob backup is available at: {}", backup_path); + return; } + + tracing::info!("Updated vTPM blob size: {} bytes", updated_blob.len()); + tracing::info!( + "Successfully saved updated vTPM blob to: {}", + vtpm_blob_path + ); + tracing::info!("Sealed key import into vTPM completed successfully"); } -/// Create a new TPM engine with blank state and return the helper and NV state blob. -pub fn create_tpm_engine_helper() -> (TpmEngineHelper, Arc>>) { - let (callbacks, nv_blob_accessor) = TestPlatformCallbacks::new(); +/// Create Anti Forensic (AF) split data structure +fn create_af_split_data(payload: &[u8]) -> Vec { + use sha2::{Digest, Sha256}; - let result = - MsTpm20RefPlatform::initialize(Box::new(callbacks), ms_tpm_20_ref::InitKind::ColdInit); - assert!(result.is_ok()); + // Use Canonical's approach: target 128KB minimum size + let min_size = 128 * 1024; // 128KB like Canonical + let stripes = (min_size / payload.len()) + 1; - let tpm_engine: MsTpm20RefPlatform = result.unwrap(); + println!( + "AF split: payload {} bytes, {} stripes, target size ~{}KB", + payload.len(), + stripes, + (payload.len() * stripes) / 1024 + ); + + let block_size = payload.len(); + let mut result = Vec::new(); + let mut block = vec![0u8; block_size]; + + // Generate stripes-1 random blocks and XOR/hash them + for _i in 0..(stripes - 1) { + let mut random_block = vec![0u8; block_size]; + getrandom::fill(&mut random_block).expect("Failed to generate random data"); + + result.extend_from_slice(&random_block); + + // XOR with accumulated block + for j in 0..block_size { + block[j] ^= random_block[j]; + } + + // Diffuse the block using hash (simplified version) + let mut hasher = Sha256::new(); + hasher.update(&block); + let hash = hasher.finalize(); + + // Simple diffusion: XOR block with repeated hash + for j in 0..block_size { + block[j] ^= hash[j % 32]; + } + } + + // Final stripe: XOR the accumulated block with original data + let mut final_stripe = vec![0u8; block_size]; + for i in 0..block_size { + final_stripe[i] = block[i] ^ payload[i]; + } + result.extend_from_slice(&final_stripe); - let tpm_helper = TpmEngineHelper { - tpm_engine, - reply_buffer: [0u8; 4096], + // Create AF split header: stripes(4) + hash_alg(4) + af_data_size(4) + data + let mut af_data = Vec::new(); + af_data.extend_from_slice(&(stripes as u32).to_le_bytes()); // 4 bytes: stripe count + af_data.extend_from_slice(&8u32.to_le_bytes()); // 4 bytes: SHA256 hash algorithm ID + af_data.extend_from_slice(&(result.len() as u32).to_le_bytes()); // 4 bytes: AF data length (changed from u16) + af_data.extend_from_slice(&result); + + af_data +} + +/// Export a newly generated key as a sealed key file (instead of exporting existing persistent key) +fn export_new_key_as_sealed_blob( + tpm_engine_helper: &mut TpmEngineHelper, + sealed_key_output_path: &str, +) { + tracing::info!("Generating new RSA key for sealed key export"); + + // Create RSA key template suitable for export/import + let key_template = create_exportable_rsa_key_template(); + + // Generate the key pair in TPM under Owner hierarchy (like SRK) + let create_result = + tpm_engine_helper.create_primary(tpm::tpm20proto::TPM20_RH_OWNER, key_template); + + let (key_handle, key_public) = match create_result { + Ok(response) => (response.object_handle, response.out_public), + Err(e) => { + tracing::error!("Failed to create new key for export: {:?}", e); + return; + } }; - (tpm_helper, nv_blob_accessor) + tracing::info!("Successfully created new key:"); + tracing::info!(" Handle: 0x{:08X}", key_handle.0.get()); + tracing::info!(" Algorithm: {:?}", key_public.public_area.my_type); + tracing::info!(" Key bits: {:?}", key_public.public_area.parameters); + tracing::info!(" Public size: {} bytes", key_public.size.get()); + + // For a complete implementation, we would need TPM2_Create to get the private key data + // For now, use the create_primary approach which gives us the public key + // The limitation is that we still need dummy private key data + + // Generate import symmetric seed for the export + let mut import_seed = vec![0u8; 128]; // 128 bytes of random seed + getrandom::fill(&mut import_seed).expect("Failed to generate import seed"); + + tracing::info!( + "Generated import symmetric seed: {} bytes", + import_seed.len() + ); + + // Since we don't have access to the actual private key from create_primary, + // we still need to create dummy private key data + // TODO: Implement TPM2_Create under SRK to get real private key data + let mut dummy_private_data = vec![0u8; 64]; + getrandom::fill(&mut dummy_private_data).expect("Failed to generate dummy private data"); + let dummy_private = Tpm2bBuffer::new(&dummy_private_data); + + // Clean up the temporary key handle + if let Err(e) = tpm_engine_helper.flush_context(key_handle) { + tracing::warn!("Failed to flush temporary key context: {:?}", e); + } + + // Create the sealed key data with the new key public area and dummy private data + let sealed_key_data = create_sealed_key_blob_v2_with_real_data( + &dummy_private.unwrap(), + &key_public, + &import_seed, + ); + + // Write the sealed key file + match fs::write(sealed_key_output_path, &sealed_key_data) { + Ok(()) => { + tracing::info!( + "Successfully exported new sealed key to: {}", + sealed_key_output_path + ); + tracing::info!("Sealed key file size: {} bytes", sealed_key_data.len()); + tracing::info!("Format: Canonical-compatible sealed key (version 2)"); + tracing::info!("Note: Contains newly generated RSA key with proper export attributes"); + } + Err(e) => { + tracing::error!( + "Failed to write sealed key file {}: {}", + sealed_key_output_path, + e + ); + } + } +} + +/// Create RSA key template optimized for export/import operations +fn create_exportable_rsa_key_template() -> TpmtPublic { + use tpm::tpm20proto::protocol::*; + use tpm::tpm20proto::*; + + let mut key_template = TpmtPublic::new_zeroed(); + + // Set up RSA key parameters + key_template.my_type = AlgId::from(AlgIdEnum::RSA); + key_template.name_alg = AlgId::from(AlgIdEnum::SHA256); + + // Object attributes suitable for import/export + // Clear FIXEDTPM and FIXEDPARENT for import compatibility + key_template.object_attributes = TpmaObjectBits::new() + .with_user_with_auth(true) // User can use key with auth + .with_decrypt(true) // Key can decrypt + .with_sign_encrypt(true) // Key can sign/encrypt + .with_sensitive_data_origin(true) // TPM generated sensitive data + .with_fixed_tpm(false) // NOT fixed to TPM (exportable) + .with_fixed_parent(false) + .into(); // NOT fixed to parent (importable) + + // RSA parameters: 2048-bit key + let mut rsa_params = TpmsRsaParams::new_zeroed(); + rsa_params.key_bits = 2048.into(); + rsa_params.exponent = 0.into(); // Use default exponent (65537) + rsa_params.scheme = TpmtRsaScheme::new_zeroed(); + + // Set RSA parameters + key_template.parameters = TpmsRsaParams::from(rsa_params); + + // No auth policy for simplicity + key_template.auth_policy = Tpm2bBuffer::new_zeroed(); + + // Empty unique field for creation + key_template.unique = Tpm2bBuffer::new_zeroed(); + + tracing::info!("Created exportable RSA key template:"); + tracing::info!(" Type: RSA 2048-bit"); + tracing::info!(" Attributes: 0x{:08X}", key_template.object_attributes.0); + tracing::info!(" Exportable: true (FIXEDTPM/FIXEDPARENT clear)"); + + key_template +} + +/// Create sealed key blob with real TPM data structures +fn create_sealed_key_blob_v2_with_real_data( + key_private: &Tpm2bBuffer, + key_public: &Tpm2bPublic, + import_seed: &[u8], +) -> Vec { + let mut sealed_data = Vec::new(); + + // Header (4 bytes): 0x55534B24 ("USK$") + sealed_data.extend_from_slice(&marshal::KEY_DATA_HEADER.to_be_bytes()); + + // Version (4 bytes): 2 + sealed_data.extend_from_slice(&marshal::CURRENT_METADATA_VERSION.to_be_bytes()); + + // Create the payload data that will be AF-split + let mut payload = Vec::new(); + + // Add real TPM2B_PRIVATE (from TPM2_Create) + let private_serialized = key_private.serialize(); + payload.extend_from_slice(&private_serialized); + tracing::info!("Added TPM2B_PRIVATE: {} bytes", private_serialized.len()); + + // Add real TPM2B_PUBLIC (from TPM2_Create) + let public_serialized = key_public.serialize(); + payload.extend_from_slice(&public_serialized); + tracing::info!("Added TPM2B_PUBLIC: {} bytes", public_serialized.len()); + + // Add auth mode hint (1 byte) + payload.push(0u8); // No authentication required + tracing::info!("Added auth mode hint: 1 byte"); + + // Add real TPM2B_ENCRYPTED_SECRET (import symmetric seed) + let import_seed_buffer = Tpm2bBuffer::new(import_seed); + let seed_serialized = import_seed_buffer.unwrap().serialize(); + payload.extend_from_slice(&seed_serialized); + tracing::info!( + "Added TPM2B_ENCRYPTED_SECRET: {} bytes", + seed_serialized.len() + ); + + tracing::info!("Created payload for AF split: {} bytes", payload.len()); + tracing::info!(" TPM2B_PRIVATE: {} bytes", private_serialized.len()); + tracing::info!(" TPM2B_PUBLIC: {} bytes", public_serialized.len()); + tracing::info!(" Auth mode hint: 1 byte"); + tracing::info!(" TPM2B_ENCRYPTED_SECRET: {} bytes", seed_serialized.len()); + + // Apply AF split to the payload + let af_split_data = create_af_split_data(&payload); + + // Append AF split data to sealed key + sealed_data.extend_from_slice(&af_split_data); + + sealed_data +} + + +// cargo test -p cvmutil test_srk_template_generation +// cargo test -p cvmutil test_platform_unique_value +#[cfg(test)] +mod tests { + use super::*; + use std::fs; + use std::path::Path; + + #[test] + fn test_srk_template_generation() { + // Create a temporary directory for testing + let temp_dir = tempfile::tempdir().unwrap(); + let template_path = temp_dir.path().join("test-srk-template.tmpl"); + + // Generate SRK template + write_srk_template(template_path.to_str().unwrap()); + + // Verify the file exists and has content + assert!(template_path.exists()); + let template_data = fs::read(&template_path).unwrap(); + assert!(!template_data.is_empty()); + + // Verify it can be deserialized back to Tpm2bPublic + let deserialized = Tpm2bPublic::deserialize(&template_data).unwrap(); + + // Verify key properties match Ubuntu expectations + assert_eq!(deserialized.public_area.my_type, AlgIdEnum::RSA.into()); + assert_eq!(deserialized.public_area.name_alg, AlgIdEnum::SHA256.into()); + assert_eq!( + deserialized.public_area.parameters.key_bits, + tpm_helper::RSA_2K_MODULUS_BITS + ); + assert_eq!( + deserialized.public_area.parameters.symmetric.algorithm, + AlgIdEnum::AES.into() + ); + assert_eq!(deserialized.public_area.parameters.symmetric.key_bits, 128); // AES-128 as expected by Ubuntu + assert_eq!( + deserialized.public_area.parameters.symmetric.mode, + AlgIdEnum::CFB.into() + ); + + // Verify object attributes match Ubuntu expectations + let attrs = TpmaObjectBits::from(deserialized.public_area.object_attributes.0.get()); + assert!(attrs.fixed_tpm()); + assert!(attrs.fixed_parent()); + assert!(attrs.sensitive_data_origin()); + assert!(attrs.user_with_auth()); + assert!(attrs.no_da()); + assert!(attrs.restricted()); + assert!(attrs.decrypt()); + + println!( + "SRK template test passed: {} bytes generated", + template_data.len() + ); + } + + #[test] + fn test_platform_unique_value() { + let (callbacks, _) = TestPlatformCallbacks::new(); + // Access the method through the trait interface + use ms_tpm_20_ref::PlatformCallbacks; + let unique_value = callbacks.get_unique_value(); + + // Verify it returns empty array as expected for deterministic SRK generation + assert_eq!(unique_value, &[] as &[u8]); + println!("Platform unique value test passed: empty array as expected"); + } } diff --git a/vm/cvmutil/src/marshal.rs b/vm/cvmutil/src/marshal.rs index 8806f88be1..cd586bacb8 100644 --- a/vm/cvmutil/src/marshal.rs +++ b/vm/cvmutil/src/marshal.rs @@ -1,8 +1,20 @@ -use tpm::tpm20proto::AlgId; +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + ///! Marshal TPM structures: selected ones only for our use case in cvmutil. ///! TPM reference documents such as TPM-Rev-2.0-Part-2-Structures-01.38.pdf is a good source. + +use tpm::tpm20proto::AlgId; use tpm::tpm20proto::protocol::Tpm2bBuffer; -use zerocopy::IntoBytes; +use crate::Tpm2bPublic; +//use tpm::tpm20proto::protocol::Tpm2bPublic; +use zerocopy::{FromZeros, IntoBytes}; +use std::io::{self, Read, Cursor}; + +// Constants for sealed key data format (from Canonical Go secboot package) +pub const KEY_DATA_HEADER: u32 = 0x55534b24; // "USK$" magic bytes +pub const KEY_POLICY_UPDATE_DATA_HEADER: u32 = 0x55534b50; +pub const CURRENT_METADATA_VERSION: u32 = 2; // Table 187 -- TPMT_SENSITIVE Structure #[repr(C)] @@ -17,8 +29,405 @@ pub struct TpmtSensitive { pub sensitive: Tpm2bBuffer, } +/// Anti-Forensic Information Splitter data structure +#[derive(Debug)] +pub struct AfSplitData { + pub stripes: u32, + pub hash_alg: u16, // TPM hash algorithm ID is 2 bytes + pub size: u32, + pub data: Vec, +} + +/// TPM Key Data structure matching Go's tpmKeyData +#[derive(Debug)] +pub struct TpmKeyData { + pub version: u32, + pub key_private: Tpm2bBuffer, // Parsed TPM2B_PRIVATE + pub key_public: Tpm2bPublic, // Parsed TPM2B_PUBLIC + pub auth_mode_hint: u8, + pub import_sym_seed: Tpm2bBuffer, // Parsed TPM2B_ENCRYPTED_SECRET + pub static_policy_data: Option>, // Placeholder for static policy data + pub dynamic_policy_data: Option>, // Placeholder for dynamic policy data +} + +/// Sealed key import blob that matches TPM2B import format (TPM2B_PUBLIC || TPM2B_PRIVATE || TPM2B_ENCRYPTED_SECRET) +#[derive(Debug)] +pub struct SealedKeyImportBlob { + pub object_public: Tpm2bPublic, + pub duplicate: Tpm2bBuffer, + pub in_sym_seed: Tpm2bBuffer, +} + +impl AfSplitData { + /// Create AF split data from payload using proper AFIS algorithm + pub fn create(payload: &[u8]) -> Self { + use sha2::{Digest, Sha256}; + + // Use Canonical's approach: target 128KB minimum size + let min_size = 128 * 1024; // 128KB like Canonical + let stripes = (min_size / payload.len()).max(1) + 1; + + tracing::info!( + "AF split: payload {} bytes, {} stripes, target size ~{}KB", + payload.len(), + stripes, + (payload.len() * stripes) / 1024 + ); + + let block_size = payload.len(); + let mut result = Vec::new(); + let mut block = vec![0u8; block_size]; + + // Generate stripes-1 random blocks and XOR/hash them + for _i in 0..(stripes - 1) { + let mut random_block = vec![0u8; block_size]; + getrandom::fill(&mut random_block).expect("Failed to generate random data"); + + result.extend_from_slice(&random_block); + + // XOR with accumulated block + for j in 0..block_size { + block[j] ^= random_block[j]; + } + + // Diffuse the block using hash (same as in merge) + let mut hasher = Sha256::new(); + hasher.update(&block); + let hash = hasher.finalize(); + + // Simple diffusion: XOR block with repeated hash + for j in 0..block_size { + block[j] ^= hash[j % 32]; + } + } + + // Final stripe: XOR the accumulated block with original data + let mut final_stripe = vec![0u8; block_size]; + for i in 0..block_size { + final_stripe[i] = block[i] ^ payload[i]; + } + result.extend_from_slice(&final_stripe); + + AfSplitData { + stripes: stripes as u32, + hash_alg: 8, // SHA256 hash algorithm ID + size: result.len() as u32, + data: result, + } + } + + /// Parse AF split data from raw bytes using TPM2 binary format + pub fn from_bytes(data: &[u8]) -> Result { + let mut cursor = Cursor::new(data); + + tracing::debug!("AF Split parsing: total data length = {}", data.len()); + + // Read stripes (4 bytes, LITTLE endian to match our export format) + if data.len() < 4 { + return Err(io::Error::new(io::ErrorKind::InvalidData, "Data too short for stripes")); + } + let stripes = u32::from_le_bytes([data[0], data[1], data[2], data[3]]); + + // Read hash algorithm ID (4 bytes, LITTLE endian - we export as u32, not u16) + if data.len() < 8 { + return Err(io::Error::new(io::ErrorKind::InvalidData, "Data too short for hash algorithm")); + } + let hash_alg_u32 = u32::from_le_bytes([data[4], data[5], data[6], data[7]]); + let hash_alg = hash_alg_u32 as u16; // Convert to u16 for compatibility + + // Read size (2 bytes, LITTLE endian to match our export format) + if data.len() < 10 { + return Err(io::Error::new(io::ErrorKind::InvalidData, "Data too short for size")); + } + // Read size (4 bytes, LITTLE endian to match our export format) + if data.len() < 12 { + return Err(io::Error::new(io::ErrorKind::InvalidData, "Data too short for size")); + } + let size = u32::from_le_bytes([data[8], data[9], data[10], data[11]]); + + tracing::debug!("AF Split header: stripes={}, hash_alg=0x{:04x}, size={}", stripes, hash_alg, size); + tracing::debug!("Expected AF data: {} stripes * {} bytes/stripe = {} total bytes", + stripes, size as usize / stripes as usize, size); + + // The data follows immediately after the header + let data_start = 12; // 4 + 4 + 4 bytes for stripes, hash_alg, size + if data.len() < data_start + size as usize { + return Err(io::Error::new(io::ErrorKind::InvalidData, + format!("AF split data truncated: expected {} bytes, got {} bytes", + data_start + size as usize, data.len()))); + } + + let split_data = data[data_start..data_start + size as usize].to_vec(); + + tracing::debug!("AF split validation: split_data.len()={}, stripes={}, remainder={}", + split_data.len(), stripes, split_data.len() % stripes as usize); + + Ok(AfSplitData { + stripes, + hash_alg, + size, + data: split_data, + }) + } + + /// Serialize the AF split data to bytes in the format expected by Ubuntu secboot + pub fn to_bytes(&self) -> Vec { + let mut af_data = Vec::new(); + af_data.extend_from_slice(&self.stripes.to_le_bytes()); // 4 bytes: stripe count + af_data.extend_from_slice(&(self.hash_alg as u32).to_le_bytes()); // 4 bytes: hash algorithm ID + af_data.extend_from_slice(&self.size.to_le_bytes()); // 4 bytes: AF data length + af_data.extend_from_slice(&self.data); + af_data + } + + /// Merge the AF split data to recover original data using proper AFIS algorithm + pub fn merge(&self) -> Result, io::Error> { + use sha2::{Sha256, Digest}; + + // Basic validation + if self.stripes < 1 { + return Err(io::Error::new(io::ErrorKind::InvalidData, "Invalid number of stripes")); + } + + tracing::info!("AF Split merge debug: stripes={}, data.len()={}, remainder={}", + self.stripes, self.data.len(), self.data.len() % self.stripes as usize); + + if self.data.len() % self.stripes as usize != 0 { + return Err(io::Error::new(io::ErrorKind::InvalidData, + format!("Data length {} is not multiple of stripes {}, remainder {}", + self.data.len(), self.stripes, self.data.len() % self.stripes as usize))); + } + + let block_size = self.data.len() / self.stripes as usize; + let mut block = vec![0u8; block_size]; + + tracing::info!("AF Split merge: {} stripes, {} bytes total, {} bytes per block", + self.stripes, self.data.len(), block_size); + + // Reverse the AF split algorithm: + // 1. XOR and hash-diffuse the first (stripes-1) blocks + for i in 0..(self.stripes - 1) as usize { + let offset = i * block_size; + let stripe_data = &self.data[offset..offset + block_size]; + + // XOR with accumulated block + for j in 0..block_size { + block[j] ^= stripe_data[j]; + } + + // Diffuse the block using hash (same as in create_af_split_data) + let mut hasher = Sha256::new(); + hasher.update(&block); + let hash = hasher.finalize(); + + // Simple diffusion: XOR block with repeated hash + for j in 0..block_size { + block[j] ^= hash[j % 32]; + } + } + + // 2. XOR the final stripe with the accumulated block to recover original data + let final_stripe_offset = ((self.stripes - 1) as usize) * block_size; + let final_stripe = &self.data[final_stripe_offset..final_stripe_offset + block_size]; + + let mut original_data = vec![0u8; block_size]; + for i in 0..block_size { + original_data[i] = block[i] ^ final_stripe[i]; + } + + tracing::info!("AF split merge successful: recovered {} bytes", original_data.len()); + Ok(original_data) + } +} + +impl SealedKeyImportBlob { + /// Create a SealedKeyImportBlob from raw bytes in TPM2B import format + pub fn _from_bytes(data: &[u8]) -> Result { + // Parse TPM2B_PUBLIC || TPM2B_PRIVATE || TPM2B_ENCRYPTED_SECRET format + let mut offset = 0; + + // Parse TPM2B_PUBLIC + let object_public = Tpm2bPublic::deserialize(&data[offset..]) + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Failed to parse TPM2B_PUBLIC"))?; + offset += object_public.payload_size(); + + // Parse TPM2B_PRIVATE (as TPM2B_BUFFER for the duplicate field) + let duplicate = Tpm2bBuffer::deserialize(&data[offset..]) + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Failed to parse TPM2B_PRIVATE"))?; + offset += duplicate.payload_size(); + + // Parse TPM2B_ENCRYPTED_SECRET + let in_sym_seed = Tpm2bBuffer::deserialize(&data[offset..]) + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Failed to parse TPM2B_ENCRYPTED_SECRET"))?; + + tracing::info!("Successfully parsed sealed key import blob:"); + tracing::info!(" TPM2B_PUBLIC size: {} bytes", object_public.payload_size()); + tracing::info!(" TPM2B_PRIVATE size: {} bytes", duplicate.payload_size()); + tracing::info!(" TPM2B_ENCRYPTED_SECRET size: {} bytes", in_sym_seed.payload_size()); + + Ok(SealedKeyImportBlob { + object_public, + duplicate, + in_sym_seed, + }) + } +} + +impl TpmKeyData { + /// Parse TPM key data from bytes + pub fn from_bytes(mut data: &[u8]) -> Result { + // Read header + if data.len() < 4 { + return Err(io::Error::new(io::ErrorKind::InvalidData, "Data too short for header")); + } + + let header = u32::from_be_bytes([data[0], data[1], data[2], data[3]]); + data = &data[4..]; + + if header != KEY_DATA_HEADER { + return Err(io::Error::new(io::ErrorKind::InvalidData, + format!("Invalid header: expected 0x{:08X}, got 0x{:08X}", KEY_DATA_HEADER, header))); + } + + // Read version + if data.len() < 4 { + return Err(io::Error::new(io::ErrorKind::InvalidData, "Data too short for version")); + } + + let version = u32::from_be_bytes([data[0], data[1], data[2], data[3]]); + data = &data[4..]; + + tracing::info!("Parsing sealed key data version: {}", version); + + match version { + 0 => Self::parse_v0(data, version), + 1 => Self::parse_v1(data, version), + 2 => Self::parse_v2(data, version), + _ => Err(io::Error::new(io::ErrorKind::InvalidData, + format!("Unsupported version: {}", version))), + } + } + + fn parse_v0(data: &[u8], version: u32) -> Result { + // Version 0 format - direct marshaling without AF split + // This is a simplified parser - full implementation would need detailed parsing + tracing::info!("Parsing version 0 sealed key data"); + + Ok(TpmKeyData { + version, + key_private: Tpm2bBuffer::new_zeroed(), // Would parse TPM2B_PRIVATE + key_public: Tpm2bPublic::new_zeroed(), // Would parse TPM2B_PUBLIC + auth_mode_hint: 0, + import_sym_seed: Tpm2bBuffer::new_zeroed(), + static_policy_data: None, + dynamic_policy_data: None, + }) + } + + fn parse_v1(data: &[u8], version: u32) -> Result { + // Version 1 format - with AF split data + tracing::info!("Parsing version 1 sealed key data"); + + let af_split_data = AfSplitData::from_bytes(data)?; + let merged_data = af_split_data.merge()?; + + // Parse the merged data - simplified implementation + Ok(TpmKeyData { + version, + key_private: Tpm2bBuffer::new_zeroed(), + key_public: Tpm2bPublic::new_zeroed(), + auth_mode_hint: 0, + import_sym_seed: Tpm2bBuffer::new_zeroed(), + static_policy_data: None, + dynamic_policy_data: None, + }) + } + + fn parse_v2(data: &[u8], version: u32) -> Result { + // Version 2 format - with AF split data and import symmetric seed + tracing::info!("Parsing version 2 sealed key data"); + + tracing::debug!("Raw data length: {} bytes", data.len()); + if data.len() >= 16 { + tracing::debug!("First 16 bytes: {:02x?}", &data[..16]); + } + + let af_split_data = AfSplitData::from_bytes(data)?; + tracing::info!("Successfully parsed AF split data"); + + let merged_data = af_split_data.merge()?; + tracing::info!("AF split data merged, {} bytes", merged_data.len()); + + // Parse the merged data which contains: TPM2B_PRIVATE || TPM2B_PUBLIC || auth_mode_hint || TPM2B_ENCRYPTED_SECRET + let mut offset = 0; + + // Parse TPM2B_PRIVATE + if merged_data.len() < offset + 2 { + return Err(io::Error::new(io::ErrorKind::InvalidData, "Data too short for TPM2B_PRIVATE")); + } + + let key_private = Tpm2bBuffer::deserialize(&merged_data[offset..]) + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Failed to parse TPM2B_PRIVATE"))?; + offset += key_private.payload_size(); + + tracing::debug!("Parsed TPM2B_PRIVATE: {} bytes", key_private.payload_size()); + + // Parse TPM2B_PUBLIC + if merged_data.len() < offset + 2 { + return Err(io::Error::new(io::ErrorKind::InvalidData, "Data too short for TPM2B_PUBLIC")); + } + + let key_public = Tpm2bPublic::deserialize(&merged_data[offset..]) + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Failed to parse TPM2B_PUBLIC"))?; + offset += key_public.payload_size(); + + tracing::debug!("Parsed TPM2B_PUBLIC: {} bytes", key_public.payload_size()); + + // Parse auth_mode_hint (1 byte) + if merged_data.len() < offset + 1 { + return Err(io::Error::new(io::ErrorKind::InvalidData, "Data too short for auth_mode_hint")); + } + + let auth_mode_hint = merged_data[offset]; + offset += 1; + + tracing::debug!("Parsed auth_mode_hint: {}", auth_mode_hint); + + // Parse TPM2B_ENCRYPTED_SECRET + if merged_data.len() < offset + 2 { + return Err(io::Error::new(io::ErrorKind::InvalidData, "Data too short for TPM2B_ENCRYPTED_SECRET")); + } + + let import_sym_seed = Tpm2bBuffer::deserialize(&merged_data[offset..]) + .ok_or_else(|| io::Error::new(io::ErrorKind::InvalidData, "Failed to parse TPM2B_ENCRYPTED_SECRET"))?; + offset += import_sym_seed.payload_size(); + + tracing::debug!("Parsed TPM2B_ENCRYPTED_SECRET: {} bytes", import_sym_seed.payload_size()); + tracing::info!("Successfully parsed all TPM structures from merged data, total offset: {}", offset); + + Ok(TpmKeyData { + version, + key_private, + key_public, + auth_mode_hint, + import_sym_seed, + static_policy_data: None, + dynamic_policy_data: None, + }) + } + + /// Extract TPM import blob format from the sealed key data + pub fn to_import_blob(&self) -> SealedKeyImportBlob { + SealedKeyImportBlob { + object_public: self.key_public, + duplicate: self.key_private, + in_sym_seed: self.import_sym_seed, + } + } +} + /// Marshals the `TpmtSensitive` structure into a buffer. -pub fn tpmt_sensitive_marshal(source: &TpmtSensitive) -> Result, std::io::Error> { +pub fn tpmt_sensitive_marshal(source: &TpmtSensitive) -> Result, io::Error> { let mut buffer = Vec::new(); // Marshal sensitive_type (TPMI_ALG_PUBLIC) - 2 bytes diff --git a/vm/cvmutil/src/vtpm_helper.rs b/vm/cvmutil/src/vtpm_helper.rs new file mode 100644 index 0000000000..8096822180 --- /dev/null +++ b/vm/cvmutil/src/vtpm_helper.rs @@ -0,0 +1,73 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +///! Helper to create and manage a TPM engine instance with in-memory NV state for testing. +use tpm::tpm_helper::{self, TpmEngineHelper}; +use std::time::Instant; +use std::sync::{Arc, Mutex}; +use ms_tpm_20_ref::MsTpm20RefPlatform; +use ms_tpm_20_ref::DynResult; +use tpm::tpm20proto::protocol::Tpm2bBuffer; +struct TestPlatformCallbacks { + blob: Vec, + time: Instant, + // Add shared access to the blob + shared_blob: Arc>>, +} + +impl TestPlatformCallbacks { + fn new() -> (Self, Arc>>) { + let shared_blob = Arc::new(Mutex::new(Vec::new())); + let callbacks = TestPlatformCallbacks { + blob: vec![], + time: Instant::now(), + shared_blob: shared_blob.clone(), + }; + (callbacks, shared_blob) + } +} + +impl ms_tpm_20_ref::PlatformCallbacks for TestPlatformCallbacks { + fn commit_nv_state(&mut self, state: &[u8]) -> DynResult<()> { + tracing::trace!("committing nv state with len {}", state.len()); + self.blob = state.to_vec(); + // Also update the shared blob + *self.shared_blob.lock().unwrap() = state.to_vec(); + + Ok(()) + } + + fn get_crypt_random(&mut self, buf: &mut [u8]) -> DynResult { + getrandom::fill(buf).expect("rng failure"); + + Ok(buf.len()) + } + + fn monotonic_timer(&mut self) -> std::time::Duration { + self.time.elapsed() + } + + fn get_unique_value(&self) -> &'static [u8] { + // Return a deterministic value for Ubuntu CVM compatibility + // Ubuntu expects an empty unique value for reproducible key generation + &[] + } +} + +/// Create a new TPM engine with blank state and return the helper and NV state blob. +pub fn create_tpm_engine_helper() -> (TpmEngineHelper, Arc>>) { + let (callbacks, nv_blob_accessor) = TestPlatformCallbacks::new(); + + let result = + MsTpm20RefPlatform::initialize(Box::new(callbacks), ms_tpm_20_ref::InitKind::ColdInit); + assert!(result.is_ok()); + + let tpm_engine: MsTpm20RefPlatform = result.unwrap(); + + let tpm_helper = TpmEngineHelper { + tpm_engine, + reply_buffer: [0u8; 4096], + }; + + (tpm_helper, nv_blob_accessor) +} diff --git a/vm/cvmutil/src/vtpm_sock_server.rs b/vm/cvmutil/src/vtpm_sock_server.rs new file mode 100644 index 0000000000..b5d4e94c1b --- /dev/null +++ b/vm/cvmutil/src/vtpm_sock_server.rs @@ -0,0 +1,495 @@ +// Copyright (c) Microsoft Corporation. +// Licensed under the MIT License. + +///! TPM socket server implementation using vTPM blob as backing state +///! This allows using standard TPM2 tools with a vTPM instance over TCP sockets. +use std::fs; +use std::io::{BufReader, BufWriter}; +use std::net::{TcpListener, TcpStream}; +use std::thread; +use std::sync::{Arc, Mutex}; + +use crate::vtpm_helper::create_tpm_engine_helper; +use tpm::tpm_helper::TpmEngineHelper; + +/// Start a TPM socket server using vTPM blob as backing state +pub fn start_tpm_socket_server(vtpm_blob_path: &str, bind_addr: &str) { + tracing::info!("Starting TPM socket server using vTPM blob: {}", vtpm_blob_path); + tracing::info!("Binding to address: {}", bind_addr); + + // Parse the bind address to extract host and port + let (host, data_port) = parse_bind_address(bind_addr); + let ctrl_port = data_port + 1; // Control port is typically data_port + 1 + + tracing::info!("Data port: {}, Control port: {}", data_port, ctrl_port); + + // Load the vTPM blob + let vtpm_blob_content = fs::read(vtpm_blob_path) + .expect("failed to read vtpm blob file"); + + // Create TPM engine helper + let (mut vtpm_engine_helper, mut nv_blob_accessor) = create_tpm_engine_helper(); + + // Restore TPM state from blob + tracing::info!("Restoring TPM state from blob ({} bytes)", vtpm_blob_content.len()); + let result = vtpm_engine_helper.tpm_engine.reset(Some(&vtpm_blob_content)); + assert!(result.is_ok(), "Failed to restore TPM state: {:?}", result); + + // Initialize the TPM engine (this does StartupType::Clear + SelfTest) + let result = vtpm_engine_helper.initialize_tpm_engine(); + assert!(result.is_ok(), "Failed to initialize TPM engine: {:?}", result); + + tracing::info!("TPM engine initialized successfully"); + + // Wrap TPM engine in Arc for thread safety + let tpm_engine = Arc::new(Mutex::new(vtpm_engine_helper)); + let nv_accessor = Arc::new(Mutex::new(nv_blob_accessor)); + + // Start both data and control listeners + let data_addr = format!("{}:{}", host, data_port); + let ctrl_addr = format!("{}:{}", host, ctrl_port); + + let data_listener = TcpListener::bind(&data_addr) + .expect(&format!("Failed to bind to data address: {}", data_addr)); + + let ctrl_listener = TcpListener::bind(&ctrl_addr) + .expect(&format!("Failed to bind to control address: {}", ctrl_addr)); + + tracing::info!("TPM socket server listening on data port: {}", data_addr); + tracing::info!("TPM socket server listening on control port: {}", ctrl_addr); + tracing::info!("Use with: export TPM2TOOLS_TCTI=\"mssim:host={},port={}\"", host, data_port); + tracing::info!("Press Ctrl+C to stop the server"); + + // Start control socket handler in a separate thread + let ctrl_tpm_engine = Arc::clone(&tpm_engine); + thread::spawn(move || { + handle_control_socket(ctrl_listener, ctrl_tpm_engine); + }); + + // Handle data connections in the main thread + for stream in data_listener.incoming() { + match stream { + Ok(stream) => { + let peer_addr = stream.peer_addr().unwrap_or_else(|_| "unknown".parse().unwrap()); + tracing::info!("New data connection from: {}", peer_addr); + + let tpm_engine_clone = Arc::clone(&tpm_engine); + let nv_accessor_clone = Arc::clone(&nv_accessor); + + // Handle each connection in a separate thread + thread::spawn(move || { + handle_tpm_data_client(stream, tpm_engine_clone, nv_accessor_clone); + }); + } + Err(e) => { + tracing::error!("Failed to accept data connection: {}", e); + } + } + } +} + +/// Parse bind address like "localhost:2321" into (host, port) +fn parse_bind_address(bind_addr: &str) -> (String, u16) { + if let Some(colon_pos) = bind_addr.rfind(':') { + let host = bind_addr[..colon_pos].to_string(); + let port_str = &bind_addr[colon_pos + 1..]; + let port = port_str.parse::() + .expect(&format!("Invalid port number: {}", port_str)); + (host, port) + } else { + panic!("Invalid bind address format. Expected host:port, got: {}", bind_addr); + } +} + +/// Handle the TPM simulator control socket +fn handle_control_socket( + listener: TcpListener, + tpm_engine: Arc>, +) { + tracing::info!("Control socket handler started"); + + for stream in listener.incoming() { + match stream { + Ok(stream) => { + let tpm_engine_clone = Arc::clone(&tpm_engine); + + thread::spawn(move || { + handle_control_client(stream, tpm_engine_clone); + }); + } + Err(e) => { + tracing::error!("Failed to accept control connection: {}", e); + } + } + } +} + +/// Handle a single control client connection +fn handle_control_client( + mut stream: TcpStream, + tpm_engine: Arc>, +) { + let peer_addr = stream.peer_addr().unwrap_or_else(|_| "unknown".parse().unwrap()); + tracing::debug!("Control client connected from: {}", peer_addr); + + let mut reader = BufReader::new(&stream); + let mut writer = BufWriter::new(&stream); + + loop { + match read_control_command(&mut reader) { + Ok(command) => { + tracing::debug!("Received control command: {:?}", command); + + let response = { + let mut engine = tpm_engine.lock().unwrap(); + process_control_command(&mut engine, &command) + }; + + if let Err(e) = write_control_response(&mut writer, &response) { + tracing::error!("Failed to send control response: {}", e); + break; + } + } + Err(e) => { + tracing::debug!("Control client disconnected or read error: {}", e); + break; + } + } + } + + tracing::debug!("Control client disconnected: {}", peer_addr); +} + +/// Control command types for Microsoft TPM Simulator +#[derive(Debug)] +enum ControlCommand { + SessionEnd, // 0x00 + Stop, // 0x01 + Reset, // 0x02 + Restart, // 0x03 + PowerOn, // 0x04 + PowerOff, // 0x05 + GetTestResult, // 0x06 + GetCapability, // 0x07 + NvOn, // 0x0B - MS_SIM_NV_ON + NvOff, // 0x0C - MS_SIM_NV_OFF + HashStart, // 0x0D + HashData, // 0x0E + HashEnd, // 0x0F + Unknown(Vec), +} + +/// Read a control command from the client +fn read_control_command(reader: &mut BufReader<&TcpStream>) -> Result { + use std::io::Read; + + // Control commands are 4 bytes (big endian) + let mut buffer = [0u8; 4]; + reader.read_exact(&mut buffer)?; + + // Parse the 4-byte command as big-endian u32 + let command_code = u32::from_be_bytes(buffer); + + // Parse Microsoft TPM Simulator control commands + match command_code { + 0x00000000 => Ok(ControlCommand::SessionEnd), + 0x00000001 => Ok(ControlCommand::Stop), + 0x00000002 => Ok(ControlCommand::Reset), + 0x00000003 => Ok(ControlCommand::Restart), + 0x00000004 => Ok(ControlCommand::PowerOn), + 0x00000005 => Ok(ControlCommand::PowerOff), + 0x00000006 => Ok(ControlCommand::GetTestResult), + 0x00000007 => Ok(ControlCommand::GetCapability), + 0x0000000B => Ok(ControlCommand::NvOn), // MS_SIM_NV_ON + 0x0000000C => Ok(ControlCommand::NvOff), // MS_SIM_NV_OFF + 0x0000000D => Ok(ControlCommand::HashStart), + 0x0000000E => Ok(ControlCommand::HashData), + 0x0000000F => Ok(ControlCommand::HashEnd), + _ => Ok(ControlCommand::Unknown(buffer.to_vec())), + } +} + +/// Process a control command +fn process_control_command( + engine: &mut TpmEngineHelper, + command: &ControlCommand, +) -> Vec { + match command { + ControlCommand::SessionEnd => { + tracing::debug!("TPM Session End requested"); + vec![0x00, 0x00, 0x00, 0x00] // Success + } + ControlCommand::Stop => { + tracing::info!("TPM Stop requested"); + vec![0x00, 0x00, 0x00, 0x00] // Success + } + ControlCommand::Reset => { + tracing::info!("TPM Reset requested"); + // You might want to reset TPM state here + vec![0x00, 0x00, 0x00, 0x00] // Success + } + ControlCommand::Restart => { + tracing::info!("TPM Restart requested"); + vec![0x00, 0x00, 0x00, 0x00] // Success + } + ControlCommand::PowerOn => { + tracing::info!("TPM Power On requested"); + + // Perform TPM power-on sequence if needed + // This might involve calling engine methods to simulate power-on + + vec![0x00, 0x00, 0x00, 0x00] // Success + } + ControlCommand::PowerOff => { + tracing::info!("TPM Power Off requested"); + vec![0x00, 0x00, 0x00, 0x00] // Success + } + ControlCommand::GetTestResult => { + tracing::debug!("TPM Get Test Result requested"); + vec![0x00, 0x00, 0x00, 0x00] // Success - no test failures + } + ControlCommand::GetCapability => { + tracing::debug!("TPM Get Capability requested"); + // Return capability information + // For now, return success with basic capabilities + vec![0x00, 0x00, 0x00, 0x00] // Success + } + ControlCommand::NvOn => { + tracing::info!("MS_SIM_NV_ON (TPM NV Enable) requested"); + + // This is the command that was failing + // Enable NV storage in the TPM + // The ms-tpm-20-ref might have specific methods for this + + // For now, acknowledge success + vec![0x00, 0x00, 0x00, 0x00] // Success + } + ControlCommand::NvOff => { + tracing::info!("MS_SIM_NV_OFF (TPM NV Disable) requested"); + vec![0x00, 0x00, 0x00, 0x00] // Success + } + ControlCommand::HashStart => { + tracing::debug!("TPM Hash Start requested"); + vec![0x00, 0x00, 0x00, 0x00] // Success + } + ControlCommand::HashData => { + tracing::debug!("TPM Hash Data requested"); + vec![0x00, 0x00, 0x00, 0x00] // Success + } + ControlCommand::HashEnd => { + tracing::debug!("TPM Hash End requested"); + vec![0x00, 0x00, 0x00, 0x00] // Success + } + ControlCommand::Unknown(data) => { + tracing::warn!("Unknown control command: {:02x?} ({})", data, u32::from_be_bytes([data[0], data[1], data[2], data[3]])); + vec![0x00, 0x00, 0x00, 0x01] // Error response + } + } +} + +/// Write a control response to the client +fn write_control_response(writer: &mut BufWriter<&TcpStream>, response: &[u8]) -> Result<(), std::io::Error> { + use std::io::Write; + + writer.write_all(response)?; + writer.flush()?; + Ok(()) +} + +/// Maximum internal buffer we can safely process (TPM_PAGE_SIZE equivalent). +const INTERNAL_MAX_CMD: usize = 4096; +const INTERNAL_MAX_RSP: usize = 4096; +const ABSOLUTE_MAX_CMD: usize = 8192; // hard safety ceiling beyond which we refuse + +#[repr(u32)] +enum IfaceCmd { + SignalHashStart = 5, + SignalHashData = 6, + SignalHashEnd = 7, + SendCommand = 8, + RemoteHandshake = 15, + SessionEnd = 20, + Stop = 21, +} + +fn handle_tpm_data_client( + stream: TcpStream, + tpm_engine: Arc>, + _nv_accessor: Arc>, +) { + use std::io::Write; + let peer_addr = stream.peer_addr().unwrap_or_else(|_| "unknown".parse().unwrap()); + tracing::info!("TPM data client connected from: {}", peer_addr); + + let mut reader = BufReader::new(&stream); + let mut writer = BufWriter::new(&stream); + + let max_cmd = INTERNAL_MAX_CMD; // internal engine limit + loop { + let cmd_code = match read_u32(&mut reader) { + Ok(v) => v, + Err(e) => { + tracing::debug!("Client {} disconnected (read cmd): {}", peer_addr, e); + break; + } + }; + + match cmd_code { + x if x == IfaceCmd::RemoteHandshake as u32 => { + let client_version = read_u32(&mut reader).unwrap(); + tracing::info!("REMOTE_HANDSHAKE client_version={}", client_version); + // serverVersion = 1; flags = tpmInRawMode|tpmPlatformAvailable|tpmSupportsPP + write_u32(&mut writer, 1); + let flags = 0x04 | 0x01 | 0x08; // raw | platform | PP + write_u32(&mut writer, flags); + } + x if x == IfaceCmd::SendCommand as u32 => { + // locality + let mut loc = [0u8;1]; + use std::io::Read; + reader.read_exact(&mut loc); + let locality = loc[0]; + let cmd_buf = read_var_bytes(&mut reader, max_cmd).unwrap(); + if cmd_buf.len() < 10 { + tracing::warn!("TPM command too short {}", cmd_buf.len()); + } + if cmd_buf.len() >= 6 { + let tpm_declared = u32::from_be_bytes([cmd_buf[2],cmd_buf[3],cmd_buf[4],cmd_buf[5]]) as usize; + if tpm_declared != cmd_buf.len() { + tracing::warn!("TPM header size {} != envelope {}", tpm_declared, cmd_buf.len()); + } + } + + let resp = { + let mut engine = tpm_engine.lock().unwrap(); + process_tpm_command(&mut engine, &cmd_buf) + .unwrap_or_else(|e| { + tracing::error!("Exec error: {}", e); + // Minimal TPM error skeleton if desired; for now empty. + vec![0u8; 0] + }) + }; + + write_var_bytes(&mut writer, &resp); + tracing::info!("SendCommand locality={} in={} out={}", locality, cmd_buf.len(), resp.len()); + } + x if x == IfaceCmd::SignalHashStart as u32 => { + // no payload + } + x if x == IfaceCmd::SignalHashEnd as u32 => { + // no payload + } + x if x == IfaceCmd::SignalHashData as u32 => { + let data = read_var_bytes(&mut reader, max_cmd).unwrap(); + tracing::debug!("HashData {} bytes (ignored pass-through)", data.len()); + } + x if x == IfaceCmd::SessionEnd as u32 => { + tracing::info!("SessionEnd requested"); + write_u32(&mut writer, 0); // status before break (consistent with C? C returns true then writes status after switch) + writer.flush(); + break; + } + x if x == IfaceCmd::Stop as u32 => { + tracing::info!("Stop requested"); + write_u32(&mut writer, 0); + writer.flush(); + // Optionally signal broader shutdown + break; + } + other => { + tracing::warn!("Unknown interface command 0x{:08x}", other); + // In C, unknown causes return (dropping connection) *after* printing and not writing status. + break; + } + } + + // Trailing status (always 0) after a handled interface command (except unknown/early failure) + write_u32(&mut writer, 0); + if let Err(e) = writer.flush() { + tracing::debug!("Flush failed: {}", e); + break; + } + } + + tracing::info!("TPM data client disconnected: {}", peer_addr); +} + +// Helpers. + +fn read_u32(reader: &mut BufReader<&TcpStream>) -> std::io::Result { + use std::io::Read; + let mut b = [0u8;4]; + reader.read_exact(&mut b)?; + Ok(u32::from_be_bytes(b)) +} + +fn write_u32(writer: &mut BufWriter<&TcpStream>, v: u32) -> std::io::Result<()> { + use std::io::Write; + writer.write_all(&v.to_be_bytes()) +} + +fn read_var_bytes(reader: &mut BufReader<&TcpStream>, max: usize) -> std::io::Result> { + let len = read_u32(reader)? as usize; + if len > max { + return Err(std::io::Error::new(std::io::ErrorKind::InvalidData, + format!("VarBytes length {} > max {}", len, max))); + } + let mut buf = vec![0u8; len]; + use std::io::Read; + reader.read_exact(&mut buf)?; + Ok(buf) +} + +fn write_var_bytes(writer: &mut BufWriter<&TcpStream>, data: &[u8]) -> std::io::Result<()> { + write_u32(writer, data.len() as u32)?; + use std::io::Write; + writer.write_all(data) +} + +/// Process a TPM command using the TPM engine +fn process_tpm_command( + vtpm_engine_helper: &mut TpmEngineHelper, + command: &[u8], +) -> Result, Box> { + tracing::debug!("Processing TPM command: {} bytes", command.len()); + + // Check command size - TPM commands should fit in the page size + if command.len() > 4096 { // TPM_PAGE_SIZE + return Err("Command too large for TPM buffer".into()); + } + + // Create a command buffer similar to how the TPM device does it + let mut command_buffer = [0u8; 4096]; // Same size as TPM_PAGE_SIZE + + // Copy the command into the buffer + command_buffer[..command.len()].copy_from_slice(command); + + tracing::trace!("Executing TPM command with engine..."); + + // Submit the command to the TPM engine + let result = vtpm_engine_helper.tpm_engine.execute_command( + &mut command_buffer, + &mut vtpm_engine_helper.reply_buffer, + ); + + match result { + Ok(response_size) => { + tracing::debug!("TPM command executed successfully, response size: {}", response_size); + + if response_size == 0 { + return Err("TPM returned zero-length response".into()); + } + + if response_size > 4096 { + return Err(format!("TPM response too large: {}", response_size).into()); + } + + // Copy the response from the helper's reply buffer + Ok(vtpm_engine_helper.reply_buffer[..response_size].to_vec()) + } + Err(e) => { + tracing::error!("TPM engine command failed: {:?}", e); + Err(format!("TPM command processing failed: {:?}", e).into()) + } + } +} \ No newline at end of file diff --git a/vm/devices/tpm/src/tpm20proto.rs b/vm/devices/tpm/src/tpm20proto.rs index 71142b91f9..61858c5bad 100644 --- a/vm/devices/tpm/src/tpm20proto.rs +++ b/vm/devices/tpm/src/tpm20proto.rs @@ -1208,9 +1208,9 @@ pub mod protocol { #[repr(C)] #[derive(Debug, Copy, Clone, FromBytes, IntoBytes, Immutable, KnownLayout, PartialEq)] pub struct TpmtSymDefObject { - algorithm: AlgId, - key_bits: u16_be, - mode: AlgId, + pub algorithm: AlgId, + pub key_bits: u16_be, + pub mode: AlgId, } impl TpmtSymDefObject { @@ -1288,8 +1288,8 @@ pub mod protocol { #[repr(C)] #[derive(Debug, Copy, Clone, FromBytes, IntoBytes, Immutable, KnownLayout, PartialEq)] pub struct TpmsRsaParams { - symmetric: TpmtSymDefObject, - scheme: TpmtRsaScheme, + pub symmetric: TpmtSymDefObject, + pub scheme: TpmtRsaScheme, pub key_bits: u16_be, pub exponent: u32_be, } @@ -1376,7 +1376,7 @@ pub mod protocol { pub my_type: AlgId, pub name_alg: AlgId, pub object_attributes: TpmaObject, - auth_policy: Tpm2bBuffer, + pub auth_policy: Tpm2bBuffer, // `TPMS_RSA_PARAMS` pub parameters: TpmsRsaParams, // `TPM2B_PUBLIC_KEY_RSA` diff --git a/vm/devices/tpm/src/tpm_helper.rs b/vm/devices/tpm/src/tpm_helper.rs index a16ac59421..4b8bb2b410 100644 --- a/vm/devices/tpm/src/tpm_helper.rs +++ b/vm/devices/tpm/src/tpm_helper.rs @@ -59,7 +59,7 @@ const MAX_NV_INDEX_SIZE: u16 = 4096; // Scale this with maximum attestation payload const MAX_ATTESTATION_INDEX_SIZE: u16 = 2600; -const RSA_2K_MODULUS_BITS: u16 = 2048; +pub const RSA_2K_MODULUS_BITS: u16 = 2048; pub const RSA_2K_MODULUS_SIZE: usize = (RSA_2K_MODULUS_BITS / 8) as usize; const RSA_2K_EXPONENT_SIZE: usize = 3; @@ -1728,7 +1728,7 @@ impl TpmEngineHelper { /// * `duplicate` - The private part of the key to be imported. /// * `in_sym_seed` - The value associated with `duplicate`. /// - fn import( + pub fn import( &mut self, auth_handle: ReservedHandle, object_public: &Tpm2bPublic, @@ -1773,7 +1773,7 @@ impl TpmEngineHelper { /// * `in_private` - The private part of the key to be loaded. /// * `in_public` - The public part of the key to be loaded. /// - fn load( + pub fn load( &mut self, auth_handle: ReservedHandle, in_private: &Tpm2bBuffer, @@ -1871,7 +1871,8 @@ pub fn ek_pub_template() -> Result { Ok(in_public) } -/// Returns the public template for SRK. +/// Returns the public template for SRK +/// https://github.com/canonical/snapd/blob/9cb4b26eed4e49eba34ea2838e6fec3404621729/secboot/secboot_sb_test.go#L367C2-L367C19 pub fn srk_pub_template() -> Result { let symmetric = TpmtSymDefObject::new( AlgIdEnum::AES.into(), @@ -1883,7 +1884,7 @@ pub fn srk_pub_template() -> Result { // Define the RSA scheme as TPM2_ALG_NULL for general use let scheme = TpmtRsaScheme::new(AlgIdEnum::NULL.into(), None); - let rsa_params = TpmsRsaParams::new(symmetric, scheme, crate::RSA_2K_MODULUS_BITS, 65537); + let rsa_params = TpmsRsaParams::new(symmetric, scheme, crate::RSA_2K_MODULUS_BITS, 0); // 0 exponent means use default (2^16 + 1) let object_attributes = TpmaObjectBits::new() .with_fixed_tpm(true) @@ -1893,11 +1894,6 @@ pub fn srk_pub_template() -> Result { .with_no_da(true) .with_restricted(true) .with_decrypt(true); - - //let mut unique: [u8; 102] = [0; 102]; // Initialize the unique field with zeros - //unique[0] = 0x01; // Set the first byte to 1 to indicate a valid unique value, required for Ubuntu. - let mut unique = [0u8; crate::RSA_2K_MODULUS_SIZE]; - unique[0] = 0x01; // Set the first byte to 1 to indicate a valid unique value, required for Ubuntu. let in_public = TpmtPublic::new( AlgIdEnum::RSA.into(), @@ -1905,7 +1901,7 @@ pub fn srk_pub_template() -> Result { object_attributes, &[], rsa_params, - unique.as_ref(), + &[0u8; crate::RSA_2K_MODULUS_SIZE], ) .map_err(TpmHelperUtilityError::InvalidInputParameter)?; From 5241c66bb8185f2b31224e4b549b7c94291bb46c Mon Sep 17 00:00:00 2001 From: Fikret Can Date: Fri, 10 Oct 2025 00:28:36 -0700 Subject: [PATCH 13/16] Cvmutil vtpm simulator: handle ctrl-c to release port --- Cargo.lock | 1 + vm/cvmutil/Cargo.toml | 1 + vm/cvmutil/src/main.rs | 17 ++++ vm/cvmutil/src/vtpm_sock_server.rs | 121 +++++++++++++++++++++++++---- vm/devices/tpm/src/tpm20proto.rs | 27 +++++++ 5 files changed, 151 insertions(+), 16 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 90c8b76ddb..1c73772e7d 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -996,6 +996,7 @@ version = "0.0.0" dependencies = [ "base64 0.22.1", "clap", + "ctrlc", "getrandom 0.3.3", "inspect", "ms-tpm-20-ref", diff --git a/vm/cvmutil/Cargo.toml b/vm/cvmutil/Cargo.toml index f702548653..4ea0ac8ecd 100644 --- a/vm/cvmutil/Cargo.toml +++ b/vm/cvmutil/Cargo.toml @@ -22,6 +22,7 @@ clap = { workspace = true, features = ["derive"] } sha2.workspace = true base64.workspace = true openssl.workspace = true +ctrlc.workspace = true [dev-dependencies] tempfile.workspace = true diff --git a/vm/cvmutil/src/main.rs b/vm/cvmutil/src/main.rs index 62190f9963..5fb243e1e0 100644 --- a/vm/cvmutil/src/main.rs +++ b/vm/cvmutil/src/main.rs @@ -469,6 +469,23 @@ fn export_vtpm_srk_pub(mut tpm_engine_helper: TpmEngineHelper, srk_out_path: &st .write_all(&srk_pub) .expect("failed to write to file"); + // Calculate and print the SRK name (algorithm ID + hash) + let mut hasher = Sha256::new(); + hasher.update(response.out_public.public_area.serialize()); + let public_area_hash = hasher.finalize(); + tracing::trace!("SRK public area SHA256 hash: {:x}", public_area_hash); + let algorithm_id = response.out_public.public_area.name_alg; + let mut srk_name = vec![0u8; 2 + public_area_hash.len()]; + srk_name[0] = (algorithm_id.0.get() >> 8) as u8; + srk_name[1] = (algorithm_id.0.get() & 0xFF) as u8; + srk_name[2..].copy_from_slice(&public_area_hash); + + let srk_name_hex = srk_name + .iter() + .map(|b| format!("{:02x}", b)) + .collect::(); + tracing::info!("SRK name: {}", srk_name_hex); + // Compute SHA256 hash of the public area let mut hasher = Sha256::new(); hasher.update(response.out_public.public_area.serialize()); diff --git a/vm/cvmutil/src/vtpm_sock_server.rs b/vm/cvmutil/src/vtpm_sock_server.rs index b5d4e94c1b..b9f970ee5a 100644 --- a/vm/cvmutil/src/vtpm_sock_server.rs +++ b/vm/cvmutil/src/vtpm_sock_server.rs @@ -8,15 +8,32 @@ use std::io::{BufReader, BufWriter}; use std::net::{TcpListener, TcpStream}; use std::thread; use std::sync::{Arc, Mutex}; +use std::sync::atomic::{AtomicBool, Ordering}; use crate::vtpm_helper::create_tpm_engine_helper; use tpm::tpm_helper::TpmEngineHelper; +/// Setup Ctrl+C signal handler to allow graceful shutdown +fn setup_signal_handler() -> Arc { + let running = Arc::new(AtomicBool::new(true)); + let r = running.clone(); + + ctrlc::set_handler(move || { + tracing::info!("Received Ctrl+C signal, shutting down TPM socket server..."); + r.store(false, Ordering::SeqCst); + }).expect("Error setting Ctrl+C handler"); + + running +} + /// Start a TPM socket server using vTPM blob as backing state pub fn start_tpm_socket_server(vtpm_blob_path: &str, bind_addr: &str) { tracing::info!("Starting TPM socket server using vTPM blob: {}", vtpm_blob_path); tracing::info!("Binding to address: {}", bind_addr); + // Setup signal handler for graceful shutdown + let running = setup_signal_handler(); + // Parse the bind address to extract host and port let (host, data_port) = parse_bind_address(bind_addr); let ctrl_port = data_port + 1; // Control port is typically data_port + 1 @@ -55,6 +72,12 @@ pub fn start_tpm_socket_server(vtpm_blob_path: &str, bind_addr: &str) { let ctrl_listener = TcpListener::bind(&ctrl_addr) .expect(&format!("Failed to bind to control address: {}", ctrl_addr)); + // Set non-blocking mode for graceful shutdown + data_listener.set_nonblocking(true) + .expect("Failed to set data listener to non-blocking"); + ctrl_listener.set_nonblocking(true) + .expect("Failed to set control listener to non-blocking"); + tracing::info!("TPM socket server listening on data port: {}", data_addr); tracing::info!("TPM socket server listening on control port: {}", ctrl_addr); tracing::info!("Use with: export TPM2TOOLS_TCTI=\"mssim:host={},port={}\"", host, data_port); @@ -62,30 +85,48 @@ pub fn start_tpm_socket_server(vtpm_blob_path: &str, bind_addr: &str) { // Start control socket handler in a separate thread let ctrl_tpm_engine = Arc::clone(&tpm_engine); - thread::spawn(move || { - handle_control_socket(ctrl_listener, ctrl_tpm_engine); + let ctrl_running = running.clone(); + let ctrl_handle = thread::spawn(move || { + handle_control_socket(ctrl_listener, ctrl_tpm_engine, ctrl_running); }); // Handle data connections in the main thread - for stream in data_listener.incoming() { - match stream { - Ok(stream) => { + while running.load(Ordering::SeqCst) { + match data_listener.accept() { + Ok((stream, _)) => { let peer_addr = stream.peer_addr().unwrap_or_else(|_| "unknown".parse().unwrap()); tracing::info!("New data connection from: {}", peer_addr); let tpm_engine_clone = Arc::clone(&tpm_engine); let nv_accessor_clone = Arc::clone(&nv_accessor); + let client_running = running.clone(); // Handle each connection in a separate thread thread::spawn(move || { - handle_tpm_data_client(stream, tpm_engine_clone, nv_accessor_clone); + handle_tpm_data_client(stream, tpm_engine_clone, nv_accessor_clone, client_running); }); } + Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { + // Non-blocking accept returned no connection, sleep briefly and continue + thread::sleep(std::time::Duration::from_millis(100)); + continue; + } Err(e) => { - tracing::error!("Failed to accept data connection: {}", e); + if running.load(Ordering::SeqCst) { + tracing::error!("Failed to accept data connection: {}", e); + } } } } + + tracing::info!("Shutting down TPM socket server..."); + + // Wait for control thread to finish + if let Err(e) = ctrl_handle.join() { + tracing::warn!("Error joining control thread: {:?}", e); + } + + tracing::info!("TPM socket server stopped"); } /// Parse bind address like "localhost:2321" into (host, port) @@ -105,29 +146,41 @@ fn parse_bind_address(bind_addr: &str) -> (String, u16) { fn handle_control_socket( listener: TcpListener, tpm_engine: Arc>, + running: Arc, ) { tracing::info!("Control socket handler started"); - for stream in listener.incoming() { - match stream { - Ok(stream) => { + while running.load(Ordering::SeqCst) { + match listener.accept() { + Ok((stream, _)) => { let tpm_engine_clone = Arc::clone(&tpm_engine); + let client_running = running.clone(); thread::spawn(move || { - handle_control_client(stream, tpm_engine_clone); + handle_control_client(stream, tpm_engine_clone, client_running); }); } + Err(ref e) if e.kind() == std::io::ErrorKind::WouldBlock => { + // Non-blocking accept returned no connection, sleep briefly and continue + thread::sleep(std::time::Duration::from_millis(100)); + continue; + } Err(e) => { - tracing::error!("Failed to accept control connection: {}", e); + if running.load(Ordering::SeqCst) { + tracing::error!("Failed to accept control connection: {}", e); + } } } } + + tracing::info!("Control socket handler stopped"); } /// Handle a single control client connection fn handle_control_client( mut stream: TcpStream, tpm_engine: Arc>, + running: Arc, ) { let peer_addr = stream.peer_addr().unwrap_or_else(|_| "unknown".parse().unwrap()); tracing::debug!("Control client connected from: {}", peer_addr); @@ -135,7 +188,11 @@ fn handle_control_client( let mut reader = BufReader::new(&stream); let mut writer = BufWriter::new(&stream); - loop { + // Set read timeout for graceful shutdown + stream.set_read_timeout(Some(std::time::Duration::from_millis(500))) + .unwrap_or_else(|e| tracing::warn!("Failed to set read timeout: {}", e)); + + while running.load(Ordering::SeqCst) { match read_control_command(&mut reader) { Ok(command) => { tracing::debug!("Received control command: {:?}", command); @@ -150,8 +207,14 @@ fn handle_control_client( break; } } + Err(ref e) if e.kind() == std::io::ErrorKind::TimedOut => { + // Read timeout, continue loop to check running flag + continue; + } Err(e) => { - tracing::debug!("Control client disconnected or read error: {}", e); + if running.load(Ordering::SeqCst) { + tracing::debug!("Control client disconnected or read error: {}", e); + } break; } } @@ -316,6 +379,7 @@ fn handle_tpm_data_client( stream: TcpStream, tpm_engine: Arc>, _nv_accessor: Arc>, + running: Arc, ) { use std::io::Write; let peer_addr = stream.peer_addr().unwrap_or_else(|_| "unknown".parse().unwrap()); @@ -324,12 +388,22 @@ fn handle_tpm_data_client( let mut reader = BufReader::new(&stream); let mut writer = BufWriter::new(&stream); + // Set read timeout for graceful shutdown + stream.set_read_timeout(Some(std::time::Duration::from_millis(500))) + .unwrap_or_else(|e| tracing::warn!("Failed to set read timeout: {}", e)); + let max_cmd = INTERNAL_MAX_CMD; // internal engine limit - loop { + while running.load(Ordering::SeqCst) { let cmd_code = match read_u32(&mut reader) { Ok(v) => v, + Err(ref e) if e.kind() == std::io::ErrorKind::TimedOut => { + // Read timeout, continue loop to check running flag + continue; + } Err(e) => { - tracing::debug!("Client {} disconnected (read cmd): {}", peer_addr, e); + if running.load(Ordering::SeqCst) { + tracing::debug!("Client {} disconnected (read cmd): {}", peer_addr, e); + } break; } }; @@ -403,6 +477,10 @@ fn handle_tpm_data_client( } } + if !running.load(Ordering::SeqCst) { + break; + } + // Trailing status (always 0) after a handled interface command (except unknown/early failure) write_u32(&mut writer, 0); if let Err(e) = writer.flush() { @@ -465,6 +543,7 @@ fn process_tpm_command( command_buffer[..command.len()].copy_from_slice(command); tracing::trace!("Executing TPM command with engine..."); + tracing::trace!("Command (hex): {:02x?}", &command_buffer[..command.len()]); // Submit the command to the TPM engine let result = vtpm_engine_helper.tpm_engine.execute_command( @@ -479,7 +558,17 @@ fn process_tpm_command( if response_size == 0 { return Err("TPM returned zero-length response".into()); } + + if response_size < 10 { + return Err("TPM returned fatal response".into()); + } + // response code are in bytes 6-9 of the response + let response_code = u32::from_be_bytes( + vtpm_engine_helper.reply_buffer[6..10].try_into().unwrap(), + ); + tracing::debug!("TPM response code: 0x{:08x}", response_code); + if response_size > 4096 { return Err(format!("TPM response too large: {}", response_size).into()); } diff --git a/vm/devices/tpm/src/tpm20proto.rs b/vm/devices/tpm/src/tpm20proto.rs index 61858c5bad..e2b77de905 100644 --- a/vm/devices/tpm/src/tpm20proto.rs +++ b/vm/devices/tpm/src/tpm20proto.rs @@ -973,9 +973,36 @@ pub mod protocol { start = end; end += size as usize; + + // COMPATIBILITY FIX: Handle Windows/Canonical TPM format compatibility + // Some TPM implementations (Windows/Microsoft TPM, Canonical's Go code) + // may create TPM2B structures with slight size field differences. + // If the exact size doesn't match, try to handle common compatibility cases. if bytes.len() < end { + // Check if this might be a Windows/Canonical compatibility issue + // Common issue: size field includes/excludes padding or structure overhead + if bytes.len() >= start && (bytes.len() - start) <= MAX_DIGEST_BUFFER_SIZE { + let actual_data_size = bytes.len() - start; + tracing::debug!( + "TPM2B_PRIVATE compatibility: expected {} bytes, got {} bytes, using actual size {}", + size, bytes.len() - start, actual_data_size + ); + + let mut buffer = [0u8; MAX_DIGEST_BUFFER_SIZE]; + buffer[..actual_data_size].copy_from_slice(&bytes[start..]); + + return Some(Self { + size: (actual_data_size as u16).into(), + buffer, + }); + } return None; } + + // if bytes.len() < end { + // return None; + // } + let mut buffer = [0u8; MAX_DIGEST_BUFFER_SIZE]; buffer[..size as usize].copy_from_slice(&bytes[start..end]); From 1bc19ee835731330cebea361caefdde0a2b8c0db Mon Sep 17 00:00:00 2001 From: Fikret Can Date: Fri, 10 Oct 2025 00:44:21 -0700 Subject: [PATCH 14/16] VMGS provenance: update vmgstool for new file ids (Seed and ProvenanceDoc) --- vm/vmgs/vmgs_format/src/lib.rs | 3 +++ 1 file changed, 3 insertions(+) diff --git a/vm/vmgs/vmgs_format/src/lib.rs b/vm/vmgs/vmgs_format/src/lib.rs index 6959f77061..add2fb45eb 100644 --- a/vm/vmgs/vmgs_format/src/lib.rs +++ b/vm/vmgs/vmgs_format/src/lib.rs @@ -46,6 +46,9 @@ open_enum! { GUEST_WATCHDOG = 10, HW_KEY_PROTECTOR = 11, GUEST_SECRET_KEY = 13, + FILE_ID_HIBERNATION_FIRMWARE = 14, + FILE_ID_PLATFORM_SEED = 15, + FILE_ID_PROVENANCE_DOC = 16, EXTENDED_FILE_TABLE = 63, } From 7afdd720d2a7f90cfc5652059877b917348240c0 Mon Sep 17 00:00:00 2001 From: Fikret Can Date: Fri, 6 Mar 2026 16:45:52 -0800 Subject: [PATCH 15/16] Fix tpm reference to tpm_lib --- Cargo.lock | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4cfdce1a47..f9e2fa69b6 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -1010,12 +1010,12 @@ dependencies = [ "openssl", "sha2", "tempfile", - "tpm", + "tpm_lib", "tpm_resources", "tracing", "tracing-subscriber", "tracing_helpers", - "zerocopy 0.8.25", + "zerocopy", ] [[package]] From 2007cadfce5879e92d0eb6dc46e07765974ed0b2 Mon Sep 17 00:00:00 2001 From: Fikret Can Date: Fri, 6 Mar 2026 18:27:18 -0800 Subject: [PATCH 16/16] Fix tpm reference to tpm_lib --- vm/cvmutil/Cargo.toml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/vm/cvmutil/Cargo.toml b/vm/cvmutil/Cargo.toml index 4ea0ac8ecd..f7c83d60cd 100644 --- a/vm/cvmutil/Cargo.toml +++ b/vm/cvmutil/Cargo.toml @@ -15,7 +15,7 @@ getrandom.workspace = true tracing.workspace = true tracing_helpers.workspace = true tracing-subscriber.workspace = true -tpm = { workspace = true, features = ["tpm"] } +tpm_lib.workspace = true tpm_resources.workspace = true zerocopy.workspace = true clap = { workspace = true, features = ["derive"] }