mirror of
https://github.com/matter-labs/teepot.git
synced 2025-07-21 07:03:56 +02:00
feat(quote): add FMSPC and CPUSVN extraction support
- Introduced new types `Fmspc`, `CpuSvn`, and `Svn` for SGX metadata. - Added methods to extract raw certificate chains and FMSPC from SGX quotes. - Created new test file for validating FMSPC extraction with example quotes. Signed-off-by: Harald Hoyer <harald@matterlabs.dev>
This commit is contained in:
parent
fca60adc1a
commit
2bbfb2415c
6 changed files with 1547 additions and 0 deletions
1
Cargo.lock
generated
1
Cargo.lock
generated
|
@ -4936,6 +4936,7 @@ name = "teepot"
|
|||
version = "0.3.0"
|
||||
dependencies = [
|
||||
"anyhow",
|
||||
"asn1_der",
|
||||
"async-trait",
|
||||
"base64",
|
||||
"bytemuck",
|
||||
|
|
|
@ -19,6 +19,7 @@ homepage = "https://github.com/matter-labs/teepot"
|
|||
[workspace.dependencies]
|
||||
actix-web = { version = "4.5", features = ["rustls-0_23"] }
|
||||
anyhow = "1.0.82"
|
||||
asn1_der = { version = "0.7", default-features = false, features = ["native_types"] }
|
||||
async-trait = "0.1.86"
|
||||
awc = { version = "3.5", features = ["rustls-0_23-webpki-roots"] }
|
||||
base64 = "0.22.0"
|
||||
|
|
|
@ -21,6 +21,7 @@ bytes.workspace = true
|
|||
|
||||
[dependencies]
|
||||
anyhow.workspace = true
|
||||
asn1_der.workspace = true
|
||||
async-trait.workspace = true
|
||||
bytemuck.workspace = true
|
||||
clap.workspace = true
|
||||
|
|
|
@ -16,6 +16,7 @@ pub mod tcblevel;
|
|||
path = "phala.rs"
|
||||
)]
|
||||
mod os;
|
||||
mod utils;
|
||||
|
||||
use crate::quote::{
|
||||
error::{QuoteContext as _, QuoteError},
|
||||
|
@ -547,6 +548,23 @@ impl Decode for Quote {
|
|||
}
|
||||
}
|
||||
|
||||
/// FMSPC (Family-Model-Stepping-Platform-CustomSKU) is a 6-byte identifier
|
||||
/// that uniquely identifies a platform's SGX TCB level.
|
||||
/// It is extracted from the PCK certificate in the SGX quote and is used to
|
||||
/// fetch TCB information from Intel's Provisioning Certification Service.
|
||||
pub type Fmspc = [u8; 6];
|
||||
|
||||
/// CPU Security Version Number (CPUSVN) is a 16-byte value representing
|
||||
/// the security version of the CPU microcode and firmware.
|
||||
/// It is used in SGX attestation to determine the security patch level
|
||||
/// of the platform.
|
||||
pub type CpuSvn = [u8; 16];
|
||||
|
||||
/// Security Version Number (SVN) is a 16-bit value representing the
|
||||
/// security version of a component (like PCE or QE).
|
||||
/// Higher values indicate newer security patches have been applied.
|
||||
pub type Svn = u16;
|
||||
|
||||
impl Quote {
|
||||
/// Parse a TEE quote from a byte slice.
|
||||
pub fn parse(quote: &[u8]) -> Result<Self, QuoteError> {
|
||||
|
@ -555,6 +573,32 @@ impl Quote {
|
|||
Ok(quote)
|
||||
}
|
||||
|
||||
/// Get the raw certificate chain from the quote.
|
||||
pub fn raw_cert_chain(&self) -> Result<&[u8], QuoteError> {
|
||||
let cert_data = match &self.auth_data {
|
||||
AuthData::V3(data) => &data.certification_data,
|
||||
AuthData::V4(data) => &data.qe_report_data.certification_data,
|
||||
};
|
||||
if cert_data.cert_type != 5 {
|
||||
QuoteError::QuoteCertificationDataUnsupported(format!(
|
||||
"Unsupported cert type: {}",
|
||||
cert_data.cert_type
|
||||
));
|
||||
}
|
||||
Ok(&cert_data.body.data)
|
||||
}
|
||||
|
||||
/// Get the FMSPC from the quote.
|
||||
pub fn fmspc(&self) -> Result<Fmspc, QuoteError> {
|
||||
let raw_cert_chain = self.raw_cert_chain()?;
|
||||
let certs = utils::extract_certs(raw_cert_chain)?;
|
||||
let cert = certs
|
||||
.first()
|
||||
.ok_or(QuoteError::Unexpected("Invalid certificate".into()))?;
|
||||
let extension_section = utils::get_intel_extension(cert)?;
|
||||
utils::get_fmspc(&extension_section)
|
||||
}
|
||||
|
||||
/// Get the report data
|
||||
pub fn get_report_data(&self) -> &[u8] {
|
||||
match &self.report {
|
||||
|
|
106
crates/teepot/src/quote/utils.rs
Normal file
106
crates/teepot/src/quote/utils.rs
Normal file
|
@ -0,0 +1,106 @@
|
|||
// SPDX-License-Identifier: Apache-2.0
|
||||
// Copyright (c) 2024-2025 Matter Labs
|
||||
|
||||
// Parts of it are Copyright (c) 2024 Phala Network
|
||||
// and copied from https://github.com/Phala-Network/dcap-qvl
|
||||
|
||||
use crate::quote::{error::QuoteError, Fmspc};
|
||||
use asn1_der::{
|
||||
typed::{DerDecodable, Sequence},
|
||||
DerObject,
|
||||
};
|
||||
use x509_cert::certificate::CertificateInner;
|
||||
|
||||
pub mod oids {
|
||||
use const_oid::ObjectIdentifier as OID;
|
||||
|
||||
const fn oid(s: &str) -> OID {
|
||||
OID::new_unwrap(s)
|
||||
}
|
||||
|
||||
pub const SGX_EXTENSION: OID = oid("1.2.840.113741.1.13.1");
|
||||
pub const FMSPC: OID = oid("1.2.840.113741.1.13.1.4");
|
||||
|
||||
#[test]
|
||||
fn const_oid_works() {
|
||||
assert_eq!(
|
||||
SGX_EXTENSION.as_bytes(),
|
||||
oid("1.2.840.113741.1.13.1").as_bytes()
|
||||
);
|
||||
}
|
||||
}
|
||||
pub fn get_intel_extension(cert: &CertificateInner) -> Result<Vec<u8>, QuoteError> {
|
||||
let mut extension_iter = cert
|
||||
.tbs_certificate
|
||||
.extensions
|
||||
.as_deref()
|
||||
.unwrap_or(&[])
|
||||
.iter()
|
||||
.filter(|e| e.extn_id == oids::SGX_EXTENSION)
|
||||
.map(|e| e.extn_value.clone());
|
||||
|
||||
let extension = extension_iter
|
||||
.next()
|
||||
.ok_or_else(|| QuoteError::Unexpected("Intel extension not found".into()))?;
|
||||
if extension_iter.next().is_some() {
|
||||
//"There should only be one section containing Intel extensions"
|
||||
return Err(QuoteError::Unexpected("Intel extension ambiguity".into()));
|
||||
}
|
||||
Ok(extension.into_bytes())
|
||||
}
|
||||
|
||||
pub fn find_extension(path: &[&[u8]], raw: &[u8]) -> Result<Vec<u8>, QuoteError> {
|
||||
let obj = DerObject::decode(raw)
|
||||
.map_err(|_| QuoteError::Unexpected("Failed to decode DER object".into()))?;
|
||||
let subobj =
|
||||
get_obj(path, obj).map_err(|_| QuoteError::Unexpected("Failed to get subobject".into()))?;
|
||||
Ok(subobj.value().to_vec())
|
||||
}
|
||||
|
||||
fn get_obj<'a>(path: &[&[u8]], mut obj: DerObject<'a>) -> Result<DerObject<'a>, QuoteError> {
|
||||
for oid in path {
|
||||
let seq = Sequence::load(obj)
|
||||
.map_err(|_| QuoteError::Unexpected("Failed to load sequence".into()))?;
|
||||
obj = sub_obj(oid, seq)
|
||||
.map_err(|_| QuoteError::Unexpected("Failed to get subobject".into()))?;
|
||||
}
|
||||
Ok(obj)
|
||||
}
|
||||
|
||||
fn sub_obj<'a>(oid: &[u8], seq: Sequence<'a>) -> Result<DerObject<'a>, QuoteError> {
|
||||
for i in 0..seq.len() {
|
||||
let entry = seq
|
||||
.get(i)
|
||||
.map_err(|_| QuoteError::Unexpected("Failed to get entry".into()))?;
|
||||
let entry = Sequence::load(entry)
|
||||
.map_err(|_| QuoteError::Unexpected("Failed to load sequence".into()))?;
|
||||
let name = entry
|
||||
.get(0)
|
||||
.map_err(|_| QuoteError::Unexpected("Failed to get name".into()))?;
|
||||
let value = entry
|
||||
.get(1)
|
||||
.map_err(|_| QuoteError::Unexpected("Failed to get value".into()))?;
|
||||
if name.value() == oid {
|
||||
return Ok(value);
|
||||
}
|
||||
}
|
||||
Err(QuoteError::Unexpected("Oid is missing".into()))
|
||||
}
|
||||
|
||||
pub fn get_fmspc(extension_section: &[u8]) -> Result<Fmspc, QuoteError> {
|
||||
let data = find_extension(&[oids::FMSPC.as_bytes()], extension_section)
|
||||
.map_err(|_| QuoteError::Unexpected("Failed to find Fmspc".into()))?;
|
||||
if data.len() != 6 {
|
||||
return Err(QuoteError::Unexpected("Fmspc length mismatch".into()));
|
||||
}
|
||||
|
||||
data.try_into()
|
||||
.map_err(|_| QuoteError::Unexpected("Failed to decode Fmspc".into()))
|
||||
}
|
||||
|
||||
pub fn extract_certs(cert_chain: &[u8]) -> Result<Vec<CertificateInner>, QuoteError> {
|
||||
let cert_chain = cert_chain.strip_suffix(&[0]).unwrap_or(cert_chain);
|
||||
|
||||
CertificateInner::<x509_cert::certificate::Rfc5280>::load_pem_chain(cert_chain)
|
||||
.map_err(|e| QuoteError::Unexpected(format!("Could not load a PEM chain: {}", e)))
|
||||
}
|
1394
crates/teepot/tests/sgx_quote_fmspc.rs
Normal file
1394
crates/teepot/tests/sgx_quote_fmspc.rs
Normal file
File diff suppressed because it is too large
Load diff
Loading…
Add table
Add a link
Reference in a new issue