diff --git a/src/error.rs b/src/error.rs index 374b9501..0bf58ce0 100644 --- a/src/error.rs +++ b/src/error.rs @@ -536,6 +536,9 @@ pub enum UserApiError { /// Invalid VMPL. VmplError, + /// Attestation Report Error + AttestationReportError(AttestationReportError), + /// Unknown error Unknown, } @@ -550,6 +553,7 @@ impl error::Error for UserApiError { Self::VmmError(vmm_error) => Some(vmm_error), Self::HashstickError(hashstick_error) => Some(hashstick_error), Self::VmplError => None, + Self::AttestationReportError(attestation_error) => Some(attestation_error), Self::Unknown => None, } } @@ -565,6 +569,9 @@ impl std::fmt::Display for UserApiError { Self::VmmError(error) => format!("VMM Error Encountered: {error}"), Self::HashstickError(error) => format!("VLEK Hashstick Error Encountered: {error}"), Self::VmplError => "Invalid VM Permission Level (VMPL)".to_string(), + Self::AttestationReportError(error) => { + format!("Attestation Report Error Encountered: {error}") + } Self::Unknown => "Unknown Error Encountered!".to_string(), }; write!(f, "{err_msg}") @@ -619,6 +626,12 @@ impl std::convert::From for UserApiError { } } +impl std::convert::From for UserApiError { + fn from(attestation_error: AttestationReportError) -> Self { + Self::AttestationReportError(attestation_error) + } +} + #[derive(Debug, PartialEq, Eq, PartialOrd, Ord)] /// Errors which may be encountered when handling Version Loaded Endorsement Keys /// (VLEK) Hashsticks. @@ -699,6 +712,37 @@ impl std::fmt::Display for CertError { impl error::Error for CertError {} +#[derive(Debug)] +/// Errors which may be encountered when handling attestation reports +pub enum AttestationReportError { + /// Bincode Error Handling + BincodeError(bincode::ErrorKind), + + /// Unsuported Attestation Report Version + UnsupportedReportVersion(u32), + + /// Field is not supported in the current version of the Attestation Report + UnsupportedField(String), +} + +impl std::fmt::Display for AttestationReportError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + AttestationReportError::BincodeError(e) => write!(f, "Bincode error encountered: {e}"), + AttestationReportError::UnsupportedReportVersion(version) => write!(f, "The encountered Attestation Report version {version} is not supported by the library yet."), + AttestationReportError::UnsupportedField(field) => write!(f,"The field {field} is not supported in the provided Attestation Report version"), + } + } +} + +impl std::convert::From for AttestationReportError { + fn from(value: bincode::ErrorKind) -> Self { + Self::BincodeError(value) + } +} + +impl error::Error for AttestationReportError {} + #[derive(Debug)] /// Errors which may be encountered when building custom guest context. pub enum GCTXError { diff --git a/src/firmware/guest/mod.rs b/src/firmware/guest/mod.rs index 43caa1d7..2a2f21dc 100644 --- a/src/firmware/guest/mod.rs +++ b/src/firmware/guest/mod.rs @@ -21,6 +21,7 @@ use crate::firmware::{ }, }; +use std::convert::TryFrom; #[cfg(target_os = "linux")] use std::fs::{File, OpenOptions}; @@ -107,7 +108,9 @@ impl Firmware { Err(FirmwareError::from(response.status))? } - Ok(response.report) + let raw_report = response.report.as_array(); + + Ok(AttestationReport::try_from(raw_report.as_slice())?) } /// Request an extended attestation report from the AMD Secure Processor. @@ -179,8 +182,12 @@ impl Firmware { Err(FirmwareError::from(report_response.status))? } + let raw_report = report_response.report.as_array(); + + let report = AttestationReport::try_from(raw_report.as_slice())?; + if ext_report_request.certs_len == 0 { - return Ok((report_response.report, None)); + return Ok((report, None)); } let mut certificates: Vec; @@ -194,7 +201,7 @@ impl Firmware { } // Return both the Attestation Report, as well as the Cert Table. - Ok((report_response.report, Some(certificates))) + Ok((report, Some(certificates))) } /// Fetches a derived key from the AMD Secure Processor. The `message_version` will default to `1` if `None` is specified. diff --git a/src/firmware/guest/types/snp.rs b/src/firmware/guest/types/snp.rs index d8cc5a3e..68e64878 100644 --- a/src/firmware/guest/types/snp.rs +++ b/src/firmware/guest/types/snp.rs @@ -1,26 +1,25 @@ // SPDX-License-Identifier: Apache-2.0 +use crate::error::AttestationReportError; use crate::{certs::snp::ecdsa::Signature, firmware::host::TcbVersion, util::hexdump}; #[cfg(any(feature = "openssl", feature = "crypto_nossl"))] use crate::certs::snp::{Certificate, Chain, Verifiable}; -use std::fmt::Display; +use crate::util::sealed; +use bitfield::bitfield; +use serde::{Deserialize, Serialize, Serializer}; +use serde_big_array::BigArray; +use std::convert::{TryFrom, TryInto}; -#[cfg(any(feature = "openssl", feature = "crypto_nossl"))] -use std::{ - convert::TryFrom, - io::{self, Error, ErrorKind}, -}; +use std::fmt::Display; -use bitfield::bitfield; +// #[cfg(any(feature = "openssl", feature = "crypto_nossl"))] +use std::io::{self, Error, ErrorKind}; #[cfg(feature = "openssl")] use openssl::{ecdsa::EcdsaSig, sha::Sha384}; -use serde::{Deserialize, Serialize}; -use serde_big_array::BigArray; - /// Structure of required data for fetching the derived key. #[derive(Copy, Clone, Debug, PartialEq, Eq, PartialOrd, Ord)] pub struct DerivedKey { @@ -103,6 +102,22 @@ bitfield! { pub get_tcb_version, set_tcb_version: 5, 5; } +/// Trait shared between attestation reports to be able to verify them against the VEK. +pub trait Attestable: Serialize + sealed::Sealed { + /// Serialize the provided Attestation Report and get the measurable bytes from it + fn measurable_bytes(&self) -> io::Result> { + let measurable_bytes: &[u8] = &bincode::serialize(self).map_err(|e| { + Error::new( + ErrorKind::Other, + format!("Unable to serialize bytes: {}", e), + ) + })?; + Ok(measurable_bytes[..0x2a0].to_vec()) + } + /// Get the attestation report signature + fn signature(&self) -> &Signature; +} + /// The guest can request that the firmware construct an attestation report. External entities can use an /// attestation report to assure the identity and security configuration of the guest. /// @@ -117,14 +132,311 @@ bitfield! { /// migration agent associated with it, the REPORT_ID_MA is filled in with the report ID of the /// migration agent. /// -/// The firmware signs the attestation report with its VCEK. The firmware uses the system wide +/// The firmware signs the attestation report with its VEK (VCEK or VLEK). The firmware uses the system wide /// ReportedTcb value as the TCB version to derive the VCEK. This value is set by the hypervisor. +/// The VLEK is generated externally and has to be loaded into the machine. /// /// The firmware guarantees that the ReportedTcb value is never greater than the installed TCB /// version +/// +/// Since the release of the 1.56 ABI, the Attestation Report was bumped from version 2 to 3. +/// Due to content differences, both versions are kept separately in order to provide backwards compatibility and most reliable security. +#[derive(Debug, Clone, Copy)] +pub enum AttestationReport { + /// Version 2 of the Attestation Report + V2(AttestationReportV2), + /// Version 3 of the Attestation Report + V3(AttestationReportV3), +} + +impl TryFrom<&[u8]> for AttestationReport { + type Error = AttestationReportError; + + fn try_from(raw_report: &[u8]) -> Result { + let version = + u32::from_le_bytes([raw_report[0], raw_report[1], raw_report[2], raw_report[3]]); + // Return the appropriate report version + match version { + 2 => { + let report_v2: AttestationReportV2 = raw_report.try_into()?; + Ok(AttestationReport::V2(report_v2)) + } + 3 => { + let report_v3: AttestationReportV3 = raw_report.try_into()?; + Ok(AttestationReport::V3(report_v3)) + } + _ => Err(AttestationReportError::UnsupportedReportVersion(version))?, + } + } +} + +/// Implement custom serialization for AttestationReport +/// This will ensure that the Attestation Report enum gets serialized into raw bytes, +/// not the serde default that tags the enum with 4 extra bytes for its own tagging mechanism. +impl Serialize for AttestationReport { + fn serialize(&self, serializer: S) -> Result + where + S: Serializer, + { + match self { + AttestationReport::V2(report) => report.serialize(serializer), + AttestationReport::V3(report) => report.serialize(serializer), + } + } +} + +impl sealed::Sealed for AttestationReport {} + +impl Attestable for AttestationReport { + fn measurable_bytes(&self) -> io::Result> { + //Return measurable bytes for the report + match self { + Self::V2(v2) => Ok(v2.measurable_bytes()?), + + Self::V3(v3) => Ok(v3.measurable_bytes()?), + } + } + fn signature(&self) -> &Signature { + match self { + Self::V2(v2) => v2.signature(), + Self::V3(v3) => v3.signature(), + } + } +} + +impl AttestationReport { + /// Convert bytes to an Attestation Report Enum + pub fn from_bytes(bytes: &[u8]) -> Result { + AttestationReport::try_from(bytes) + } + + /// Serialize the Attestation Report enum to raw bytes + pub fn to_bytes(&self) -> Result, AttestationReportError> { + bincode::serialize(self).map_err(|e| AttestationReportError::BincodeError(*e)) + } + /// Get Attestation Report Version + pub fn version(&self) -> u32 { + match self { + Self::V2(report) => report.version, + Self::V3(report) => report.version, + } + } + + /// Get the guest SVN + pub fn guest_svn(&self) -> u32 { + match self { + Self::V2(report) => report.guest_svn, + Self::V3(report) => report.guest_svn, + } + } + /// Get the guest policy. + pub fn policy(&self) -> GuestPolicy { + match self { + Self::V2(report) => report.policy, + Self::V3(report) => report.policy, + } + } + + /// Get Family ID + pub fn family_id(&self) -> [u8; 16] { + match self { + Self::V2(report) => report.family_id, + Self::V3(report) => report.family_id, + } + } + /// Get Image ID + pub fn image_id(&self) -> [u8; 16] { + match self { + Self::V2(report) => report.image_id, + Self::V3(report) => report.image_id, + } + } + + /// Get request VMPL for the attestation report. + pub fn vmpl(&self) -> u32 { + match self { + Self::V2(report) => report.vmpl, + Self::V3(report) => report.vmpl, + } + } + /// Get signature alorithm used to sign this report + pub fn sig_algo(&self) -> u32 { + match self { + Self::V2(report) => report.sig_algo, + Self::V3(report) => report.sig_algo, + } + } + + /// Get current TCB + pub fn current_tcb(&self) -> TcbVersion { + match self { + Self::V2(report) => report.current_tcb, + Self::V3(report) => report.current_tcb, + } + } + + /// Get Platform Info + pub fn plat_info(&self) -> PlatformInfo { + match self { + Self::V2(report) => PlatformInfo::V1(report.plat_info), + Self::V3(report) => PlatformInfo::V2(report.plat_info), + } + } + + /// Get Key Information + pub fn key_info(&self) -> KeyInfo { + match self { + Self::V2(report) => report.key_info, + Self::V3(report) => report.key_info, + } + } + + /// Get the guest provided report data + pub fn report_data(&self) -> [u8; 64] { + match self { + Self::V2(report) => report.report_data, + Self::V3(report) => report.report_data, + } + } + + /// Get the measurement calculated at launch. + pub fn measurement(&self) -> [u8; 48] { + match self { + Self::V2(report) => report.measurement, + Self::V3(report) => report.measurement, + } + } + + /// Get data provided by the hypervisor at launch. + pub fn host_data(&self) -> [u8; 32] { + match self { + Self::V2(report) => report.host_data, + Self::V3(report) => report.host_data, + } + } + + /// Get the SHA-384 digest of the ID public key that signed the ID block. + pub fn id_key_digest(&self) -> [u8; 48] { + match self { + Self::V2(report) => report.id_key_digest, + Self::V3(report) => report.id_key_digest, + } + } + + /// Get the SHA-384 digest of the author public key that certified the ID key,. + pub fn author_key_digest(&self) -> [u8; 48] { + match self { + Self::V2(report) => report.author_key_digest, + Self::V3(report) => report.author_key_digest, + } + } + + /// Get report ID of this guest + pub fn report_id(&self) -> [u8; 32] { + match self { + Self::V2(report) => report.report_id, + Self::V3(report) => report.report_id, + } + } + + /// Get report ID of this guest's migration agent (if applicable). + pub fn report_id_ma(&self) -> [u8; 32] { + match self { + Self::V2(report) => report.report_id_ma, + Self::V3(report) => report.report_id_ma, + } + } + + /// Get the Reported TCB of the report + pub fn reported_tcb(&self) -> TcbVersion { + match self { + Self::V2(report) => report.reported_tcb, + Self::V3(report) => report.reported_tcb, + } + } + + /// Get the CPUID info from the report (V3 only) + pub fn cpuid(&self) -> Result<(u8, u8, u8), AttestationReportError> { + match self { + Self::V2(_) => Err(AttestationReportError::UnsupportedField( + "cpuid information".to_string(), + )), + Self::V3(report) => Ok((report.cpuid_fam_id, report.cpuid_mod_id, report.cpuid_step)), + } + } + + /// Get the CHIP ID of the attestation report + pub fn chip_id(&self) -> [u8; 64] { + match self { + Self::V2(report) => report.chip_id, + Self::V3(report) => report.chip_id, + } + } + + /// Get commited TCB + pub fn commited_tcb(&self) -> TcbVersion { + match self { + Self::V2(report) => report.committed_tcb, + Self::V3(report) => report.committed_tcb, + } + } + + /// Get the current version in the report (major,minor,build) + pub fn current_version(&self) -> (u8, u8, u8) { + match self { + Self::V2(report) => ( + report.current_major, + report.current_minor, + report.current_build, + ), + Self::V3(report) => ( + report.current_major, + report.current_minor, + report.current_build, + ), + } + } + + /// Get the committed version in the report (major,minor,build) + pub fn commited_version(&self) -> (u8, u8, u8) { + match self { + Self::V2(report) => ( + report.committed_major, + report.committed_minor, + report.committed_build, + ), + Self::V3(report) => ( + report.committed_major, + report.committed_minor, + report.committed_build, + ), + } + } + + /// Get launch TCB + pub fn launch_tcb(&self) -> TcbVersion { + match self { + Self::V2(report) => report.launch_tcb, + Self::V3(report) => report.launch_tcb, + } + } +} + +impl Display for AttestationReport { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + match self { + AttestationReport::V2(report) => write!(f, "{}", report), + AttestationReport::V3(report) => write!(f, "{}", report), + } + } +} + +/// Version 2 of the attestation report +/// The first upstream supported attestation report +/// Systems that contain firmware prior to the spec release 1.56 will use this attestation report. #[repr(C)] #[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)] -pub struct AttestationReport { +pub struct AttestationReportV2 { /// Version number of this attestation report. Set to 2h for this specification. pub version: u32, /// The guest SVN. @@ -142,7 +454,7 @@ pub struct AttestationReport { /// Current TCB. See SNPTcbVersion pub current_tcb: TcbVersion, /// Information about the platform. See PlatformInfo - pub plat_info: PlatformInfo, + pub plat_info: PlatformInfoV1, /// Information related to signing keys in the report. See KeyInfo pub key_info: KeyInfo, _reserved_0: u32, @@ -198,7 +510,7 @@ pub struct AttestationReport { pub signature: Signature, } -impl Default for AttestationReport { +impl Default for AttestationReportV2 { fn default() -> Self { Self { version: Default::default(), @@ -238,7 +550,7 @@ impl Default for AttestationReport { } } -impl Display for AttestationReport { +impl Display for AttestationReportV2 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, @@ -309,112 +621,345 @@ Launch TCB: } } -#[cfg(feature = "openssl")] -impl Verifiable for (&Chain, &AttestationReport) { - type Output = (); - - fn verify(self) -> io::Result { - let vek = self.0.verify()?; - - let sig = EcdsaSig::try_from(&self.1.signature)?; - let measurable_bytes: &[u8] = &bincode::serialize(self.1).map_err(|e| { - Error::new( - ErrorKind::Other, - format!("Unable to serialize bytes: {}", e), - ) - })?[..0x2a0]; - - let mut hasher = Sha384::new(); - hasher.update(measurable_bytes); - let base_digest = hasher.finish(); - - let ec = vek.public_key()?.ec_key()?; - let signed = sig.verify(&base_digest, &ec)?; +impl sealed::Sealed for AttestationReportV2 {} - match signed { - true => Ok(()), - false => Err(Error::new( - ErrorKind::Other, - "VEK does not sign the attestation report", - )), - } +impl Attestable for AttestationReportV2 { + fn signature(&self) -> &Signature { + &self.signature } } -#[cfg(feature = "openssl")] -impl Verifiable for (&Certificate, &AttestationReport) { - type Output = (); - - fn verify(self) -> io::Result { - let vek = self.0; - - let sig = EcdsaSig::try_from(&self.1.signature)?; - let measurable_bytes: &[u8] = &bincode::serialize(self.1).map_err(|e| { - Error::new( - ErrorKind::Other, - format!("Unable to serialize bytes: {}", e), - ) - })?[..0x2a0]; - - let mut hasher = Sha384::new(); - hasher.update(measurable_bytes); - let base_digest = hasher.finish(); - - let ec = vek.public_key()?.ec_key()?; - let signed = sig.verify(&base_digest, &ec)?; +impl TryFrom<&[u8]> for AttestationReportV2 { + type Error = AttestationReportError; - match signed { - true => Ok(()), - false => Err(Error::new( - ErrorKind::Other, - "VEK does not sign the attestation report", - )), - } + fn try_from(bytes: &[u8]) -> Result { + bincode::deserialize(bytes).map_err(|e| AttestationReportError::BincodeError(*e)) } } -#[cfg(feature = "crypto_nossl")] -impl Verifiable for (&Chain, &AttestationReport) { - type Output = (); - - fn verify(self) -> io::Result { - // According to Chapter 3 of the Versioned Chip Endorsement Key (VCEK) Certificate and the Versioned Loaded Endorsement Key (VLEK) - // Certificate specifications, both Versioned Endorsement Key certificates certify an ECDSA public key on curve P-384, - // with the signature hash algorithm being SHA-384. - let vek = self.0.verify()?; - - let sig = p384::ecdsa::Signature::try_from(&self.1.signature)?; - - let measurable_bytes: &[u8] = &bincode::serialize(self.1).map_err(|e| { - Error::new( - ErrorKind::Other, - format!("Unable to serialize bytes: {}", e), - ) - })?[..0x2a0]; - - use sha2::Digest; - let base_digest = sha2::Sha384::new_with_prefix(measurable_bytes); +/// Version 3 of the attestation report +/// Systems that contain firmware starting from the spec release 1.56 will use this attestation report. +/// This version adds: +/// The CPUID Family, Model and Stepping fields +/// The Alias_Check_Complete field in the PlatformInfo field +#[repr(C)] +#[derive(Debug, Clone, Copy, Deserialize, Serialize, PartialEq, Eq, PartialOrd, Ord)] +pub struct AttestationReportV3 { + /// Version number of this attestation report. Set to 2h for this specification. + pub version: u32, + /// The guest SVN. + pub guest_svn: u32, + /// The guest policy. + pub policy: GuestPolicy, + /// The family ID provided at launch. + pub family_id: [u8; 16], + /// The image ID provided at launch. + pub image_id: [u8; 16], + /// The request VMPL for the attestation report. + pub vmpl: u32, + /// The signature algorithm used to sign this report. + pub sig_algo: u32, + /// Current TCB. See SNPTcbVersion + pub current_tcb: TcbVersion, + /// Information about the platform. See PlatformInfo + pub plat_info: PlatformInfoV2, + /// Information related to signing keys in the report. See KeyInfo + pub key_info: KeyInfo, + _reserved_0: u32, + #[serde(with = "BigArray")] + /// Guest-provided 512 Bits of Data + pub report_data: [u8; 64], + #[serde(with = "BigArray")] + /// The measurement calculated at launch. + pub measurement: [u8; 48], + /// Data provided by the hypervisor at launch. + pub host_data: [u8; 32], + #[serde(with = "BigArray")] + /// SHA-384 digest of the ID public key that signed the ID block provided + /// in SNP_LANUNCH_FINISH. + pub id_key_digest: [u8; 48], + #[serde(with = "BigArray")] + /// SHA-384 digest of the Author public key that certified the ID key, + /// if provided in SNP_LAUNCH_FINSIH. Zeroes if AUTHOR_KEY_EN is 1. + pub author_key_digest: [u8; 48], + /// Report ID of this guest. + pub report_id: [u8; 32], + /// Report ID of this guest's migration agent (if applicable). + pub report_id_ma: [u8; 32], + /// Reported TCB version used to derive the VCEK that signed this report. + pub reported_tcb: TcbVersion, + /// CPUID Familiy ID (Combined Extended Family ID and Family ID) + pub cpuid_fam_id: u8, + /// CPUID Model (Combined Extended Model and Model fields) + pub cpuid_mod_id: u8, + /// CPUID Stepping + pub cpuid_step: u8, + _reserved_1: [u8; 21], + #[serde(with = "BigArray")] + /// If MaskChipId is set to 0, Identifier unique to the chip. + /// Otherwise set to 0h. + pub chip_id: [u8; 64], + /// CommittedTCB + pub committed_tcb: TcbVersion, + /// The build number of CurrentVersion + pub current_build: u8, + /// The minor number of CurrentVersion + pub current_minor: u8, + /// The major number of CurrentVersion + pub current_major: u8, + _reserved_2: u8, + /// The build number of CommittedVersion + pub committed_build: u8, + /// The minor number of CommittedVersion + pub committed_minor: u8, + /// The major number of CommittedVersion + pub committed_major: u8, + _reserved_3: u8, + /// The CurrentTcb at the time the guest was launched or imported. + pub launch_tcb: TcbVersion, + #[serde(with = "BigArray")] + _reserved_4: [u8; 168], + /// Signature of bytes 0 to 0x29F inclusive of this report. + /// The format of the signature is found within Signature. + pub signature: Signature, +} - let verifying_key = p384::ecdsa::VerifyingKey::from_sec1_bytes(vek.public_key_sec1()) - .map_err(|e| { - io::Error::new( - ErrorKind::Other, - format!("failed to deserialize public key from sec1 bytes: {e:?}"), - ) - })?; +impl Default for AttestationReportV3 { + fn default() -> Self { + Self { + version: Default::default(), + guest_svn: Default::default(), + policy: Default::default(), + family_id: Default::default(), + image_id: Default::default(), + vmpl: Default::default(), + sig_algo: Default::default(), + current_tcb: Default::default(), + plat_info: Default::default(), + key_info: Default::default(), + _reserved_0: Default::default(), + report_data: [0; 64], + measurement: [0; 48], + host_data: Default::default(), + id_key_digest: [0; 48], + author_key_digest: [0; 48], + report_id: Default::default(), + report_id_ma: Default::default(), + reported_tcb: Default::default(), + cpuid_fam_id: Default::default(), + cpuid_mod_id: Default::default(), + cpuid_step: Default::default(), + _reserved_1: Default::default(), + chip_id: [0; 64], + committed_tcb: Default::default(), + current_build: Default::default(), + current_minor: Default::default(), + current_major: Default::default(), + _reserved_2: Default::default(), + committed_build: Default::default(), + committed_minor: Default::default(), + committed_major: Default::default(), + _reserved_3: Default::default(), + launch_tcb: Default::default(), + _reserved_4: [0; 168], + signature: Default::default(), + } + } +} + +impl Display for AttestationReportV3 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + r#" +Attestation Report ({} bytes): +Version: {} +Guest SVN: {} +{} +Family ID: {} +Image ID: {} +VMPL: {} +Signature Algorithm: {} +Current TCB: +{} +{} +{} +Report Data: {} +Measurement: {} +Host Data: {} +ID Key Digest: {} +Author Key Digest: {} +Report ID: {} +Report ID Migration Agent: {} +Reported TCB: {} +CPUID Family ID: {} +CPUID Model ID: {} +CPUID Stepping: {} +Chip ID: {} +Committed TCB: +{} +Current Build: {} +Current Minor: {} +Current Major: {} +Committed Build: {} +Committed Minor: {} +Committed Major: {} +Launch TCB: +{} +{} +"#, + std::mem::size_of_val(self), + self.version, + self.guest_svn, + self.policy, + hexdump(&self.family_id), + hexdump(&self.image_id), + self.vmpl, + self.sig_algo, + self.current_tcb, + self.plat_info, + self.key_info, + hexdump(&self.report_data), + hexdump(&self.measurement), + hexdump(&self.host_data), + hexdump(&self.id_key_digest), + hexdump(&self.author_key_digest), + hexdump(&self.report_id), + hexdump(&self.report_id_ma), + self.reported_tcb, + self.cpuid_fam_id, + self.cpuid_mod_id, + self.cpuid_step, + hexdump(&self.chip_id), + self.committed_tcb, + self.current_build, + self.current_minor, + self.current_major, + self.committed_build, + self.committed_minor, + self.committed_major, + self.launch_tcb, + self.signature + ) + } +} +impl sealed::Sealed for AttestationReportV3 {} + +impl Attestable for AttestationReportV3 { + fn signature(&self) -> &Signature { + &self.signature + } +} + +impl TryFrom<&[u8]> for AttestationReportV3 { + type Error = AttestationReportError; + + fn try_from(bytes: &[u8]) -> Result { + bincode::deserialize(bytes).map_err(|e| AttestationReportError::BincodeError(*e)) + } +} + +#[cfg(feature = "openssl")] +impl Verifiable for (&Chain, &T) +where + T: Attestable, +{ + type Output = (); + + fn verify(self) -> io::Result { + let vek = self.0.verify()?; + + let sig = EcdsaSig::try_from(self.1.signature())?; + let measurable_bytes = self.1.measurable_bytes()?; + + let mut hasher = Sha384::new(); + hasher.update(&measurable_bytes); + let base_digest = hasher.finish(); + + let ec = vek.public_key()?.ec_key()?; + let signed = sig.verify(&base_digest, &ec)?; + match signed { + true => Ok(()), + false => Err(Error::new( + ErrorKind::Other, + "VEK does not sign the attestation report", + )), + } + } +} + +#[cfg(feature = "openssl")] +impl Verifiable for (&Certificate, &T) +where + T: Attestable, +{ + type Output = (); + + fn verify(self) -> io::Result { + let vek = self.0; + + let sig = EcdsaSig::try_from(self.1.signature())?; + let measurable_bytes = self.1.measurable_bytes()?; + + let mut hasher = Sha384::new(); + hasher.update(&measurable_bytes); + let base_digest = hasher.finish(); + + let ec = vek.public_key()?.ec_key()?; + let signed = sig.verify(&base_digest, &ec)?; + match signed { + true => Ok(()), + false => Err(Error::new( + ErrorKind::Other, + "VEK does not sign the attestation report", + )), + } + } +} + +#[cfg(feature = "crypto_nossl")] +impl Verifiable for (&Chain, &T) +where + T: Attestable, +{ + type Output = (); + + fn verify(self) -> io::Result { + // According to Chapter 3 of the Versioned Chip Endorsement Key (VCEK) Certificate and the Versioned Loaded Endorsement Key (VLEK) + // Certificate specifications, both Versioned Endorsement Key certificates certify an ECDSA public key on curve P-384, + // with the signature hash algorithm being SHA-384. + + let vek = self.0.verify()?; + + let sig = p384::ecdsa::Signature::try_from(self.1.signature())?; + + let measurable_bytes = self.1.measurable_bytes()?; + + use sha2::Digest; + let base_digest = sha2::Sha384::new_with_prefix(measurable_bytes); + let verifying_key = p384::ecdsa::VerifyingKey::from_sec1_bytes(vek.public_key_sec1()) + .map_err(|e| { + io::Error::new( + ErrorKind::Other, + format!("failed to deserialize public key from sec1 bytes: {e:?}"), + ) + })?; use p384::ecdsa::signature::DigestVerifier; verifying_key.verify_digest(base_digest, &sig).map_err(|e| { io::Error::new( ErrorKind::Other, - format!("VCEK does not sign the attestation report: {e:?}"), + format!("VEK does not sign the attestation report: {e:?}"), ) }) } } #[cfg(feature = "crypto_nossl")] -impl Verifiable for (&Certificate, &AttestationReport) { +impl Verifiable for (&Certificate, &T) +where + T: Attestable, +{ type Output = (); fn verify(self) -> io::Result { @@ -425,18 +970,12 @@ impl Verifiable for (&Certificate, &AttestationReport) { let vek = self.0; - let sig = p384::ecdsa::Signature::try_from(&self.1.signature)?; + let sig = p384::ecdsa::Signature::try_from(self.1.signature())?; - let measurable_bytes: &[u8] = &bincode::serialize(self.1).map_err(|e| { - Error::new( - ErrorKind::Other, - format!("Unable to serialize bytes: {}", e), - ) - })?[..0x2a0]; + let measurable_bytes = self.1.measurable_bytes()?; use sha2::Digest; let base_digest = sha2::Sha384::new_with_prefix(measurable_bytes); - let verifying_key = p384::ecdsa::VerifyingKey::from_sec1_bytes(vek.public_key_sec1()) .map_err(|e| { io::Error::new( @@ -444,12 +983,11 @@ impl Verifiable for (&Certificate, &AttestationReport) { format!("failed to deserialize public key from sec1 bytes: {e:?}"), ) })?; - use p384::ecdsa::signature::DigestVerifier; verifying_key.verify_digest(base_digest, &sig).map_err(|e| { io::Error::new( ErrorKind::Other, - format!("VCEK does not sign the attestation report: {e:?}"), + format!("VEK does not sign the attestation report: {e:?}"), ) }) } @@ -538,7 +1076,67 @@ impl From for u64 { } } +/// Enum Containing the different versions of the Platform Info Bitfield +pub enum PlatformInfo { + /// Version 1 of Platform Info + V1(PlatformInfoV1), + /// Version 2 of Platform Info + V2(PlatformInfoV2), +} + +impl PlatformInfo { + /// Get SMT enablement status + pub fn smt_enabled(&self) -> u64 { + match self { + Self::V1(field) => field.smt_enabled(), + Self::V2(field) => field.smt_enabled(), + } + } + + /// Get TSME enablement status + pub fn tsme_enabled(&self) -> u64 { + match self { + Self::V1(field) => field.tsme_enabled(), + Self::V2(field) => field.tsme_enabled(), + } + } + /// Get ECC memory status + pub fn ecc_enabled(&self) -> u64 { + match self { + Self::V1(field) => field.ecc_enabled(), + Self::V2(field) => field.ecc_enabled(), + } + } + + /// Get RAPL enablement status + pub fn rapl_disabled(&self) -> u64 { + match self { + Self::V1(field) => field.rapl_disabled(), + Self::V2(field) => field.rapl_disabled(), + } + } + + /// Get cyphertext hiding status + pub fn cypertext_hiding_enabled(&self) -> u64 { + match self { + Self::V1(field) => field.ciphertext_hiding_enabled(), + Self::V2(field) => field.ciphertext_hiding_enabled(), + } + } + + /// Get alias check complete + pub fn alias_check_complete(&self) -> Result { + match self { + Self::V1(_) => Err(AttestationReportError::UnsupportedField( + "Alias Check Complete".to_string(), + )), + Self::V2(field) => Ok(field.alias_check_complete()), + } + } +} + bitfield! { + /// Version 1 PlatformInfo bitfield /// A structure with a bit-field unsigned 64 bit integer: /// Bit 0 representing the status of SMT enablement. /// Bit 1 representing the status of TSME enablement. @@ -548,7 +1146,7 @@ bitfield! { /// Bits 5-63 are reserved. #[repr(C)] #[derive(Default, Deserialize, Clone, Copy, Serialize, PartialEq, Eq, PartialOrd, Ord)] - pub struct PlatformInfo(u64); + pub struct PlatformInfoV1(u64); impl Debug; /// Returns the bit state of SMT pub smt_enabled, _: 0, 0; @@ -564,7 +1162,7 @@ bitfield! { reserved, _: 63, 5; } -impl Display for PlatformInfo { +impl Display for PlatformInfoV1 { fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { write!( f, @@ -586,6 +1184,60 @@ Platform Info ({}): } } +bitfield! { + /// Version 2 PlatformInfo bitfield + /// A structure with a bit-field unsigned 64 bit integer: + /// Bit 0 representing the status of SMT enablement. + /// Bit 1 representing the status of TSME enablement. + /// Bit 2 indicates if ECC memory is used. + /// Bit 3 indicates if RAPL is disabled. + /// Bit 4 indicates if ciphertext hiding is enabled + /// Bit 5 indicates that alias detection has completed since the last system reset and there are no aliasing addresses. Resets to 0. + /// Bits 5-63 are reserved. + #[repr(C)] + #[derive(Default, Deserialize, Clone, Copy, Serialize, PartialEq, Eq, PartialOrd, Ord)] + pub struct PlatformInfoV2(u64); + impl Debug; + /// Returns the bit state of SMT + pub smt_enabled, _: 0, 0; + /// Returns the bit state of TSME. + pub tsme_enabled, _: 1, 1; + /// Indicates that the platform is currently using ECC memory + pub ecc_enabled, _: 2, 2; + /// Indicates that the RAPL feature is disabled + pub rapl_disabled, _: 3, 3; + /// Indicates that ciphertext hiding is enabled + pub ciphertext_hiding_enabled, _: 4, 4; + /// Indicates that alias detection has completed since the last system reset and there are no aliasing addresses. Resets to 0. + pub alias_check_complete, _: 5, 5; + /// reserved + reserved, _: 6, 63; +} + +impl Display for PlatformInfoV2 { + fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result { + write!( + f, + r#" +Platform Info ({}): + SMT Enabled: {} + TSME Enabled: {} + ECC Enabled: {} + RAPL Disabled: {} + Ciphertext Hiding Enabled: {} + Alias Check Complete: {} +"#, + self.0, + self.smt_enabled(), + self.tsme_enabled(), + self.ecc_enabled(), + self.rapl_disabled(), + self.ciphertext_hiding_enabled(), + self.alias_check_complete() + ) + } +} + bitfield! { /// When an attestation report is requested, the user can request to have the report to not be signed, or sign with different keys. The user may also /// pass in the author key when launching the guest. This field provides that information and will be present in the attestation report. @@ -692,102 +1344,324 @@ mod tests { assert_eq!(actual.get_tcb_version(), 1); } - #[test] - fn test_guest_field_select_all_off() { - let actual: GuestFieldSelect = GuestFieldSelect(0); + #[test] + fn test_guest_field_select_all_off() { + let actual: GuestFieldSelect = GuestFieldSelect(0); + + assert_eq!(actual.get_guest_policy(), 0); + assert_eq!(actual.get_image_id(), 0); + assert_eq!(actual.get_family_id(), 0); + assert_eq!(actual.get_measurement(), 0); + assert_eq!(actual.get_svn(), 0); + assert_eq!(actual.get_tcb_version(), 0); + } + + #[test] + fn test_attestation_report_v2() { + let expected: AttestationReportV2 = AttestationReportV2 { + version: 0, + guest_svn: 0, + policy: GuestPolicy(0), + family_id: [0; 16], + image_id: [0; 16], + vmpl: 0, + sig_algo: 0, + current_tcb: TcbVersion::default(), + plat_info: PlatformInfoV1::default(), + key_info: KeyInfo::default(), + _reserved_0: 0, + report_data: [0; 64], + measurement: [0; 48], + host_data: [0; 32], + id_key_digest: [0; 48], + author_key_digest: [0; 48], + report_id: [0; 32], + report_id_ma: [0; 32], + reported_tcb: TcbVersion::default(), + _reserved_1: [0; 24], + chip_id: [0; 64], + committed_tcb: TcbVersion::default(), + current_build: 0, + current_minor: 0, + current_major: 0, + _reserved_2: 0, + committed_build: 0, + committed_minor: 0, + committed_major: 0, + _reserved_3: 0, + launch_tcb: TcbVersion::default(), + _reserved_4: [0; 168], + signature: Signature::default(), + }; + + assert_eq!(AttestationReportV2::default(), expected); + } + + #[test] + fn test_attestation_report_v3() { + let expected: AttestationReportV3 = AttestationReportV3 { + version: 0, + guest_svn: 0, + policy: GuestPolicy(0), + family_id: [0; 16], + image_id: [0; 16], + vmpl: 0, + sig_algo: 0, + current_tcb: TcbVersion::default(), + plat_info: PlatformInfoV2::default(), + key_info: KeyInfo::default(), + _reserved_0: 0, + report_data: [0; 64], + measurement: [0; 48], + host_data: [0; 32], + id_key_digest: [0; 48], + author_key_digest: [0; 48], + report_id: [0; 32], + report_id_ma: [0; 32], + reported_tcb: TcbVersion::default(), + cpuid_fam_id: 0, + cpuid_mod_id: 0, + cpuid_step: 0, + _reserved_1: [0; 21], + chip_id: [0; 64], + committed_tcb: TcbVersion::default(), + current_build: 0, + current_minor: 0, + current_major: 0, + _reserved_2: 0, + committed_build: 0, + committed_minor: 0, + committed_major: 0, + _reserved_3: 0, + launch_tcb: TcbVersion::default(), + _reserved_4: [0; 168], + signature: Signature::default(), + }; + + assert_eq!(AttestationReportV3::default(), expected); + } + + #[test] + fn test_attestation_report_v2_default() { + let expected: AttestationReportV2 = AttestationReportV2 { + version: Default::default(), + guest_svn: Default::default(), + policy: GuestPolicy::default(), + family_id: Default::default(), + image_id: Default::default(), + vmpl: Default::default(), + sig_algo: Default::default(), + current_tcb: TcbVersion::default(), + plat_info: PlatformInfoV1::default(), + key_info: KeyInfo::default(), + _reserved_0: Default::default(), + report_data: [0; 64], + measurement: [0; 48], + host_data: Default::default(), + id_key_digest: [0; 48], + author_key_digest: [0; 48], + report_id: Default::default(), + report_id_ma: Default::default(), + reported_tcb: TcbVersion::default(), + _reserved_1: Default::default(), + chip_id: [0; 64], + committed_tcb: TcbVersion::default(), + current_build: Default::default(), + current_minor: Default::default(), + current_major: Default::default(), + _reserved_2: Default::default(), + committed_build: Default::default(), + committed_minor: Default::default(), + committed_major: Default::default(), + _reserved_3: Default::default(), + launch_tcb: TcbVersion::default(), + _reserved_4: [0; 168], + signature: Signature::default(), + }; + + assert_eq!(AttestationReportV2::default(), expected); + } + + #[test] + fn test_attestation_report_v3_default() { + let expected: AttestationReportV3 = AttestationReportV3 { + version: Default::default(), + guest_svn: Default::default(), + policy: GuestPolicy::default(), + family_id: Default::default(), + image_id: Default::default(), + vmpl: Default::default(), + sig_algo: Default::default(), + current_tcb: TcbVersion::default(), + plat_info: PlatformInfoV2::default(), + key_info: KeyInfo::default(), + _reserved_0: Default::default(), + report_data: [0; 64], + measurement: [0; 48], + host_data: Default::default(), + id_key_digest: [0; 48], + author_key_digest: [0; 48], + report_id: Default::default(), + report_id_ma: Default::default(), + reported_tcb: TcbVersion::default(), + cpuid_fam_id: Default::default(), + cpuid_mod_id: Default::default(), + cpuid_step: Default::default(), + _reserved_1: Default::default(), + chip_id: [0; 64], + committed_tcb: TcbVersion::default(), + current_build: Default::default(), + current_minor: Default::default(), + current_major: Default::default(), + _reserved_2: Default::default(), + committed_build: Default::default(), + committed_minor: Default::default(), + committed_major: Default::default(), + _reserved_3: Default::default(), + launch_tcb: TcbVersion::default(), + _reserved_4: [0; 168], + signature: Signature::default(), + }; + + assert_eq!(AttestationReportV3::default(), expected); + } + + #[test] + fn test_attestation_report_v2_fmt() { + let expected: &str = r#" +Attestation Report (1184 bytes): +Version: 0 +Guest SVN: 0 + + Guest Policy (0x0): + ABI Major: 0 + ABI Minor: 0 + SMT Allowed: 0 + Migrate MA: 0 + Debug Allowed: 0 + Single Socket: 0 +Family ID: +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + +Image ID: +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + +VMPL: 0 +Signature Algorithm: 0 +Current TCB: + +TCB Version: + Microcode: 0 + SNP: 0 + TEE: 0 + Boot Loader: 0 + + +Platform Info (0): + SMT Enabled: 0 + TSME Enabled: 0 + ECC Enabled: 0 + RAPL Disabled: 0 + Ciphertext Hiding Enabled: 0 + + +Key Information: + author key enabled: false + mask chip key: 0 + signing key: vcek + +Report Data: +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + +Measurement: +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + +Host Data: +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + +ID Key Digest: +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + +Author Key Digest: +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + +Report ID: +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + +Report ID Migration Agent: +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + +Reported TCB: +TCB Version: + Microcode: 0 + SNP: 0 + TEE: 0 + Boot Loader: 0 + +Chip ID: +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 + +Committed TCB: - assert_eq!(actual.get_guest_policy(), 0); - assert_eq!(actual.get_image_id(), 0); - assert_eq!(actual.get_family_id(), 0); - assert_eq!(actual.get_measurement(), 0); - assert_eq!(actual.get_svn(), 0); - assert_eq!(actual.get_tcb_version(), 0); - } +TCB Version: + Microcode: 0 + SNP: 0 + TEE: 0 + Boot Loader: 0 + +Current Build: 0 +Current Minor: 0 +Current Major: 0 +Committed Build: 0 +Committed Minor: 0 +Committed Major: 0 +Launch TCB: - #[test] - fn test_attestation_report() { - let expected: AttestationReport = AttestationReport { - version: 0, - guest_svn: 0, - policy: GuestPolicy(0), - family_id: [0; 16], - image_id: [0; 16], - vmpl: 0, - sig_algo: 0, - current_tcb: TcbVersion::default(), - plat_info: PlatformInfo::default(), - key_info: KeyInfo::default(), - _reserved_0: 0, - report_data: [0; 64], - measurement: [0; 48], - host_data: [0; 32], - id_key_digest: [0; 48], - author_key_digest: [0; 48], - report_id: [0; 32], - report_id_ma: [0; 32], - reported_tcb: TcbVersion::default(), - _reserved_1: [0; 24], - chip_id: [0; 64], - committed_tcb: TcbVersion::default(), - current_build: 0, - current_minor: 0, - current_major: 0, - _reserved_2: 0, - committed_build: 0, - committed_minor: 0, - committed_major: 0, - _reserved_3: 0, - launch_tcb: TcbVersion::default(), - _reserved_4: [0; 168], - signature: Signature::default(), - }; +TCB Version: + Microcode: 0 + SNP: 0 + TEE: 0 + Boot Loader: 0 + - assert_eq!(AttestationReport::default(), expected); - } +Signature: + R: +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 - #[test] - fn test_attestation_report_default() { - let expected: AttestationReport = AttestationReport { - version: Default::default(), - guest_svn: Default::default(), - policy: GuestPolicy::default(), - family_id: Default::default(), - image_id: Default::default(), - vmpl: Default::default(), - sig_algo: Default::default(), - current_tcb: TcbVersion::default(), - plat_info: PlatformInfo::default(), - key_info: KeyInfo::default(), - _reserved_0: Default::default(), - report_data: [0; 64], - measurement: [0; 48], - host_data: Default::default(), - id_key_digest: [0; 48], - author_key_digest: [0; 48], - report_id: Default::default(), - report_id_ma: Default::default(), - reported_tcb: TcbVersion::default(), - _reserved_1: Default::default(), - chip_id: [0; 64], - committed_tcb: TcbVersion::default(), - current_build: Default::default(), - current_minor: Default::default(), - current_major: Default::default(), - _reserved_2: Default::default(), - committed_build: Default::default(), - committed_minor: Default::default(), - committed_major: Default::default(), - _reserved_3: Default::default(), - launch_tcb: TcbVersion::default(), - _reserved_4: [0; 168], - signature: Signature::default(), - }; + S: +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 +00 00 00 00 00 00 00 00 + + +"#; - assert_eq!(AttestationReport::default(), expected); + assert_eq!(expected, AttestationReportV2::default().to_string()) } #[test] - fn test_attestation_report_fmt() { + fn test_attestation_report_v3_fmt() { let expected: &str = r#" Attestation Report (1184 bytes): Version: 0 @@ -869,6 +1743,10 @@ TCB Version: SNP: 0 TEE: 0 Boot Loader: 0 + +CPUID Family ID: 0 +CPUID Model ID: 0 +CPUID Stepping: 0 Chip ID: 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 00 @@ -917,23 +1795,23 @@ Signature: "#; - assert_eq!(expected, AttestationReport::default().to_string()) + assert_eq!(expected, AttestationReportV3::default().to_string()) } #[test] - fn test_attestation_report_clone() { - let expected: AttestationReport = AttestationReport::default(); + fn test_attestation_report_v2_copy() { + let expected: AttestationReportV2 = AttestationReportV2::default(); - let copy: AttestationReport = expected; + let copy: AttestationReportV2 = expected; assert_eq!(expected, copy); } #[test] - fn test_attestation_report_copy() { - let expected: AttestationReport = AttestationReport::default(); + fn test_attestation_report_v3_copy() { + let expected: AttestationReportV3 = AttestationReportV3::default(); - let copy: AttestationReport = expected; + let copy: AttestationReportV3 = expected; assert_eq!(expected, copy); } @@ -1026,25 +1904,63 @@ Signature: } #[test] - fn test_platform_info_zeroed() { - let expected: PlatformInfo = PlatformInfo(0); + fn test_platform_info_v1_zeroed() { + let expected: PlatformInfoV1 = PlatformInfoV1(0); + + assert_eq!(expected.smt_enabled(), 0); + assert_eq!(expected.tsme_enabled(), 0); + assert_eq!(expected.ecc_enabled(), 0); + assert_eq!(expected.rapl_disabled(), 0); + assert_eq!(expected.ciphertext_hiding_enabled(), 0); + } + #[test] + fn test_platform_info_v2_zeroed() { + let expected: PlatformInfoV2 = PlatformInfoV2(0); assert_eq!(expected.smt_enabled(), 0); assert_eq!(expected.tsme_enabled(), 0); assert_eq!(expected.ecc_enabled(), 0); assert_eq!(expected.rapl_disabled(), 0); assert_eq!(expected.ciphertext_hiding_enabled(), 0); + assert_eq!(expected.alias_check_complete(), 0); + } + + #[test] + fn test_platform_v1_info_full() { + let expected: PlatformInfoV1 = PlatformInfoV1(0b11111); + + assert_eq!(expected.smt_enabled(), 1); + assert_eq!(expected.tsme_enabled(), 1); + assert_eq!(expected.ecc_enabled(), 1); + assert_eq!(expected.rapl_disabled(), 1); + assert_eq!(expected.ciphertext_hiding_enabled(), 1); } #[test] - fn test_platform_info_full() { - let expected: PlatformInfo = PlatformInfo(0b11111); + fn test_platform_v2_info_full() { + let expected: PlatformInfoV2 = PlatformInfoV2(0b111111); assert_eq!(expected.smt_enabled(), 1); assert_eq!(expected.tsme_enabled(), 1); assert_eq!(expected.ecc_enabled(), 1); assert_eq!(expected.rapl_disabled(), 1); assert_eq!(expected.ciphertext_hiding_enabled(), 1); + assert_eq!(expected.alias_check_complete(), 1); + } + + #[test] + fn test_platform_v1_info_fmt() { + let expected: &str = r#" +Platform Info (0): + SMT Enabled: 0 + TSME Enabled: 0 + ECC Enabled: 0 + RAPL Disabled: 0 + Ciphertext Hiding Enabled: 0 +"#; + let actual: PlatformInfoV1 = PlatformInfoV1(0); + + assert_eq!(expected, actual.to_string()); } #[test] @@ -1056,8 +1972,9 @@ Platform Info (0): ECC Enabled: 0 RAPL Disabled: 0 Ciphertext Hiding Enabled: 0 + Alias Check Complete: 0 "#; - let actual: PlatformInfo = PlatformInfo(0); + let actual: PlatformInfoV2 = PlatformInfoV2(0); assert_eq!(expected, actual.to_string()); } @@ -1134,12 +2051,22 @@ Key Information: } #[test] - fn test_platform_info_serialization() { - let original = PlatformInfo(0b11111); + fn test_platform_info_v1_serialization() { + let original = PlatformInfoV1(0b11111); + + // Test bincode + let binary = bincode::serialize(&original).unwrap(); + let from_binary: PlatformInfoV1 = bincode::deserialize(&binary).unwrap(); + assert_eq!(original, from_binary); + } + + #[test] + fn test_platform_info_v2_serialization() { + let original = PlatformInfoV2(0b11111); // Test bincode let binary = bincode::serialize(&original).unwrap(); - let from_binary: PlatformInfo = bincode::deserialize(&binary).unwrap(); + let from_binary: PlatformInfoV2 = bincode::deserialize(&binary).unwrap(); assert_eq!(original, from_binary); } @@ -1171,8 +2098,25 @@ Key Information: } #[test] - fn test_attestation_report_serialization() { - let original: AttestationReport = AttestationReport { + fn test_attestation_report_v2_serialization() { + let original: AttestationReportV2 = AttestationReportV2 { + version: 2, + guest_svn: 1, + policy: GuestPolicy(3), + family_id: [1; 16], + image_id: [2; 16], + ..Default::default() + }; + + // Test bincode + let binary = bincode::serialize(&original).unwrap(); + let from_binary: AttestationReportV2 = bincode::deserialize(&binary).unwrap(); + assert_eq!(original, from_binary); + } + + #[test] + fn test_attestation_report_v3_serialization() { + let original: AttestationReportV3 = AttestationReportV3 { version: 2, guest_svn: 1, policy: GuestPolicy(3), @@ -1183,14 +2127,139 @@ Key Information: // Test bincode let binary = bincode::serialize(&original).unwrap(); - let from_binary: AttestationReport = bincode::deserialize(&binary).unwrap(); + let from_binary: AttestationReportV3 = bincode::deserialize(&binary).unwrap(); assert_eq!(original, from_binary); } + #[test] + fn test_attestation_report_enum_v2_serialization() { + let report_v2 = AttestationReportV2 { + version: 2, + guest_svn: 1, + policy: GuestPolicy::default(), + family_id: Default::default(), + image_id: Default::default(), + vmpl: Default::default(), + sig_algo: Default::default(), + current_tcb: TcbVersion::default(), + plat_info: PlatformInfoV1::default(), + key_info: KeyInfo::default(), + _reserved_0: Default::default(), + report_data: [0; 64], + measurement: [0; 48], + host_data: Default::default(), + id_key_digest: [0; 48], + author_key_digest: [0; 48], + report_id: Default::default(), + report_id_ma: Default::default(), + reported_tcb: TcbVersion::default(), + _reserved_1: Default::default(), + chip_id: [0; 64], + committed_tcb: TcbVersion::default(), + current_build: Default::default(), + current_minor: Default::default(), + current_major: Default::default(), + _reserved_2: Default::default(), + committed_build: Default::default(), + committed_minor: Default::default(), + committed_major: Default::default(), + _reserved_3: Default::default(), + launch_tcb: TcbVersion::default(), + _reserved_4: [0; 168], + signature: Signature::default(), + }; + + let attestation_report = AttestationReport::V2(report_v2); + + let serialized = attestation_report.to_bytes().expect("Serialization failed"); + + assert_eq!(serialized, attestation_report.to_bytes().unwrap()); + + let deserialized = + AttestationReport::from_bytes(&serialized).expect("Deserialization failed"); + + assert_eq!(attestation_report.version(), deserialized.version()); + assert_eq!(attestation_report.guest_svn(), deserialized.guest_svn()); + assert_eq!(attestation_report.policy(), deserialized.policy()); + } + + #[test] + fn test_attestation_report_enum_v3_serialization() { + let report_v3 = AttestationReportV3 { + version: 2, + guest_svn: 1, + policy: GuestPolicy::default(), + family_id: Default::default(), + image_id: Default::default(), + vmpl: Default::default(), + sig_algo: Default::default(), + current_tcb: TcbVersion::default(), + plat_info: PlatformInfoV2::default(), + key_info: KeyInfo::default(), + _reserved_0: Default::default(), + report_data: [0; 64], + measurement: [0; 48], + host_data: Default::default(), + id_key_digest: [0; 48], + author_key_digest: [0; 48], + report_id: Default::default(), + report_id_ma: Default::default(), + reported_tcb: TcbVersion::default(), + cpuid_fam_id: Default::default(), + cpuid_mod_id: Default::default(), + cpuid_step: Default::default(), + _reserved_1: Default::default(), + chip_id: [0; 64], + committed_tcb: TcbVersion::default(), + current_build: Default::default(), + current_minor: Default::default(), + current_major: Default::default(), + _reserved_2: Default::default(), + committed_build: Default::default(), + committed_minor: Default::default(), + committed_major: Default::default(), + _reserved_3: Default::default(), + launch_tcb: TcbVersion::default(), + _reserved_4: [0; 168], + signature: Signature::default(), + }; + + let attestation_report = AttestationReport::V3(report_v3); + + let serialized = attestation_report.to_bytes().expect("Serialization failed"); + + assert_eq!(serialized, attestation_report.to_bytes().unwrap()); + + let deserialized = + AttestationReport::from_bytes(&serialized).expect("Deserialization failed"); + + assert_eq!(attestation_report.version(), deserialized.version()); + assert_eq!(attestation_report.guest_svn(), deserialized.guest_svn()); + assert_eq!(attestation_report.policy(), deserialized.policy()); + } + + #[test] + fn test_invalid_version_deserialization() { + let invalid_bytes = [5, 0, 0, 0, 0, 1, 2, 3]; // Version 5 is unsupported + let result = AttestationReport::from_bytes(&invalid_bytes); + assert!(result.is_err()); + } + + #[test] + fn test_version_extraction() { + let raw_v2 = [2, 0, 0, 0]; // Version 2 + let version = u32::from_le_bytes([raw_v2[0], raw_v2[1], raw_v2[2], raw_v2[3]]); + assert_eq!(version, 2); + + let raw_v3 = [3, 0, 0, 0]; // Version 3 + let version = u32::from_le_bytes([raw_v3[0], raw_v3[1], raw_v3[2], raw_v3[3]]); + assert_eq!(version, 3); + } + #[test] fn test_boundary_value_serialization() { // Test max values - let platform_info = PlatformInfo(u64::MAX); + let platform_info = PlatformInfoV1(u64::MAX); let key_info = KeyInfo(u32::MAX); let guest_policy = GuestPolicy(u64::MAX); @@ -1254,7 +2323,7 @@ Key Information: #[test] fn test_attestation_report_fields() { - let report: AttestationReport = AttestationReport { + let report: AttestationReportV2 = AttestationReportV2 { version: 2, guest_svn: 1, vmpl: 3, @@ -1268,7 +2337,7 @@ Key Information: #[test] fn test_platform_info_reserved() { - let info = PlatformInfo(0xFF); + let info = PlatformInfoV1(0xFF); assert_eq!(info.reserved(), 0x7); } diff --git a/src/firmware/linux/guest/types.rs b/src/firmware/linux/guest/types.rs index 4c31cc11..341f0b23 100644 --- a/src/firmware/linux/guest/types.rs +++ b/src/firmware/linux/guest/types.rs @@ -1,6 +1,6 @@ // SPDX-License-Identifier: Apache-2.0 -use crate::{error::*, firmware::guest::*}; +use crate::{error::*, firmware::guest::*, util::large_array::LargeArray}; use static_assertions::const_assert; @@ -157,6 +157,8 @@ impl ReportReq { } } +const REPORT_SIZE: usize = std::mem::size_of::(); + /// The response from the PSP containing the generated attestation report. /// /// The Report is padded to exactly 4000 Bytes to make sure the page size @@ -183,12 +185,10 @@ pub struct ReportRsp { pub report_size: u32, reserved_0: [u8; 24], /// The attestation report generated by the firmware. - pub report: AttestationReport, + pub report: LargeArray, /// Padding bits to meet the memory page alignment. reserved_1: [u8; 4000 - - (std::mem::size_of::() - + (std::mem::size_of::() * 2) - + std::mem::size_of::<[u8; 24]>())], + - (REPORT_SIZE + (std::mem::size_of::() * 2) + std::mem::size_of::<[u8; 24]>())], } // Compile-time check that the size is what is expected. @@ -207,7 +207,7 @@ impl Default for ReportRsp { reserved_0: Default::default(), report: Default::default(), reserved_1: [0u8; 4000 - - (std::mem::size_of::() + - (REPORT_SIZE + (std::mem::size_of::() * 2) + std::mem::size_of::<[u8; 24]>())], } diff --git a/src/measurement/idblock_types.rs b/src/measurement/idblock_types.rs index 25187421..329357f6 100644 --- a/src/measurement/idblock_types.rs +++ b/src/measurement/idblock_types.rs @@ -15,9 +15,8 @@ use serde::{Deserialize, Serialize}; use std::convert::{TryFrom, TryInto}; use crate::{ - error::IdBlockError, - firmware::guest::GuestPolicy, - measurement::{large_array::LargeArray, snp::SnpLaunchDigest}, + error::IdBlockError, firmware::guest::GuestPolicy, measurement::snp::SnpLaunchDigest, + util::large_array::LargeArray, }; pub(crate) const DEFAULT_ID_VERSION: u32 = 1; diff --git a/src/measurement/mod.rs b/src/measurement/mod.rs index f0a8b4b1..99cf46ec 100644 --- a/src/measurement/mod.rs +++ b/src/measurement/mod.rs @@ -29,5 +29,3 @@ pub mod idblock; #[cfg(all(feature = "snp", feature = "openssl"))] pub mod idblock_types; - -pub mod large_array; diff --git a/src/measurement/snp.rs b/src/measurement/snp.rs index 654fa828..90c1a517 100644 --- a/src/measurement/snp.rs +++ b/src/measurement/snp.rs @@ -6,12 +6,12 @@ use crate::{ launch::snp::PageType, measurement::{ gctx::{Gctx, Updating, VMSA_GPA}, - large_array::LargeArray, ovmf::{OvmfSevMetadataSectionDesc, SectionType, OVMF}, sev_hashes::SevHashes, vcpu_types::CpuType, vmsa::{GuestFeatures, VMMType, VMSA}, }, + util::large_array::LargeArray, }; use hex::FromHex; use serde::{Deserialize, Serialize}; diff --git a/src/measurement/vmsa.rs b/src/measurement/vmsa.rs index 5a5f05a7..dad3cc20 100644 --- a/src/measurement/vmsa.rs +++ b/src/measurement/vmsa.rs @@ -2,8 +2,7 @@ //! Operations to build and interact with an SEV-ES VMSA use crate::{ - error::MeasurementError, - measurement::{large_array::LargeArray, vcpu_types::CpuType}, + error::MeasurementError, measurement::vcpu_types::CpuType, util::large_array::LargeArray, }; use bitfield::bitfield; use serde::{Deserialize, Serialize}; diff --git a/src/measurement/large_array.rs b/src/util/large_array.rs similarity index 100% rename from src/measurement/large_array.rs rename to src/util/large_array.rs diff --git a/src/util/mod.rs b/src/util/mod.rs index 40c5f892..e2215142 100644 --- a/src/util/mod.rs +++ b/src/util/mod.rs @@ -4,6 +4,8 @@ pub mod cached_chain; mod impl_const_id; +pub mod large_array; +pub(crate) mod sealed; use std::{ io::{Read, Result, Write}, diff --git a/src/util/sealed.rs b/src/util/sealed.rs new file mode 100644 index 00000000..8b7c0ce2 --- /dev/null +++ b/src/util/sealed.rs @@ -0,0 +1,4 @@ +// SPDX-License-Identifier: Apache-2.0 + +//! Trait to use to seal other traits in crate. +pub trait Sealed {} diff --git a/tests/certs.rs b/tests/certs.rs index 73a8c147..e3046352 100644 --- a/tests/certs.rs +++ b/tests/certs.rs @@ -24,6 +24,8 @@ mod sev { #[cfg(all(feature = "snp", any(feature = "openssl", feature = "crypto_nossl")))] mod snp { + use std::convert::TryFrom; + use sev::certs::snp::{builtin::milan, ca, Certificate, Chain, Verifiable}; const TEST_MILAN_VCEK_DER: &[u8] = include_bytes!("certs_data/vcek_milan.der"); @@ -85,8 +87,8 @@ mod snp { let chain = Chain { ca, vek: vcek }; let report_bytes = hex::decode(TEST_MILAN_ATTESTATION_REPORT).unwrap(); - let report: AttestationReport = - unsafe { std::ptr::read(report_bytes.as_ptr() as *const _) }; + + let report = AttestationReport::try_from(report_bytes.as_slice()).unwrap(); assert_eq!((&chain, &report).verify().ok(), Some(())); } @@ -104,9 +106,8 @@ mod snp { let chain = Chain { ca, vek: vcek }; let mut report_bytes = hex::decode(TEST_MILAN_ATTESTATION_REPORT).unwrap(); - report_bytes[0] ^= 0x80; - let report: AttestationReport = - unsafe { std::ptr::read(report_bytes.as_ptr() as *const _) }; + report_bytes[21] ^= 0x80; + let report = AttestationReport::try_from(report_bytes.as_slice()).unwrap(); assert_eq!((&chain, &report).verify().ok(), None); }