Add CvmUtil support for CPSinTEE provisioning and vTPM-backed disk-unlock workflows. - #4475
Fikret Can (canfikret) wants to merge 23 commits into
Conversation
…TPM simulator for online unsealing
There was a problem hiding this comment.
🟡 Changes recommended
Critical sealing, key export/import, serialization, and simulator correctness issues remain unresolved.
Get a fresh assessment by requesting another Copilot review.
Pull request overview
Adds cvmutil support for vTPM provisioning, sealed-key workflows, TPM import/export, and socket-based TPM simulation.
Changes:
- Extends TPM protocol and library helpers.
- Adds sealing, unsealing, SRK, and import/export workflows.
- Adds persistent vTPM socket simulator support.
- Registers the new workspace package and dependencies.
File summaries
| File | Description |
|---|---|
vm/devices/tpm/tpm_protocol/src/tpm20proto.rs |
TPM compatibility parsing and structure updates |
vm/devices/tpm/tpm_protocol/Cargo.toml |
Adds tracing dependency |
vm/devices/tpm/tpm_lib/src/lib.rs |
TPM helpers, SRK templates, and tests |
vm/devices/tpm/tpm_device/src/lib.rs |
TPM handle and index constants |
vm/cvmutil/src/vtpm_sock_server.rs |
Socket-based TPM simulator |
vm/cvmutil/src/vtpm_helper.rs |
vTPM engine and state callbacks |
vm/cvmutil/src/marshal.rs |
Sealed-key and AF-split serialization |
vm/cvmutil/src/main.rs |
CLI workflows and TPM operations |
vm/cvmutil/Cargo.toml |
New utility package dependencies |
Cargo.toml |
Workspace membership |
Cargo.lock |
Dependency lock updates |
Review details
Suppressed comments (5)
vm/cvmutil/Cargo.toml:7
- The newly added
cvmutilexposes multiple user-facing provisioning, sealing, import/export, and socket-server commands, but there is no corresponding Guide developer-tool page or SUMMARY/mapping entry. This leaves the documented command contract and security-sensitive workflow out of the repository documentation.
[package]
name = "cvmutil"
edition.workspace = true
rust-version.workspace = true
vm/cvmutil/src/main.rs:35
- The new
cvmutilcommand-line tool introduces multiple user-facing commands, but it has no corresponding Guide documentation or entry. Please add a developer-tool/reference page covering the flags, argument order, blob formats, and socket ports, and link it fromGuide/src/SUMMARY.md; this is needed to keep the user-facing interface discoverable and synchronized.
#[derive(Parser, Debug)]
#[clap(name = "cvmutil", about = "Tool to interact with vTPM blobs.")]
vm/cvmutil/src/vtpm_sock_server.rs:315
- The
Stopcommand only returns success and breaks the control-client loop; it never changesrunning, so the server and data listener continue accepting connections. A simulator client requesting Stop cannot actually stop this server.
ControlCommand::Stop => {
tracing::info!("TPM Stop requested");
vec![0x00, 0x00, 0x00, 0x00] // Success
}
vm/cvmutil/src/vtpm_sock_server.rs:46
- The documented bind address accepts port 65535, but adding one for the control listener overflows
u16and panics. Reject the maximum data port (or choose a separately validated control port) before performing this addition.
// 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);
vm/cvmutil/src/vtpm_sock_server.rs:355
- The TPM simulator acknowledges
MS_SIM_NV_ONwith success without changing any TPM state. Clients use this control command to enable NV access before issuing commands; reporting success while leaving NV disabled can make subsequent provisioning fail or behave differently from the advertised simulator protocol. Implement the state transition or return an unsupported/error response.
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
- Files reviewed: 10/11 changed files
- Comments generated: 14
- Review effort level: Lite
💡 Add a code-review agent skill or configure MCP servers for context-aware, tailored reviews. Learn more in the docs.
| /// 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; |
There was a problem hiding this comment.
this command is for testing only.
| // 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"); |
| // Create a TPM2B_PRIVATE structure | ||
| // 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(); |
| // 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, |
| // Copyright (c) Microsoft Corporation. | ||
| // Licensed under the MIT License. | ||
|
|
||
| //! The module includes the CvmUtil, which is a tool to create and manage vTPM blobs. |
There was a problem hiding this comment.
| //! The module includes the CvmUtil, which is a tool to create and manage vTPM blobs. | |
| //! CvmUtil is a tool to create and manage vTPM blobs. |
There was a problem hiding this comment.
We probably want more explanation here on what these blobs contain and how they're used.
There was a problem hiding this comment.
Also probably a better name. Is there anything about this that's really CVM specific? Could it just be tpm_util?
There was a problem hiding this comment.
The name is historically cvmutil and currently CPS expects that. I'm open to rename it.
There was a problem hiding this comment.
Might rename to vtpm_util
|
Lets also make sure #4481 gets merged first, and this PR can be rebased on top of it and add a guide page. |
Changes
Validation
cargo clippy --all-targets -p cvmutilcargo doc --no-deps -p cvmutilcargo test -p cvmutilcargo xtask fmt --fix