diff --git a/Cargo.toml b/Cargo.toml index afdd551..4e276bf 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -49,6 +49,7 @@ 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" [dependencies.webpki] version = "0.102.8" diff --git a/src/quote.rs b/src/quote.rs index 02076f5..61531a8 100644 --- a/src/quote.rs +++ b/src/quote.rs @@ -2,12 +2,13 @@ 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 crate::{constants::*, utils, Error}; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, PartialEq, Eq)] pub struct Data { pub data: Vec, _marker: core::marker::PhantomData, @@ -25,7 +26,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 +51,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 +158,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 +246,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 +327,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 +379,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 +387,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 +424,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 +471,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 +529,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); + } +}