diff --git a/Cargo.toml b/Cargo.toml index afdd551..f6ab5d0 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,9 @@ getrandom = { version = "0.2", optional = true, features = ["js"] } serde-wasm-bindgen = { version = "0.4", optional = true} wasm-bindgen = { version = "0.2.95", optional = true } serde_bytes = { version = "0.11" } +proptest = "1.5.0" +sha2 = "0.10.8" +sha3 = "0.10.8" [dependencies.webpki] version = "0.102.8" diff --git a/src/quote.rs b/src/quote.rs index 02076f5..4e8d2e0 100644 --- a/src/quote.rs +++ b/src/quote.rs @@ -2,12 +2,184 @@ use alloc::string::String; use alloc::vec::Vec; use anyhow::Result; -use scale::{Decode, Input}; +use proptest::prelude::*; +use scale::{Decode, Encode, Input}; use serde::{Deserialize, Serialize}; +use sha2::{Sha384,Digest}; use crate::{constants::*, utils, Error}; -#[derive(Debug, Clone)] +#[derive(Decode, Encode, PartialEq, Eq, Debug, Serialize)] +pub struct TdxEventLog { + /// IMR index, starts from 0 + pub imr: u32, + /// Event type + pub event_type: u32, + /// Digest + #[serde(serialize_with = "hex::serialize")] + pub digest: [u8; 48], + /// Event name + pub event: String, + /// Event payload + #[serde(serialize_with = "hex::serialize")] + pub event_payload: Vec, +} + +// Add this helper module for hex serialization +mod hex { + use serde::Serializer; + + pub fn serialize(bytes: T, serializer: S) -> Result + where + S: Serializer, + T: AsRef<[u8]>, + { + serializer.serialize_str(&hex::encode(bytes)) + } +} + +impl Arbitrary for TdxEventLog { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + // Create a strategy for imr that only generates values 0-3, which represent to rtmr0 ~ rtmr3 + let imr_strategy = 0..=3u32; + + ( + imr_strategy, + any::(), + any::<[u8; 48]>(), + any::>(), + ) + .prop_map(|(imr, event_type, digest, event_payload)| { + TdxEventLog { + imr, + event_type, + digest, + event: "".to_string(), + event_payload, + } + }) + .boxed() + } +} + +#[derive(Debug)] +pub struct TdxEventLogsParams { + pub imr0_size: Option, + pub imr1_size: Option, + pub imr2_size: Option, + pub imr3_size: Option, +} + +impl Default for TdxEventLogsParams { + fn default() -> Self { + TdxEventLogsParams { + imr0_size: None, + imr1_size: None, + imr2_size: None, + imr3_size: None, + } + } +} + +#[derive(Decode, Encode, PartialEq, Eq, Debug, Serialize)] +pub struct TdxEventLogs { + pub logs: Vec, +} + +impl TdxEventLogs { + /// Convert event logs to a vector of RTMR values by accumulating digests with SHA384. + /// Returns a vector of 48-byte arrays representing the RTMR values, + /// where the index corresponds to the IMR index. + pub fn get_rtmr(&self) -> Vec<[u8; 48]> { + let mut rtmrs = vec![[0u8; 48]; 4]; // Initialize with 4 zero-filled RTMRs + for imr_idx in 0..4 { + let mut current_rtmr = [0u8; 48]; + let imr_events: Vec<_> = self.logs.iter() + .filter(|event| event.imr == imr_idx as u32) + .collect(); + if !imr_events.is_empty() { + for event in imr_events { + let mut hasher = Sha384::new(); + hasher.update(¤t_rtmr); + hasher.update(&event.digest); + current_rtmr = hasher.finalize().into(); + } + } + rtmrs[imr_idx] = current_rtmr; + } + rtmrs + } + + /// Convert event logs to a JSON string + pub fn to_json(&self) -> Result { + serde_json::to_string(&self.logs) + } +} + +impl Arbitrary for TdxEventLogs { + type Parameters = TdxEventLogsParams; + type Strategy = BoxedStrategy; + + fn arbitrary_with(args: Self::Parameters) -> Self::Strategy { + let default_imr0_size = 6..=12usize; + let default_remaining_size = 1..=8usize; + + let imr0_strategy = match args.imr0_size { + Some(size) => size..=size, + None => default_imr0_size, + }; + let imr1_strategy = match args.imr1_size { + Some(size) => size..=size, + None => default_remaining_size.clone(), + }; + let imr2_strategy = match args.imr2_size { + Some(size) => size..=size, + None => default_remaining_size.clone(), + }; + let imr3_strategy = match args.imr3_size { + Some(size) => size..=size, + None => default_remaining_size, + }; + + ( + prop::collection::vec(any::().prop_map(|mut log| { + log.imr = 0; + log + }), imr0_strategy), + prop::collection::vec(any::().prop_map(|mut log| { + log.imr = 1; + log + }), imr1_strategy), + prop::collection::vec(any::().prop_map(|mut log| { + log.imr = 2; + log + }), imr2_strategy), + prop::collection::vec(any::().prop_map(|mut log| { + log.imr = 3; + log + }), imr3_strategy), + ) + .prop_map(|(mut imr0_logs, mut imr1_logs, mut imr2_logs, mut imr3_logs)| { + let mut logs = Vec::new(); + logs.append(&mut imr0_logs); + logs.append(&mut imr1_logs); + logs.append(&mut imr2_logs); + logs.append(&mut imr3_logs); + if logs.len() > 32 { + logs.sort_by_key(|log| log.imr); + logs.truncate(32); + debug_assert!(logs.iter().filter(|log| log.imr == 0).count() >= 6); + } + TdxEventLogs { logs } + }) + .boxed() + } +} + +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Data { pub data: Vec, _marker: core::marker::PhantomData, @@ -25,7 +197,21 @@ impl> Decode for Data { } } -#[derive(Decode, Debug)] +impl Encode for Data +where + T: Encode + TryFrom, + >::Error: std::fmt::Debug, +{ + fn encode(&self) -> Vec { + let mut encoded = Vec::new(); + let len = T::try_from(self.data.len()).expect("Length conversion failed"); + encoded.extend(len.encode()); + encoded.extend(&self.data); + encoded + } +} + +#[derive(Decode, Encode, PartialEq, Eq, Debug)] pub struct Header { pub version: u16, pub attestation_key_type: u16, @@ -36,13 +222,89 @@ pub struct Header { pub user_data: [u8; 20], } -#[derive(Decode, Debug)] +impl Arbitrary for Header { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + let version_strategy = prop_oneof![Just(3u16), Just(4u16), Just(5u16)]; + let attestation_key_strategy = prop_oneof![Just(2u16), Just(3u16)]; + let tee_type_strategy = prop::strategy::Union::new_weighted(vec![ + (1, Just(TEE_TYPE_SGX).boxed()), + (1, Just(TEE_TYPE_TDX).boxed()), + ]); + + ( + version_strategy, + attestation_key_strategy, + tee_type_strategy, + any::(), + any::(), + any::<[u8; 16]>(), + any::<[u8; 20]>(), + any::(), + ) + .prop_flat_map( + |( + version, + attestation_key_type, + tee_type, + qe_svn, + pce_svn, + qe_vendor_id, + user_data, + v5_tee_type, + )| { + let tee_type = match version { + 3 => TEE_TYPE_SGX, + 4 => tee_type, + 5 => v5_tee_type, + _ => unreachable!(), + }; + + ( + Just(version), + Just(attestation_key_type), + Just(tee_type), + Just(qe_svn), + Just(pce_svn), + Just(qe_vendor_id), + Just(user_data), + ) + }, + ) + .prop_map( + |( + version, + attestation_key_type, + tee_type, + qe_svn, + pce_svn, + qe_vendor_id, + user_data, + )| { + Header { + version, + attestation_key_type, + tee_type, + qe_svn, + pce_svn, + qe_vendor_id, + user_data, + } + }, + ) + .boxed() + } +} + +#[derive(Decode, Encode, PartialEq, Eq, Debug)] pub struct Body { pub body_type: u16, pub size: u32, } -#[derive(Serialize, Deserialize, Decode, Debug, Clone)] +#[derive(Serialize, Deserialize, Decode, Encode, PartialEq, Eq, Debug, Clone)] pub struct EnclaveReport { #[serde(with = "serde_bytes")] pub cpu_svn: [u8; 16], @@ -67,7 +329,61 @@ pub struct EnclaveReport { pub report_data: [u8; 64], } -#[derive(Decode, Debug, Clone, Serialize, Deserialize)] +impl Arbitrary for EnclaveReport { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + ( + any::<[u8; 16]>(), + any::(), + any::<[u8; 28]>(), + any::<[u8; 16]>(), + any::<[u8; 32]>(), + any::<[u8; 32]>(), + any::<[u8; 32]>(), + any::<[u8; 96]>(), + any::(), + any::(), + any::<[u8; 60]>(), + any::<[u8; 64]>(), + ) + .prop_map( + |( + cpu_svn, + misc_select, + reserved1, + attributes, + mr_enclave, + reserved2, + mr_signer, + reserved3, + isv_prod_id, + isv_svn, + reserved4, + report_data, + )| { + EnclaveReport { + cpu_svn, + misc_select, + reserved1, + attributes, + mr_enclave, + reserved2, + mr_signer, + reserved3, + isv_prod_id, + isv_svn, + reserved4, + report_data, + } + }, + ) + .boxed() + } +} + +#[derive(Decode, Encode, PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] pub struct TDReport10 { #[serde(with = "serde_bytes")] pub tee_tcb_svn: [u8; 16], @@ -101,7 +417,79 @@ pub struct TDReport10 { pub report_data: [u8; 64], } -#[derive(Decode, Debug, Clone, Serialize, Deserialize)] +impl Arbitrary for TDReport10 { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + let part1 = ( + any::<[u8; 16]>(), + any::<[u8; 48]>(), + any::<[u8; 48]>(), + any::<[u8; 8]>(), + any::<[u8; 8]>(), + any::<[u8; 8]>(), + any::<[u8; 48]>(), + ); + + let part2 = ( + any::<[u8; 48]>(), + any::<[u8; 48]>(), + any::<[u8; 48]>(), + any::<[u8; 48]>(), + any::<[u8; 48]>(), + any::<[u8; 48]>(), + any::<[u8; 48]>(), + any::<[u8; 64]>(), + ); + + (part1, part2) + .prop_map( + |( + ( + tee_tcb_svn, + mr_seam, + mr_signer_seam, + seam_attributes, + td_attributes, + xfam, + mr_td, + ), + ( + mr_config_id, + mr_owner, + mr_owner_config, + rt_mr0, + rt_mr1, + rt_mr2, + rt_mr3, + report_data, + ), + )| { + TDReport10 { + tee_tcb_svn, + mr_seam, + mr_signer_seam, + seam_attributes, + td_attributes, + xfam, + mr_td, + mr_config_id, + mr_owner, + mr_owner_config, + rt_mr0, + rt_mr1, + rt_mr2, + rt_mr3, + report_data, + } + }, + ) + .boxed() + } +} + +#[derive(Decode, Encode, PartialEq, Eq, Debug, Clone, Serialize, Deserialize)] pub struct TDReport15 { pub base: TDReport10, #[serde(with = "serde_bytes")] @@ -110,12 +498,48 @@ pub struct TDReport15 { pub mr_service_td: [u8; 48], } -#[derive(Decode)] +impl Arbitrary for TDReport15 { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + (any::(), any::<[u8; 16]>(), any::<[u8; 48]>()) + .prop_map(|(base, tee_tcb_svn2, mr_service_td)| TDReport15 { + base, + tee_tcb_svn2, + mr_service_td, + }) + .boxed() + } +} + +#[derive(Decode, Encode, PartialEq, Eq)] pub struct CertificationData { pub cert_type: u16, pub body: Data, } +impl Arbitrary for CertificationData { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + any::() + .prop_map(|cert_type| { + let data = vec![0u8; 10]; + let body = Data { + data, + _marker: core::marker::PhantomData, + }; + CertificationData { + cert_type, + body: body, + } + }) + .boxed() + } +} + impl core::fmt::Debug for CertificationData { fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { let body_str = String::from_utf8_lossy(&self.body.data); @@ -126,7 +550,7 @@ impl core::fmt::Debug for CertificationData { } } -#[derive(Decode, Debug)] +#[derive(Decode, Encode, PartialEq, Eq, Debug)] pub struct QEReportCertificationData { pub qe_report: [u8; ENCLAVE_REPORT_BYTE_LEN], pub qe_report_signature: [u8; QE_REPORT_SIG_BYTE_LEN], @@ -134,7 +558,34 @@ pub struct QEReportCertificationData { pub certification_data: CertificationData, } -#[derive(Decode, Debug)] +impl Arbitrary for QEReportCertificationData { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + ( + any::<[u8; ENCLAVE_REPORT_BYTE_LEN]>(), + any::<[u8; QE_REPORT_SIG_BYTE_LEN]>(), + any::(), + ) + .prop_map(|(qe_report, qe_report_signature, certification_data)| { + let data = vec![0u8; 10]; + let qe_auth_data = Data { + data, + _marker: core::marker::PhantomData, + }; + QEReportCertificationData { + qe_report, + qe_report_signature, + qe_auth_data, + certification_data, + } + }) + .boxed() + } +} + +#[derive(Decode, Encode, PartialEq, Eq, Debug)] pub struct AuthDataV3 { pub ecdsa_signature: [u8; ECDSA_SIGNATURE_BYTE_LEN], pub ecdsa_attestation_key: [u8; ECDSA_PUBKEY_BYTE_LEN], @@ -144,7 +595,46 @@ pub struct AuthDataV3 { pub certification_data: CertificationData, } -#[derive(Debug)] +impl Arbitrary for AuthDataV3 { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + ( + any::<[u8; ECDSA_SIGNATURE_BYTE_LEN]>(), + any::<[u8; ECDSA_PUBKEY_BYTE_LEN]>(), + any::<[u8; ENCLAVE_REPORT_BYTE_LEN]>(), + any::<[u8; QE_REPORT_SIG_BYTE_LEN]>(), + any::(), + ) + .prop_map( + |( + ecdsa_signature, + ecdsa_attestation_key, + qe_report, + qe_report_signature, + certification_data, + )| { + let data = vec![0u8; 10]; + let qe_auth_data = Data { + data, + _marker: core::marker::PhantomData, + }; + AuthDataV3 { + ecdsa_signature, + ecdsa_attestation_key, + qe_report, + qe_report_signature, + qe_auth_data, + certification_data, + } + }, + ) + .boxed() + } +} + +#[derive(Debug, PartialEq, Eq)] pub struct AuthDataV4 { pub ecdsa_signature: [u8; ECDSA_SIGNATURE_BYTE_LEN], pub ecdsa_attestation_key: [u8; ECDSA_PUBKEY_BYTE_LEN], @@ -152,6 +642,35 @@ pub struct AuthDataV4 { pub qe_report_data: QEReportCertificationData, } +impl Arbitrary for AuthDataV4 { + type Parameters = (); + type Strategy = BoxedStrategy; + + fn arbitrary_with(_args: Self::Parameters) -> Self::Strategy { + ( + any::<[u8; ECDSA_SIGNATURE_BYTE_LEN]>(), + any::<[u8; ECDSA_PUBKEY_BYTE_LEN]>(), + any::(), + ) + .prop_map(|(ecdsa_signature, ecdsa_attestation_key, qe_report_data)| { + let certification_data = CertificationData { + cert_type: 0, + body: Data { + data: qe_report_data.encode(), + _marker: core::marker::PhantomData, + }, + }; + AuthDataV4 { + ecdsa_signature, + ecdsa_attestation_key, + certification_data, + qe_report_data, + } + }) + .boxed() + } +} + impl AuthDataV4 { pub fn into_v3(self) -> AuthDataV3 { AuthDataV3 { @@ -181,6 +700,16 @@ impl Decode for AuthDataV4 { } } +impl Encode for AuthDataV4 { + fn encode(&self) -> Vec { + let mut encoded = Vec::new(); + encoded.extend(self.ecdsa_signature.encode()); + encoded.extend(self.ecdsa_attestation_key.encode()); + encoded.extend(self.certification_data.encode()); + encoded + } +} + #[derive(Debug)] pub enum AuthData { V3(AuthDataV3), diff --git a/tests/encode_quote.rs b/tests/encode_quote.rs new file mode 100644 index 0000000..1f9cc42 --- /dev/null +++ b/tests/encode_quote.rs @@ -0,0 +1,84 @@ +use dcap_qvl::quote::{ + AuthDataV3, AuthDataV4, CertificationData, EnclaveReport, Header, QEReportCertificationData, + TDReport10, TDReport15, +}; +use proptest::prelude::*; +use scale::{Decode, Encode}; + +proptest! { + #[test] + fn test_header_constraints(header: Header) { + prop_assert!(header.version == 3 || header.version == 4 || header.version == 5); + + match header.version { + 3 => prop_assert_eq!(header.tee_type, 0x00000000), + 4 => prop_assert!(header.tee_type == 0x00000000 || header.tee_type == 0x00000081), + 5 => (), + _ => panic!("Unexpected version"), + } + } + + #[test] + fn test_header_encode_decode(header: Header) { + let mut encoded = vec![]; + header.encode_to(&mut encoded); + let decoded = Header::decode(&mut encoded.as_slice()).unwrap(); + prop_assert_eq!(header, decoded); + } + + #[test] + fn test_enclave_report_encode_decode(enclave_report: EnclaveReport) { + let mut encoded = vec![]; + enclave_report.encode_to(&mut encoded); + let decoded = EnclaveReport::decode(&mut encoded.as_slice()).unwrap(); + prop_assert_eq!(enclave_report, decoded); + } + + #[test] + fn test_tdreport10_encode_decode(tdreport10: TDReport10) { + let mut encoded = vec![]; + tdreport10.encode_to(&mut encoded); + let decoded = TDReport10::decode(&mut encoded.as_slice()).unwrap(); + prop_assert_eq!(tdreport10, decoded); + } + + #[test] + fn test_tdreport15_encode_decode(tdreport15: TDReport15) { + let mut encoded = vec![]; + tdreport15.encode_to(&mut encoded); + let decoded = TDReport15::decode(&mut encoded.as_slice()).unwrap(); + prop_assert_eq!(tdreport15, decoded); + } + + #[test] + fn test_certification_data_encode_decode(certification_data: CertificationData) { + let mut encoded = vec![]; + certification_data.encode_to(&mut encoded); + let decoded = CertificationData::decode(&mut encoded.as_slice()).unwrap(); + prop_assert_eq!(certification_data, decoded); + } + + #[test] + fn test_qe_report_certification_data_encode_decode(qe_report_certification_data: QEReportCertificationData) { + let mut encoded = vec![]; + qe_report_certification_data.encode_to(&mut encoded); + let decoded = QEReportCertificationData::decode(&mut encoded.as_slice()).unwrap(); + prop_assert_eq!(qe_report_certification_data, decoded); + } + + #[test] + fn test_auth_data_v3_encode_decode(auth_data_v3: AuthDataV3) { + let mut encoded = vec![]; + auth_data_v3.encode_to(&mut encoded); + let decoded = AuthDataV3::decode(&mut encoded.as_slice()).unwrap(); + prop_assert_eq!(auth_data_v3, decoded); + } + + #[test] + fn test_auth_data_v4_encode_decode(auth_data_v4: AuthDataV4) { + let mut encoded = vec![]; + auth_data_v4.encode_to(&mut encoded); + let decoded = AuthDataV4::decode(&mut encoded.as_slice()).unwrap(); + prop_assert_eq!(auth_data_v4, decoded); + } +}