oasis_core_runtime/common/sgx/
mod.rs

1//! SGX-specific functionality.
2
3pub mod egetkey;
4pub mod ias;
5pub mod pcs;
6pub mod seal;
7
8use anyhow::Result;
9use chrono::prelude::*;
10
11use crate::common::time::{insecure_posix_time, update_insecure_posix_time};
12
13/// Maximum age of a quote from the viewpoint of the enclave.
14pub const MAX_QUOTE_AGE: i64 = 24 * 60 * 60; // 24 hours
15
16impl_bytes!(MrEnclave, 32, "Enclave hash (MRENCLAVE).");
17impl_bytes!(MrSigner, 32, "Enclave signer hash (MRSIGNER).");
18
19/// Enclave identity.
20#[derive(Debug, Default, Clone, Hash, Eq, PartialEq, cbor::Encode, cbor::Decode)]
21pub struct EnclaveIdentity {
22    pub mr_enclave: MrEnclave,
23    pub mr_signer: MrSigner,
24}
25
26impl EnclaveIdentity {
27    /// Enclave identity for the current enclave (when available).
28    pub fn current() -> Option<Self> {
29        cfg_if::cfg_if! {
30            if #[cfg(target_env = "sgx")] {
31                // SGX builds, generate actual report.
32                let report = sgx_isa::Report::for_self();
33                Some(EnclaveIdentity {
34                    mr_enclave: MrEnclave(report.mrenclave),
35                    mr_signer: MrSigner(report.mrsigner),
36                })
37            } else if #[cfg(feature = "tdx")] {
38                // TDX builds, generate TD report.
39                let report = crate::common::tdx::report::get_report(&[0; 64]).expect("failed to get report");
40                Some(report.as_enclave_identity())
41            } else if #[cfg(feature = "debug-mock-sgx")] {
42                // Non-SGX builds, mock SGX enabled, generate mock report. The mock MRENCLAVE is
43                // expected to be passed in by the mock SGX runner.
44                Some(Self::fortanix_test(std::env::var("OASIS_MOCK_MRENCLAVE").unwrap().parse().unwrap()))
45            } else {
46                // Non-SGX builds, mock SGX disabled, no enclave identity.
47                None
48            }
49        }
50    }
51
52    /// Enclave identity using a test MRSIGNER from Fortanix with a well-known private key.
53    pub fn fortanix_test(mr_enclave: MrEnclave) -> Self {
54        Self {
55            mr_enclave,
56            mr_signer: MrSigner::from(
57                "9affcfae47b848ec2caf1c49b4b283531e1cc425f93582b36806e52a43d78d1a",
58            ),
59        }
60    }
61}
62
63/// An unverified SGX remote attestation quote, depending on the attestation scheme.
64#[derive(Clone, Debug, PartialEq, Eq, cbor::Encode, cbor::Decode)]
65pub enum Quote {
66    #[cbor(rename = "ias")]
67    Ias(ias::AVR),
68
69    #[cbor(rename = "pcs")]
70    Pcs(pcs::QuoteBundle),
71}
72
73impl Quote {
74    /// Verify the remote attestation quote.
75    pub fn verify(&self, policy: &QuotePolicy) -> Result<VerifiedQuote> {
76        let mut verified_quote = match self {
77            Quote::Ias(avr) => ias::verify(avr, &policy.ias.clone().unwrap_or_default()),
78            Quote::Pcs(qb) => {
79                let now = Utc.timestamp_opt(insecure_posix_time(), 0).unwrap();
80                Ok(qb.verify(&policy.pcs.clone().unwrap_or_default(), now)?)
81            }
82        }?;
83
84        // Force-ratchet the clock forward, to at least the time in the verified quote.
85        update_insecure_posix_time(verified_quote.timestamp);
86        verified_quote.timestamp = insecure_posix_time();
87
88        Ok(verified_quote)
89    }
90
91    /// Whether the quote should be considered fresh.
92    pub fn is_fresh(&self, now: i64, ts: i64, policy: &QuotePolicy) -> bool {
93        // Check general freshness requirement.
94        if (now - ts).abs() > MAX_QUOTE_AGE {
95            return false;
96        }
97
98        // Check quote-specific expiration policy.
99        match self {
100            Quote::Ias(_) => true, // No additional checks for IAS quotes.
101            Quote::Pcs(_) => !policy.pcs.clone().unwrap_or_default().is_expired(now, ts),
102        }
103    }
104}
105
106/// Quote validity policy.
107#[derive(Clone, Debug, Default, PartialEq, Eq, cbor::Encode, cbor::Decode)]
108pub struct QuotePolicy {
109    #[cbor(rename = "ias")]
110    pub ias: Option<ias::QuotePolicy>,
111
112    #[cbor(rename = "pcs")]
113    pub pcs: Option<pcs::QuotePolicy>,
114}
115
116/// A remote attestation quote that has undergone verification.
117#[derive(Debug, Default, Clone)]
118pub struct VerifiedQuote {
119    pub report_data: Vec<u8>,
120    pub identity: EnclaveIdentity,
121    pub timestamp: i64,
122}
123
124/// Generate a report for the given target enclave.
125#[cfg(target_env = "sgx")]
126pub fn report_for(target_info: &sgx_isa::Targetinfo, report_data: &[u8; 64]) -> sgx_isa::Report {
127    sgx_isa::Report::for_target(target_info, report_data)
128}
129
130/// Generate a report for the given target enclave.
131#[cfg(not(target_env = "sgx"))]
132pub fn report_for(_target_info: &sgx_isa::Targetinfo, report_data: &[u8; 64]) -> sgx_isa::Report {
133    let ei = EnclaveIdentity::current().expect("mock enclave identity not available");
134
135    // In non-SGX mode, reports are mocked.
136    sgx_isa::Report {
137        mrenclave: ei.mr_enclave.into(),
138        mrsigner: ei.mr_signer.into(),
139        cpusvn: [8, 9, 14, 13, 255, 255, 1, 0, 0, 0, 0, 0, 0, 0, 0, 0],
140        attributes: sgx_isa::Attributes {
141            flags: sgx_isa::AttributesFlags::INIT
142                | sgx_isa::AttributesFlags::DEBUG
143                | sgx_isa::AttributesFlags::MODE64BIT,
144            xfrm: 3,
145        },
146        reportdata: *report_data,
147        ..Default::default()
148    }
149}