Skip to content

Quote V5 #21

New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Open
wants to merge 6 commits into
base: dev
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
Binary file added data/v5/alibaba_quote_5.dat
Binary file not shown.
1 change: 1 addition & 0 deletions data/v5/qe_td.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"enclaveIdentity":{"id":"TD_QE","version":2,"issueDate":"2025-06-04T09:40:18Z","nextUpdate":"2025-07-04T09:40:18Z","tcbEvaluationDataNumber":17,"miscselect":"00000000","miscselectMask":"FFFFFFFF","attributes":"11000000000000000000000000000000","attributesMask":"FBFFFFFFFFFFFFFF0000000000000000","mrsigner":"DC9E2A7C6F948F17474E34A7FC43ED030F7C1563F1BABDDF6340C82E0E54A8C5","isvprodid":2,"tcbLevels":[{"tcb":{"isvsvn":4},"tcbDate":"2024-03-13T00:00:00Z","tcbStatus":"UpToDate"}]},"signature":"182c2576c96697f002cc71f062cfd3f895cb39dc767050432600c2a095e576854c86ba4c41f7f36ead28142be9f6bf358d21d69463a8a720ee62785509c537c2"}
32 changes: 32 additions & 0 deletions data/v5/signing_cert.pem
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
-----BEGIN CERTIFICATE-----
MIICjTCCAjKgAwIBAgIUfjiC1ftVKUpASY5FhAPpFJG99FUwCgYIKoZIzj0EAwIw
aDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv
cnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ
BgNVBAYTAlVTMB4XDTI1MDUwNjA5MjUwMFoXDTMyMDUwNjA5MjUwMFowbDEeMBwG
A1UEAwwVSW50ZWwgU0dYIFRDQiBTaWduaW5nMRowGAYDVQQKDBFJbnRlbCBDb3Jw
b3JhdGlvbjEUMBIGA1UEBwwLU2FudGEgQ2xhcmExCzAJBgNVBAgMAkNBMQswCQYD
VQQGEwJVUzBZMBMGByqGSM49AgEGCCqGSM49AwEHA0IABENFG8xzydWRfK92bmGv
P+mAh91PEyV7Jh6FGJd5ndE9aBH7R3E4A7ubrlh/zN3C4xvpoouGlirMba+W2lju
ypajgbUwgbIwHwYDVR0jBBgwFoAUImUM1lqdNInzg7SVUr9QGzknBqwwUgYDVR0f
BEswSTBHoEWgQ4ZBaHR0cHM6Ly9jZXJ0aWZpY2F0ZXMudHJ1c3RlZHNlcnZpY2Vz
LmludGVsLmNvbS9JbnRlbFNHWFJvb3RDQS5kZXIwHQYDVR0OBBYEFH44gtX7VSlK
QEmORYQD6RSRvfRVMA4GA1UdDwEB/wQEAwIGwDAMBgNVHRMBAf8EAjAAMAoGCCqG
SM49BAMCA0kAMEYCIQDdmmRuAo3qCO8TC1IoJMITAoOEw4dlgEBHzSz1TuMSTAIh
AKVTqOkt59+co0O3m3hC+v5Fb00FjYWcgeu3EijOULo5
-----END CERTIFICATE-----
-----BEGIN CERTIFICATE-----
MIICjzCCAjSgAwIBAgIUImUM1lqdNInzg7SVUr9QGzknBqwwCgYIKoZIzj0EAwIw
aDEaMBgGA1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENv
cnBvcmF0aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJ
BgNVBAYTAlVTMB4XDTE4MDUyMTEwNDUxMFoXDTQ5MTIzMTIzNTk1OVowaDEaMBgG
A1UEAwwRSW50ZWwgU0dYIFJvb3QgQ0ExGjAYBgNVBAoMEUludGVsIENvcnBvcmF0
aW9uMRQwEgYDVQQHDAtTYW50YSBDbGFyYTELMAkGA1UECAwCQ0ExCzAJBgNVBAYT
AlVTMFkwEwYHKoZIzj0CAQYIKoZIzj0DAQcDQgAEC6nEwMDIYZOj/iPWsCzaEKi7
1OiOSLRFhWGjbnBVJfVnkY4u3IjkDYYL0MxO4mqsyYjlBalTVYxFP2sJBK5zlKOB
uzCBuDAfBgNVHSMEGDAWgBQiZQzWWp00ifODtJVSv1AbOScGrDBSBgNVHR8ESzBJ
MEegRaBDhkFodHRwczovL2NlcnRpZmljYXRlcy50cnVzdGVkc2VydmljZXMuaW50
ZWwuY29tL0ludGVsU0dYUm9vdENBLmRlcjAdBgNVHQ4EFgQUImUM1lqdNInzg7SV
Ur9QGzknBqwwDgYDVR0PAQH/BAQDAgEGMBIGA1UdEwEB/wQIMAYBAf8CAQEwCgYI
KoZIzj0EAwIDSQAwRgIhAOW/5QkR+S9CiSDcNoowLuPRLsWGf/Yi7GSX94BgwTwg
AiEA4J0lrHoMs+Xo5o/sX6O9QWxHRAvZUGOdRQ7cvqRXaqI=
-----END CERTIFICATE-----
1 change: 1 addition & 0 deletions data/v5/tcbinfov3_90C06F000000.json
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
{"tcbInfo":{"id":"TDX","version":3,"issueDate":"2025-06-05T03:43:45Z","nextUpdate":"2025-07-05T03:43:45Z","fmspc":"90C06F000000","pceId":"0000","tcbType":0,"tcbEvaluationDataNumber":17,"tdxModule":{"mrsigner":"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","attributes":"0000000000000000","attributesMask":"FFFFFFFFFFFFFFFF"},"tdxModuleIdentities":[{"id":"TDX_03","mrsigner":"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","attributes":"0000000000000000","attributesMask":"FFFFFFFFFFFFFFFF","tcbLevels":[{"tcb":{"isvsvn":3},"tcbDate":"2024-03-13T00:00:00Z","tcbStatus":"UpToDate"}]},{"id":"TDX_01","mrsigner":"000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000000","attributes":"0000000000000000","attributesMask":"FFFFFFFFFFFFFFFF","tcbLevels":[{"tcb":{"isvsvn":4},"tcbDate":"2024-03-13T00:00:00Z","tcbStatus":"UpToDate"},{"tcb":{"isvsvn":2},"tcbDate":"2023-08-09T00:00:00Z","tcbStatus":"OutOfDate"}]}],"tcbLevels":[{"tcb":{"sgxtcbcomponents":[{"svn":2,"category":"BIOS","type":"Early Microcode Update"},{"svn":2,"category":"OS/VMM","type":"SGX Late Microcode Update"},{"svn":2,"category":"OS/VMM","type":"TXT SINIT"},{"svn":2,"category":"BIOS"},{"svn":3,"category":"BIOS"},{"svn":1,"category":"BIOS"},{"svn":0},{"svn":5,"category":"OS/VMM","type":"SEAMLDR ACM"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}],"pcesvn":13,"tdxtcbcomponents":[{"svn":5,"category":"OS/VMM","type":"TDX Module"},{"svn":0,"category":"OS/VMM","type":"TDX Module"},{"svn":2,"category":"OS/VMM","type":"TDX Late Microcode Update"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}]},"tcbDate":"2024-03-13T00:00:00Z","tcbStatus":"UpToDate"},{"tcb":{"sgxtcbcomponents":[{"svn":2,"category":"BIOS","type":"Early Microcode Update"},{"svn":2,"category":"OS/VMM","type":"SGX Late Microcode Update"},{"svn":2,"category":"OS/VMM","type":"TXT SINIT"},{"svn":2,"category":"BIOS"},{"svn":3,"category":"BIOS"},{"svn":1,"category":"BIOS"},{"svn":0},{"svn":5,"category":"OS/VMM","type":"SEAMLDR ACM"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}],"pcesvn":5,"tdxtcbcomponents":[{"svn":5,"category":"OS/VMM","type":"TDX Module"},{"svn":0,"category":"OS/VMM","type":"TDX Module"},{"svn":2,"category":"OS/VMM","type":"TDX Late Microcode Update"},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0},{"svn":0}]},"tcbDate":"2018-01-04T00:00:00Z","tcbStatus":"OutOfDate","advisoryIDs":["INTEL-SA-00106","INTEL-SA-00115","INTEL-SA-00135","INTEL-SA-00203","INTEL-SA-00220","INTEL-SA-00233","INTEL-SA-00270","INTEL-SA-00293","INTEL-SA-00320","INTEL-SA-00329","INTEL-SA-00381","INTEL-SA-00389","INTEL-SA-00477","INTEL-SA-00837"]}]},"signature":"a4c32be31be23ed7bd805429d80bb4ca597987e4ad0beac18172d4cc677f972d803c8696d667164a4a2e41bc29cfd980df0ad444f978a34b62b08d37b8495f7b"}
71 changes: 69 additions & 2 deletions src/lib.rs
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
pub mod tdx;
pub mod trust_store;
pub mod types;
pub mod utils;
Expand All @@ -11,6 +12,8 @@ use p256::ecdsa::{Signature, VerifyingKey, signature::Verifier};
#[cfg(feature = "full")]
use std::time::SystemTime;
#[cfg(feature = "full")]
use tdx::*;
#[cfg(feature = "full")]
use trust_store::{TrustStore, TrustedIdentity};
#[cfg(feature = "full")]
use types::{
Expand Down Expand Up @@ -44,11 +47,17 @@ pub fn verify_dcap_quote(
) -> anyhow::Result<VerifiedOutput> {
// 1. Verify the integrity of the signature chain from the Quote to the Intel-issued PCK
// certificate, and that no keys in the chain have been revoked.
use crate::types::quote::QuoteBody;
let tcb_info = verify_integrity(current_time, &collateral, &quote)?;

// 2. Verify the Quoting Enclave source and all signatures in the Quote.
let qe_tcb_status = verify_quote(current_time, &collateral, &quote)?;

assert!(
qe_tcb_status != QeTcbStatus::Revoked,
"Quoting Enclave TCB Revoked"
);

// 3. Verify the status of Intel SGX TCB described in the chain.
let pck_extension = quote.signature.get_pck_extension()?;
let (sgx_tcb_status, tdx_tcb_status, advisory_ids) =
Expand All @@ -70,7 +79,7 @@ pub fn verify_dcap_quote(
if quote.header.tee_type == TDX_TEE_TYPE {
tcb_status = tdx_tcb_status;
let tdx_module_status =
tcb_info.verify_tdx_module(quote.body.as_tdx_report_body().unwrap())?;
verify_tdx_module(&tcb_info, quote.body.as_tdx_report_body().unwrap())?;
tcb_status = TcbInfo::converge_tcb_status_with_tdx_module(tcb_status, tdx_module_status);
} else {
tcb_status = sgx_tcb_status;
Expand All @@ -79,6 +88,24 @@ pub fn verify_dcap_quote(
// 5. Converge platform TCB status with QE TCB status
tcb_status = TcbInfo::converge_tcb_status_with_qe_tcb(tcb_status, qe_tcb_status.into());

// 6. Perform Relaunch Check if the quote contains a TD 1.5 Report
if let QuoteBody::Td15QuoteBody(td_report) = &quote.body {
let (relaunch_needed, configuration_needed) = check_for_relaunch(
&tcb_info,
td_report,
qe_tcb_status,
sgx_tcb_status,
tdx_tcb_status,
);
if relaunch_needed {
if configuration_needed {
tcb_status = TcbStatus::RelaunchAdvisedConfigurationNeeded;
} else {
tcb_status = TcbStatus::RelaunchAdvised;
}
}
}

Ok(VerifiedOutput {
quote_version: quote.header.version.get(),
tee_type: quote.header.tee_type.to_le(), // Compatible with VerifiedOutput defined on-chain
Expand Down Expand Up @@ -325,6 +352,11 @@ pub fn verify_quote_signatures(quote: &Quote) -> anyhow::Result<()> {
let body_bytes = quote.body.as_bytes();
let mut data = Vec::with_capacity(header_bytes.len() + body_bytes.len());
data.extend_from_slice(header_bytes);
if quote.header.version.get() > 4 {
// For version 5 and above, we include the quote body type and size
data.extend_from_slice(&quote.body_type.to_le_bytes());
data.extend_from_slice(&quote.body_size.to_le_bytes());
}
data.extend_from_slice(body_bytes);

let sig = Signature::from_slice(quote.signature.isv_signature)?;
Expand Down Expand Up @@ -424,6 +456,21 @@ mod tests {
(collateral, quote)
}

fn tdx_quote_v5_data() -> (Collateral, Quote<'static>) {
let quote = include_bytes!("../data/v5/alibaba_quote_5.dat");
let quote = Quote::read(&mut quote.as_slice()).unwrap();

let collateral = Collateral::new(
include_bytes!("../data/intel_root_ca_crl.der"),
include_bytes!("../data/pck_platform_crl.der"),
include_bytes!("../data/v5/signing_cert.pem"),
include_str!("../data/v5/tcbinfov3_90C06F000000.json"),
include_str!("../data/v5/qe_td.json"),
).expect("Failed to load collaterals");

(collateral, quote)
}

fn test_sgx_time() -> SystemTime {
// Aug 29th 4:20pm, ~24 hours after quote was generated
SystemTime::UNIX_EPOCH + Duration::from_secs(1724962800)
Expand All @@ -434,8 +481,20 @@ mod tests {
SystemTime::UNIX_EPOCH + Duration::from_secs(1725950994)
}

fn test_tdx_v5_time() -> SystemTime {
// June 15th, 2025, 12am UTC
SystemTime::UNIX_EPOCH + Duration::from_secs(1749945600)
}

#[test]
fn parse_tdx_v5_quote() {
let bytes = include_bytes!("../data/v5/alibaba_quote_5.dat");
let quote = Quote::read(&mut bytes.as_slice()).unwrap();
println!("{:?}", quote);
}

#[test]
fn parse_tdx_quote() {
fn parse_tdx_v4_quote() {
let bytes = include_bytes!("../data/quote_tdx.bin");
let quote = Quote::read(&mut bytes.as_slice()).unwrap();
println!("{:?}", quote);
Expand Down Expand Up @@ -468,4 +527,12 @@ mod tests {
super::verify_dcap_quote(test_tdx_time(), collateral, quote)
.expect("certificate chain integrity should succeed");
}

#[test]
fn e2e_tdx_v5_quote() {
let (collateral, quote) = tdx_quote_v5_data();
let output = super::verify_dcap_quote(test_tdx_v5_time(), collateral, quote)
.expect("certificate chain integrity should succeed");
println!("{:?}", output);
}
}
143 changes: 143 additions & 0 deletions src/tdx.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
use crate::types::enclave_identity::QeTcbStatus;
use crate::types::report::{Td10ReportBody, Td15ReportBody};
use crate::types::tcb_info::{TcbInfo, TcbStatus, TdxModuleIdentity};

pub fn verify_tdx_module(
tcb_info: &TcbInfo,
td_report: &Td10ReportBody,
) -> anyhow::Result<TcbStatus> {
if tcb_info.tdx_module.is_none() {
return Err(anyhow::anyhow!("no tdx module found in tcb info"));
}

if tcb_info.tdx_module_identities.is_none() {
return Err(anyhow::anyhow!(
"no tdx module identities found in tcb info"
));
}

let (tdx_module_isv_svn, tdx_module_version) =
(td_report.tee_tcb_svn[0], td_report.tee_tcb_svn[1]);

let tdx_module_identity =
find_tdx_module_identity(tdx_module_version, tcb_info).ok_or(anyhow::anyhow!(
"no tdx module identity found for version {}",
tdx_module_version
))?;

// Get the TDX module reference based on version
let (mrsigner, attributes) = if tdx_module_version > 0 {
(
&tdx_module_identity.mrsigner,
&tdx_module_identity.attributes,
)
} else {
let tdx_module = tcb_info.tdx_module.as_ref().unwrap();
(&tdx_module.mrsigner, &tdx_module.attributes)
};

// Convert mrsigner and attributes to the appropriate type
let mrsigner_bytes: [u8; 48] = hex::decode(mrsigner).unwrap().try_into().unwrap();
let attributes_bytes: [u8; 8] = hex::decode(attributes).unwrap().try_into().unwrap();

// Check for mismatches with a single validation
if mrsigner_bytes != td_report.mr_signer_seam {
return Err(anyhow::anyhow!(
"mrsigner mismatch between tdx module identity and tdx quote body"
));
}

if attributes_bytes != td_report.seam_attributes {
return Err(anyhow::anyhow!(
"attributes mismatch between tdx module identity and tdx quote body"
));
}

let tcb_level = tdx_module_identity
.tcb_levels
.iter()
.find(|level| level.in_tcb_level(tdx_module_isv_svn))
.ok_or(anyhow::anyhow!(
"no tcb level found for tdx module identity within tdx module levels"
))?;

Ok(tcb_level.tcb_status)
}

/// https://github.com/intel/SGX-TDX-DCAP-QuoteVerificationLibrary/blob/stable/Src/AttestationLibrary/src/Verifiers/Checks/TDRelaunchCheck.cpp
/// Returns a tuple of (tdx_relaunch_needed, tdx_relaunch_and_configuration_needed)
pub fn check_for_relaunch(
tcb_info: &TcbInfo,
td_report: &Td15ReportBody,
qe_tcb_status: QeTcbStatus,
sgx_tcb_status: TcbStatus,
tdx_tcb_status: TcbStatus,
) -> (bool, bool) {
let mut relaunch_needed = false;
let mut configuration_needed = false;

if qe_tcb_status != QeTcbStatus::OutOfDate
|| qe_tcb_status != QeTcbStatus::OutOfDateConfigurationNeeded
{
if !is_out_of_date(sgx_tcb_status) {
if is_out_of_date(tdx_tcb_status) {
configuration_needed = is_configuration_needed(sgx_tcb_status)
|| is_configuration_needed(tdx_tcb_status);

let latest_tcb_level_tdx_svns = tcb_info
.tcb_levels
.first()
.unwrap()
.tcb
.tdx_tcb_components()
.unwrap();
let tdx_module_version = td_report.tee_tcb_svn2[1];
let tdx_module_svns = td_report.tee_tcb_svn2;

if tdx_module_version == 0 {
if tdx_module_svns[0] >= latest_tcb_level_tdx_svns[0]
&& tdx_module_svns[2] >= latest_tcb_level_tdx_svns[2]
{
relaunch_needed = true;
}
} else {
let tdx_module_identity =
find_tdx_module_identity(tdx_module_version, tcb_info).unwrap();
if tdx_module_svns[0] >= tdx_module_identity.tcb_levels[0].tcb.isvsvn
&& tdx_module_svns[2] >= latest_tcb_level_tdx_svns[2]
{
relaunch_needed = true;
}
}
}
}
}

(relaunch_needed, configuration_needed)
}

fn find_tdx_module_identity(
tdx_module_version: u8,
tcb_info: &TcbInfo,
) -> Option<&TdxModuleIdentity> {
let tdx_module_identity_id = format!("TDX_{:02x}", tdx_module_version);

let tdx_module_identity = tcb_info
.tdx_module_identities
.as_ref()
.unwrap()
.iter()
.find(|identity| identity.id == tdx_module_identity_id);

tdx_module_identity
}

fn is_configuration_needed(tcb_status: TcbStatus) -> bool {
tcb_status == TcbStatus::ConfigurationAndSWHardeningNeeded
|| tcb_status == TcbStatus::ConfigurationNeeded
|| tcb_status == TcbStatus::OutOfDateConfigurationNeeded
}

fn is_out_of_date(tcb_status: TcbStatus) -> bool {
tcb_status == TcbStatus::OutOfDate || tcb_status == TcbStatus::OutOfDateConfigurationNeeded
}
57 changes: 55 additions & 2 deletions src/types/collateral.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,11 @@
#[cfg(feature = "zero-copy")]
use crate::utils::cert_chain_processor;
use crate::utils::{cert_chain, crl};
use anyhow::Result;
use serde::{Deserialize, Serialize};
use x509_cert::{Certificate, crl::CertificateList};
#[cfg(not(feature = "zero-copy"))]
use x509_cert::certificate::CertificateInner;
use x509_cert::{Certificate, crl::CertificateList, der::Decode};

use super::{enclave_identity::QuotingEnclaveIdentityAndSignature, tcb_info::TcbInfoAndSignature};

Expand Down Expand Up @@ -38,12 +43,60 @@ pub struct Collateral {
pub qe_identity: QuotingEnclaveIdentityAndSignature,
}

impl Collateral {
pub fn new(
root_ca_crl_der: &[u8],
pck_crl_der: &[u8],
tcb_info_and_qe_identity_issuer_chain_pem_bytes: &[u8],
tcb_info_json_str: &str,
qe_identity_json_str: &str,
) -> Result<Self> {
let root_ca_crl = CertificateList::from_der(root_ca_crl_der)?;
let pck_crl = CertificateList::from_der(pck_crl_der)?;
#[cfg(not(feature = "zero-copy"))]
let tcb_info_and_qe_identity_issuer_chain: Vec<Certificate> =
CertificateInner::load_pem_chain(tcb_info_and_qe_identity_issuer_chain_pem_bytes)?;
#[cfg(feature = "zero-copy")]
let tcb_info_and_qe_identity_issuer_chain: Vec<Certificate> =
cert_chain_processor::load_pem_chain_bpf_friendly(
tcb_info_and_qe_identity_issuer_chain_pem_bytes,
)?;
let tcb_info: TcbInfoAndSignature = serde_json::from_str(tcb_info_json_str)?;
let qe_identity: QuotingEnclaveIdentityAndSignature =
serde_json::from_str(qe_identity_json_str)?;

Ok(Self {
root_ca_crl,
pck_crl,
tcb_info_and_qe_identity_issuer_chain,
tcb_info,
qe_identity,
})
}
}

#[cfg(test)]
mod tests {
use super::Collateral;

#[test]
fn encode_decode_collateral_json() {
fn test_encode_collateral() {
let collateral = Collateral::new(
include_bytes!("../../data/intel_root_ca_crl.der"),
include_bytes!("../../data/pck_platform_crl.der"),
include_bytes!("../../data/tcb_signing_cert.pem"),
include_str!("../../data/tcb_info_v2.json"),
include_str!("../../data/qeidentityv2.json"),
)
.expect("collateral to be created");

let json = serde_json::to_string(&collateral).expect("collateral to serialize");
assert!(!json.is_empty(), "collateral JSON should not be empty");
println!("Collateral JSON: {}", json);
}

#[test]
fn test_decode_collateral_json() {
let json = include_str!("../../data/full_collateral_sgx.json");
let _collateral: Collateral = serde_json::from_str(json).expect("json to parse");
}
Expand Down
Loading