Skip to main content

dcap_qvl/
intel.rs

1use anyhow::{anyhow, bail, Context, Result};
2use asn1_der::{
3    typed::{DerDecodable, Sequence},
4    DerObject,
5};
6
7#[cfg(feature = "default-x509")]
8use crate::configs::DefaultConfig;
9use crate::{
10    config::{Config, ParsedCert, PckCa, X509Codec},
11    constants::{self, CpuSvn, Fmspc, Svn},
12    oids,
13    quote::{AuthData, Quote},
14    utils,
15};
16
17/// Parsed values from the Intel SGX extension in a PCK certificate.
18#[derive(Debug, Clone, PartialEq, Eq)]
19pub struct PckExtension {
20    pub ppid: Vec<u8>,
21    pub cpu_svn: CpuSvn,
22    pub pce_svn: Svn,
23    pub pce_id: Vec<u8>,
24    pub fmspc: Fmspc,
25    pub sgx_type: u64,
26    pub platform_instance_id: Option<Vec<u8>>,
27    pub raw_extension: Vec<u8>,
28    /// Whether the platform can be extended with additional packages
29    /// (Platform CA certs only; `None` for Processor CA certs)
30    pub dynamic_platform: Option<bool>,
31    /// Whether platform root keys are cached by SGX Registration Backend
32    /// (Platform CA certs only; `None` for Processor CA certs)
33    pub cached_keys: Option<bool>,
34    /// Whether SMT (simultaneous multithreading / hyperthreading) is enabled
35    /// (Platform CA certs only; `None` for Processor CA certs)
36    pub smt_enabled: Option<bool>,
37}
38
39impl PckExtension {
40    /// Look up an arbitrary OID inside the raw Intel SGX extension.
41    ///
42    /// The search is recursive: nested SEQUENCE containers are walked
43    /// automatically so the caller only needs to supply the leaf OID.
44    pub fn get_value(&self, oid: &const_oid::ObjectIdentifier) -> Result<Option<Vec<u8>>> {
45        let obj = DerObject::decode(&self.raw_extension).context("Failed to decode DER object")?;
46        find_recursive(oid, obj, 0)
47    }
48}
49
50const MAX_DER_RECURSION_DEPTH: usize = 10;
51
52/// Return the PCK certificate chain (DER encoded) embedded inside the quote.
53///
54/// * For certification data type 5 this returns the entire chain.
55/// * For certification data type 4 this returns the single PCK certificate.
56pub fn extract_cert_chain(quote: &Quote) -> Result<Vec<Vec<u8>>> {
57    if let Ok(chain_bytes) = quote.raw_cert_chain() {
58        let certs = utils::extract_certs(chain_bytes)?;
59        return Ok(certs
60            .into_iter()
61            .map(|cert| cert.as_ref().to_vec())
62            .collect());
63    }
64
65    let cert_data = match &quote.auth_data {
66        AuthData::V3(data) => &data.certification_data,
67        AuthData::V4(data) => &data.qe_report_data.certification_data,
68    };
69    if cert_data.cert_type == constants::PCK_ID_PCK_CERTIFICATE {
70        return Ok(vec![cert_data.body.data.clone()]);
71    }
72
73    bail!(
74        "Certification data type {} is not supported (expecting 4 or 5)",
75        cert_data.cert_type
76    );
77}
78
79/// Generic version of [`parse_pck_extension`] using a custom [`Config`].
80pub fn parse_pck_extension_with<C: Config>(cert_der: &[u8]) -> Result<PckExtension> {
81    let extension = utils::get_intel_extension_with::<C>(cert_der)?;
82
83    let ppid = find_extension_required(&[oids::PPID], &extension)?;
84    let cpu_svn = utils::get_cpu_svn(&extension)?;
85    let pce_svn = utils::get_pce_svn(&extension)?;
86    let pce_id = find_extension_required(&[oids::PCEID], &extension)?;
87    let fmspc = utils::get_fmspc(&extension)?;
88    let sgx_type = decode_enumerated(&find_extension_required(&[oids::SGX_TYPE], &extension)?)?;
89    let platform_instance_id = find_extension_optional(&[oids::PLATFORM_INSTANCE_ID], &extension)?;
90
91    // Configuration flags (only present in Platform CA certs, under OID 1.2.840.113741.1.13.1.7)
92    let dynamic_platform =
93        find_extension_optional(&[oids::CONFIGURATION, oids::DYNAMIC_PLATFORM], &extension)?
94            .map(|v| decode_boolean(&v))
95            .transpose()?;
96    let cached_keys =
97        find_extension_optional(&[oids::CONFIGURATION, oids::CACHED_KEYS], &extension)?
98            .map(|v| decode_boolean(&v))
99            .transpose()?;
100    let smt_enabled =
101        find_extension_optional(&[oids::CONFIGURATION, oids::SMT_ENABLED], &extension)?
102            .map(|v| decode_boolean(&v))
103            .transpose()?;
104
105    Ok(PckExtension {
106        ppid,
107        cpu_svn,
108        pce_svn,
109        pce_id,
110        fmspc,
111        sgx_type,
112        platform_instance_id,
113        raw_extension: extension,
114        dynamic_platform,
115        cached_keys,
116        smt_enabled,
117    })
118}
119
120/// Parse the Intel SGX extension values from a DER-encoded PCK certificate.
121///
122/// Uses the audited [`DefaultConfig`]. For a custom backend, use
123/// [`parse_pck_extension_with`].
124#[cfg(feature = "default-x509")]
125pub fn parse_pck_extension(cert_der: &[u8]) -> Result<PckExtension> {
126    parse_pck_extension_with::<DefaultConfig>(cert_der)
127}
128
129/// Generic version of [`parse_pck_extension_from_pem`] using a custom [`Config`].
130pub fn parse_pck_extension_from_pem_with<C: Config>(pem_data: &[u8]) -> Result<PckExtension> {
131    let certs = utils::extract_certs(pem_data)?;
132    let leaf = certs
133        .first()
134        .ok_or_else(|| anyhow!("No certificates found in PEM chain"))?;
135    parse_pck_extension_with::<C>(leaf)
136}
137
138/// Parse the Intel SGX extension from a PEM-encoded certificate chain.
139///
140/// The first (leaf) certificate in the chain is used. Uses the audited
141/// [`DefaultConfig`]. For a custom backend, use
142/// [`parse_pck_extension_from_pem_with`].
143#[cfg(feature = "default-x509")]
144pub fn parse_pck_extension_from_pem(pem_data: &[u8]) -> Result<PckExtension> {
145    parse_pck_extension_from_pem_with::<DefaultConfig>(pem_data)
146}
147
148/// Classify the PCK certificate authority of a leaf cert as
149/// [`PckCa::Processor`] or [`PckCa::Platform`] by inspecting the issuer DN.
150///
151/// Generic over [`Config`]; the classification goes through the
152/// configured [`X509Codec`]. An unrecognised issuer is reported as
153/// [`PckCa::Processor`] for backwards compatibility — see comment.
154pub fn pck_ca_with<C: Config>(cert_der: &[u8]) -> Result<PckCa> {
155    let parsed = C::X509::from_der(cert_der).context("Failed to decode certificate")?;
156    // Legacy fallback: an unrecognised issuer is treated as Processor. This
157    // policy lives at the call site, not in the backend, so future callers
158    // can opt to error or log on `None` without needing trait changes.
159    Ok(parsed.pck_ca().unwrap_or(PckCa::Processor))
160}
161
162/// [`pck_ca_with`] under the audited [`DefaultConfig`].
163#[cfg(feature = "default-x509")]
164pub fn pck_ca(cert_der: &[u8]) -> Result<PckCa> {
165    pck_ca_with::<DefaultConfig>(cert_der)
166}
167
168/// Return the FMSPC of a quote's PCK leaf certificate.
169///
170/// Convenience over [`extract_cert_chain`] + [`parse_pck_extension_with`];
171/// generic over [`Config`].
172pub fn quote_fmspc_with<C: Config>(quote: &Quote) -> Result<Fmspc> {
173    let chain = extract_cert_chain(quote)?;
174    let leaf = chain.first().context("Empty PCK certificate chain")?;
175    Ok(parse_pck_extension_with::<C>(leaf)?.fmspc)
176}
177
178/// [`quote_fmspc_with`] under the audited [`DefaultConfig`].
179#[cfg(feature = "default-x509")]
180pub fn quote_fmspc(quote: &Quote) -> Result<Fmspc> {
181    quote_fmspc_with::<DefaultConfig>(quote)
182}
183
184/// Return the PCK CA classification of a quote's PCK leaf certificate.
185///
186/// Generic over [`Config`].
187pub fn quote_ca_with<C: Config>(quote: &Quote) -> Result<PckCa> {
188    let chain = extract_cert_chain(quote)?;
189    let leaf = chain.first().context("Empty PCK certificate chain")?;
190    pck_ca_with::<C>(leaf)
191}
192
193/// [`quote_ca_with`] under the audited [`DefaultConfig`].
194#[cfg(feature = "default-x509")]
195pub fn quote_ca(quote: &Quote) -> Result<PckCa> {
196    quote_ca_with::<DefaultConfig>(quote)
197}
198
199fn find_extension_required(
200    path: &[const_oid::ObjectIdentifier],
201    extension: &[u8],
202) -> Result<Vec<u8>> {
203    find_extension_optional(path, extension)?
204        .ok_or_else(|| anyhow!("Intel extension path {path:?} is missing"))
205}
206
207fn find_extension_optional(
208    path: &[const_oid::ObjectIdentifier],
209    extension: &[u8],
210) -> Result<Option<Vec<u8>>> {
211    let mut obj = DerObject::decode(extension).context("Failed to decode DER object")?;
212    for oid in path {
213        let seq = Sequence::load(obj).context("Failed to load sequence")?;
214        match sub_object_opt(oid, seq)? {
215            Some(value) => obj = value,
216            None => return Ok(None),
217        }
218    }
219    Ok(Some(obj.value().to_vec()))
220}
221
222fn sub_object_opt<'a>(
223    oid: &const_oid::ObjectIdentifier,
224    seq: Sequence<'a>,
225) -> Result<Option<DerObject<'a>>> {
226    for idx in 0..seq.len() {
227        let entry = seq
228            .get(idx)
229            .context("Failed to read entry inside Intel extension")?;
230        let entry_seq = Sequence::load(entry).context("Failed to load nested sequence")?;
231        let name = entry_seq.get(0).context("Failed to read OID")?;
232        let value = entry_seq.get(1).context("Failed to read value")?;
233        if name.value() == oid.as_bytes() {
234            return Ok(Some(value));
235        }
236    }
237    Ok(None)
238}
239
240fn find_recursive<'a>(
241    oid: &const_oid::ObjectIdentifier,
242    obj: DerObject<'a>,
243    depth: usize,
244) -> Result<Option<Vec<u8>>> {
245    if depth > MAX_DER_RECURSION_DEPTH {
246        bail!("DER recursion depth exceeded");
247    }
248    let seq = match Sequence::load(obj) {
249        Ok(s) => s,
250        Err(_) => return Ok(None),
251    };
252    for idx in 0..seq.len() {
253        let entry = match seq.get(idx) {
254            Ok(e) => e,
255            Err(_) => continue,
256        };
257        let entry_seq = match Sequence::load(entry) {
258            Ok(s) => s,
259            Err(_) => continue,
260        };
261        let name = match entry_seq.get(0) {
262            Ok(n) => n,
263            Err(_) => continue,
264        };
265        let value = match entry_seq.get(1) {
266            Ok(v) => v,
267            Err(_) => continue,
268        };
269        if name.value() == oid.as_bytes() {
270            return Ok(Some(value.value().to_vec()));
271        }
272        // Tag 0x30 = SEQUENCE — recurse into nested containers
273        if value.tag() == 0x30 {
274            let next_depth = depth
275                .checked_add(1)
276                .context("DER recursion depth overflow")?;
277            if let Some(found) = find_recursive(oid, value, next_depth)? {
278                return Ok(Some(found));
279            }
280        }
281    }
282    Ok(None)
283}
284
285fn decode_boolean(bytes: &[u8]) -> Result<bool> {
286    match bytes[..] {
287        [0x00] => Ok(false),
288        [_] => Ok(true),
289        _ => bail!("Unexpected BOOLEAN length: {}", bytes.len()),
290    }
291}
292
293fn decode_enumerated(bytes: &[u8]) -> Result<u64> {
294    match bytes[..] {
295        [byte0] => Ok(u64::from(byte0)),
296        [byte0, byte1] => Ok(u16::from_be_bytes([byte0, byte1]) as u64),
297        _ => bail!("Unexpected ENUMERATED length"),
298    }
299}