Skip to content

Commit 9af3f2c

Browse files
committed
kernel/svsm: load vTPM wrapped key and decrypt state file
Update SVSM guest to read the first sector from the virtio block device to extract the 256-byte wrapped key (wrapped with HSM's public key) from the "SVSMvTPM" header at offset 64. Modify the SVSM attestation driver interface to accept and send this wrap key payload directly to aproxy within the attestation request. Update vTPM state loading process to start reading the encrypted state payload from offset 320. If the guest's block device does not contain a valid SVSMvTPM magic header or wrapped key, abort the initialization to prevent silent boot failures. This is a PoC implementation commit, and is intended to be replaced with the cocoon-fs implementation at a later stage. Signed-off-by: Arun Menon <armenon@redhat.com>
1 parent 6cfe74e commit 9af3f2c

2 files changed

Lines changed: 170 additions & 12 deletions

File tree

kernel/src/attest.rs

Lines changed: 12 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -31,6 +31,7 @@ use cocoon_tpm_utils_common::{
3131
io_slices::{self, IoSlicesIterCommon as _},
3232
};
3333
use kbs_types::Tee;
34+
pub use libaproxy::SecretRequest;
3435
use libaproxy::*;
3536
use serde::Serialize;
3637
use zerocopy::{FromBytes, IntoBytes};
@@ -141,10 +142,13 @@ impl AttestationDriver<'_> {
141142
}
142143

