diff --git a/Cargo.lock b/Cargo.lock index 6804c2e18..62951e649 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -947,6 +947,7 @@ dependencies = [ "log", "packit", "rustc_version", + "sha2", "syscall", "test", "verify_external", diff --git a/Cargo.toml b/Cargo.toml index 1d3a7bf20..deca45202 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -47,7 +47,7 @@ intrusive-collections = "0.9.6" libfuzzer-sys = "0.4" log = "0.4.17" p384 = { version = "0.13.0" } -sha2 = "0.10.8" +sha2 = { version = "0.10.8", default-features = false} uuid = "1.6.1" # Add the derive feature by default because all crates use it. zerocopy = { version = "0.8.2", features = ["alloc", "derive"] } diff --git a/kernel/Cargo.toml b/kernel/Cargo.toml index 33e328032..dcdf712c8 100644 --- a/kernel/Cargo.toml +++ b/kernel/Cargo.toml @@ -35,6 +35,7 @@ log = { workspace = true, features = ["max_level_info", "release_max_level_info" packit.workspace = true libtcgtpm = { workspace = true, optional = true } zerocopy.workspace = true +sha2 = { workspace = true, features = ["force-soft"] } builtin = { workspace = true, optional = true } builtin_macros = { workspace = true } diff --git a/kernel/src/protocols/attestation.rs b/kernel/src/protocols/attestation.rs new file mode 100644 index 000000000..1e99e9e6d --- /dev/null +++ b/kernel/src/protocols/attestation.rs @@ -0,0 +1,356 @@ +// SPDX-License-Identifier: MIT +// +// Copyright (c) 2024 Hewlett Packard Enterprise Development LP +// +// Author: Geoffrey Ndu (gtn@hpe.com) + +//! Attestation protocol implementation + +extern crate alloc; + +use crate::protocols::{errors::SvsmReqError, RequestParams}; +use crate::{ + address::{Address, PhysAddr}, + mm::{valid_phys_address, PerCPUPageMappingGuard}, + types::PAGE_SIZE, +}; +#[cfg(all(feature = "vtpm", not(test)))] +use crate::{ + greq::{ + pld_report::{SnpReportResponse, USER_DATA_SIZE}, + services::get_regular_report, + }, + vtpm::vtpm_get_ekpub, +}; +use alloc::vec::Vec; +#[cfg(all(feature = "vtpm", not(test)))] +use core::slice::from_raw_parts_mut; +use core::{mem::size_of, slice::from_raw_parts}; +#[cfg(all(feature = "vtpm", not(test)))] +use sha2::{Digest, Sha512}; +const SVSM_ATTEST_SERVICES: u32 = 0; +const SVSM_ATTEST_SINGLE_SERVICE: u32 = 1; + +#[cfg(all(feature = "vtpm", not(test)))] +const SVSM_ATTEST_VTPM_GUID: u128 = u128::from_le_bytes([ + 0xeb, 0xf1, 0x76, 0xc4, 0x23, 0x01, 0xa5, 0x45, 0x96, 0x41, 0xb4, 0xe7, 0xdd, 0xe5, 0xbf, 0xe3, +]); + +// Attest Single Service Operation structure, as defined in +// Table 13 of Secure VM Service Module for SEV-SNP Guests +// 58019 Rev. 1.00 July 2023 +#[repr(C, packed)] +#[derive(Clone, Copy, Debug)] +pub struct AttestSingleServiceOp { + report_gpa: u64, + report_size: u32, + reserved_1: [u8; 4], + nonce_gpa: u64, + nonce_size: u16, + reserved_2: [u8; 6], + manifest_gpa: u64, + manifest_size: u32, + reserved_3: [u8; 4], + certificate_gpa: u64, + certificate_size: u32, + reserved_4: [u8; 4], + guid: [u8; 16], + manifest_ver: u32, + reserved_5: [u8; 4], +} + +impl AttestSingleServiceOp { + /// Take a slice and return a reference for Self + pub fn try_from_as_ref(buffer: &[u8]) -> Result<&Self, SvsmReqError> { + let buffer = buffer + .get(..size_of::()) + .ok_or_else(SvsmReqError::invalid_parameter)?; + + // SAFETY: AttestSingleServiceOp has no invalid representations, as it is + // comprised entirely of integer types. It is repr(packed), so its + // required alignment is simply 1. We have checked the size, so this + // is entirely safe. + let ops = unsafe { &*buffer.as_ptr().cast::() }; + + if !ops.is_reserved_clear() || !ops.is_manifest_version_valid() { + return Err(SvsmReqError::invalid_parameter()); + } + + Ok(ops) + } + /// Checks if reserved fields are all set to zero + pub fn is_reserved_clear(&self) -> bool { + self.reserved_1.iter().all(|&x| x == 0) + && self.reserved_2.iter().all(|&x| x == 0) + && self.reserved_3.iter().all(|&x| x == 0) + && self.reserved_4.iter().all(|&x| x == 0) + && self.reserved_5.iter().all(|&x| x == 0) + } + + /// Returns the nonce + pub fn get_nonce(&self) -> Result, SvsmReqError> { + let (gpa, size) = self.get_nonce_gpa_and_size()?; + // get_nonce_gpa_and_size() already validated that gpa is page + // aligned, valid and does not cross page boundary. + let start = gpa.page_align(); + let offset = gpa.page_offset(); + + let guard = PerCPUPageMappingGuard::create_4k(start)?; + let vaddr = guard.virt_addr() + offset; + + // Check that the nonce length is not greater than 4k. + // If it is, return an error as something is wrong with the request + if size > PAGE_SIZE { + return Err(SvsmReqError::invalid_parameter()); + } + + // SAFETY: vaddr points to a new mapped page region. And get_nonce_gpa_and_size() already + // validated that gpa is page aligned, valid and does not cross. We also checked earlier + // that size is not greater than PAGE_SIZE, so we can safely read the nonce. + let buffer = unsafe { from_raw_parts(vaddr.as_mut_ptr::(), size) }; + let nonce = buffer.to_vec(); + + Ok(nonce) + } + + /// Returns the nonce GPA and size + /// Checks if gpa is page aligned, valid and does not cross page boundary. + pub fn get_nonce_gpa_and_size(&self) -> Result<(PhysAddr, usize), SvsmReqError> { + let gpa = PhysAddr::from(self.nonce_gpa); + if !gpa.is_aligned(8) || !valid_phys_address(gpa) || gpa.crosses_page(8) { + return Err(SvsmReqError::invalid_parameter()); + } + + //cast won't panic on amd64 as usize > u32 always + let size = self.nonce_size as usize; + + Ok((gpa, size)) + } + + /// Returns the manifest GPA and size + /// Checks if gpa is page aligned, valid and does not cross page boundary. + pub fn get_manifest_gpa_and_size(&self) -> Result<(PhysAddr, usize), SvsmReqError> { + let gpa = PhysAddr::from(self.manifest_gpa); + if !gpa.is_aligned(8) || !valid_phys_address(gpa) || gpa.crosses_page(8) { + return Err(SvsmReqError::invalid_parameter()); + } + + // Won't panic on amd64 as usize > u32 always + let size = self.manifest_size as usize; + + Ok((gpa, size)) + } + + /// Returns the guid + /// Checks if gpa is page aligned, valid and does not cross page boundary + pub fn get_report_gpa_and_size(&self) -> Result<(PhysAddr, usize), SvsmReqError> { + let gpa = PhysAddr::from(self.report_gpa); + if !gpa.is_aligned(8) || !valid_phys_address(gpa) || gpa.crosses_page(8) { + return Err(SvsmReqError::invalid_parameter()); + } + + // Won't panic on amd64 as usize > u32 always + let size = self.report_size as usize; + + Ok((gpa, size)) + } + + /// Returns the certificate GPA and size + /// Checks if gpa is page aligned, valid and does not cross page boundary + /// pub fn get_report_gpa_and_size(&self) -> Result<(PhysAddr, usize), SvsmReqError> { + /// Currently not implemented supported as no service attestation currently supports + /// returning certificates. Implement when needed by copying get_report_gpa_and_size() + /// and replacing report's gpa and size with certificate's. + /// } + + pub fn get_manifest_version(&self) -> u32 { + self.manifest_ver + } + + fn is_manifest_version_valid(&self) -> bool { + //Currently only manifest version 0 is supported + self.manifest_ver == 0 + } + pub fn get_guid(&self) -> u128 { + u128::from_le_bytes(self.guid) + } +} + +#[cfg(all(feature = "vtpm", not(test)))] +fn get_attestation_report(nonce: &[u8]) -> Result, SvsmReqError> { + //Construct attestation request message to send to SNP + let mut report_req = Vec::::with_capacity(size_of::()); + let mut buf = Vec::::with_capacity(USER_DATA_SIZE); + buf.fill(0); + + if nonce.len() > USER_DATA_SIZE { + // If the nonce is greater than the user data size, return an error as something is wrong. + return Err(SvsmReqError::invalid_parameter()); + } + // Copy user attestation request nonce to buffer + buf.extend_from_slice(nonce); + report_req.extend_from_slice(&buf[..nonce.len()]); + + // Set request VMPL to 0 + report_req.extend_from_slice(&0_u32.to_le_bytes()); + + // Set reserved bytes to zeros + report_req.extend_from_slice(&[0; 28]); + + // Make sure buffer is big enough to hold the report + report_req.resize(size_of::(), 0); + + //send request to snp + let response_size = get_regular_report(report_req.as_mut_slice()).unwrap(); + + // Per Table 24 of "SEV Secure Nested Paging Firmware ABI Specification, Revision 1.56", + // attestation report starts at byte offset 0x20 + if response_size < 0x20 { + // If the response size is less than 0x20, return an error as something is wrong with + // the report or SNP + return Err(SvsmReqError::unsupported_protocol()); + } + report_req.drain(0..0x20); + + Ok(report_req) +} + +#[cfg(all(feature = "vtpm", not(test)))] +fn attest_single_vtpm( + params: &mut RequestParams, + ops: &AttestSingleServiceOp, +) -> Result<(), SvsmReqError> { + let nonce = ops.get_nonce()?; + + // Get the cached EKpub from the VTPM. Returns an error if the EKpub is not cached. + let manifest = vtpm_get_ekpub()?; + + // Concatenate nonce and manifest and hash per page 29 of + // "Secure VM Service Module for SEV-SNP Guests 58019 Rev. 1.00". + let nonce_and_manifest = [&nonce[..], &manifest[..]].concat(); + let hash = Sha512::digest(&nonce_and_manifest); + + // Get attestation report from PSP with Sha512(nonce||manifest) as REPORT_DATA. + let report = get_attestation_report(hash.as_slice())?; + + // Get attestation report buffer's gPA from call's Attest Single Service Operation structure + let (report_gpa, _) = ops.get_report_gpa_and_size()?; + let report_start = report_gpa.page_align(); + let report_offset = report_gpa.page_offset(); + + let report_guard = PerCPUPageMappingGuard::create_4k(report_start)?; + let report_vaddr = report_guard.virt_addr() + report_offset; + + // Check that the attestation report length is not greater than 4k. + // If it is, return an error as something is wrong with the report or SNP + // Per page 32 of "Secure VM Service Module for SEV-SNP Guests 58019 Rev. 1.00", + // return 0x8000_1000 i.e. SVSM::UNSUPPORTED_PROTOCOL. + if report.len() > PAGE_SIZE { + log::error!("Malformed VTPM service attestation report"); + return Err(SvsmReqError::unsupported_protocol()); + } + + // SAFETY: report_vaddr points to a new mapped region of size PAGE_SIZE. report_gpa is obtained + // from a guest-provided physical address (untrusted), so it needs to be validated that it + // belongs to the guest and only the guest. That was done inside get_report_gpa_and_size(). + // get_report_gpa_and_size() also validated that report_gpa is page aligned and does not cross + // a page boundary. + // Since we also checked that report.len() is not greater than PAGE_SIZE, we can safely write + // the report to guest_report_buffer. + let guest_report_buffer = + unsafe { from_raw_parts_mut(report_vaddr.as_mut_ptr::(), PAGE_SIZE) }; + guest_report_buffer[..report.len()].copy_from_slice(&report); + + // Set report size in bytes in r8 register + params.r8 = report.len() as u64; + + // Get manifest buffer's GPA from call's Attest Single Service Operation structure + let (manifest_gpa, _) = ops.get_manifest_gpa_and_size()?; + let manifest_start = manifest_gpa.page_align(); + let manifest_offset = manifest_gpa.page_offset(); + + let manifest_guard = PerCPUPageMappingGuard::create_4k(manifest_start)?; + let manifest_vaddr = manifest_guard.virt_addr() + manifest_offset; + + // Check that the length of the manifest is not greater than 4k. + // If it is, return an error as something is wrong with the manifest or vTPM + // Per page 32 of Secure VM Service Module for SEV-SNP Guests 58019 Rev. 1.00 July 2023 + // return SVSM::UNSUPPORTED_PROTOCOL (i.e., 0x8000_1000). + if manifest.len() > PAGE_SIZE { + log::error!("Malformed VTPM service attestation manifest"); + return Err(SvsmReqError::unsupported_protocol()); + } + + // SAFETY: manifest_vaddr points to a new mapped region of size PAGE_SIZE. report_gpa is obtained + // from a guest-provided physical address (untrusted), so it needs to be validated that it + // belongs to the guest and only the guest. That was done inside get_manifest_gpa_and_size(). + // get_manifest_gpa_and_size() also validated that manifest_gpa is page aligned and does not cross + // a page boundary. + // Since we also checked that manifest.len() is not greater than PAGE_SIZE, we can safely write + // the report to guest_manifest_buffer. + let guest_manifest_buffer = + unsafe { from_raw_parts_mut(manifest_vaddr.as_mut_ptr::(), PAGE_SIZE) }; + guest_manifest_buffer[..manifest.len()].copy_from_slice(&manifest); + + // Set the manifest size in bytes in rcx register + params.rcx = manifest.len() as u64; + + // Does not support certificate currently, so setting certificate size to 0 + params.rdx = 0; + + Ok(()) +} + +fn attest_multiple_service(_params: &RequestParams) -> Result<(), SvsmReqError> { + Err(SvsmReqError::unsupported_protocol()) +} + +#[allow(clippy::needless_pass_by_ref_mut)] +fn attest_single_service_handler(params: &mut RequestParams) -> Result<(), SvsmReqError> { + // Get the gpa of Attest Single Service Operation structure + let gpa = PhysAddr::from(params.rcx); + + if !gpa.is_aligned(8) || !valid_phys_address(gpa) || gpa.crosses_page(8) { + return Err(SvsmReqError::invalid_parameter()); + } + + let offset = gpa.page_offset(); + let paddr = gpa.page_align(); + + // Map the Attest Single Service Operation structure page + // Per Table 13 of the spec "Secure VM Service Module for SEV-SNP Guests + // 58019 Rev. 1.00", we only need the first 0x58 bytes. + let guard = PerCPUPageMappingGuard::create_4k(paddr)?; + let vaddr = guard.virt_addr() + offset; + + // SAFETY: The untrusted GPA from the guest is validated above as a valid address. + // The guard ensures that the page is newly mapped and not controlled by the guest. + // We only use a portion of the page, less than the full page size. + let buffer = + unsafe { from_raw_parts(vaddr.as_ptr::(), size_of::()) }; + let attest_op = AttestSingleServiceOp::try_from_as_ref(buffer)?; + + // Extract the GUID from the Attest Single Service Operation structure. + // The GUID is used to determine the specific service to be attested. + // Currently, only the VTPM service with the GUID 0xebf176c4_2301a545_9641b4e7_dde5bfe3 + // is supported, see 8.3.1 of the spec "Secure VM Service Module for SEV-SNP Guests + // 58019 Rev. 1.00" for more details. + let guid = attest_op.get_guid(); + + match guid { + #[cfg(all(feature = "vtpm", not(test)))] + SVSM_ATTEST_VTPM_GUID => attest_single_vtpm(params, attest_op), + _ => Err(SvsmReqError::unsupported_protocol()), + } +} + +pub fn attestation_protocol_request( + request: u32, + params: &mut RequestParams, +) -> Result<(), SvsmReqError> { + match request { + SVSM_ATTEST_SERVICES => attest_multiple_service(params), + SVSM_ATTEST_SINGLE_SERVICE => attest_single_service_handler(params), + _ => Err(SvsmReqError::unsupported_protocol()), + } +} diff --git a/kernel/src/protocols/mod.rs b/kernel/src/protocols/mod.rs index ccadb987f..81bba4b43 100644 --- a/kernel/src/protocols/mod.rs +++ b/kernel/src/protocols/mod.rs @@ -5,6 +5,7 @@ // Author: Dov Murik pub mod apic; +pub mod attestation; pub mod core; pub mod errors; #[cfg(all(feature = "vtpm", not(test)))] @@ -14,6 +15,7 @@ use cpuarch::vmsa::{GuestVMExit, VMSA}; // SVSM protocols pub const SVSM_CORE_PROTOCOL: u32 = 0; +pub const SVSM_ATTESTATION_PROTOCOL: u32 = 1; pub const SVSM_VTPM_PROTOCOL: u32 = 2; pub const SVSM_APIC_PROTOCOL: u32 = 3; diff --git a/kernel/src/requests.rs b/kernel/src/requests.rs index 599a40d34..24afa0114 100644 --- a/kernel/src/requests.rs +++ b/kernel/src/requests.rs @@ -13,6 +13,7 @@ use crate::protocols::core::core_protocol_request; use crate::protocols::errors::{SvsmReqError, SvsmResultCode}; use crate::sev::ghcb::switch_to_vmpl; +use crate::protocols::{attestation::attestation_protocol_request, SVSM_ATTESTATION_PROTOCOL}; #[cfg(all(feature = "vtpm", not(test)))] use crate::protocols::{vtpm::vtpm_protocol_request, SVSM_VTPM_PROTOCOL}; use crate::protocols::{RequestParams, SVSM_APIC_PROTOCOL, SVSM_CORE_PROTOCOL}; @@ -108,6 +109,7 @@ fn request_loop_once( match protocol { SVSM_CORE_PROTOCOL => core_protocol_request(request, params).map(|_| true), + SVSM_ATTESTATION_PROTOCOL => attestation_protocol_request(request, params).map(|_| true), #[cfg(all(feature = "vtpm", not(test)))] SVSM_VTPM_PROTOCOL => vtpm_protocol_request(request, params).map(|_| true), SVSM_APIC_PROTOCOL => apic_protocol_request(request, params).map(|_| true), diff --git a/kernel/src/vtpm/mod.rs b/kernel/src/vtpm/mod.rs index a83df1d3b..6019c896b 100644 --- a/kernel/src/vtpm/mod.rs +++ b/kernel/src/vtpm/mod.rs @@ -10,6 +10,9 @@ /// TPM 2.0 Reference Implementation pub mod tcgtpm; +extern crate alloc; +use alloc::vec::Vec; + use crate::vtpm::tcgtpm::TcgTpm as Vtpm; use crate::{locking::LockGuard, protocols::vtpm::TpmPlatformCommand}; use crate::{locking::SpinLock, protocols::errors::SvsmReqError}; @@ -66,6 +69,29 @@ pub trait VtpmInterface: TcgTpmSimulatorInterface { /// Prepare the TPM to be used for the first time. At this stage, /// the TPM is manufactured. fn init(&mut self) -> Result<(), SvsmReqError>; + + /// Create RSA 2048 Endorsement Key (EK) and cache the public key + /// + /// This function creates an RSA 2048-bit Endorsement Key (EK) from the TPM's Endorsement + /// Primary Seed (EPS) and caches the public key as TMPT_PUBLIC structure. The cached EK + /// public key can be retrieved later and used to provide vTPM service attestation. The + /// EK is created with the TCG default EK template as shown in Table 4 of the "TCG EK + /// Credential Profile For TPM Family 2.0; Level 0 Version 2.5 Revision 2.0". + /// + /// Since the EK is created from the EPS, following the TCG EK Credential Profile, the EK can + /// be recreated at any time. For example, one can recreate the same EK in an OS using TSS2 + /// "tpm2_createek -c ek.ctx -G rsa -u ek.pub command". + /// + /// Retrieve the EK public key using the get_ekpub() function. + /// + /// # Warning + /// + /// The function should only be called after the TPM is powered on and initialised + fn create_ek_rsa2048(&mut self) -> Result<(), SvsmReqError>; + + /// Returns the cached EK public key if it exists, otherwise it returns an error indicating + /// that the EK public key does not exist. + fn get_ekpub(&self) -> Result, SvsmReqError>; } static VTPM: SpinLock = SpinLock::new(Vtpm::new()); @@ -84,3 +110,10 @@ pub fn vtpm_init() -> Result<(), SvsmReqError> { pub fn vtpm_get_locked<'a>() -> LockGuard<'a, Vtpm> { VTPM.lock() } + +/// Get the TPM EK public key by calling the get_ekpub() implementation of the +/// [`VtpmInterface`] +pub fn vtpm_get_ekpub() -> Result, SvsmReqError> { + let vtpm = VTPM.lock(); + vtpm.get_ekpub() +} diff --git a/kernel/src/vtpm/tcgtpm/mod.rs b/kernel/src/vtpm/tcgtpm/mod.rs index 70474f46c..63dff30f6 100644 --- a/kernel/src/vtpm/tcgtpm/mod.rs +++ b/kernel/src/vtpm/tcgtpm/mod.rs @@ -14,6 +14,7 @@ mod wrapper; extern crate alloc; use alloc::vec::Vec; + use core::ffi::c_void; use libtcgtpm::bindings::{ TPM_Manufacture, TPM_TearDown, _plat__LocalitySet, _plat__NVDisable, _plat__NVEnable, @@ -27,15 +28,30 @@ use crate::{ vtpm::{TcgTpmSimulatorInterface, VtpmInterface, VtpmProtocolInterface}, }; -#[derive(Debug, Copy, Clone, Default)] +// Definitions from "Trusted Platform Module Library Part 4: Supporting Routines – Code, +// Family “2.0”, Level 00, Revision 01.38" +const TPM_ST_SESSIONS: u16 = 0x8002; +const TPM_CC_CREATEPRIMARY: u32 = 0x00000131; +const TPM_RH_ENDORSEMENT: u32 = 0x4000000B; +const TPM_ALG_RSA: u16 = 0x0001; +const TPM_ALG_SHA256: u16 = 0x000B; +const TPM_ALG_AES: u16 = 0x0006; +const TPM_ALG_CFB: u16 = 0x0043; +const TPM_ALG_NULL: u16 = 0x0010; +const TPM_KEY_BITS_2048: u16 = 2048; +const TPM_RS_PW: u32 = 0x40000009; + +#[derive(Debug, Clone, Default)] pub struct TcgTpm { is_powered_on: bool, + ekpub: Option>, } impl TcgTpm { pub const fn new() -> TcgTpm { TcgTpm { is_powered_on: false, + ekpub: None, } } @@ -84,8 +100,10 @@ impl TcgTpmSimulatorInterface for TcgTpm { locality: u8, ) -> Result<(), SvsmReqError> { if !self.is_powered_on { + log::error!("TPM is not powered on"); return Err(SvsmReqError::invalid_request()); } + if *length > TPM_BUFFER_MAX_SIZE || *length > buffer.len() { return Err(SvsmReqError::invalid_parameter()); } @@ -148,12 +166,137 @@ impl TcgTpmSimulatorInterface for TcgTpm { } impl VtpmInterface for TcgTpm { + fn get_ekpub(&self) -> Result, SvsmReqError> { + self.ekpub.clone().ok_or_else(SvsmReqError::invalid_request) + } + + fn create_ek_rsa2048(&mut self) -> Result<(), SvsmReqError> { + // Creates RSA 2048-bit EK using TPM2_CreatePrimary command and TCG default EK template + // + // TPM2_CreatePrimary command is defined in Table 173 — TPM2_CreatePrimary Command, 365 of + // "Trusted Platform Module Library Part 3: Commands-Codes, Family “2.0”, Level 00, + // Revision 01.38". + // + // The TCG default EK template is defined in "Table 2: Default EK Template (TPMT_PUBLIC) + // L-1: RSA 2048 (Storage)" of "TCG EK Credential Profile For TPM Family 2.0; Level 0 + // Version 2.5 Revision 2". + // + // See also "TCG TSS 2.0 Overview and Common Structures Specification, Version 1.0, + // Level 2 Revision 10". + + let authpolicy: [u8; 32] = [ + 0x83, 0x71, 0x97, 0x67, 0x44, 0x84, 0xb3, 0xf8, 0x1a, 0x90, 0xcc, 0x8d, 0x46, 0xa5, + 0xd7, 0x24, 0xfd, 0x52, 0xd7, 0x6e, 0x06, 0x52, 0x0b, 0x64, 0xf2, 0xa1, 0xda, 0x1b, + 0x33, 0x14, 0x69, 0xaa, + ]; + + let object_attributes: u32 = 0x000300b2; + + // See Table 12 — Password Authorization Command of + // Trusted Platform Module Library Part 1: Architecture, + // Family “2.0”, Level 00, Revision 01.38 + let mut auth_block: Vec = Vec::new(); + auth_block.extend_from_slice(&TPM_RS_PW.to_be_bytes()); + // nonce == empty buffer + auth_block.extend_from_slice(&[0x00, 0x00]); + // session attributes = continueSession = 0x01 + auth_block.extend_from_slice(&[0x01]); + // password = empty buffer + auth_block.extend_from_slice(&[0x00, 0x00]); + + // TPM2B_PUBLIC with TCG default EK template, + // see Table 2: Default EK Template (TPMT_PUBLIC) L-1: RSA 2048 (Storage) + // of TCG EK Credential Profile For TPM Family 2.0; Level 0 Version 2.5 Revision 2 + let mut public_area: Vec = Vec::new(); + // type + public_area.extend_from_slice(&TPM_ALG_RSA.to_be_bytes()); + // nameAlg + public_area.extend_from_slice(&TPM_ALG_SHA256.to_be_bytes()); + // objectAttributes + public_area.extend_from_slice(&object_attributes.to_be_bytes()); + // authPolicy size + public_area.extend_from_slice(&(authpolicy.len() as u16).to_be_bytes()); + // authPolicy + public_area.extend_from_slice(authpolicy.as_slice()); + // parameters + // symmetric algorithm + public_area.extend_from_slice(&TPM_ALG_AES.to_be_bytes()); + // symmetric keyBits + public_area.extend_from_slice(&128_u16.to_be_bytes()); + // symmetric mode + public_area.extend_from_slice(&TPM_ALG_CFB.to_be_bytes()); + + // scheme + public_area.extend_from_slice(&TPM_ALG_NULL.to_be_bytes()); + // keyBits + public_area.extend_from_slice(&TPM_KEY_BITS_2048.to_be_bytes()); + // exponent + public_area.extend_from_slice(&0_u32.to_be_bytes()); + + // unique size + public_area.extend_from_slice(&256_u16.to_be_bytes()); + // unique + public_area.extend_from_slice(&[0x00; 256]); + + let mut cmd = Vec::::with_capacity(TPM_BUFFER_MAX_SIZE); + + // TPM Command header + cmd.extend_from_slice(&TPM_ST_SESSIONS.to_be_bytes()); + // Placeholder for command size + cmd.extend_from_slice(&(0u32).to_be_bytes()); + cmd.extend_from_slice(&TPM_CC_CREATEPRIMARY.to_be_bytes()); + cmd.extend_from_slice(&TPM_RH_ENDORSEMENT.to_be_bytes()); + + // Authorization block + cmd.extend_from_slice(&(auth_block.len() as u32).to_be_bytes()); + cmd.extend_from_slice(auth_block.as_slice()); + + // inSensitive parameter + // + // TPM2B_SENSITIVE_CREATE structure is defined in + // Table 132 — Definition of TPM2B_SENSITIVE_CREATE Structure, + // Trusted Platform Module Library Part 2: Structures + // sensitive data size + cmd.extend_from_slice(&4_u16.to_be_bytes()); + // user auth + cmd.extend_from_slice(&[0x00, 0x00, 0x00, 0x00]); + + // inPublic parameter + // parameters size + cmd.extend_from_slice(&(public_area.len() as u16).to_be_bytes()); + // parameters + cmd.extend_from_slice(public_area.as_slice()); + + // outsideInfo parameter + cmd.extend_from_slice(&0_u32.to_be_bytes()); + cmd.extend_from_slice(&0_u16.to_be_bytes()); + + // Update command size + let mut command_size = cmd.len(); + cmd[2..6].copy_from_slice(&(command_size as u32).to_be_bytes()); + + cmd.resize(TPM_BUFFER_MAX_SIZE, 0); + + self.send_tpm_command(&mut cmd[..], &mut command_size, 0)?; + + // Check that TPM_RC(UINT32) at byte offset 6 is 0x00000000 (TPM_RC_SUCCESS) + if cmd[6..10] != [0x00, 0x00, 0x00, 0x00] { + return Err(SvsmReqError::incomplete()); + } + + // Get size (UINT16) of TPMT_PUBLIC at offset 18 + let size_of_tpmt_public = u16::from_be_bytes([cmd[18], cmd[19]]); + self.ekpub = Some(cmd[20..(20 + size_of_tpmt_public) as usize].to_vec()); + + Ok(()) + } + fn is_powered_on(&self) -> bool { self.is_powered_on } fn init(&mut self) -> Result<(), SvsmReqError> { - // Initialize the TPM TCG following the same steps done in the Simulator: + // Initialize the TPM TCG following the same steps done in the Simulator and generate EK: // // 1. Manufacture it for the first time // 2. Make sure it does not fail if it is re-manufactured @@ -161,6 +304,13 @@ impl VtpmInterface for TcgTpm { // 4. Manufacture it for the first time // 5. Power it on indicating it requires startup. By default, OVMF will start // and selftest it. + // 6. Selftest it + // 7. Start it up on for next step + // 8. Create RSA2004 EK and cache EKpub for VTPM service attestation requests + // + // Since we have already run TPM2_Startup here, when OVMF runs TPM2_Startup, it will + // get back TPM_RC_INITIALIZE indicating that TPM2_Startup is not required. See, + // https://github.com/tianocore/edk2/blob/master/SecurityPkg/Library/Tpm2CommandLib/Tpm2Startup.c#L75 unsafe { _plat__NVEnable(VirtAddr::null().as_mut_ptr::()) }; @@ -184,6 +334,19 @@ impl VtpmInterface for TcgTpm { self.signal_poweron(false)?; self.signal_nvon()?; + // TPM2_CC_SelfTest + let selftest_cmd: &mut [u8] = &mut [ + 0x80, 0x01, 0x00, 0x00, 0x00, 0x0b, 0x00, 0x00, 0x01, 0x43, 0x00, + ]; + // TPM2_CC_Startup + let startup_cmd: &mut [u8] = &mut [ + 0x80, 0x01, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x01, 0x44, 0x00, 0x00, + ]; + self.send_tpm_command(selftest_cmd, &mut selftest_cmd.len(), 0)?; + self.send_tpm_command(startup_cmd, &mut startup_cmd.len(), 0)?; + + self.create_ek_rsa2048()?; + log::info!("VTPM: TPM 2.0 Reference Implementation initialized"); Ok(())