diff --git a/Cargo.lock b/Cargo.lock index 4e538cc9..e8c989f8 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 27937811..7866d056 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 86412d34..13b3cd6e 100644 --- a/src/error.rs +++ b/src/error.rs @@ -720,6 +720,87 @@ impl std::fmt::Display for SevHashError { impl std::error::Error for SevHashError {} +/// Possible errors when working with the large array type +#[derive(Debug)] +pub struct LargeArrayError { + pub(crate) error_msg: String, +} + +impl std::fmt::Display for LargeArrayError { + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { + write!(f, "Error when handling large array: {}", self.error_msg) + } +} + +impl std::error::Error for LargeArrayError {} + +/// 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 { @@ -747,6 +828,9 @@ pub enum MeasurementError { /// SEV Hash Error Handling SevHashError(SevHashError), + /// Id Block Error Handling + IdBlockError(IdBlockError), + /// Invalid VCPU provided InvalidVcpuTypeError(String), @@ -774,6 +858,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..cd32f518 --- /dev/null +++ b/src/measurement/idblock.rs @@ -0,0 +1,118 @@ +// 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_from_pem(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_from_pem(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, + )) +} + +///Read a pem key file and return a private EcKey. +/// Key has to be an EC P-384 key. +pub fn load_priv_key_from_pem(path: PathBuf) -> Result, IdBlockError> { + let mut pem_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 pem_data) + .map_err(IdBlockError::FileError)?; + + let pkey = EcKey::private_key_from_pem(&pem_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_from_pem(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..8ac76323 --- /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::vmsa::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/mod.rs b/src/measurement/mod.rs index 424ebf80..c76e7699 100644 --- a/src/measurement/mod.rs +++ b/src/measurement/mod.rs @@ -23,3 +23,9 @@ 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; diff --git a/src/measurement/vmsa.rs b/src/measurement/vmsa.rs index 6b052884..fd89d441 100644 --- a/src/measurement/vmsa.rs +++ b/src/measurement/vmsa.rs @@ -1,7 +1,7 @@ // SPDX-License-Identifier: Apache-2.0 //! Operations to build and interact with an SEV-ES VMSA -use crate::error::MeasurementError; +use crate::error::{LargeArrayError, MeasurementError}; use crate::measurement::vcpu_types::CpuType; use serde::{Deserialize, Serialize}; use serde_big_array::BigArray; @@ -100,7 +100,7 @@ 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]) +pub struct LargeArray(#[serde(with = "BigArray")] [T; N]) where T: for<'a> Deserialize<'a> + Serialize; @@ -113,6 +113,71 @@ where } } +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 { + if vec.len() == N { + let mut array = [Default::default(); N]; + array.copy_from_slice(&vec[..]); + Ok(LargeArray(array)) + } else { + Err(LargeArrayError { + error_msg: "Incorrect vecotr size for conversion to LargeArray".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<&[u8]> for LargeArray +where + T: std::marker::Copy + std::default::Default + for<'a> Deserialize<'a> + Serialize, +{ + type Error = LargeArrayError; + + fn try_from(slice: &[u8]) -> Result { + if slice.len() == N { + let mut array = [Default::default(); N]; + for (size, i) in array.iter_mut().enumerate().take(N) { + let offset = size * std::mem::size_of::(); + *i = unsafe { + // Safe because we checked that the slice length matches the array size + *(slice.get_unchecked(offset) as *const u8 as *const T) + }; + } + Ok(LargeArray(array)) + } else { + Err(LargeArrayError { + error_msg: "Incorrect vecotr size for conversion to LargeArray".to_string(), + }) + } + } +} + +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 + } +} + /// SEV-ES VMSA page /// The names of the fields are taken from struct sev_es_work_area in the linux kernel: /// https://github.com/AMDESE/linux/blob/sev-snp-v12/arch/x86/include/asm/svm.h#L318 diff --git a/tests/id-block.rs b/tests/id-block.rs new file mode 100644 index 00000000..467593cb --- /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_from_pem, 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_from_pem(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_from_pem(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).to_string(); + + // Comparing auth_blocks + assert_eq!(id_auth_str, expected_auth_block); +} 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