diff --git a/Cargo.lock b/Cargo.lock index e6293920..f7500a95 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -14,6 +14,12 @@ version = "0.2.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "4c7f02d4ea65f2c1853089ffd8d2787bdbc63de2f0d29dedbcf8ccdfa0ccd4cf" +[[package]] +name = "base64" +version = "0.21.7" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "9d297deb1925b89f2ccc13d7635fa0714f12c87adce1c75356b39ca9b7178567" + [[package]] name = "base64ct" version = "1.6.0" @@ -854,6 +860,7 @@ dependencies = [ name = "sev" version = "3.0.0" dependencies = [ + "base64", "bincode", "bitfield", "bitflags 1.3.2", diff --git a/Cargo.toml b/Cargo.toml index 07c2241e..ddd8b47e 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -67,6 +67,7 @@ rsa = { version = "0.9.2", optional = true } sha2 = { version = "0.10.8", optional = true } x509-cert = { version = "0.2.4", optional = true } byteorder = "1.4.3" +base64 = "0.21.5" [dev-dependencies] kvm-ioctls = ">=0.12" diff --git a/src/error.rs b/src/error.rs index 3998dab8..61a74a4b 100644 --- a/src/error.rs +++ b/src/error.rs @@ -740,6 +740,104 @@ impl std::fmt::Display for SevHashError { impl std::error::Error for SevHashError {} +/// Possible errors when working with the large array type +#[derive(Debug)] +pub enum LargeArrayError { + /// Error when trying from slice + SliceError(TryFromSliceError), + + /// Error when converting from vector + VectorError(String), +} + +impl std::fmt::Display for LargeArrayError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + LargeArrayError::SliceError(error) => { + write!(f, "Error when trying from slice: {error}") + } + LargeArrayError::VectorError(error) => { + write!(f, "Error when trying from vector: {error}") + } + } + } +} + +impl std::error::Error for LargeArrayError {} + +impl std::convert::From for LargeArrayError { + fn from(value: TryFromSliceError) -> Self { + Self::SliceError(value) + } +} + +/// Errors when calculating the ID BLOCK +#[derive(Debug)] +pub enum IdBlockError { + #[cfg(all(feature = "snp", feature = "openssl"))] + /// TryFrom Slice Error handling + CryptoErrorStack(openssl::error::ErrorStack), + + /// Large Array Error handling + LargeArrayError(LargeArrayError), + + /// File Error Handling + FileError(std::io::Error), + + /// Bincode Error Handling + BincodeError(bincode::ErrorKind), + + /// Error from when handling SEV Curve algorithm + SevCurveError(), + + /// Error when handling SEV ECDSA Signature + SevEcsdsaSigError(String), +} + +impl std::fmt::Display for IdBlockError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + match self { + #[cfg(all(feature = "snp", feature = "openssl"))] + IdBlockError::CryptoErrorStack(e) => write!(f, "Error when with OPENSSL: {e}"), + IdBlockError::LargeArrayError(e) => write!(f, "{e}"), + IdBlockError::FileError(e) => write!(f, "Failed handling file: {e}"), + IdBlockError::BincodeError(e) => write!(f, "Bincode error encountered: {e}"), + IdBlockError::SevCurveError() => { + write!(f, "Wrong curve used in the provided private key") + } + IdBlockError::SevEcsdsaSigError(msg) => { + write!(f, "Error validation SEV signature: {msg}") + } + } + } +} + +impl std::error::Error for IdBlockError {} + +#[cfg(all(feature = "snp", feature = "openssl"))] +impl std::convert::From for IdBlockError { + fn from(value: openssl::error::ErrorStack) -> Self { + Self::CryptoErrorStack(value) + } +} + +impl std::convert::From for IdBlockError { + fn from(value: LargeArrayError) -> Self { + Self::LargeArrayError(value) + } +} + +impl std::convert::From for IdBlockError { + fn from(value: std::io::Error) -> Self { + Self::FileError(value) + } +} +impl std::convert::From for IdBlockError { + fn from(value: bincode::ErrorKind) -> Self { + Self::BincodeError(value) + } +} + /// Errors which may be encountered when calculating the guest measurement. #[derive(Debug)] pub enum MeasurementError { @@ -767,6 +865,9 @@ pub enum MeasurementError { /// SEV Hash Error Handling SevHashError(SevHashError), + /// Id Block Error Handling + IdBlockError(IdBlockError), + /// Invalid VCPU provided InvalidVcpuTypeError(String), @@ -794,6 +895,7 @@ impl std::fmt::Display for MeasurementError { MeasurementError::GCTXError(e) => write!(f, "GCTX Error Encountered: {e}"), MeasurementError::OVMFError(e) => write!(f, "OVMF Error Encountered: {e}"), MeasurementError::SevHashError(e) => write!(f, "Sev hash Error Encountered: {e}"), + MeasurementError::IdBlockError(e) => write!(f, "Id Block Error Encountered: {e}"), MeasurementError::InvalidVcpuTypeError(value) => { write!(f, "Invalid VCPU type value provided: {value}") } diff --git a/src/measurement/idblock.rs b/src/measurement/idblock.rs new file mode 100644 index 00000000..124c056d --- /dev/null +++ b/src/measurement/idblock.rs @@ -0,0 +1,139 @@ +// SPDX-License-Identifier: Apache-2.0 + +//! Functions to use to calculate the ID-BLOCK and the AUTH-BLOCK. + +use bincode; +use openssl::{ec::EcKey, pkey::Private, sha::sha384}; +use std::{ + convert::{TryFrom, TryInto}, + fs::File, + io::Read, + path::PathBuf, +}; + +use crate::{ + error::IdBlockError, + measurement::idblock_types::{ + FamilyId, IdAuth, IdBlock, IdBlockLaunchDigest, IdMeasurements, ImageId, SevEcdsaPubKey, + SevEcdsaSig, CURVE_P384_NID, + }, +}; + +/// Generate an AUTH-BLOCK using 2 EC P-384 keys and an already calculated ID-BlOCK +pub fn gen_id_auth_block( + id_block: &IdBlock, + id_key_file: PathBuf, + author_key_file: PathBuf, +) -> Result { + let id_ec_priv_key = load_priv_key(id_key_file)?; + let id_ec_pub_key = SevEcdsaPubKey::try_from(&id_ec_priv_key)?; + let id_sig = SevEcdsaSig::try_from(( + id_ec_priv_key, + bincode::serialize(id_block) + .map_err(|e| IdBlockError::BincodeError(*e))? + .as_slice(), + ))?; + + let author_ec_priv_key = load_priv_key(author_key_file)?; + let author_pub_key = SevEcdsaPubKey::try_from(&author_ec_priv_key)?; + let author_sig = SevEcdsaSig::try_from(( + author_ec_priv_key, + bincode::serialize(&id_ec_pub_key) + .map_err(|e| IdBlockError::BincodeError(*e))? + .as_slice(), + ))?; + + Ok(IdAuth::new( + None, + None, + id_sig, + id_ec_pub_key, + author_sig, + author_pub_key, + )) +} + +enum KeyFormat { + Pem, + Der, +} + +/// Identifies the format of a key based upon the first twenty-seven +/// bytes of a byte stream. A non-PEM format assumes DER format. +fn identify_priv_key_format(bytes: &[u8]) -> KeyFormat { + const PEM_START: &[u8] = b"-----BEGIN PRIVATE KEY-----"; + match &bytes[0..27] { + PEM_START => KeyFormat::Pem, + _ => KeyFormat::Der, + } +} +///Read a key file and return a private EcKey. +/// Key has to be an EC P-384 key. +pub fn load_priv_key(path: PathBuf) -> Result, IdBlockError> { + let mut key_data = Vec::new(); + let mut file = match File::open(path) { + Ok(file) => file, + Err(e) => return Err(IdBlockError::FileError(e)), + }; + + file.read_to_end(&mut key_data) + .map_err(IdBlockError::FileError)?; + + let pkey = match identify_priv_key_format(&key_data) { + KeyFormat::Pem => { + EcKey::private_key_from_pem(&key_data).map_err(IdBlockError::CryptoErrorStack)? + } + KeyFormat::Der => { + EcKey::private_key_from_der(&key_data).map_err(IdBlockError::CryptoErrorStack)? + } + }; + + pkey.check_key().map_err(IdBlockError::CryptoErrorStack)?; + + if let Some(name) = pkey.group().curve_name() { + if name != CURVE_P384_NID { + return Err(IdBlockError::SevCurveError()); + }; + }; + + Ok(pkey) +} + +/// Generate the sha384 digest of the provided pem key +pub fn generate_key_digest(key_path: PathBuf) -> Result { + let ec_key = load_priv_key(key_path)?; + + let pub_key = SevEcdsaPubKey::try_from(&ec_key)?; + + Ok(IdBlockLaunchDigest::new( + sha384( + bincode::serialize(&pub_key) + .map_err(|e| IdBlockError::BincodeError(*e))? + .as_slice(), + ) + .try_into()?, + )) +} + +/// Calculate the different pieces needed for a complete pre-attestation. +/// ID-BLOCK, AUTH-BLOCK, id-key digest and auth-key digest. +pub fn snp_calculate_id( + ld: Option, + family_id: Option, + image_id: Option, + svn: Option, + policy: Option, + id_key_file: PathBuf, + auth_key_file: PathBuf, +) -> Result { + let id_block = IdBlock::new(ld, family_id, image_id, svn, policy)?; + + Ok(IdMeasurements { + id_block, + id_auth: gen_id_auth_block(&id_block, id_key_file.clone(), auth_key_file.clone())?, + + id_key_digest: generate_key_digest(id_key_file)?, + + auth_key_digest: generate_key_digest(auth_key_file)?, + }) +} diff --git a/src/measurement/idblock_types.rs b/src/measurement/idblock_types.rs new file mode 100644 index 00000000..282852d4 --- /dev/null +++ b/src/measurement/idblock_types.rs @@ -0,0 +1,361 @@ +// SPDX-License-Identifier: Apache-2.0 + +//! Different structures needed to calculate the different pieces needed for ID calculation for pre-attestation + +use openssl::{ + bn::{BigNum, BigNumContext}, + ec::{EcGroup, EcKey}, + ecdsa::EcdsaSig, + md::Md, + md_ctx::MdCtx, + nid::Nid, + pkey::{PKey, Private}, +}; +use serde::{Deserialize, Serialize}; +use std::convert::{TryFrom, TryInto}; + +use crate::{error::IdBlockError, measurement::large_array::LargeArray}; + +pub(crate) const DEFAULT_ID_VERSION: u32 = 1; +pub(crate) const DEFAULT_ID_POLICY: u64 = 0x300000; + +pub(crate) const CURVE_P384_NID: Nid = openssl::nid::Nid::SECP384R1; +pub(crate) const DEFAULT_KEY_ALGO: u32 = 1; +pub(crate) const CURVE_P384: u32 = 2; + +pub(crate) const ID_BLK_DIGEST_BITS: usize = 384; +pub(crate) const ID_BLK_DIGEST_BYTES: usize = ID_BLK_DIGEST_BITS / 8; + +pub(crate) const ID_BLK_ID_BITS: usize = 128; +pub(crate) const ID_BLK_ID_BYTES: usize = ID_BLK_ID_BITS / 8; + +pub(crate) const ID_AUTH_RESERVED1_BYTES: usize = 0x03F - 0x008 + 1; +pub(crate) const ID_AUTH_RESERVED2_BYTES: usize = 0x67F - 0x644 + 1; +pub(crate) const ID_AUTH_RESERVED3_BYTES: usize = 0xFFF - 0xC84 + 1; + +pub(crate) const ECDSA_POINT_SIZE_BITS: usize = 576; +pub(crate) const ECDSA_POINT_SIZE_BYTES: usize = ECDSA_POINT_SIZE_BITS / 8; + +pub(crate) const ECDSA_PUBKEY_RESERVED: usize = 0x403 - 0x94 + 1; +pub(crate) const ECDSA_SIG_RESERVED: usize = 0x1ff - 0x90 + 1; + +/// The expected launch digest of the guest +#[repr(C)] +#[derive(Default, Serialize, Deserialize, Clone, Copy)] +pub struct IdBlockLaunchDigest(LargeArray); + +impl TryFrom<&[u8]> for IdBlockLaunchDigest { + type Error = IdBlockError; + + fn try_from(bytes: &[u8]) -> Result { + Ok(IdBlockLaunchDigest(bytes.try_into()?)) + } +} + +impl TryInto> for IdBlockLaunchDigest { + type Error = &'static str; + + fn try_into(self) -> Result, Self::Error> { + let array = self.0.as_array(); + let vec: Vec = array.to_vec(); // Convert the array into a Vec + Ok(vec) + } +} + +impl IdBlockLaunchDigest { + /// Create Launch Digest from large array + pub fn new(data: LargeArray) -> Self { + Self(data) + } +} + +/// Family ID of the guest, provided by the guest owner and uninterpreted by the firmware. +#[repr(C)] +#[derive(Default, Serialize, Deserialize, Clone, Copy)] +pub struct FamilyId([u8; ID_BLK_ID_BYTES]); + +/// Image ID to be provided to the ID-BLOCK +#[repr(C)] +#[derive(Default, Serialize, Deserialize, Clone, Copy)] +pub struct ImageId([u8; ID_BLK_ID_BYTES]); + +/// The way the ECDSA SEV signature is strucutred. Need it in this format to calculate the AUTH-ID. +#[repr(C)] +#[derive(Default, Serialize, Deserialize, Clone, Copy)] +pub struct SevEcdsaSig { + r: LargeArray, + s: LargeArray, + reserved: LargeArray, +} + +// Derive SEV ECDSA signature from a private EC KEY +impl TryFrom<(EcKey, &[u8])> for SevEcdsaSig { + type Error = IdBlockError; + + fn try_from((priv_key, data): (EcKey, &[u8])) -> Result { + // Mirror what sev-guest (firmware) is doing + let mut ctx = MdCtx::new().map_err(IdBlockError::CryptoErrorStack)?; + + let pkey = PKey::try_from(priv_key).map_err(IdBlockError::CryptoErrorStack)?; + + ctx.digest_sign_init::(Some(Md::sha384()), pkey.as_ref()) + .map_err(IdBlockError::CryptoErrorStack)?; + + let sig_size = ctx + .digest_sign(data, None) + .map_err(IdBlockError::CryptoErrorStack)?; + + let mut signature = vec![0_u8; sig_size]; + + ctx.digest_sign(data, Some(&mut signature)) + .map_err(IdBlockError::CryptoErrorStack)?; + + if signature.len() != sig_size { + return Err(IdBlockError::SevEcsdsaSigError( + "Signature is not of the expected length!".to_string(), + )); + }; + + // Create ECDSA sig from der sig + let ecdsa_sig = + EcdsaSig::from_der(signature.as_slice()).map_err(IdBlockError::CryptoErrorStack)?; + + // Extract r and s + let mut pad_r = ecdsa_sig + .r() + .to_vec_padded(ECDSA_POINT_SIZE_BYTES as i32) + .map_err(IdBlockError::CryptoErrorStack)?; + pad_r.reverse(); + + let mut pad_s = ecdsa_sig + .s() + .to_vec_padded(ECDSA_POINT_SIZE_BYTES as i32) + .map_err(IdBlockError::CryptoErrorStack)?; + pad_s.reverse(); + + Ok(SevEcdsaSig { + r: pad_r.try_into()?, + s: pad_s.try_into()?, + ..Default::default() + }) + } +} + +/// Data inside the SEV ECDSA key +#[repr(C)] +#[derive(Default, Serialize, Deserialize, Clone, Copy)] +pub struct SevEcdsaKeyData { + /// QX component of the ECDSA public key + pub qx: LargeArray, + /// QY component of the ECDSA public key + pub qy: LargeArray, + /// Reserved + reserved: LargeArray, +} + +/// SEV ECDSA public key. Need it in this format to calculate the AUTH-ID. +#[derive(Default, Serialize, Deserialize, Clone, Copy)] +pub struct SevEcdsaPubKey { + /// curve type for the public key (defaults to P384) + pub curve: u32, + /// public key data + pub data: SevEcdsaKeyData, +} + +// Create SEV ECDSA public key from EC private key +impl TryFrom<&EcKey> for SevEcdsaPubKey { + type Error = IdBlockError; + + fn try_from(priv_key: &EcKey) -> Result { + let pub_key = priv_key.public_key(); + + let mut sev_key = SevEcdsaPubKey { + curve: CURVE_P384, + ..Default::default() + }; + + let mut big_num_ctx = BigNumContext::new().map_err(IdBlockError::CryptoErrorStack)?; + + let curve_group = + EcGroup::from_curve_name(CURVE_P384_NID).map_err(IdBlockError::CryptoErrorStack)?; + + let mut x = BigNum::new().map_err(IdBlockError::CryptoErrorStack)?; + let mut y = BigNum::new().map_err(IdBlockError::CryptoErrorStack)?; + + pub_key + .affine_coordinates(&curve_group, &mut x, &mut y, &mut big_num_ctx) + .map_err(IdBlockError::CryptoErrorStack)?; + + let mut pad_x = x + .to_vec_padded(ECDSA_POINT_SIZE_BYTES as i32) + .map_err(IdBlockError::CryptoErrorStack)?; + pad_x.reverse(); + + let mut pad_y = y + .to_vec_padded(ECDSA_POINT_SIZE_BYTES as i32) + .map_err(IdBlockError::CryptoErrorStack)?; + pad_y.reverse(); + + let key_data = SevEcdsaKeyData { + qx: pad_x.try_into()?, + qy: pad_y.try_into()?, + ..Default::default() + }; + + sev_key.data = key_data; + + Ok(sev_key) + } +} + +/// SEV ID-BLOCK +#[repr(C)] +#[derive(Serialize, Deserialize, Clone, Copy)] +pub struct IdBlock { + /// The expected launch digest of the guest (aka measurement) + pub launch_digest: IdBlockLaunchDigest, + /// Family ID of the guest, provided by the guest owner and uninterpreted by the firmware. + pub family_id: FamilyId, + /// Image ID of the guest, provided by the guest owner and uninterpreted by the firmware. + pub image_id: ImageId, + /// Version of the ID block format + pub version: u32, + /// SVN of the guest. + pub guest_svn: u32, + ///The policy of the guest. + pub policy: u64, +} + +impl Default for IdBlock { + fn default() -> Self { + Self { + launch_digest: Default::default(), + family_id: Default::default(), + image_id: Default::default(), + version: DEFAULT_ID_VERSION, + guest_svn: Default::default(), + policy: DEFAULT_ID_POLICY, + } + } +} + +impl IdBlock { + /// Function to create a new ID-BLOCK with provided parameters. + pub fn new( + ld: Option, + family_id: Option, + image_id: Option, + svn: Option, + policy: Option, + ) -> Result { + let mut id_block = IdBlock::default(); + + if let Some(launch_digest) = ld { + id_block.launch_digest = launch_digest + }; + + if let Some(fam_id) = family_id { + id_block.family_id = fam_id + }; + + if let Some(img_id) = image_id { + id_block.image_id = img_id + }; + + if let Some(guest_svn) = svn { + id_block.guest_svn = guest_svn + }; + if let Some(guest_policy) = policy { + id_block.policy = guest_policy + }; + + Ok(id_block) + } +} + +#[repr(C)] +#[derive(Serialize, Deserialize, Clone, Copy)] +///ID Authentication Information Structure +pub struct IdAuth { + /// The algorithm of the ID Key. Defaults to P-384 + pub id_key_algo: u32, + /// The algorithm of the Author Key. Defaults to P-384 + pub author_key_algo: u32, + /// Reserved + reserved1: LargeArray, + /// The signature of all bytes of the ID block + pub id_block_sig: SevEcdsaSig, + /// The public component of the ID key + pub id_pubkey: SevEcdsaPubKey, + /// Reserved + reserved2: LargeArray, + /// The signature of the ID_KEY + pub id_key_sig: SevEcdsaSig, + /// The public component of the Author key + pub author_pub_key: SevEcdsaPubKey, + /// Reserved + reserved3: LargeArray, +} + +impl IdAuth { + /// Create a new IdAuth with the provided parameters + pub fn new( + id_key_algo: Option, + author_key_algo: Option, + id_block_sig: SevEcdsaSig, + id_pubkey: SevEcdsaPubKey, + id_key_sig: SevEcdsaSig, + author_pub_key: SevEcdsaPubKey, + ) -> Self { + let id_algo = match id_key_algo { + Some(algo) => algo, + None => DEFAULT_KEY_ALGO, + }; + + let key_algo = match author_key_algo { + Some(algo) => algo, + None => DEFAULT_KEY_ALGO, + }; + + Self { + id_key_algo: id_algo, + author_key_algo: key_algo, + reserved1: Default::default(), + id_block_sig, + id_pubkey, + reserved2: Default::default(), + id_key_sig, + author_pub_key, + reserved3: Default::default(), + } + } +} + +impl Default for IdAuth { + fn default() -> Self { + Self { + id_key_algo: DEFAULT_KEY_ALGO, + author_key_algo: DEFAULT_KEY_ALGO, + reserved1: Default::default(), + id_block_sig: Default::default(), + id_pubkey: Default::default(), + reserved2: Default::default(), + id_key_sig: Default::default(), + author_pub_key: Default::default(), + reserved3: Default::default(), + } + } +} + +#[derive(Default)] +/// All the measurments that can be used for pre-attestation +pub struct IdMeasurements { + /// ID-BLOCK + pub id_block: IdBlock, + /// ID-AUTH-BLOCK + pub id_auth: IdAuth, + /// ID-KEY DIGEST + pub id_key_digest: IdBlockLaunchDigest, + /// AUTH-KEY DIGEST + pub auth_key_digest: IdBlockLaunchDigest, +} diff --git a/src/measurement/large_array.rs b/src/measurement/large_array.rs new file mode 100644 index 00000000..7c915625 --- /dev/null +++ b/src/measurement/large_array.rs @@ -0,0 +1,79 @@ +// SPDX-License-Identifier: Apache-2.0 + +//! Helpful structure to deal with arrays with a size larger than 32 bytes + +use crate::error::LargeArrayError; +use serde::{Deserialize, Serialize}; +use serde_big_array::BigArray; +use std::convert::{TryFrom, TryInto}; + +/// Large array structure to serialize and default arrays larger than 32 bytes. +#[derive(Debug, Serialize, Deserialize, Clone, Copy)] +#[repr(C)] +pub struct LargeArray(#[serde(with = "BigArray")] [T; N]) +where + T: for<'a> Deserialize<'a> + Serialize; + +impl Default for LargeArray +where + T: std::marker::Copy + std::default::Default + for<'a> Deserialize<'a> + Serialize, +{ + fn default() -> Self { + Self([T::default(); N]) + } +} + +impl TryFrom> for LargeArray +where + T: std::marker::Copy + std::default::Default + for<'a> Deserialize<'a> + Serialize, +{ + type Error = LargeArrayError; + + fn try_from(vec: Vec) -> Result { + Ok(LargeArray(vec.try_into().map_err(|_| { + LargeArrayError::VectorError("Vector is the wrong size".to_string()) + })?)) + } +} + +impl TryFrom<[T; N]> for LargeArray +where + T: std::marker::Copy + std::default::Default + for<'a> Deserialize<'a> + Serialize, +{ + type Error = LargeArrayError; + + fn try_from(array: [T; N]) -> Result { + Ok(LargeArray(array)) + } +} + +impl TryFrom<&[T]> for LargeArray +where + T: std::marker::Copy + std::default::Default + for<'a> Deserialize<'a> + Serialize, +{ + type Error = LargeArrayError; + + fn try_from(slice: &[T]) -> Result { + Ok(LargeArray(slice.try_into()?)) + } +} + +impl LargeArray +where + T: std::marker::Copy + std::default::Default + for<'a> Deserialize<'a> + Serialize, +{ + /// Get the large array as a regular array format + pub fn as_array(&self) -> [T; N] { + self.0 + } + + /// Get the large array as a slice + pub fn as_slice(&self) -> &[T; N] { + &self.0 + } + + /// Get the large array as a mutable slice + pub fn as_mut_slice(&mut self) -> &mut [T; N] { + &mut self.0 + } +} diff --git a/src/measurement/mod.rs b/src/measurement/mod.rs index 612ca357..3362e597 100644 --- a/src/measurement/mod.rs +++ b/src/measurement/mod.rs @@ -23,3 +23,11 @@ pub mod snp; #[cfg(all(feature = "sev", feature = "openssl"))] pub mod sev; + +#[cfg(all(feature = "snp", feature = "openssl"))] +pub mod idblock; + +#[cfg(all(feature = "snp", feature = "openssl"))] +pub mod idblock_types; + +pub mod large_array; diff --git a/src/measurement/sev.rs b/src/measurement/sev.rs index 6358da3e..85a6275e 100644 --- a/src/measurement/sev.rs +++ b/src/measurement/sev.rs @@ -5,7 +5,7 @@ use crate::measurement::{ ovmf::OVMF, sev_hashes::SevHashes, vcpu_types::CpuType, - vmsa::{SevMode, VMMType, VMSA}, + vmsa::{GuestFeatures, VMMType, VMSA}, }; use std::path::PathBuf; @@ -67,11 +67,11 @@ pub fn seves_calc_launch_digest( }; let vmsa = VMSA::new( - SevMode::SevEs, ovmf.sev_es_reset_eip()?.into(), CpuType::from_str(sev_es_measurement.vcpu_type.as_str())?, official_vmm_type, Some(sev_es_measurement.vcpus as u64), + GuestFeatures(0x0), ); for vmsa_page in vmsa.pages(sev_es_measurement.vcpus as usize)?.iter() { diff --git a/src/measurement/snp.rs b/src/measurement/snp.rs index 433a3ba9..6db51068 100644 --- a/src/measurement/snp.rs +++ b/src/measurement/snp.rs @@ -8,7 +8,7 @@ use crate::{ ovmf::{OvmfSevMetadataSectionDesc, SectionType, OVMF}, sev_hashes::SevHashes, vcpu_types::CpuType, - vmsa::{SevMode, VMMType, VMSA}, + vmsa::{VMMType, VMSA}, }, }; use hex::FromHex; @@ -17,7 +17,7 @@ use std::str::FromStr; use crate::error::*; -use super::gctx::LD_SIZE; +use super::{gctx::LD_SIZE, vmsa::GuestFeatures}; const _PAGE_MASK: u64 = 0xfff; @@ -131,6 +131,8 @@ pub struct SnpMeasurementArgs<'a> { pub vcpu_type: String, /// Path to OVMF file pub ovmf_file: PathBuf, + /// Active kernel guest features + pub guest_features: GuestFeatures, /// Path to kernel file pub kernel_file: Option, /// Path to initrd file @@ -180,11 +182,11 @@ pub fn snp_calc_launch_digest( snp_update_metadata_pages(&mut gctx, &ovmf, sev_hashes.as_ref(), official_vmm_type)?; let vmsa = VMSA::new( - SevMode::SevSnp, ovmf.sev_es_reset_eip()?.into(), CpuType::from_str(snp_measurement.vcpu_type.as_str())?, official_vmm_type, Some(snp_measurement.vcpus as u64), + snp_measurement.guest_features, ); for vmsa_page in vmsa.pages(snp_measurement.vcpus as usize)?.iter() { diff --git a/src/measurement/vmsa.rs b/src/measurement/vmsa.rs index 6b052884..2162d0b3 100644 --- a/src/measurement/vmsa.rs +++ b/src/measurement/vmsa.rs @@ -1,10 +1,12 @@ // SPDX-License-Identifier: Apache-2.0 //! Operations to build and interact with an SEV-ES VMSA -use crate::error::MeasurementError; -use crate::measurement::vcpu_types::CpuType; +use crate::{ + error::MeasurementError, + measurement::{large_array::LargeArray, vcpu_types::CpuType}, +}; +use bitfield::bitfield; use serde::{Deserialize, Serialize}; -use serde_big_array::BigArray; use std::{convert::TryFrom, fmt, str::FromStr}; /// Different Possible SEV modes @@ -97,20 +99,66 @@ impl VmcbSeg { } } -/// Large array structure to serialize and default arrays larger than 32 bytes. -#[derive(Debug, Serialize, Deserialize, Clone, Copy)] -#[repr(C)] -pub(crate) struct LargeArray(#[serde(with = "BigArray")] [T; N]) -where - T: for<'a> Deserialize<'a> + Serialize; - -impl Default for LargeArray -where - T: std::marker::Copy + std::default::Default + for<'a> Deserialize<'a> + Serialize, -{ - fn default() -> Self { - Self([T::default(); N]) - } +bitfield! { + /// Kernel features that when enabled could affect the VMSA. + /// + /// | Bit(s) | Name + /// |--------|------| + /// | 0 | SNPActive | + /// | 1 | vTOM | + /// | 2 | ReflectVC | + /// | 3 | RestrictedInjection | + /// | 4 | AlternateInjection | + /// | 5 | DebugSwap | + /// | 6 | PreventHostIBS | + /// | 7 | BTBIsolation | + /// | 8 | VmplSSS | + /// | 9 | SecureTSC | + /// | 10 | VmgexitParameter | + /// | 11 | Reserved, SBZ | + /// | 12 | IbsVirtualization | + /// | 13 | Reserved, SBZ | + /// | 14 | VmsaRegProt | + /// | 15 | SmtProtection | + /// | 63:16 | Reserved, SBZ | + #[repr(C)] + #[derive(Default, Copy, Clone, PartialEq, Eq, Serialize, Deserialize)] + pub struct GuestFeatures(u64); + impl Debug; + /// SNPActive + pub snp_active, _: 0, 0; + /// vTom + pub v_tom, _: 1, 1; + /// ReflectVC + pub reflect_vc, _: 2, 2; + /// RestrictedInjection + pub restricted_injection, _: 3,3; + /// AlternateInjection + pub alternate_injection, _: 4,4; + /// DebugSwap + pub debug_swap, _: 5,5; + /// PreventHostIbs + pub prevent_host_ibs, _: 6,6; + /// BTBIsolation + pub btb_isolation, _: 7,7; + /// VmplSSS + pub vmpl_sss, _: 8,8; + /// SecureTSC + pub secure_tsc, _: 9,9; + /// VmgExitParameter + pub vmg_exit_parameter, _: 10,10; + /// Reserved, SBZ + reserved_1, _: 11,11; + /// IbsVirtualization + pub ibs_virtualization, _: 12,12; + /// Reserved, SBZ + reserved_2, _: 13,13; + /// VmsaRegProt + pub vmsa_reg_prot, _: 14,14; + ///SmtProtection + pub smt_protection, _: 15,15; + /// Reserved, SBZ + reserved_3, sbz: 16, 63; } /// SEV-ES VMSA page @@ -241,24 +289,19 @@ impl VMSA { /// Generate a new SEV-ES VMSA /// One Bootstrap and an auxiliary save area if needed pub fn new( - sev_mode: SevMode, ap_eip: u64, vcpu_type: CpuType, vmm_type: VMMType, cpu_num: Option, + guest_features: GuestFeatures, ) -> Self { - let sev_features: u64 = match sev_mode { - SevMode::SevSnp => 0x1, - SevMode::Sev | SevMode::SevEs => 0x0, - }; - let bsp_save_area = - Self::build_save_area(BSP_EIP, sev_features, vcpu_type, vmm_type, cpu_num); + Self::build_save_area(BSP_EIP, guest_features, vcpu_type, vmm_type, cpu_num); let ap_save_area = if ap_eip > 0 { Some(Self::build_save_area( ap_eip, - sev_features, + guest_features, vcpu_type, vmm_type, cpu_num, @@ -276,7 +319,7 @@ impl VMSA { /// Generate a save area fn build_save_area( eip: u64, - sev_features: u64, + guest_features: GuestFeatures, vcpu_type: CpuType, vmm_type: VMMType, cpu_num: Option, @@ -338,7 +381,7 @@ impl VMSA { area.rip = eip & 0xffff; area.g_pat = 0x7040600070406; area.rdx = rdx; - area.sev_features = sev_features; + area.sev_features = guest_features.0; area.xcr0 = 0x1; area diff --git a/tests/id-block.rs b/tests/id-block.rs new file mode 100644 index 00000000..5f8680b2 --- /dev/null +++ b/tests/id-block.rs @@ -0,0 +1,168 @@ +// SPDX-License-Identifier: Apache-2.0 + +#![cfg(feature = "openssl")] + +use std::{ + convert::{TryFrom, TryInto}, + fs, + io::Read, + path::PathBuf, +}; + +use base64::{engine::general_purpose, Engine as _}; +use hex::{self, FromHex}; + +use sev::measurement::{ + idblock::{load_priv_key, snp_calculate_id}, + idblock_types::{IdAuth, IdBlockLaunchDigest, SevEcdsaPubKey, SevEcdsaSig}, +}; + +// Testing that the appropriate id-block and key digests are being generated. +#[test] +fn test_id_block_and_key_digests() { + // Expected ID-BLOCk, ID-KEY digest and AUTH-KEY digest + let expected_id_block = "oU1jg0HlZ0Yo/h4C++3r5eWrT68d1y2JZgaOYVu5nv0T0hSzXCMXFbOOJj3GBZdFAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAABAAAAAAAAAAAAMAAAAAAA".to_string(); + let expected_id_key_digest = + "WfssaG36T+9J84M5qndckAUcKNVzg93DuvO160sUlt87DbvcuuX+J2d6ZEggdz/i".to_string(); + let expected_auth_key_digest = + "7tgRafsMteDsNHlPEo/U4MSkb2fGWhrfqB/LJLzAiMJyNpt//D6ytq9qQoShs2xf".to_string(); + + // Pre-generated launch digest + let launch_digest = Vec::from_hex("a14d638341e5674628fe1e02fbedebe5e5ab4faf1dd72d8966068e615bb99efd13d214b35c231715b38e263dc6059745").unwrap(); + let id_launch_digest: IdBlockLaunchDigest = + IdBlockLaunchDigest::new(launch_digest.try_into().unwrap()); + + // Generating ID-block and key digests + let block_calculations = snp_calculate_id( + Some(id_launch_digest), + None, + None, + None, + None, + "./tests/measurement/test_id_key.pem".into(), + "./tests/measurement/test_auth_key.pem".into(), + ) + .unwrap(); + + // Converting ID-block and key digests into BASE64 + let id_block_string = + general_purpose::STANDARD.encode(bincode::serialize(&block_calculations.id_block).unwrap()); + let id_key_digest_string = general_purpose::STANDARD + .encode::>(block_calculations.id_key_digest.try_into().unwrap()); + let auth_key_digest_string = general_purpose::STANDARD + .encode::>(block_calculations.auth_key_digest.try_into().unwrap()); + + // Comparing results + assert_eq!(id_block_string, expected_id_block); + assert_eq!(id_key_digest_string, expected_id_key_digest); + assert_eq!(auth_key_digest_string, expected_auth_key_digest); +} + +/// Using private test keys and a pre-generated signatures, the application should always generate the same AUTH-BLOCK. +#[test] +fn test_auth_block_generation() { + // Expected AUTH-BLOCK + let expected_auth_block = "AQAAAAEAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAOXTv32b+eBhvU6PfGrK4FkJqcPGnwTzGPATXq5x/30F71yMaxvIwEdhOntvAbc42gAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAANQZTcpn3oOB7G0mfAMrA908H16vpqph6Slk5VSL7zMrGukh8m3hasM7ZCdE0Zel2AAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAgAAAB9tPIC4+57yn/fdR+jeP8uV3ZBDh2ixkyxVIzwMR131TMq4yC\ + wv8iwbzwkmTT529gAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD6+k5w4NQsIzZvaUDyFOA39fGAS9xlmdnsyFMveqDetHW2+C\ + hmxUMfBzblH0KjXzAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAC7iHuUVX9Whon/SFXD9+2ZgQw9VOz\ + UKWURNNwY3QIOyqdxo0y5dmZ3GqMfDrRi6pkAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAADyYei5XacbKjVCX0iiGad9uw/uJS\ + 9k0YzpmIi5+09wsQQBG33+o+A0k3Giv69vkTMAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAIAAACMhkXa\ + 1XNJo8X/xsGl90pWnw+DPAeLsizXlHTw/pLOcWpzmFuimWIz0UyQ1ovHcGIAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAD1rId\ + x4JERA8nyov5IQ3cZKsyziMw6/bdZtVY+zQiTX6niAoEIEHqnSITJojB5PIYAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA\ + AAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAAA==" + .to_string(); + + // Test keys in pem format + let id_path: PathBuf = "./tests/measurement/test_id_key.pem".into(); + let auth_path: PathBuf = "./tests/measurement/test_auth_key.pem".into(); + + // Get id private test key from pem + let id_ec_priv_key = load_priv_key(id_path).unwrap(); + + // Generate id public key, should always be the same + let id_ec_pub_key = SevEcdsaPubKey::try_from(&id_ec_priv_key).unwrap(); + + // Get id signature from file (can't regenerate, different each time) + let mut id_sig_file = fs::File::open("./tests/measurement/test_id_sig.bin").unwrap(); + let mut id_block_bytes = Vec::new(); + id_sig_file.read_to_end(&mut id_block_bytes).unwrap(); + let id_block_sig: SevEcdsaSig = bincode::deserialize(&id_block_bytes).unwrap(); + + // Get author private test key from pem + let author_ec_priv_key = load_priv_key(auth_path).unwrap(); + + // Generate author public key, should always be the same + let author_pub_key = SevEcdsaPubKey::try_from(&author_ec_priv_key).unwrap(); + + // Get auth signature from file (can't regenerate, different each time) + let mut auth_sig_file = fs::File::open("./tests/measurement/test_auth_sig.bin").unwrap(); + let mut auth_block_bytes = Vec::new(); + auth_sig_file.read_to_end(&mut auth_block_bytes).unwrap(); + let auth_block_sig: SevEcdsaSig = bincode::deserialize(&auth_block_bytes).unwrap(); + + let auth_block = IdAuth::new( + None, + None, + id_block_sig, + id_ec_pub_key, + auth_block_sig, + author_pub_key, + ); + + // Generate Generate auth_block string + let id_auth_bytes = bincode::serialize(&auth_block).unwrap(); + let id_auth_str = general_purpose::STANDARD.encode(id_auth_bytes); + + // Comparing auth_blocks + assert_eq!(id_auth_str, expected_auth_block); +} diff --git a/tests/measurement.rs b/tests/measurement.rs index 24da9ccc..92666d6a 100644 --- a/tests/measurement.rs +++ b/tests/measurement.rs @@ -4,16 +4,20 @@ #[cfg(feature = "snp")] mod snp_tests { - use sev::measurement::{snp::*, vmsa::VMMType}; - // Testing if we can generate a good OVMF hash + use sev::measurement::{ + snp::*, + vmsa::{GuestFeatures, VMMType}, + }; + // Test if we can compute a full LD from a pre generated hash using snp only kernel #[test] - fn test_snp_ovmf_hash_gen() { + fn test_snp_ovmf_hash_gen_snp_only() { let ovmf_hash = "cab7e085874b3acfdbe2d96dcaa3125111f00c35c6fc9708464c2ae74bfdb048a198cb9a9ccae0b3e5e1a33f5f249819"; let arguments = SnpMeasurementArgs { vcpus: 1, vcpu_type: "EPYC-v4".into(), ovmf_file: "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), + guest_features: GuestFeatures(0x1), kernel_file: Some("/dev/null".into()), initrd_file: Some("/dev/null".into()), append: None, @@ -30,9 +34,35 @@ mod snp_tests { assert_eq!(ld_hex.as_str(), exp_result); } - // Test if we can compute a full LD from the OVMF hash + // Test if we can compute a full LD from a pre generated hash using the default kernel setting #[test] - fn test_snp_ovmf_hash_full() { + fn test_snp_ovmf_hash_gen_default() { + let ovmf_hash = "cab7e085874b3acfdbe2d96dcaa3125111f00c35c6fc9708464c2ae74bfdb048a198cb9a9ccae0b3e5e1a33f5f249819"; + + let arguments = SnpMeasurementArgs { + vcpus: 1, + vcpu_type: "EPYC-v4".into(), + ovmf_file: "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), + guest_features: GuestFeatures(0x21), + kernel_file: Some("/dev/null".into()), + initrd_file: Some("/dev/null".into()), + append: None, + ovmf_hash_str: Some(ovmf_hash), + vmm_type: Some(VMMType::QEMU), + }; + + let ld = snp_calc_launch_digest(arguments).unwrap(); + + let ld_hex = hex::encode(ld); + + let exp_result = "a076e1b0e6cf55fd94c82e2c25245f8c15f76690b941ba379b31527f82eafe7ad489777ff510d080bac9cd14d41bc205"; + + assert_eq!(ld_hex.as_str(), exp_result); + } + + // Test if we can compute a full LD from the OVMF hash usin snp only kernel + #[test] + fn test_snp_ovmf_hash_full_snp_only() { let ovmf_hash = hex::encode( calc_snp_ovmf_hash("./tests/measurement/ovmf_AmdSev_suffix.bin".into()).unwrap(), ); @@ -45,6 +75,7 @@ mod snp_tests { vcpus: 1, vcpu_type: "EPYC-v4".into(), ovmf_file: "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), + guest_features: GuestFeatures(0x1), kernel_file: Some("/dev/null".into()), initrd_file: Some("/dev/null".into()), append: Some("console=ttyS0 loglevel=7"), @@ -61,13 +92,46 @@ mod snp_tests { assert_eq!(ld_hex.as_str(), exp_result); } - // Test EC2 vmm type + // Test if we can compute a full LD from the OVMF hash using default kernel #[test] - fn test_snp_ec2() { + fn test_snp_ovmf_hash_full_default() { + let ovmf_hash = hex::encode( + calc_snp_ovmf_hash("./tests/measurement/ovmf_AmdSev_suffix.bin".into()).unwrap(), + ); + + let exp_hash = "edcf6d1c57ce868a167c990f58c8667c698269ef9e0803246419eea914186343054d557e1f17acd93b032c106bc70d25"; + + assert_eq!(ovmf_hash.as_str(), exp_hash); + let arguments = SnpMeasurementArgs { vcpus: 1, vcpu_type: "EPYC-v4".into(), ovmf_file: "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), + guest_features: GuestFeatures(0x21), + kernel_file: Some("/dev/null".into()), + initrd_file: Some("/dev/null".into()), + append: Some("console=ttyS0 loglevel=7"), + ovmf_hash_str: Some(ovmf_hash.as_str()), + vmm_type: None, + }; + + let ld = snp_calc_launch_digest(arguments).unwrap(); + + let ld_hex = hex::encode(ld); + + let exp_result = "314e4f0794187ffef05702a36546ea5fe02698041b7f7f17d9f418da2d5e4d5cff25256cef9d34888a0dd64dea438780"; + + assert_eq!(ld_hex.as_str(), exp_result); + } + + // Test EC2 vmm type with SNP only kernel + #[test] + fn test_snp_ec2_snp_only() { + let arguments = SnpMeasurementArgs { + vcpus: 1, + vcpu_type: "EPYC-v4".into(), + ovmf_file: "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), + guest_features: GuestFeatures(0x1), kernel_file: Some("/dev/null".into()), initrd_file: Some("/dev/null".into()), append: None, @@ -84,13 +148,38 @@ mod snp_tests { assert_eq!(ld_hex.as_str(), exp_result); } - // Test a regular snp type + // Test EC2 vmm type with default kernel #[test] - fn test_snp() { + fn test_snp_ec2_default() { let arguments = SnpMeasurementArgs { vcpus: 1, vcpu_type: "EPYC-v4".into(), ovmf_file: "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), + guest_features: GuestFeatures(0x21), + kernel_file: Some("/dev/null".into()), + initrd_file: Some("/dev/null".into()), + append: None, + ovmf_hash_str: None, + vmm_type: Some(VMMType::EC2), + }; + + let ld = snp_calc_launch_digest(arguments).unwrap(); + + let ld_hex = hex::encode(ld); + + let exp_result = "cd4a4690a1f679ac8f3d6e446aab8d0061d535cc94615d98c7d7dbe4b16dbceeaf7fc7944e7874b202e27041f179e7e6"; + + assert_eq!(ld_hex.as_str(), exp_result); + } + + // Test a regular snp type with snp only kernel + #[test] + fn test_snp_snp_only() { + let arguments = SnpMeasurementArgs { + vcpus: 1, + vcpu_type: "EPYC-v4".into(), + ovmf_file: "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), + guest_features: GuestFeatures(0x1), kernel_file: Some("/dev/null".into()), initrd_file: Some("/dev/null".into()), append: Some("console=ttyS0 loglevel=7"), @@ -107,13 +196,38 @@ mod snp_tests { assert_eq!(ld_hex.as_str(), exp_result); } - // Test a regular snp without specified kernel + // Test a regular snp type with default kernel + #[test] + fn test_snp_default() { + let arguments = SnpMeasurementArgs { + vcpus: 1, + vcpu_type: "EPYC-v4".into(), + ovmf_file: "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), + guest_features: GuestFeatures(0x21), + kernel_file: Some("/dev/null".into()), + initrd_file: Some("/dev/null".into()), + append: Some("console=ttyS0 loglevel=7"), + ovmf_hash_str: None, + vmm_type: None, + }; + + let ld = snp_calc_launch_digest(arguments).unwrap(); + + let ld_hex = hex::encode(ld); + + let exp_result = "314e4f0794187ffef05702a36546ea5fe02698041b7f7f17d9f418da2d5e4d5cff25256cef9d34888a0dd64dea438780"; + + assert_eq!(ld_hex.as_str(), exp_result); + } + + // Test a regular snp without specified kernel using an snp only guest kernel #[test] - fn test_snp_without_kernel() { + fn test_snp_without_kernel_snp_only() { let arguments = SnpMeasurementArgs { vcpus: 1, vcpu_type: "EPYC-v4".into(), ovmf_file: "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), + guest_features: GuestFeatures(0x1), kernel_file: None, initrd_file: None, append: None, @@ -130,13 +244,38 @@ mod snp_tests { assert_eq!(ld_hex.as_str(), exp_result); } - // Test snp with multiple cpus + // Test a regular snp without specified kernel using a default guest kernel + #[test] + fn test_snp_without_kernel_default() { + let arguments = SnpMeasurementArgs { + vcpus: 1, + vcpu_type: "EPYC-v4".into(), + ovmf_file: "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), + guest_features: GuestFeatures(0x21), + kernel_file: None, + initrd_file: None, + append: None, + ovmf_hash_str: None, + vmm_type: None, + }; + + let ld = snp_calc_launch_digest(arguments).unwrap(); + + let ld_hex = hex::encode(ld); + + let exp_result = "6d9054ed9872a64c968cfbcfa1247cafa792e3f9a395306d95c9937aaa081c643d25f369ccbd34409dafcae90bff55f3"; + + assert_eq!(ld_hex.as_str(), exp_result); + } + + // Test snp with multiple cpus with an snp only guest kernel #[test] - fn test_snp_with_multiple_vcpus() { + fn test_snp_with_multiple_vcpus_snp_only() { let arguments = SnpMeasurementArgs { vcpus: 4, vcpu_type: "EPYC-v4".into(), ovmf_file: "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), + guest_features: GuestFeatures(0x1), kernel_file: Some("/dev/null".into()), initrd_file: Some("/dev/null".into()), append: None, @@ -153,13 +292,38 @@ mod snp_tests { assert_eq!(ld_hex.as_str(), exp_result); } - // Test snp with with ovmf64 and no kernel + // Test snp with multiple cpus with a default guest kernel #[test] - fn test_snp_with_ovmfx64_without_kernel() { + fn test_snp_with_multiple_vcpus_default() { + let arguments = SnpMeasurementArgs { + vcpus: 4, + vcpu_type: "EPYC-v4".into(), + ovmf_file: "./tests/measurement/ovmf_AmdSev_suffix.bin".into(), + guest_features: GuestFeatures(0x21), + kernel_file: Some("/dev/null".into()), + initrd_file: Some("/dev/null".into()), + append: None, + ovmf_hash_str: None, + vmm_type: None, + }; + + let ld = snp_calc_launch_digest(arguments).unwrap(); + + let ld_hex = hex::encode(ld); + + let exp_result = "3aa1bdf5a87fad15960f099e82a09e428901c590f2b68d71aa246c168db5e75daf4819d017a9530c56bed2da5c0cdbd7"; + + assert_eq!(ld_hex.as_str(), exp_result); + } + + // Test snp with with ovmf64 and no kernel using a snp only guest kernel + #[test] + fn test_snp_with_ovmfx64_without_kernel_snp_only() { let arguments = SnpMeasurementArgs { vcpus: 1, vcpu_type: "EPYC-v4".into(), ovmf_file: "./tests/measurement/ovmf_OvmfX64_suffix.bin".into(), + guest_features: GuestFeatures(0x1), kernel_file: None, initrd_file: None, append: None, @@ -176,6 +340,30 @@ mod snp_tests { assert_eq!(ld_hex.as_str(), exp_result); } + // Test snp with with ovmf64 and no kernel using a default guest kernel + #[test] + fn test_snp_with_ovmfx64_without_kernel_default() { + let arguments = SnpMeasurementArgs { + vcpus: 1, + vcpu_type: "EPYC-v4".into(), + ovmf_file: "./tests/measurement/ovmf_OvmfX64_suffix.bin".into(), + guest_features: GuestFeatures(0x21), + kernel_file: None, + initrd_file: None, + append: None, + ovmf_hash_str: None, + vmm_type: None, + }; + + let ld = snp_calc_launch_digest(arguments).unwrap(); + + let ld_hex = hex::encode(ld); + + let exp_result = "37a9efc939f360a9ccfaaf1a7702137b81ea00c38d0361c8523285fad1b10e94ad8c1ecd7c82ff589cb120670be74a99"; + + assert_eq!(ld_hex.as_str(), exp_result); + } + // Test non-SNP OVMF and SNP measure should fail #[test] #[should_panic( @@ -186,6 +374,7 @@ mod snp_tests { vcpus: 1, vcpu_type: "EPYC-v4".into(), ovmf_file: "./tests/measurement/ovmf_OvmfX64_suffix.bin".into(), + guest_features: GuestFeatures(0x21), kernel_file: Some("/dev/null".into()), initrd_file: Some("/dev/null".into()), append: None, diff --git a/tests/measurement/test_auth_block.bin b/tests/measurement/test_auth_block.bin new file mode 100644 index 00000000..4bd15cb1 Binary files /dev/null and b/tests/measurement/test_auth_block.bin differ diff --git a/tests/measurement/test_auth_key.pem b/tests/measurement/test_auth_key.pem new file mode 100644 index 00000000..1b74b7e8 --- /dev/null +++ b/tests/measurement/test_auth_key.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDAyp3rzsOsAMVIVbtmS +QEU83YC5r0EOWZfSX3wwiBQytEjoVsXDBPTpvvyzM2aZ27ChZANiAARicMeL1pBM +0TNimaJbmHNqcc6S/vB0lNcssosHPIMPn1ZK96XBxv/Fo0lz1dpFhoyGPHkwosmE +SKd6EAiBAuKpX5MIzT5WtVm3/TrMiLPMKhl3Q0j+ovLJAxGR4HGHrPU= +-----END PRIVATE KEY----- diff --git a/tests/measurement/test_auth_sig.bin b/tests/measurement/test_auth_sig.bin new file mode 100644 index 00000000..def7b06d Binary files /dev/null and b/tests/measurement/test_auth_sig.bin differ diff --git a/tests/measurement/test_id_key.pem b/tests/measurement/test_id_key.pem new file mode 100644 index 00000000..b3ff65dc --- /dev/null +++ b/tests/measurement/test_id_key.pem @@ -0,0 +1,6 @@ +-----BEGIN PRIVATE KEY----- +MIG2AgEAMBAGByqGSM49AgEGBSuBBAAiBIGeMIGbAgEBBDDPZQkU2FP1ii90R+e7 +/ShQ/rzUzsS8X7kVJ0nJAxcrS1oMD8nKjfsKKfBWJ8nbCyqhZANiAAT2dj5NJgnP +GyzyLyzIuMpM9V1HDDwjVSyTsWiHQ5Ddlcs/3uhH3fef8p77uIA8bR/M16jQR7nN +wcdQsRkKvm0drTeo3ssUMnt2Zhn3EmB8/Q04hTxQ2pvNCAs1OJyTvj4= +-----END PRIVATE KEY----- diff --git a/tests/measurement/test_id_sig.bin b/tests/measurement/test_id_sig.bin new file mode 100644 index 00000000..d8ac2146 Binary files /dev/null and b/tests/measurement/test_id_sig.bin differ