143144
/// Attest SVSM's launch state by communicating with the attestation proxy.
144-
pub fn attest(&mut self) -> Result<SecretSlice, SvsmError> {
145+
pub fn attest(
146+
&mut self,
147+
secret_request: Option<SecretRequest>,
148+
) -> Result<SecretSlice, SvsmError> {
145149
let negotiation = self.negotiation()?;
146150

147-
Ok(self.attestation(negotiation)?)
151+
Ok(self.attestation(negotiation, secret_request)?)
148152
}
149153

150154
/// Send a negotiation request to the proxy. Proxy should reply with Negotiation parameters
@@ -168,7 +172,11 @@ impl AttestationDriver<'_> {
168172
/// Send an attestation request to the proxy. Proxy should reply with attestation response
169173
/// containing the status (success/fail) and an optional secret returned from the server upon
170174
/// successful attestation.
171-
fn attestation(&mut self, n: NegotiationResponse) -> Result<SecretSlice, AttestationError> {
175+
fn attestation(
176+
&mut self,
177+
n: NegotiationResponse,
178+
secret_request: Option<SecretRequest>,
179+
) -> Result<SecretSlice, AttestationError> {
172180
let pub_key = self.get_tpm_pub_key()?;
173181

174182
let evidence = evidence(&self.tee, prepare_report_data(&n)?)?;
@@ -178,7 +186,7 @@ impl AttestationDriver<'_> {
178186
evidence,
179187
challenge: n.challenge.clone(),
180188
key: (self.ecc.pub_key().get_curve_id(), &pub_key).into(),
181-
secret_request: None,
189+
secret_request,
182190
};
183191

184192
self.write(req)?;

kernel/src/svsm.rs

Lines changed: 158 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ use core::panic::PanicInfo;
1616
use core::ptr::NonNull;
1717
use svsm::address::{Address, PhysAddr, VirtAddr};
1818
#[cfg(feature = "attest")]
19-
use svsm::attest::AttestationDriver;
19+
use svsm::attest::{AttestationDriver, SecretRequest};
2020
use svsm::boot_params::BootParamBox;
2121
use svsm::boot_params::BootParams;
2222
use svsm::console::install_console_logger;
@@ -77,6 +77,7 @@ use svsm::virtio::probe_mmio_slots;
7777
use svsm::vtpm::vtpm_init;
7878

7979
use alloc::string::String;
80+
use alloc::vec::Vec;
8081
use release::COCONUT_VERSION;
8182

8283
#[cfg(feature = "attest")]
@@ -466,6 +467,133 @@ fn free_init_bsp_stack() {
466467
free_multiple_pages(stack_base, stack_pages);
467468
}
468469

470+
fn load_vtpm_wrapped_key() -> Option<Vec<u8>> {
471+
use alloc::vec;
472+
use svsm::block::BLOCK_DEVICE;
473+
use virtio_drivers::device::blk::SECTOR_SIZE;
474+
475+
let blk_guard = BLOCK_DEVICE.try_get_inner().ok()?;
476+
log::info!("SVSM: Loading wrapped KEK from block device");
477+
478+
let disk_size = blk_guard.size();
479+
if disk_size < SECTOR_SIZE {
480+
return None;
481+
}
482+
483+
let mut header_buf = vec![0u8; SECTOR_SIZE];
484+
if let Err(e) = blk_guard.read_blocks(0, &mut header_buf) {
485+
log::error!("Failed to read header block for wrapped key: {e:?}");
486+
return None;
487+
}
488+
489+
if &header_buf[0..8] != b"SVSMvTPM" {
490+
log::info!("SVSM: block device does not contain a valid SVSMvTPM header");
491+
return None;
492+
}
493+
494+
// Read the wrapped key from sector 0
495+
let wrapped_key_offset = 64;
496+
let wrapped_key_len = 256;
497+
let wrapped_key = header_buf[wrapped_key_offset..wrapped_key_offset + wrapped_key_len].to_vec();
498+
499+
log::info!(
500+
"SVSM: Successfully loaded wrapped KEK from block device (len={})",
501+
wrapped_key.len()
502+
);
503+
Some(wrapped_key)
504+
}
505+
506+
fn load_vtpm_state_poc(kbs_key: &[u8]) -> Option<Vec<u8>> {
507+
use aes_gcm::{AeadInPlace, Aes256Gcm, KeyInit, Nonce, aead::generic_array::GenericArray};
508+
use alloc::vec;
509+
use svsm::block::BLOCK_DEVICE;
510+
use virtio_drivers::device::blk::SECTOR_SIZE;
511+
512+
let blk_guard = BLOCK_DEVICE.try_get_inner().ok()?;
513+
log::info!("Loading encrypted vTPM state from block device...");
514+
let disk_size = blk_guard.size();
515+
if disk_size < SECTOR_SIZE {
516+
return None;
517+
}
518+
519+
let mut header_buf = vec![0u8; SECTOR_SIZE];
520+
if let Err(e) = blk_guard.read_blocks(0, &mut header_buf) {
521+
log::error!("Failed to read header block: {e:?}");
522+
return None;
523+
}
524+
525+
if &header_buf[0..8] != b"SVSMvTPM" {
526+
log::info!("Block device does not contain a valid SVSMvTPM header");
527+
return None;
528+
}
529+
530+
let version = u16::from_le_bytes(header_buf[8..10].try_into().unwrap());
531+
let cipher_id = u16::from_le_bytes(header_buf[10..12].try_into().unwrap());
532+
let payload_size = u32::from_le_bytes(header_buf[12..16].try_into().unwrap()) as usize;
533+
534+
let mut iv = [0u8; 12];
535+
iv.copy_from_slice(&header_buf[16..28]);
536+
537+
let mut tag = [0u8; 16];
538+
tag.copy_from_slice(&header_buf[28..44]);
539+
540+
log::info!(
541+
"Found vTPM header: v{}, cipher={}, size={}, secretlen={}",
542+
version,
543+
cipher_id,
544+
payload_size,
545+
kbs_key.len()
546+
);
547+
548+
if cipher_id != 1 || kbs_key.len() != 32 {
549+
log::error!(
550+
"Unsupported cipher ID ({}) or invalid kbs key length (expected 32, got {})",
551+
cipher_id,
552+
kbs_key.len()
553+
);
554+
}
555+
556+
let payload_offset = 320; // After 64 header + 256 wrapped key
557+
let total_bytes_to_read = payload_offset + payload_size;
558+
559+
if disk_size < total_bytes_to_read {
560+
log::error!("Disk size smaller than declared payload");
561+
}
562+
563+
// Read the entire payload (rounding up to sector size)
564+
let total_sectors = total_bytes_to_read.div_ceil(SECTOR_SIZE);
565+
let mut disk_buf = vec![0u8; total_sectors * SECTOR_SIZE];
566+
567+
for pos in 0..total_sectors {
568+
let start = pos * SECTOR_SIZE;
569+
let end = start + SECTOR_SIZE;
570+
if let Err(e) = blk_guard.read_blocks(pos, &mut disk_buf[start..end]) {
571+
log::error!("Failed to read payload block {pos}: {e:?}");
572+
return None;
573+
}
574+
}
575+
576+
let ciphertext = &mut disk_buf[payload_offset..payload_offset + payload_size];
577+
let cipher = Aes256Gcm::new(GenericArray::from_slice(kbs_key));
578+
match cipher.decrypt_in_place_detached(
579+
Nonce::from_slice(&iv),
580+
b"",
581+
ciphertext,
582+
GenericArray::from_slice(&tag),
583+
) {
584+
Ok(_) => {
585+
log::info!("Successfully decrypted vTPM state");
586+
let mut final_state = vec![0u8; payload_size];
587+
final_state.copy_from_slice(ciphertext);
588+
Some(final_state)
589+
}
590+
Err(e) => {
591+
log::error!("Failed to decrypt vTPM state: {e:?}");
592+
None
593+
}
594+
}
595+
}
596+
469597
fn svsm_init(launch_info: &KernelLaunchInfo) {
470598
// If required, the GDB stub can be started earlier, just after the console
471599
// is initialised in svsm_start() above.
@@ -547,16 +675,38 @@ fn svsm_init(launch_info: &KernelLaunchInfo) {
547675
initialize_virtio_mmio(&boot_params).expect("Failed to initialize virtio-mmio drivers");
548676

549677
#[cfg(feature = "attest")]
550-
{
551-
let mut proxy = AttestationDriver::try_from(Tee::Snp).unwrap();
552-
let _data = proxy.attest().unwrap();
678+
let kbs_secret = {
679+
let wrapped_key = load_vtpm_wrapped_key().expect(
680+
"SVSM: Cryptographic header or wrapped key is missing from block device! Boot aborted",
681+
);
553682

554-
// Nothing to do with data at the moment, simply print a success message.
555-
log::info!("attestation successful");
556-
}
683+
let mut proxy =
684+
AttestationDriver::try_from(Tee::Snp).expect("Failed to initialize Attestation Driver");
685+
686+
log::info!("SVSM: Initiating HSM wrap-key attestation");
687+
let secret_request = SecretRequest::Pkcs11Unwrap { wrapped_key };
688+
let decryption_key = proxy
689+
.attest(Some(secret_request))
690+
.expect("Attestation failed: could not retrieve key from KBS HSM");
691+
692+
Some(decryption_key)
693+
};
694+
695+
#[cfg(not(feature = "attest"))]
696+
let kbs_secret: Option<Vec<u8>> = None;
557697

558698
#[cfg(all(feature = "vtpm", not(test)))]
559-
vtpm_init(None, None).expect("vTPM failed to initialize");
699+
{
700+
let mut vtpm_state: Option<Vec<u8>> = None;
701+
702+
#[cfg(all(feature = "attest", feature = "block"))]
703+
{
704+
if let Some(secret) = &kbs_secret {
705+
vtpm_state = load_vtpm_state_poc(secret);
706+
}
707+
}
708+
vtpm_init(vtpm_state, kbs_secret.as_deref()).expect("vTPM failed to initialize");
709+
}
560710

561711
#[cfg(all(feature = "uefivars", not(test)))]
562712
uefi_mm_protocol_init().expect("uefi mm protocol failed to initialize");

0 commit comments

Comments
 (0)