Merge pull request #300 from matter-labs/darwin

feat: compat code for non `x86_64-linux`
This commit is contained in:
Harald Hoyer 2025-04-10 13:25:33 +02:00 committed by GitHub
commit 93c35dad38
No known key found for this signature in database
GPG key ID: B5690EEEBB952194
44 changed files with 1543 additions and 531 deletions

38
.github/workflows/nix-non-x86.yml vendored Normal file
View file

@ -0,0 +1,38 @@
name: nix-non-x86
permissions:
contents: read
pull-requests: read
on:
pull_request:
branches: ["main"]
push:
branches: ["main"]
tags: ["*"]
jobs:
macos-latest:
runs-on: macos-latest
steps:
- uses: actions/checkout@11bd71901bbe5b1630ceea73d27597364c9af683 # v4
- uses: cachix/install-nix-action@v31
with:
extra_nix_config: |
access-tokens = github.com=${{ github.token }}
trusted-public-keys = cache.nixos.org-1:6NCHdD59X431o0gWypbMrAURkbJ16ZPMQFGspcDShjY= tee-pot:SS6HcrpG87S1M6HZGPsfo7d1xJccCGev7/tXc5+I4jg=
substituters = https://cache.nixos.org/ https://attic.teepot.org/tee-pot
sandbox = true
- name: Setup Attic cache
uses: ryanccn/attic-action@v0
with:
endpoint: https://attic.teepot.org/
cache: tee-pot
token: ${{ secrets.ATTIC_TOKEN }}
- name: nixci
# FIXME: this prevents it from running on macos
# https://github.com/NixOS/nix/pull/12570
# run: nix run github:nixos/nixpkgs/nixos-24.11#nixci -- build
run: nix build -L .#teepot

557
Cargo.lock generated

File diff suppressed because it is too large Load diff

View file

@ -1,5 +1,6 @@
[workspace] [workspace]
members = ["crates/*", "bin/*", "crates/teepot-vault/bin/*"] members = ["crates/*", "bin/*", "crates/teepot-vault/bin/*"]
exclude = ["crates/teepot-tee-quote-verification-rs"]
resolver = "2" resolver = "2"
[profile.release] [profile.release]
@ -30,8 +31,6 @@ enumset = { version = "1.1", features = ["serde"] }
getrandom = { version = "0.3.1", features = ["std"] } getrandom = { version = "0.3.1", features = ["std"] }
gpt = "4.0.0" gpt = "4.0.0"
hex = { version = "0.4.3", features = ["std"], default-features = false } hex = { version = "0.4.3", features = ["std"], default-features = false }
intel-tee-quote-verification-rs = { package = "teepot-tee-quote-verification-rs", path = "crates/teepot-tee-quote-verification-rs", version = "0.3.0" }
intel-tee-quote-verification-sys = { version = "0.2.1" }
num-integer = "0.1.46" num-integer = "0.1.46"
num-traits = "0.2.18" num-traits = "0.2.18"
opentelemetry = { version = "0.28.0", features = ["default", "logs"] } opentelemetry = { version = "0.28.0", features = ["default", "logs"] }

View file

@ -12,7 +12,7 @@ use std::{
}; };
use teepot::{ use teepot::{
log::{setup_logging, LogLevelParser}, log::{setup_logging, LogLevelParser},
tdx::rtmr::UEFI_MARKER_DIGEST_BYTES, tdx::UEFI_MARKER_DIGEST_BYTES,
}; };
use tracing::{debug, info, level_filters::LevelFilter}; use tracing::{debug, info, level_filters::LevelFilter};

View file

@ -6,53 +6,64 @@
#![deny(missing_docs)] #![deny(missing_docs)]
#![deny(clippy::all)] #![deny(clippy::all)]
use anyhow::{Context, Result}; use tracing::error;
use clap::Parser;
use teepot::{
log::{setup_logging, LogLevelParser},
tdx::rtmr::TdxRtmrEvent,
util::pad,
};
use tracing::{error, level_filters::LevelFilter};
/// Extend a TDX rtmr with a hash digest for measured boot. #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
#[derive(Parser, Debug)] mod os {
#[command(author, version, about, long_about = None)] use anyhow::{Context as _, Result};
struct Arguments { use clap::Parser;
/// digest in hex to extend the rtmr with use teepot::{
#[arg(long)] log::{setup_logging, LogLevelParser},
digest: String, tdx::rtmr::TdxRtmrEvent,
/// the number or the rtmr util::pad,
#[arg(long, default_value = "2")] };
rtmr: u64, use tracing::level_filters::LevelFilter;
/// Log level for the log output.
/// Valid values are: `off`, `error`, `warn`, `info`, `debug`, `trace` /// Extend a TDX rtmr with a hash digest for measured boot.
#[clap(long, default_value_t = LevelFilter::WARN, value_parser = LogLevelParser)] #[derive(Parser, Debug)]
pub log_level: LevelFilter, #[command(author, version, about, long_about = None)]
struct Arguments {
/// digest in hex to extend the rtmr with
#[arg(long)]
digest: String,
/// the number or the rtmr
#[arg(long, default_value = "2")]
rtmr: u64,
/// Log level for the log output.
/// Valid values are: `off`, `error`, `warn`, `info`, `debug`, `trace`
#[clap(long, default_value_t = LevelFilter::WARN, value_parser = LogLevelParser)]
pub log_level: LevelFilter,
}
pub fn main_with_error() -> Result<()> {
let args = Arguments::parse();
tracing::subscriber::set_global_default(setup_logging(
env!("CARGO_CRATE_NAME"),
&args.log_level,
)?)?;
// Parse the digest string as a hex array
let digest_bytes = hex::decode(&args.digest).context("Invalid digest format")?;
let extend_data: [u8; 48] = pad(&digest_bytes).context("Invalid digest length")?;
// Extend the TDX measurement with the extend data
TdxRtmrEvent::default()
.with_extend_data(extend_data)
.with_rtmr_index(args.rtmr)
.extend()?;
Ok(())
}
} }
fn main_with_error() -> Result<()> { #[cfg(not(all(target_os = "linux", target_arch = "x86_64")))]
let args = Arguments::parse(); mod os {
tracing::subscriber::set_global_default(setup_logging( pub fn main_with_error() -> anyhow::Result<()> {
env!("CARGO_CRATE_NAME"), anyhow::bail!("OS or architecture not supported");
&args.log_level, }
)?)?;
// Parse the digest string as a hex array
let digest_bytes = hex::decode(&args.digest).context("Invalid digest format")?;
let extend_data: [u8; 48] = pad(&digest_bytes).context("Invalid digest length")?;
// Extend the TDX measurement with the extend data
TdxRtmrEvent::default()
.with_extend_data(extend_data)
.with_rtmr_index(args.rtmr)
.extend()?;
Ok(())
} }
fn main() -> anyhow::Result<()> {
fn main() -> Result<()> { let ret = os::main_with_error();
let ret = main_with_error();
if let Err(e) = &ret { if let Err(e) = &ret {
error!(error = %e, "Execution failed"); error!(error = %e, "Execution failed");
} }

View file

@ -6,21 +6,10 @@
#![deny(missing_docs)] #![deny(missing_docs)]
#![deny(clippy::all)] #![deny(clippy::all)]
use anyhow::{Context, Result}; use anyhow::Result;
use clap::Parser; use clap::Parser;
use secp256k1::{rand, Secp256k1}; use std::ffi::OsString;
use std::{ffi::OsString, os::unix::process::CommandExt, process::Command};
use teepot::{
ethereum::public_key_to_ethereum_address,
prover::reportdata::ReportDataV1,
quote::get_quote,
tdx::rtmr::{TdxRtmrEvent, UEFI_MARKER_DIGEST_BYTES},
};
use tracing::error; use tracing::error;
use tracing_log::LogTracer;
use tracing_subscriber::{fmt, prelude::*, EnvFilter, Registry};
const TEE_QUOTE_FILE: &str = "/tmp/tee_quote";
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(author, version, about, long_about = None)] #[command(author, version, about, long_about = None)]
@ -33,7 +22,21 @@ struct Args {
cmd_args: Vec<OsString>, cmd_args: Vec<OsString>,
} }
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn main_with_error() -> Result<()> { fn main_with_error() -> Result<()> {
use anyhow::Context;
use secp256k1::{rand, Secp256k1};
use std::{os::unix::process::CommandExt, process::Command};
use teepot::tdx::rtmr::TdxRtmrEvent;
use teepot::{
ethereum::public_key_to_ethereum_address, prover::reportdata::ReportDataV1,
quote::get_quote,
};
use tracing_log::LogTracer;
use tracing_subscriber::{fmt, prelude::*, EnvFilter, Registry};
const TEE_QUOTE_FILE: &str = "/tmp/tee_quote";
LogTracer::init().context("Failed to set logger")?; LogTracer::init().context("Failed to set logger")?;
let subscriber = Registry::default() let subscriber = Registry::default()
@ -54,7 +57,7 @@ fn main_with_error() -> Result<()> {
// so that any breach can't generate a new attestation with the expected RTMRs // so that any breach can't generate a new attestation with the expected RTMRs
TdxRtmrEvent::default() TdxRtmrEvent::default()
.with_rtmr_index(3) .with_rtmr_index(3)
.with_extend_data(UEFI_MARKER_DIGEST_BYTES) .with_extend_data(teepot::tdx::UEFI_MARKER_DIGEST_BYTES)
.extend()?; .extend()?;
// save quote to file // save quote to file
@ -94,6 +97,11 @@ fn main_with_error() -> Result<()> {
}) })
} }
#[cfg(not(all(target_os = "linux", target_arch = "x86_64")))]
fn main_with_error() -> Result<()> {
anyhow::bail!("OS or architecture not supported");
}
fn main() -> Result<()> { fn main() -> Result<()> {
let ret = main_with_error(); let ret = main_with_error();
if let Err(e) = &ret { if let Err(e) = &ret {

View file

@ -7,10 +7,7 @@ use anyhow::{bail, Context, Result};
use clap::Parser; use clap::Parser;
use std::{fs, io::Read, path::PathBuf, str::FromStr, time::UNIX_EPOCH}; use std::{fs, io::Read, path::PathBuf, str::FromStr, time::UNIX_EPOCH};
use teepot::quote::{ use teepot::quote::{get_collateral, verify_quote_with_collateral, QuoteVerificationResult};
error, tcblevel::TcbLevel, tee_qv_get_collateral, verify_quote_with_collateral,
QuoteVerificationResult,
};
#[derive(Parser, Debug)] #[derive(Parser, Debug)]
#[command(author = "Matter Labs", version, about = "SGX attestation and batch signature verifier", long_about = None)] #[command(author = "Matter Labs", version, about = "SGX attestation and batch signature verifier", long_about = None)]
@ -62,10 +59,7 @@ fn verify_attestation_quote(attestation_quote_bytes: &[u8]) -> Result<QuoteVerif
"Verifying quote ({} bytes)...", "Verifying quote ({} bytes)...",
attestation_quote_bytes.len() attestation_quote_bytes.len()
); );
let collateral = error::QuoteContext::context( let collateral = get_collateral(attestation_quote_bytes)?;
tee_qv_get_collateral(attestation_quote_bytes),
"Failed to get collateral",
)?;
let unix_time: i64 = std::time::SystemTime::now() let unix_time: i64 = std::time::SystemTime::now()
.duration_since(UNIX_EPOCH)? .duration_since(UNIX_EPOCH)?
.as_secs() as _; .as_secs() as _;
@ -76,7 +70,7 @@ fn verify_attestation_quote(attestation_quote_bytes: &[u8]) -> Result<QuoteVerif
fn print_quote_verification_summary(quote_verification_result: &QuoteVerificationResult) { fn print_quote_verification_summary(quote_verification_result: &QuoteVerificationResult) {
let QuoteVerificationResult { let QuoteVerificationResult {
collateral_expired, collateral_expired,
result, result: tcblevel,
quote, quote,
advisories, advisories,
.. ..
@ -84,7 +78,6 @@ fn print_quote_verification_summary(quote_verification_result: &QuoteVerificatio
if *collateral_expired { if *collateral_expired {
println!("Freshly fetched collateral expired"); println!("Freshly fetched collateral expired");
} }
let tcblevel = TcbLevel::from(*result);
for advisory in advisories { for advisory in advisories {
println!("\tInfo: Advisory ID: {advisory}"); println!("\tInfo: Advisory ID: {advisory}");
} }

View file

@ -61,12 +61,12 @@ impl Proof {
pub fn is_permanently_ignored(&self) -> bool { pub fn is_permanently_ignored(&self) -> bool {
self.status self.status
.as_ref() .as_ref()
.map_or(false, |s| s.eq_ignore_ascii_case("permanently_ignored")) .is_some_and(|s| s.eq_ignore_ascii_case("permanently_ignored"))
} }
/// Check if the proof is failed or picked by a prover /// Check if the proof is failed or picked by a prover
pub fn is_failed_or_picked(&self) -> bool { pub fn is_failed_or_picked(&self) -> bool {
self.status.as_ref().map_or(false, |s| { self.status.as_ref().is_some_and(|s| {
s.eq_ignore_ascii_case("failed") || s.eq_ignore_ascii_case("picked_by_prover") s.eq_ignore_ascii_case("failed") || s.eq_ignore_ascii_case("picked_by_prover")
}) })
} }

View file

@ -1,10 +1,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2023-2025 Matter Labs // Copyright (c) 2023-2025 Matter Labs
use teepot::quote::{ use teepot::quote::{get_collateral, verify_quote_with_collateral, QuoteVerificationResult};
error::QuoteContext, tee_qv_get_collateral, verify_quote_with_collateral,
QuoteVerificationResult,
};
use crate::error; use crate::error;
@ -15,10 +12,7 @@ impl AttestationVerifier {
/// Verify an attestation quote /// Verify an attestation quote
pub fn verify_quote(attestation_quote_bytes: &[u8]) -> error::Result<QuoteVerificationResult> { pub fn verify_quote(attestation_quote_bytes: &[u8]) -> error::Result<QuoteVerificationResult> {
// Get collateral for the quote // Get collateral for the quote
let collateral = QuoteContext::context( let collateral = get_collateral(attestation_quote_bytes)?;
tee_qv_get_collateral(attestation_quote_bytes),
"Failed to get collateral!",
)?;
// Get current time for verification // Get current time for verification
let unix_time: i64 = std::time::SystemTime::now() let unix_time: i64 = std::time::SystemTime::now()

View file

@ -19,7 +19,7 @@ impl PolicyEnforcer {
quote_verification_result: &QuoteVerificationResult, quote_verification_result: &QuoteVerificationResult,
) -> Result<()> { ) -> Result<()> {
let quote = &quote_verification_result.quote; let quote = &quote_verification_result.quote;
let tcblevel = TcbLevel::from(quote_verification_result.result); let tcblevel = quote_verification_result.result;
match &quote.report { match &quote.report {
Report::SgxEnclave(report_body) => { Report::SgxEnclave(report_body) => {

View file

@ -1,7 +1,7 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2023-2025 Matter Labs // Copyright (c) 2023-2025 Matter Labs
use teepot::quote::{tcblevel::TcbLevel, QuoteVerificationResult}; use teepot::quote::QuoteVerificationResult;
/// Handles reporting and logging of verification results /// Handles reporting and logging of verification results
pub struct VerificationReporter; pub struct VerificationReporter;
@ -11,7 +11,7 @@ impl VerificationReporter {
pub fn log_quote_verification_summary(quote_verification_result: &QuoteVerificationResult) { pub fn log_quote_verification_summary(quote_verification_result: &QuoteVerificationResult) {
let QuoteVerificationResult { let QuoteVerificationResult {
collateral_expired, collateral_expired,
result, result: tcblevel,
quote, quote,
advisories, advisories,
.. ..
@ -21,7 +21,6 @@ impl VerificationReporter {
tracing::warn!("Freshly fetched collateral expired!"); tracing::warn!("Freshly fetched collateral expired!");
} }
let tcblevel = TcbLevel::from(*result);
let advisories = if advisories.is_empty() { let advisories = if advisories.is_empty() {
"None".to_string() "None".to_string()
} else { } else {

View file

@ -5,10 +5,12 @@ name = "teepot-tee-quote-verification-rs"
version = "0.3.0" version = "0.3.0"
edition = "2021" edition = "2021"
license = "BSD-3-Clause" license = "BSD-3-Clause"
repository.workspace = true repository = "https://github.com/matter-labs/teepot"
homepage.workspace = true homepage = "https://github.com/matter-labs/teepot"
description = "Fork of intel-tee-quote-verification-rs" description = "Fork of intel-tee-quote-verification-rs"
[dependencies] [dependencies]
intel-tee-quote-verification-sys.workspace = true serde = { version = "1", features = ["derive", "rc"] }
serde.workspace = true
[target.'cfg(all(target_os = "linux", target_arch = "x86_64"))'.dependencies]
intel-tee-quote-verification-sys = { version = "0.2.1" }

View file

@ -17,14 +17,12 @@ clap.workspace = true
const-oid.workspace = true const-oid.workspace = true
futures-core = { version = "0.3.30", features = ["alloc"], default-features = false } futures-core = { version = "0.3.30", features = ["alloc"], default-features = false }
hex.workspace = true hex.workspace = true
intel-tee-quote-verification-rs.workspace = true
pgp.workspace = true pgp.workspace = true
rustls.workspace = true rustls.workspace = true
serde.workspace = true serde.workspace = true
serde_json.workspace = true serde_json.workspace = true
serde_with.workspace = true serde_with.workspace = true
sha2.workspace = true sha2.workspace = true
tdx-attest-rs.workspace = true
teepot.workspace = true teepot.workspace = true
thiserror.workspace = true thiserror.workspace = true
tracing.workspace = true tracing.workspace = true

View file

@ -8,13 +8,15 @@
pub mod vault; pub mod vault;
use crate::server::pki::{RaTlsCollateralExtension, RaTlsQuoteExtension}; use crate::server::{
attestation::Collateral,
pki::{RaTlsCollateralExtension, RaTlsQuoteExtension},
};
use actix_web::http::header; use actix_web::http::header;
use anyhow::Result; use anyhow::Result;
use awc::{Client, Connector}; use awc::{Client, Connector};
use clap::Args; use clap::Args;
use const_oid::AssociatedOid; use const_oid::AssociatedOid;
use intel_tee_quote_verification_rs::Collateral;
use rustls::{ use rustls::{
client::{ client::{
danger::{HandshakeSignatureValid, ServerCertVerifier}, danger::{HandshakeSignatureValid, ServerCertVerifier},
@ -25,10 +27,9 @@ use rustls::{
}; };
use sha2::{Digest, Sha256}; use sha2::{Digest, Sha256};
use std::{sync::Arc, time, time::Duration}; use std::{sync::Arc, time, time::Duration};
pub use teepot::{ pub use teepot::quote::{
quote::tcblevel::{parse_tcb_levels, EnumSet, TcbLevel}, tcblevel::{parse_tcb_levels, EnumSet, TcbLevel},
quote::{verify_quote_with_collateral, QuoteVerificationResult}, verify_quote_with_collateral, QuoteVerificationResult,
sgx::sgx_ql_qv_result_t,
}; };
use teepot::{quote::Report, sgx::Quote}; use teepot::{quote::Report, sgx::Quote};
use tracing::{debug, error, info, trace, warn}; use tracing::{debug, error, info, trace, warn};
@ -194,7 +195,7 @@ impl TeeConnection {
let QuoteVerificationResult { let QuoteVerificationResult {
collateral_expired, collateral_expired,
result, result: tcblevel,
quote, quote,
advisories, advisories,
earliest_expiration_date, earliest_expiration_date,
@ -206,7 +207,7 @@ impl TeeConnection {
return Err(Error::General("TDX quote and not SGX quote".into())); return Err(Error::General("TDX quote and not SGX quote".into()));
}; };
if collateral_expired || result != sgx_ql_qv_result_t::SGX_QL_QV_RESULT_OK { if collateral_expired || tcblevel != TcbLevel::Ok {
if collateral_expired { if collateral_expired {
error!( error!(
"Collateral is out of date! Expired {}", "Collateral is out of date! Expired {}",
@ -218,11 +219,10 @@ impl TeeConnection {
))); )));
} }
let tcblevel = TcbLevel::from(result);
if self if self
.args .args
.sgx_allowed_tcb_levels .sgx_allowed_tcb_levels
.map_or(true, |levels| !levels.contains(tcblevel)) .is_none_or(|levels| !levels.contains(tcblevel))
{ {
error!("Quote verification result: {}", tcblevel); error!("Quote verification result: {}", tcblevel);
return Err(Error::General(format!( return Err(Error::General(format!(

View file

@ -20,7 +20,6 @@ use awc::{
}; };
use bytes::Bytes; use bytes::Bytes;
use futures_core::Stream; use futures_core::Stream;
use intel_tee_quote_verification_rs::tee_qv_get_collateral;
use rustls::ClientConfig; use rustls::ClientConfig;
use serde_json::{json, Value}; use serde_json::{json, Value};
use std::{ use std::{
@ -28,13 +27,13 @@ use std::{
sync::Arc, sync::Arc,
time, time,
}; };
use teepot::quote::error::QuoteContext; use teepot::quote::get_collateral;
pub use teepot::{ pub use teepot::{
quote::{ quote::{
tcblevel::{parse_tcb_levels, EnumSet, TcbLevel}, tcblevel::{parse_tcb_levels, EnumSet, TcbLevel},
verify_quote_with_collateral, QuoteVerificationResult, verify_quote_with_collateral, QuoteVerificationResult,
}, },
sgx::{sgx_gramine_get_quote, sgx_ql_qv_result_t, Collateral}, sgx::{sgx_gramine_get_quote, Collateral},
}; };
use tracing::{debug, error, info, trace}; use tracing::{debug, error, info, trace};
@ -158,7 +157,7 @@ impl VaultConnection {
info!("Getting attestation report"); info!("Getting attestation report");
let attestation_url = AuthRequest::URL; let attestation_url = AuthRequest::URL;
let quote = sgx_gramine_get_quote(&self.key_hash).context("Failed to get own quote")?; let quote = sgx_gramine_get_quote(&self.key_hash).context("Failed to get own quote")?;
let collateral = tee_qv_get_collateral(&quote).context("Failed to get own collateral")?; let collateral = get_collateral(&quote).context("Failed to get own collateral")?;
let auth_req = AuthRequest { let auth_req = AuthRequest {
name: self.name.clone(), name: self.name.clone(),
@ -298,7 +297,7 @@ impl VaultConnection {
} }
/// set a secret in the vault /// set a secret in the vault
pub async fn store_secret<'de, T: serde::Serialize>( pub async fn store_secret<T: serde::Serialize>(
&self, &self,
val: T, val: T,
rel_path: &str, rel_path: &str,
@ -307,7 +306,7 @@ impl VaultConnection {
} }
/// set a secret in the vault for a different TEE /// set a secret in the vault for a different TEE
pub async fn store_secret_for_tee<'de, T: serde::Serialize>( pub async fn store_secret_for_tee<T: serde::Serialize>(
&self, &self,
tee_name: &str, tee_name: &str,
val: T, val: T,
@ -331,7 +330,7 @@ impl VaultConnection {
} }
/// get a secret from the vault /// get a secret from the vault
pub async fn load_secret<'de, T: serde::de::DeserializeOwned>( pub async fn load_secret<T: serde::de::DeserializeOwned>(
&self, &self,
rel_path: &str, rel_path: &str,
) -> Result<Option<T>, HttpResponseError> { ) -> Result<Option<T>, HttpResponseError> {
@ -339,7 +338,7 @@ impl VaultConnection {
} }
/// get a secret from the vault for a specific TEE /// get a secret from the vault for a specific TEE
pub async fn load_secret_for_tee<'de, T: serde::de::DeserializeOwned>( pub async fn load_secret_for_tee<T: serde::de::DeserializeOwned>(
&self, &self,
tee_name: &str, tee_name: &str,
rel_path: &str, rel_path: &str,

View file

@ -10,6 +10,15 @@ edition.workspace = true
authors.workspace = true authors.workspace = true
repository.workspace = true repository.workspace = true
[target.'cfg(all(target_os = "linux", target_arch = "x86_64"))'.dependencies]
tdx-attest-rs = { version = "0.1.2", git = "https://github.com/intel/SGXDataCenterAttestationPrimitives.git", rev = "aa239d25a437a28f3f4de92c38f5b6809faac842" }
teepot-tee-quote-verification-rs = { path = "../teepot-tee-quote-verification-rs" }
[target.'cfg(not(all(target_os = "linux", target_arch = "x86_64")))'.dependencies]
dcap-qvl = "0.2.3"
chrono = "0.4.40"
bytes.workspace = true
[dependencies] [dependencies]
anyhow.workspace = true anyhow.workspace = true
async-trait.workspace = true async-trait.workspace = true
@ -18,9 +27,9 @@ clap.workspace = true
config.workspace = true config.workspace = true
const-oid.workspace = true const-oid.workspace = true
enumset.workspace = true enumset.workspace = true
futures = "0.3.31"
getrandom.workspace = true getrandom.workspace = true
hex.workspace = true hex.workspace = true
intel-tee-quote-verification-rs.workspace = true
num-integer.workspace = true num-integer.workspace = true
num-traits.workspace = true num-traits.workspace = true
opentelemetry.workspace = true opentelemetry.workspace = true
@ -39,8 +48,8 @@ serde_json.workspace = true
sha2.workspace = true sha2.workspace = true
sha3.workspace = true sha3.workspace = true
signature.workspace = true signature.workspace = true
tdx-attest-rs.workspace = true
thiserror.workspace = true thiserror.workspace = true
tokio.workspace = true
tracing.workspace = true tracing.workspace = true
tracing-futures.workspace = true tracing-futures.workspace = true
tracing-log.workspace = true tracing-log.workspace = true

View file

@ -2,13 +2,12 @@
// Copyright (c) 2023-2025 Matter Labs // Copyright (c) 2023-2025 Matter Labs
//! Create a private key and a signed and self-signed certificates //! Create a private key and a signed and self-signed certificates
use crate::quote::{error::QuoteContext, get_quote}; use crate::quote::{get_collateral, get_quote};
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use const_oid::{ use const_oid::{
db::rfc5280::{ID_KP_CLIENT_AUTH, ID_KP_SERVER_AUTH}, db::rfc5280::{ID_KP_CLIENT_AUTH, ID_KP_SERVER_AUTH},
AssociatedOid, AssociatedOid,
}; };
use intel_tee_quote_verification_rs::tee_qv_get_collateral;
use p256::{ecdsa::DerSignature, pkcs8::EncodePrivateKey}; use p256::{ecdsa::DerSignature, pkcs8::EncodePrivateKey};
use pkcs8::der; use pkcs8::der;
use rustls::pki_types::PrivatePkcs8KeyDer; use rustls::pki_types::PrivatePkcs8KeyDer;
@ -148,7 +147,7 @@ pub fn make_self_signed_cert(
debug!("quote.len: {:?}", quote.len()); debug!("quote.len: {:?}", quote.len());
// Create a relative distinguished name. // Create a relative distinguished name.
let rdns = RdnSequence::from_str(dn)?; let rdns = RdnSequence::from_str(dn)?;
let collateral = tee_qv_get_collateral(&quote).context("Failed to get own collateral")?; let collateral = get_collateral(&quote).context("Failed to get own collateral")?;
let mut serial = [0u8; 16]; let mut serial = [0u8; 16];
getrandom::fill(&mut serial)?; getrandom::fill(&mut serial)?;

View file

@ -4,13 +4,11 @@
//! Common attestation API for all TEEs //! Common attestation API for all TEEs
use crate::quote::{ use crate::quote::{
error::QuoteContext, get_collateral, get_quote,
get_quote,
tcblevel::{EnumSet, TcbLevel}, tcblevel::{EnumSet, TcbLevel},
verify_quote_with_collateral, Collateral, QuoteVerificationResult, verify_quote_with_collateral, Collateral, QuoteVerificationResult,
}; };
use anyhow::{bail, Context, Result}; use anyhow::{bail, Context, Result};
use intel_tee_quote_verification_rs::tee_qv_get_collateral;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
sync::{Arc, RwLock}, sync::{Arc, RwLock},
@ -46,8 +44,7 @@ pub fn get_quote_and_collateral(
static ATTESTATION: RwLock<Option<Attestation>> = RwLock::new(None); static ATTESTATION: RwLock<Option<Attestation>> = RwLock::new(None);
let unix_time: i64 = std::time::SystemTime::now() let unix_time: i64 = std::time::SystemTime::now()
.duration_since(UNIX_EPOCH) .duration_since(UNIX_EPOCH)?
.unwrap()
.as_secs() as _; .as_secs() as _;
if let Some(attestation) = ATTESTATION.read().unwrap().as_ref() { if let Some(attestation) = ATTESTATION.read().unwrap().as_ref() {
@ -65,11 +62,11 @@ pub fn get_quote_and_collateral(
} }
let (_tee_type, myquote) = get_quote(report_data).context("Failed to get own quote")?; let (_tee_type, myquote) = get_quote(report_data).context("Failed to get own quote")?;
let collateral = tee_qv_get_collateral(&myquote).context("Failed to get own collateral")?; let collateral = get_collateral(&myquote).context("Failed to get own collateral")?;
let QuoteVerificationResult { let QuoteVerificationResult {
collateral_expired, collateral_expired,
result, result: tcblevel,
earliest_expiration_date, earliest_expiration_date,
tcb_level_date_tag, tcb_level_date_tag,
quote, quote,
@ -83,9 +80,8 @@ pub fn get_quote_and_collateral(
bail!("Freshly fetched collateral expired"); bail!("Freshly fetched collateral expired");
} }
let tcblevel = TcbLevel::from(result);
if tcblevel != TcbLevel::Ok if tcblevel != TcbLevel::Ok
&& allowed_tcb_levels.map_or(false, |levels| !levels.contains(tcblevel)) && allowed_tcb_levels.is_some_and(|levels| !levels.contains(tcblevel))
{ {
error!("Quote verification result: {}", tcblevel); error!("Quote verification result: {}", tcblevel);
bail!("Quote verification result: {}", tcblevel); bail!("Quote verification result: {}", tcblevel);

View file

@ -1,10 +1,10 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2024 Matter Labs // Copyright (c) 2024-2025 Matter Labs
//! Quote Error type //! Quote Error type
use intel_tee_quote_verification_rs::quote3_error_t;
use std::io; use std::io;
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
use tdx_attest_rs::tdx_attest_error_t; use tdx_attest_rs::tdx_attest_error_t;
use thiserror::Error; use thiserror::Error;
@ -22,8 +22,7 @@ pub enum QuoteError {
InvalidTeeType, InvalidTeeType,
#[error("unsupported body type")] #[error("unsupported body type")]
UnsupportedBodyType, UnsupportedBodyType,
#[error("quote verification error {msg}: {inner:?}")] #[cfg(all(target_os = "linux", target_arch = "x86_64"))]
Quote3Error { inner: quote3_error_t, msg: String },
#[error("tdx_att_get_quote error {msg}: {inner:?}")] #[error("tdx_att_get_quote error {msg}: {inner:?}")]
TdxAttGetQuote { TdxAttGetQuote {
inner: tdx_attest_error_t, inner: tdx_attest_error_t,
@ -35,8 +34,31 @@ pub enum QuoteError {
ReportDataSize, ReportDataSize,
#[error("can't get a quote: unknown TEE")] #[error("can't get a quote: unknown TEE")]
UnknownTee, UnknownTee,
#[error("{0}: invalid parameter")]
InvalidParameter(String),
#[error("{0}: platform lib unavailable")]
PlatformLibUnavailable(String),
#[error("{0}: pck cert chain error")]
PckCertChainError(String),
#[error("{0}: pck cert unsupported format")]
PckCertUnsupportedFormat(String),
#[error("{0}: quote format unsupported")]
QuoteFormatUnsupported(String),
#[error("{0}: out of memory")]
OutOfMemory(String),
#[error("{0}: no quote collateral data")]
NoQuoteCollateralData(String),
#[error("{0}: unexpected error")]
Unexpected(String),
#[error("{0}: quote certification data unsupported")]
QuoteCertificationDataUnsupported(String),
#[error("{0}: unable to generate report")]
UnableToGenerateReport(String),
#[error("{0}: CRL unsupported format")]
CrlUnsupportedFormat(String),
} }
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
impl From<tdx_attest_error_t> for QuoteError { impl From<tdx_attest_error_t> for QuoteError {
fn from(code: tdx_attest_error_t) -> Self { fn from(code: tdx_attest_error_t) -> Self {
Self::TdxAttGetQuote { Self::TdxAttGetQuote {
@ -64,16 +86,29 @@ impl<T> QuoteContext for Result<T, std::io::Error> {
} }
} }
impl<T> QuoteContext for Result<T, quote3_error_t> { impl<T> QuoteContext for Option<T> {
type Ok = T; type Ok = T;
fn context<I: Into<String>>(self, msg: I) -> Result<T, QuoteError> { fn context<I: Into<String>>(self, msg: I) -> Result<T, QuoteError> {
self.map_err(|e| QuoteError::Quote3Error { self.ok_or(QuoteError::Unexpected(msg.into()))
msg: msg.into(),
inner: e,
})
} }
} }
/// Usability trait for easy QuoteError annotation
pub trait QuoteContextErr {
/// The Ok Type
type Ok;
/// The Context
fn str_context<I: std::fmt::Display>(self, msg: I) -> Result<Self::Ok, QuoteError>;
}
impl<T, E: std::fmt::Display> QuoteContextErr for Result<T, E> {
type Ok = T;
fn str_context<I: std::fmt::Display>(self, msg: I) -> Result<T, QuoteError> {
self.map_err(|e| QuoteError::Unexpected(format!("{}: {}", msg, e)))
}
}
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
impl<T> QuoteContext for Result<T, tdx_attest_error_t> { impl<T> QuoteContext for Result<T, tdx_attest_error_t> {
type Ok = T; type Ok = T;
fn context<I: Into<String>>(self, msg: I) -> Result<T, QuoteError> { fn context<I: Into<String>>(self, msg: I) -> Result<T, QuoteError> {

View file

@ -0,0 +1,260 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2025 Matter Labs
//! Drive Intel DCAP verification crate, which is using the C library
use crate::{
quote::{
error::QuoteError, tcblevel::TcbLevel, Collateral, Quote, QuoteVerificationResult, TEEType,
},
sgx::sgx_gramine_get_quote,
};
use bytemuck::cast_slice;
use std::{ffi::CStr, mem, mem::MaybeUninit, pin::Pin};
use tdx_attest_rs::{tdx_att_get_quote, tdx_attest_error_t, tdx_report_data_t};
use teepot_tee_quote_verification_rs::{
quote3_error_t as _quote3_error_t, sgx_ql_qv_result_t, sgx_ql_qv_supplemental_t,
tee_get_supplemental_data_version_and_size, tee_qv_get_collateral, tee_supp_data_descriptor_t,
tee_verify_quote, Collateral as IntelCollateral,
};
use tracing::{trace, warn};
/// Convert SGX QV result to our TcbLevel enum
fn convert_tcb_level(value: sgx_ql_qv_result_t) -> TcbLevel {
match value {
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_OK => TcbLevel::Ok,
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_OUT_OF_DATE => TcbLevel::OutOfDate,
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_OUT_OF_DATE_CONFIG_NEEDED => {
TcbLevel::OutOfDateConfigNeeded
}
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_SW_HARDENING_NEEDED => TcbLevel::SwHardeningNeeded,
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_CONFIG_AND_SW_HARDENING_NEEDED => {
TcbLevel::ConfigAndSwHardeningNeeded
}
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_CONFIG_NEEDED => TcbLevel::ConfigNeeded,
_ => TcbLevel::Invalid,
}
}
/// Convert quote3_error_t to QuoteError with context
fn convert_quote_error(error: _quote3_error_t, context: impl Into<String>) -> QuoteError {
let context = context.into();
match error {
_quote3_error_t::SGX_QL_ERROR_INVALID_PARAMETER => QuoteError::InvalidParameter(context),
_quote3_error_t::SGX_QL_PCK_CERT_CHAIN_ERROR => QuoteError::PckCertChainError(context),
_quote3_error_t::SGX_QL_PCK_CERT_UNSUPPORTED_FORMAT => {
QuoteError::PckCertUnsupportedFormat(context)
}
_quote3_error_t::SGX_QL_QUOTE_FORMAT_UNSUPPORTED => {
QuoteError::QuoteFormatUnsupported(context)
}
_quote3_error_t::SGX_QL_ERROR_OUT_OF_MEMORY => QuoteError::OutOfMemory(context),
_quote3_error_t::SGX_QL_NO_QUOTE_COLLATERAL_DATA => {
QuoteError::NoQuoteCollateralData(context)
}
_quote3_error_t::SGX_QL_ERROR_UNEXPECTED => QuoteError::Unexpected(context),
_quote3_error_t::SGX_QL_QUOTE_CERTIFICATION_DATA_UNSUPPORTED => {
QuoteError::QuoteCertificationDataUnsupported(context)
}
_quote3_error_t::SGX_QL_UNABLE_TO_GENERATE_REPORT => {
QuoteError::UnableToGenerateReport(context)
}
_quote3_error_t::SGX_QL_CRL_UNSUPPORTED_FORMAT => QuoteError::CrlUnsupportedFormat(context),
_ => QuoteError::Unexpected(context),
}
}
/// Convert internal Collateral type to Intel library Collateral type
fn convert_to_intel_collateral(collateral: &Collateral) -> IntelCollateral {
IntelCollateral {
major_version: collateral.major_version,
minor_version: collateral.minor_version,
tee_type: collateral.tee_type,
pck_crl_issuer_chain: collateral.pck_crl_issuer_chain.clone(),
root_ca_crl: collateral.root_ca_crl.clone(),
pck_crl: collateral.pck_crl.clone(),
tcb_info_issuer_chain: collateral.tcb_info_issuer_chain.clone(),
tcb_info: collateral.tcb_info.clone(),
qe_identity_issuer_chain: collateral.qe_identity_issuer_chain.clone(),
qe_identity: collateral.qe_identity.clone(),
}
}
/// Get collateral data for a quote
pub fn get_collateral(quote: &[u8]) -> Result<Collateral, QuoteError> {
let collateral = tee_qv_get_collateral(quote)
.map_err(|e| convert_quote_error(e, "tee_qv_get_collateral"))?;
Ok(Collateral {
major_version: collateral.major_version,
minor_version: collateral.minor_version,
tee_type: collateral.tee_type,
pck_crl_issuer_chain: collateral.pck_crl_issuer_chain,
root_ca_crl: collateral.root_ca_crl,
pck_crl: collateral.pck_crl,
tcb_info_issuer_chain: collateral.tcb_info_issuer_chain,
tcb_info: collateral.tcb_info,
qe_identity_issuer_chain: collateral.qe_identity_issuer_chain,
qe_identity: collateral.qe_identity,
})
}
/// Extract advisory information from supplemental data
fn extract_supplemental_data_info(supp_data: sgx_ql_qv_supplemental_t) -> (Vec<String>, i64, i64) {
// Convert to valid UTF-8 string
let advisories = CStr::from_bytes_until_nul(cast_slice(&supp_data.sa_list[..]))
.ok()
.and_then(|s| CStr::to_str(s).ok())
.into_iter()
.flat_map(|s| s.split(',').map(str::trim).map(String::from))
.filter(|s| !s.is_empty())
.collect();
(
advisories,
supp_data.earliest_expiration_date,
supp_data.tcb_level_date_tag,
)
}
/// Verifies a quote with optional collateral material
pub(crate) fn verify_quote_with_collateral(
quote: &[u8],
collateral: Option<&crate::quote::Collateral>,
current_time: i64,
) -> Result<QuoteVerificationResult, QuoteError> {
// Convert collateral if provided
let intel_collateral = collateral.map(convert_to_intel_collateral);
let TeeSuppDataDescriptor {
supp_data,
mut supp_data_descriptor,
} = initialize_supplemental_data(quote)?;
let (collateral_expiration_status, result) = tee_verify_quote(
quote,
intel_collateral.as_ref(),
current_time,
None,
supp_data_descriptor.as_mut(),
)
.map_err(|e| convert_quote_error(e, "tee_verify_quote"))?;
// Extract supplemental data if available
let (advisories, earliest_expiration_date, tcb_level_date_tag) =
if supp_data_descriptor.is_some() {
let supp_data = unsafe { supp_data.assume_init() };
extract_supplemental_data_info(supp_data)
} else {
(vec![], 0, 0)
};
let quote = Quote::parse(quote)?;
Ok(QuoteVerificationResult {
collateral_expired: collateral_expiration_status != 0,
earliest_expiration_date,
tcb_level_date_tag,
result: convert_tcb_level(result),
quote,
advisories,
})
}
struct TeeSuppDataDescriptor {
supp_data: Pin<Box<MaybeUninit<sgx_ql_qv_supplemental_t>>>,
supp_data_descriptor: Option<tee_supp_data_descriptor_t>,
}
fn initialize_supplemental_data(quote: &[u8]) -> Result<TeeSuppDataDescriptor, QuoteError> {
let mut supp_data = Box::pin(mem::MaybeUninit::zeroed());
let mut supp_data_desc = tee_supp_data_descriptor_t {
major_version: 0,
data_size: 0,
p_data: supp_data.as_mut_ptr() as *mut u8,
};
trace!("tee_get_supplemental_data_version_and_size");
let (_, supp_size) = tee_get_supplemental_data_version_and_size(quote)
.map_err(|e| convert_quote_error(e, "tee_get_supplemental_data_version_and_size"))?;
trace!(
"tee_get_supplemental_data_version_and_size supp_size: {}",
supp_size
);
let p_supplemental_data = if supp_size == mem::size_of::<sgx_ql_qv_supplemental_t>() as u32 {
supp_data_desc.data_size = supp_size;
Some(supp_data_desc)
} else {
supp_data_desc.data_size = 0;
trace!(
"tee_get_supplemental_data_version_and_size supp_size: {}",
supp_size
);
trace!(
"mem::size_of::<sgx_ql_qv_supplemental_t>(): {}",
mem::size_of::<sgx_ql_qv_supplemental_t>()
);
warn!("Quote supplemental data size is different between DCAP QVL and QvE, please make sure you install DCAP QVL and QvE from the same release.");
None
};
Ok(TeeSuppDataDescriptor {
supp_data,
supp_data_descriptor: p_supplemental_data,
})
}
/// Prepare report data for quote generation
fn prepare_report_data(report_data: &[u8]) -> Result<[u8; 64], QuoteError> {
if report_data.len() > 64 {
return Err(QuoteError::ReportDataSize);
}
let mut report_data_fixed = [0u8; 64];
report_data_fixed[..report_data.len()].copy_from_slice(report_data);
Ok(report_data_fixed)
}
/// Get a TDX quote
fn tdx_get_quote(report_data_bytes: &[u8; 64]) -> Result<Box<[u8]>, QuoteError> {
let mut tdx_report_data = tdx_report_data_t { d: [0; 64usize] };
tdx_report_data.d.copy_from_slice(report_data_bytes);
let (error, quote) = tdx_att_get_quote(Some(&tdx_report_data), None, None, 0);
if error == tdx_attest_error_t::TDX_ATTEST_SUCCESS {
if let Some(quote) = quote {
Ok(quote.into())
} else {
Err(QuoteError::TdxAttGetQuote {
msg: "tdx_att_get_quote: No quote returned".into(),
inner: error,
})
}
} else {
Err(error.into())
}
}
/// Detect the TEE environment
fn detect_tee_environment() -> Result<TEEType, QuoteError> {
if std::fs::metadata("/dev/attestation").is_ok() {
Ok(TEEType::SGX)
} else if std::fs::metadata("/dev/tdx_guest").is_ok() {
Ok(TEEType::TDX)
} else {
Err(QuoteError::UnknownTee)
}
}
/// Get the attestation quote from a TEE
pub(crate) fn get_quote(report_data: &[u8]) -> Result<(TEEType, Box<[u8]>), QuoteError> {
let report_data_fixed = prepare_report_data(report_data)?;
match detect_tee_environment()? {
TEEType::SGX => Ok((TEEType::SGX, sgx_gramine_get_quote(&report_data_fixed)?)),
TEEType::TDX => Ok((TEEType::TDX, tdx_get_quote(&report_data_fixed)?)),
_ => Err(QuoteError::UnknownTee), // For future TEE types
}
}

View file

@ -10,27 +10,25 @@ pub mod attestation;
pub mod error; pub mod error;
pub mod tcblevel; pub mod tcblevel;
use crate::{ #[cfg_attr(all(target_os = "linux", target_arch = "x86_64"), path = "intel.rs")]
quote::error::{QuoteContext as _, QuoteError}, #[cfg_attr(
sgx::sgx_gramine_get_quote, not(all(target_os = "linux", target_arch = "x86_64")),
tdx::tgx_get_quote, path = "phala.rs"
}; )]
use bytemuck::{cast_slice, AnyBitPattern}; mod os;
use intel_tee_quote_verification_rs::{
sgx_ql_qv_result_t, sgx_ql_qv_supplemental_t, tee_get_supplemental_data_version_and_size, use crate::quote::{
tee_supp_data_descriptor_t, tee_verify_quote, Collateral, error::{QuoteContext as _, QuoteError},
tcblevel::TcbLevel,
}; };
use bytemuck::AnyBitPattern;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
ffi::CStr,
fmt::{Display, Formatter}, fmt::{Display, Formatter},
io::Read, io::Read,
mem,
str::FromStr, str::FromStr,
}; };
use tracing::{trace, warn}; use tracing::trace;
pub use intel_tee_quote_verification_rs::tee_qv_get_collateral;
#[allow(missing_docs)] #[allow(missing_docs)]
pub const TEE_TYPE_SGX: u32 = 0x00000000; pub const TEE_TYPE_SGX: u32 = 0x00000000;
@ -603,35 +601,13 @@ impl FromStr for TEEType {
/// Get the attestation quote from a TEE /// Get the attestation quote from a TEE
pub fn get_quote(report_data: &[u8]) -> Result<(TEEType, Box<[u8]>), QuoteError> { pub fn get_quote(report_data: &[u8]) -> Result<(TEEType, Box<[u8]>), QuoteError> {
// check, if we are running in a TEE os::get_quote(report_data)
if std::fs::metadata("/dev/attestation").is_ok() {
if report_data.len() > 64 {
return Err(QuoteError::ReportDataSize);
}
let mut report_data_fixed = [0u8; 64];
report_data_fixed[..report_data.len()].copy_from_slice(report_data);
Ok((TEEType::SGX, sgx_gramine_get_quote(&report_data_fixed)?))
} else if std::fs::metadata("/dev/tdx_guest").is_ok() {
if report_data.len() > 64 {
return Err(QuoteError::ReportDataSize);
}
let mut report_data_fixed = [0u8; 64];
report_data_fixed[..report_data.len()].copy_from_slice(report_data);
Ok((TEEType::TDX, tgx_get_quote(&report_data_fixed)?))
} else {
// if not, return an error
Err(QuoteError::UnknownTee)
}
} }
/// The result of the quote verification /// The result of the quote verification
pub struct QuoteVerificationResult { pub struct QuoteVerificationResult {
/// the raw result /// the raw result
pub result: sgx_ql_qv_result_t, pub result: TcbLevel,
/// indicates if the collateral is expired /// indicates if the collateral is expired
pub collateral_expired: bool, pub collateral_expired: bool,
/// the earliest expiration date of the collateral /// the earliest expiration date of the collateral
@ -644,93 +620,41 @@ pub struct QuoteVerificationResult {
pub quote: Quote, pub quote: Quote,
} }
/// The collateral data needed to do remote attestation for SGX and TDX
#[derive(Debug, Clone, Serialize, Deserialize)]
pub struct Collateral {
/// Major version of the collateral data
pub major_version: u16,
/// Minor version of the collateral data
pub minor_version: u16,
/// Type of TEE (SGX=0, TDX=0x81)
pub tee_type: u32,
/// The PCK CRL issuer chain used for validating the PCK CRL
pub pck_crl_issuer_chain: Box<[u8]>,
/// The root CA CRL used for validating the PCK CRL issuer chain
pub root_ca_crl: Box<[u8]>,
/// The PCK CRL used for validating the PCK certificate
pub pck_crl: Box<[u8]>,
/// The TCB info issuer chain used for validating the TCB info
pub tcb_info_issuer_chain: Box<[u8]>,
/// The TCB info used for determining the TCB level
pub tcb_info: Box<[u8]>,
/// The QE identity issuer chain used for validating the QE identity
pub qe_identity_issuer_chain: Box<[u8]>,
/// The QE identity used for validating the QE
pub qe_identity: Box<[u8]>,
}
/// Get the collateral data from an SGX or TDX quote
pub fn get_collateral(quote: &[u8]) -> Result<Collateral, QuoteError> {
os::get_collateral(quote)
}
/// Verifies a quote with optional collateral material /// Verifies a quote with optional collateral material
pub fn verify_quote_with_collateral( pub fn verify_quote_with_collateral(
quote: &[u8], quote: &[u8],
collateral: Option<&Collateral>, collateral: Option<&Collateral>,
current_time: i64, current_time: i64,
) -> Result<QuoteVerificationResult, QuoteError> { ) -> Result<QuoteVerificationResult, QuoteError> {
let mut supp_data: mem::MaybeUninit<sgx_ql_qv_supplemental_t> = mem::MaybeUninit::zeroed(); os::verify_quote_with_collateral(quote, collateral, current_time)
let mut supp_data_desc = tee_supp_data_descriptor_t {
major_version: 0,
data_size: 0,
p_data: supp_data.as_mut_ptr() as *mut u8,
};
trace!("tee_get_supplemental_data_version_and_size");
let (_, supp_size) =
tee_get_supplemental_data_version_and_size(quote).map_err(|e| QuoteError::Quote3Error {
msg: "tee_get_supplemental_data_version_and_size".into(),
inner: e,
})?;
trace!(
"tee_get_supplemental_data_version_and_size supp_size: {}",
supp_size
);
if supp_size == mem::size_of::<sgx_ql_qv_supplemental_t>() as u32 {
supp_data_desc.data_size = supp_size;
} else {
supp_data_desc.data_size = 0;
trace!(
"tee_get_supplemental_data_version_and_size supp_size: {}",
supp_size
);
trace!(
"mem::size_of::<sgx_ql_qv_supplemental_t>(): {}",
mem::size_of::<sgx_ql_qv_supplemental_t>()
);
warn!("Quote supplemental data size is different between DCAP QVL and QvE, please make sure you installed DCAP QVL and QvE from same release.")
}
let p_supplemental_data = match supp_data_desc.data_size {
0 => None,
_ => Some(&mut supp_data_desc),
};
let has_sup = p_supplemental_data.is_some();
trace!("tee_verify_quote");
let (collateral_expiration_status, result) =
tee_verify_quote(quote, collateral, current_time, None, p_supplemental_data)
.context("tee_verify_quote")?;
trace!("tee_verify_quote end");
// check supplemental data if necessary
let (advisories, earliest_expiration_date, tcb_level_date_tag) = if has_sup {
unsafe {
let supp_data = supp_data.assume_init();
// convert to valid UTF-8 string
let ads = CStr::from_bytes_until_nul(cast_slice(&supp_data.sa_list[..]))
.ok()
.and_then(|s| CStr::to_str(s).ok())
.into_iter()
.flat_map(|s| s.split(',').map(str::trim).map(String::from))
.filter(|s| !s.is_empty())
.collect();
(
ads,
supp_data.earliest_expiration_date,
supp_data.tcb_level_date_tag,
)
}
} else {
(vec![], 0, 0)
};
trace!("Quote::parse");
let quote = Quote::parse(quote)?;
let res = QuoteVerificationResult {
collateral_expired: collateral_expiration_status != 0,
earliest_expiration_date,
tcb_level_date_tag,
result,
quote,
advisories,
};
Ok(res)
} }

View file

@ -0,0 +1,259 @@
// SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2023-2025 Matter Labs
//! Emulate Intel DCAP library collateral and verification
use crate::quote::{
error::{QuoteContext, QuoteContextErr, QuoteError},
tcblevel::TcbLevel,
Collateral, Quote, QuoteVerificationResult, TEEType,
};
use bytes::Bytes;
use dcap_qvl::{verify::VerifiedReport, QuoteCollateralV3};
use std::ffi::{CStr, CString};
use std::str::FromStr;
/// Helper function to extract a required header value from a response
fn extract_header_value(
response: &reqwest::Response,
header_name: &str,
) -> Result<String, QuoteError> {
response
.headers()
.get(header_name)
.ok_or_else(|| QuoteError::Unexpected(format!("Missing required header: {header_name}")))?
.to_str()
.map_err(|e| QuoteError::Unexpected(format!("Invalid header value: {e}")))
.map(|val| val.to_string())
}
/// Fetch collateral data from Intel's Provisioning Certification Service
async fn fetch_pcs_collateral(
quote: &[u8],
) -> Result<(QuoteCollateralV3, String, Bytes), QuoteError> {
let client = reqwest::Client::new();
let response = client
.get("https://api.trustedservices.intel.com/sgx/certification/v4/pckcrl?ca=platform")
.send()
.await
.map_err(|e| QuoteError::Unexpected(format!("Failed to fetch collateral: {e}")))?;
// Extract required fields
let issuer_chain = extract_header_value(&response, "SGX-PCK-CRL-Issuer-Chain")?;
let pck_crl_data = response
.bytes()
.await
.map_err(|e| QuoteError::Unexpected(format!("Failed to fetch collateral data: {e}")))?;
// Fetch the full collateral
dcap_qvl::collateral::get_collateral_from_pcs(quote, std::time::Duration::from_secs(1000))
.await
.map(|collateral| (collateral, issuer_chain, pck_crl_data))
.str_context("Fetching PCS collateral with `get_collateral_from_pcs`")
}
/// Get collateral for a quote, handling the async operations
pub(crate) fn get_collateral(quote: &[u8]) -> Result<Collateral, QuoteError> {
// Execute the async operation in a separate thread
let result = std::thread::scope(|s| {
s.spawn(|| {
// Create a minimal runtime for this thread only
let rt = tokio::runtime::Builder::new_current_thread()
.enable_all()
.build()
.context("Failed to build tokio runtime")?;
// Run the async function
rt.block_on(fetch_pcs_collateral(quote))
})
.join()
.map_err(|_| QuoteError::Unexpected("Thread panic in get_collateral".into()))
})??;
// Destructure the result
let (collateral, pck_crl, pck_issuer_chain) = result;
// Convert QuoteCollateralV3 to Collateral
convert_to_collateral(collateral, pck_crl, pck_issuer_chain)
}
// Helper function to convert QuoteCollateralV3 to Collateral
fn convert_to_collateral(
collateral: QuoteCollateralV3,
pck_crl: String,
pck_issuer_chain: Bytes,
) -> Result<Collateral, QuoteError> {
let QuoteCollateralV3 {
tcb_info_issuer_chain,
tcb_info,
tcb_info_signature,
qe_identity_issuer_chain,
qe_identity,
qe_identity_signature,
} = collateral;
let tcb_info_signature = hex::encode(tcb_info_signature);
let qe_identity_signature = hex::encode(qe_identity_signature);
// Create strings with proper formatting
let tcb_info_json =
format!("{{ \"tcbInfo\": {tcb_info}, \"signature\": \"{tcb_info_signature}\" }}");
let qe_identity_json = format!(
"{{ \"enclaveIdentity\": {qe_identity}, \"signature\": \"{qe_identity_signature}\" }}"
);
// Helper to create CString and convert to Box<[u8]>
let to_bytes_with_nul = |s: String, context: &str| -> Result<Box<[u8]>, QuoteError> {
Ok(CString::new(s)
.str_context(context)?
.as_bytes_with_nul()
.into())
};
Ok(Collateral {
// Default/unhandled values
major_version: 0,
minor_version: 0,
tee_type: 0,
root_ca_crl: Box::new([]),
// Converted values
pck_crl_issuer_chain: pck_issuer_chain.as_ref().into(),
pck_crl: pck_crl.as_bytes().into(),
tcb_info_issuer_chain: to_bytes_with_nul(tcb_info_issuer_chain, "tcb_info_issuer_chain")?,
tcb_info: to_bytes_with_nul(tcb_info_json, "tcb_info")?,
qe_identity_issuer_chain: to_bytes_with_nul(
qe_identity_issuer_chain,
"qe_identity_issuer_chain",
)?,
qe_identity: to_bytes_with_nul(qe_identity_json, "qe_identity")?,
})
}
/// Split the last zero byte
fn get_str_from_bytes(bytes: &[u8], context: &str) -> Result<String, QuoteError> {
let c_str = CStr::from_bytes_until_nul(bytes)
.str_context(format!("Failed to extract CString: {}", context))?;
Ok(c_str.to_string_lossy().into_owned())
}
/// Parse JSON field from collateral data
fn parse_json_field(data: &[u8], context: &str) -> Result<serde_json::Value, QuoteError> {
serde_json::from_str(&get_str_from_bytes(data, context)?)
.str_context(format!("Failed to parse JSON: {}", context))
}
/// Convert Collateral to QuoteCollateralV3
fn convert_collateral(collateral: &Collateral) -> Result<QuoteCollateralV3, QuoteError> {
// Parse TCB info
let tcb_info_json = parse_json_field(collateral.tcb_info.as_ref(), "tcb_info_json")?;
let tcb_info = tcb_info_json["tcbInfo"].to_string();
let tcb_info_signature = tcb_info_json
.get("signature")
.context("TCB Info missing 'signature' field")?
.as_str()
.context("TCB Info signature must be a string")?;
let tcb_info_signature = hex::decode(tcb_info_signature)
.ok()
.context("TCB Info signature must be valid hex")?;
// Parse QE identity
let qe_identity_json = parse_json_field(collateral.qe_identity.as_ref(), "qe_identity_json")?;
let qe_identity = qe_identity_json
.get("enclaveIdentity")
.context("QE Identity missing 'enclaveIdentity' field")?
.to_string();
let qe_identity_signature = qe_identity_json
.get("signature")
.context("QE Identity missing 'signature' field")?
.as_str()
.context("QE Identity signature must be a string")?;
let qe_identity_signature = hex::decode(qe_identity_signature)
.ok()
.context("QE Identity signature must be valid hex")?;
Ok(QuoteCollateralV3 {
tcb_info_issuer_chain: get_str_from_bytes(
collateral.tcb_info_issuer_chain.as_ref(),
"convert_collateral: tcb_info_issuer_chain",
)?,
tcb_info,
tcb_info_signature,
qe_identity_issuer_chain: get_str_from_bytes(
collateral.qe_identity_issuer_chain.as_ref(),
"convert_collateral: qe_identity_issuer_chain",
)?,
qe_identity,
qe_identity_signature,
})
}
/// Verify a quote with the provided collateral
pub(crate) fn verify_quote_with_collateral(
quote: &[u8],
collateral: Option<&Collateral>,
current_time: i64,
) -> Result<QuoteVerificationResult, QuoteError> {
// Convert collateral or return error if not provided
let collateral = collateral
.ok_or_else(|| QuoteError::Unexpected("No collateral provided".into()))
.and_then(convert_collateral)?;
// Convert current time to u64
let current_time_u64 = current_time
.try_into()
.str_context("Failed to convert current_time to u64")?;
// Verify the quote
let verified_report = dcap_qvl::verify::verify(quote, &collateral, current_time_u64)
.expect("Failed to verify quote");
let VerifiedReport {
status,
advisory_ids,
report: _,
} = verified_report;
// Parse TCB level from status
let result =
TcbLevel::from_str(&status).str_context("Failed to parse TCB level from status")?;
// Parse quote
let quote = Quote::parse(quote)?;
let tcb_info_json: serde_json::Value =
serde_json::from_str(&String::from_utf8_lossy(collateral.tcb_info.as_ref()))
.str_context("verify_quote_with_collateral tcb_info_json")?;
let next_update = tcb_info_json
.get("nextUpdate")
.context("verify_quote_with_collateral: TCB Info missing 'nextUpdate' field")?
.as_str()
.context("verify_quote_with_collateral: TCB Info nextUpdate must be a string")?;
let next_update = chrono::DateTime::parse_from_rfc3339(next_update)
.ok()
.context("verify_quote_with_collateral: Failed to parse next update")?;
Ok(QuoteVerificationResult {
result,
collateral_expired: result == TcbLevel::OutOfDate,
earliest_expiration_date: next_update
.signed_duration_since(chrono::DateTime::UNIX_EPOCH)
.num_seconds(),
tcb_level_date_tag: 0,
advisories: advisory_ids,
quote,
})
}
/// Get the attestation quote from a TEE
pub fn get_quote(_report_data: &[u8]) -> Result<(TEEType, Box<[u8]>), QuoteError> {
Err(QuoteError::UnknownTee)
}

View file

@ -4,7 +4,6 @@
//! Intel SGX Enclave TCB level wrapper //! Intel SGX Enclave TCB level wrapper
use enumset::EnumSetType; use enumset::EnumSetType;
use intel_tee_quote_verification_rs::sgx_ql_qv_result_t;
use serde::{Deserialize, Serialize}; use serde::{Deserialize, Serialize};
use std::{ use std::{
fmt::{Display, Formatter}, fmt::{Display, Formatter},
@ -34,37 +33,20 @@ pub enum TcbLevel {
Invalid, Invalid,
} }
impl From<sgx_ql_qv_result_t> for TcbLevel {
fn from(value: sgx_ql_qv_result_t) -> Self {
match value {
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_OK => TcbLevel::Ok,
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_OUT_OF_DATE => TcbLevel::OutOfDate,
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_OUT_OF_DATE_CONFIG_NEEDED => {
TcbLevel::OutOfDateConfigNeeded
}
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_SW_HARDENING_NEEDED => TcbLevel::SwHardeningNeeded,
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_CONFIG_AND_SW_HARDENING_NEEDED => {
TcbLevel::ConfigAndSwHardeningNeeded
}
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_CONFIG_NEEDED => TcbLevel::ConfigNeeded,
_ => TcbLevel::Invalid,
}
}
}
impl FromStr for TcbLevel { impl FromStr for TcbLevel {
type Err = (); type Err = String;
fn from_str(s: &str) -> Result<Self, Self::Err> { fn from_str(s: &str) -> Result<Self, Self::Err> {
match s { match s.to_ascii_lowercase().as_str() {
"Ok" => Ok(TcbLevel::Ok), "ok" => Ok(TcbLevel::Ok),
"ConfigNeeded" => Ok(TcbLevel::ConfigNeeded), "uptodate" => Ok(TcbLevel::Ok),
"ConfigAndSwHardeningNeeded" => Ok(TcbLevel::ConfigAndSwHardeningNeeded), "configneeded" => Ok(TcbLevel::ConfigNeeded),
"SwHardeningNeeded" => Ok(TcbLevel::SwHardeningNeeded), "configandswhardeningneeded" => Ok(TcbLevel::ConfigAndSwHardeningNeeded),
"OutOfDate" => Ok(TcbLevel::OutOfDate), "swhardeningneeded" => Ok(TcbLevel::SwHardeningNeeded),
"OutOfDateConfigNeeded" => Ok(TcbLevel::OutOfDateConfigNeeded), "outofdate" => Ok(TcbLevel::OutOfDate),
"Invalid" => Ok(TcbLevel::Invalid), "outofdateconfigneeded" => Ok(TcbLevel::OutOfDateConfigNeeded),
_ => Err(()), "invalid" => Ok(TcbLevel::Invalid),
_ => Err(format!("Invalid TCB level: {}", s)),
} }
} }
} }

View file

@ -8,9 +8,8 @@
pub mod sign; pub mod sign;
use crate::quote::error::QuoteContext; use crate::quote::error::QuoteContext;
pub use crate::quote::error::QuoteError; pub use crate::quote::{error::QuoteError, Collateral};
use bytemuck::{try_from_bytes, AnyBitPattern, PodCastError}; use bytemuck::{try_from_bytes, AnyBitPattern, PodCastError};
pub use intel_tee_quote_verification_rs::{sgx_ql_qv_result_t, Collateral};
use std::{ use std::{
fs::OpenOptions, fs::OpenOptions,
io::{Read, Write}, io::{Read, Write},

View file

@ -3,29 +3,17 @@
//! Intel TDX helper functions. //! Intel TDX helper functions.
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
pub mod rtmr; pub mod rtmr;
use crate::sgx::QuoteError; /// The sha384 digest of 0u32, which is used in the UEFI TPM protocol
pub use intel_tee_quote_verification_rs::Collateral; /// as a marker. Used to advance the PCR.
use tdx_attest_rs::{tdx_att_get_quote, tdx_attest_error_t, tdx_report_data_t}; /// ```shell
/// $ echo -n -e "\000\000\000\000" | sha384sum -b
/// Get a TDX quote /// 394341b7182cd227c5c6b07ef8000cdfd86136c4292b8e576573ad7ed9ae41019f5818b4b971c9effc60e1ad9f1289f0 *-
pub fn tgx_get_quote(report_data_bytes: &[u8; 64]) -> Result<Box<[u8]>, QuoteError> { /// ```
let mut tdx_report_data = tdx_report_data_t { d: [0; 64usize] }; pub const UEFI_MARKER_DIGEST_BYTES: [u8; 48] = [
tdx_report_data.d.copy_from_slice(report_data_bytes); 0x39, 0x43, 0x41, 0xb7, 0x18, 0x2c, 0xd2, 0x27, 0xc5, 0xc6, 0xb0, 0x7e, 0xf8, 0x00, 0x0c, 0xdf,
0xd8, 0x61, 0x36, 0xc4, 0x29, 0x2b, 0x8e, 0x57, 0x65, 0x73, 0xad, 0x7e, 0xd9, 0xae, 0x41, 0x01,
let (error, quote) = tdx_att_get_quote(Some(&tdx_report_data), None, None, 0); 0x9f, 0x58, 0x18, 0xb4, 0xb9, 0x71, 0xc9, 0xef, 0xfc, 0x60, 0xe1, 0xad, 0x9f, 0x12, 0x89, 0xf0,
];
if error == tdx_attest_error_t::TDX_ATTEST_SUCCESS {
if let Some(quote) = quote {
Ok(quote.into())
} else {
Err(QuoteError::TdxAttGetQuote {
msg: "tdx_att_get_quote: No quote returned".into(),
inner: error,
})
}
} else {
Err(error.into())
}
}

View file

@ -5,18 +5,6 @@
use crate::sgx::QuoteError; use crate::sgx::QuoteError;
/// The sha384 digest of 0u32, which is used in the UEFI TPM protocol
/// as a marker. Used to advance the PCR.
/// ```shell
/// $ echo -n -e "\000\000\000\000" | sha384sum -b
/// 394341b7182cd227c5c6b07ef8000cdfd86136c4292b8e576573ad7ed9ae41019f5818b4b971c9effc60e1ad9f1289f0 *-
/// ```
pub const UEFI_MARKER_DIGEST_BYTES: [u8; 48] = [
0x39, 0x43, 0x41, 0xb7, 0x18, 0x2c, 0xd2, 0x27, 0xc5, 0xc6, 0xb0, 0x7e, 0xf8, 0x00, 0x0c, 0xdf,
0xd8, 0x61, 0x36, 0xc4, 0x29, 0x2b, 0x8e, 0x57, 0x65, 0x73, 0xad, 0x7e, 0xd9, 0xae, 0x41, 0x01,
0x9f, 0x58, 0x18, 0xb4, 0xb9, 0x71, 0xc9, 0xef, 0xfc, 0x60, 0xe1, 0xad, 0x9f, 0x12, 0x89, 0xf0,
];
/// The actual rtmr event data handled in DCAP /// The actual rtmr event data handled in DCAP
#[repr(C, packed)] #[repr(C, packed)]
pub struct TdxRtmrEvent { pub struct TdxRtmrEvent {
@ -103,7 +91,7 @@ impl From<TdxRtmrEvent> for Vec<u8> {
#[cfg(test)] #[cfg(test)]
mod test { mod test {
use super::UEFI_MARKER_DIGEST_BYTES; use crate::tdx::UEFI_MARKER_DIGEST_BYTES;
#[test] #[test]
fn test_uefi_marker_digest() { fn test_uefi_marker_digest() {

View file

@ -1,13 +1,15 @@
// SPDX-License-Identifier: Apache-2.0 // SPDX-License-Identifier: Apache-2.0
// Copyright (c) 2024 Matter Labs // Copyright (c) 2024-2025 Matter Labs
mod sgx { mod sgx {
use anyhow::{Context, Result}; use anyhow::{Context, Result};
use intel_tee_quote_verification_rs::{sgx_ql_qv_result_t, Collateral};
use std::time::{Duration, UNIX_EPOCH}; use std::time::{Duration, UNIX_EPOCH};
use teepot::{ use teepot::{
prover::reportdata::{ReportData, ReportDataV1}, prover::reportdata::{ReportData, ReportDataV1},
quote::{verify_quote_with_collateral, Quote, QuoteVerificationResult, Report}, quote::{
tcblevel::TcbLevel, verify_quote_with_collateral, Collateral, Quote,
QuoteVerificationResult, Report,
},
}; };
use tracing_test::traced_test; use tracing_test::traced_test;
@ -17,7 +19,7 @@ mod sgx {
current_time: i64, current_time: i64,
expected_mrsigner: &[u8], expected_mrsigner: &[u8],
expected_reportdata: &[u8], expected_reportdata: &[u8],
expected_result: sgx_ql_qv_result_t, expected_result: TcbLevel,
) -> Result<()> { ) -> Result<()> {
let QuoteVerificationResult { let QuoteVerificationResult {
collateral_expired, collateral_expired,
@ -28,7 +30,7 @@ mod sgx {
tcb_level_date_tag, tcb_level_date_tag,
} = verify_quote_with_collateral(quote, collateral, current_time)?; } = verify_quote_with_collateral(quote, collateral, current_time)?;
if collateral_expired || result != sgx_ql_qv_result_t::SGX_QL_QV_RESULT_OK { if collateral_expired || result != TcbLevel::Ok {
print!("Attestation failed: "); print!("Attestation failed: ");
if collateral_expired { if collateral_expired {
@ -40,28 +42,7 @@ mod sgx {
earliest_expiration_date earliest_expiration_date
); );
} }
println!("{result:?}");
match result {
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_OK => (),
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_CONFIG_NEEDED => println!("Config needed"),
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_CONFIG_AND_SW_HARDENING_NEEDED => {
println!("Config and Software hardening needed")
}
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_OUT_OF_DATE => println!("Out of date"),
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_OUT_OF_DATE_CONFIG_NEEDED => {
println!("Out of Date and Config needed")
}
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_SW_HARDENING_NEEDED => {
println!("Software hardening needed")
}
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_INVALID_SIGNATURE => {
println!("Invalid signature")
}
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_REVOKED => println!("Revoked"),
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_UNSPECIFIED => println!("Unspecified"),
_ => println!("Unknown state!"),
}
} }
for advisory in advisories { for advisory in advisories {
@ -85,6 +66,8 @@ mod sgx {
} }
#[test] #[test]
// alternative quote verification cannot cope with old collateral data format
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn sw_hardening() { fn sw_hardening() {
let quote = [ let quote = [
0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x0e, 0x00, 0x93, 0x9a, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x0e, 0x00, 0x93, 0x9a,
@ -1144,12 +1127,14 @@ mod sgx {
current_time, current_time,
&mrsigner, &mrsigner,
&report_data, &report_data,
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_SW_HARDENING_NEEDED, TcbLevel::SwHardeningNeeded,
) )
.unwrap(); .unwrap();
} }
#[test] #[test]
// alternative quote verification cannot cope with old collateral data format
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
fn out_of_date() { fn out_of_date() {
let quote = [ let quote = [
0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x0e, 0x00, 0x93, 0x9a, 0x03, 0x00, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x09, 0x00, 0x0e, 0x00, 0x93, 0x9a,
@ -2215,7 +2200,7 @@ mod sgx {
current_time, current_time,
&mrsigner, &mrsigner,
&report_data, &report_data,
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_OUT_OF_DATE, TcbLevel::OutOfDate,
) )
.unwrap(); .unwrap();
} }
@ -2598,12 +2583,14 @@ mod sgx {
current_time, current_time,
&mrsigner, &mrsigner,
&report_data, &report_data,
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_SW_HARDENING_NEEDED, TcbLevel::SwHardeningNeeded,
) )
.unwrap(); .unwrap();
} }
#[test] #[test]
#[cfg(all(target_os = "linux", target_arch = "x86_64"))]
// alternative quote verification errors on debug flag
fn vault_proxy() { fn vault_proxy() {
let quote = [ let quote = [
3, 0, 2, 0, 0, 0, 0, 0, 9, 0, 14, 0, 147, 154, 114, 51, 247, 156, 76, 169, 148, 10, 13, 3, 0, 2, 0, 0, 0, 0, 0, 9, 0, 14, 0, 147, 154, 114, 51, 247, 156, 76, 169, 148, 10, 13,
@ -3681,7 +3668,7 @@ mod sgx {
current_time, current_time,
&mrsigner, &mrsigner,
&report_data, &report_data,
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_SW_HARDENING_NEEDED, TcbLevel::SwHardeningNeeded,
) )
.unwrap(); .unwrap();
} }
@ -4809,7 +4796,7 @@ mod sgx {
current_time as i64, current_time as i64,
&mrsigner, &mrsigner,
&report_data, &report_data,
sgx_ql_qv_result_t::SGX_QL_QV_RESULT_OK, TcbLevel::Ok,
) )
.context("check_quote")?; .context("check_quote")?;
Ok(()) Ok(())

View file

@ -32,6 +32,7 @@ allow = [
"BSD-3-Clause", "BSD-3-Clause",
"OpenSSL", "OpenSSL",
"CC0-1.0", "CC0-1.0",
"Zlib",
] ]
confidence-threshold = 0.8 confidence-threshold = 0.8
exceptions = [] exceptions = []

6
flake.lock generated
View file

@ -234,11 +234,11 @@
] ]
}, },
"locked": { "locked": {
"lastModified": 1734661750, "lastModified": 1743993291,
"narHash": "sha256-BI58NBdimxu1lnpOrG9XxBz7Cwqy+qIf99zunWofX5w=", "narHash": "sha256-u8GHvduU1gCtoFXvTS/wGjH1ouv5S/GRGq6MAT+sG/k=",
"owner": "oxalica", "owner": "oxalica",
"repo": "rust-overlay", "repo": "rust-overlay",
"rev": "7d3d910d5fd575e6e8c5600d83d54e5c47273bfe", "rev": "0cb3c8979c65dc6a5812dfe67499a8c7b8b4325b",
"type": "github" "type": "github"
}, },
"original": { "original": {

View file

@ -2,10 +2,12 @@
# Copyright (c) 2024 Matter Labs # Copyright (c) 2024 Matter Labs
{ teepot { teepot
, pkgs , pkgs
, stdenv
, container-name ? "teepot-self-attestation-test-sgx-azure" , container-name ? "teepot-self-attestation-test-sgx-azure"
, tag ? null , tag ? null
, isAzure ? true , isAzure ? true
}: }:
if (stdenv.hostPlatform.system != "x86_64-linux") then { } else
pkgs.lib.tee.sgxGramineContainer { pkgs.lib.tee.sgxGramineContainer {
name = container-name; name = container-name;
inherit tag; inherit tag;

View file

@ -1,6 +1,7 @@
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2024 Matter Labs # Copyright (c) 2024 Matter Labs
{ lib { lib
, stdenv
, openssl , openssl
, curl , curl
, dockerTools , dockerTools
@ -8,28 +9,31 @@
, teepot , teepot
, nixsgx , nixsgx
}: }:
dockerTools.buildLayeredImage { if (stdenv.hostPlatform.isDarwin) then {
name = "tdx-test"; # FIXME: dockerTools.buildLayeredImage seems to be broken on Darwin
} else
dockerTools.buildLayeredImage {
name = "tdx-test";
config.Entrypoint = [ "${teepot.teepot.tdx_test}/bin/tdx-test-dcap" ]; config.Entrypoint = [ "${teepot.teepot.tdx_test}/bin/tdx-test" ];
config.Env = [ config.Env = [
"SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt" "SSL_CERT_FILE=/etc/ssl/certs/ca-bundle.crt"
];
contents = buildEnv {
name = "image-root";
paths = with dockerTools;[
teepot.teepot.tdx_test
openssl.out
curl.out
nixsgx.sgx-dcap.quote_verify
nixsgx.sgx-dcap.default_qpl
usrBinEnv
binSh
caCertificates
fakeNss
]; ];
pathsToLink = [ "/bin" "/lib" "/etc" "/share" ];
}; contents = buildEnv {
} name = "image-root";
paths = with dockerTools; [
teepot.teepot.tdx_test
openssl.out
curl.out
# nixsgx.sgx-dcap.quote_verify
# nixsgx.sgx-dcap.default_qpl
usrBinEnv
binSh
caCertificates
fakeNss
];
pathsToLink = [ "/bin" "/lib" "/etc" "/share" ];
};
}

View file

@ -2,6 +2,7 @@
# Copyright (c) 2024 Matter Labs # Copyright (c) 2024 Matter Labs
{ teepot { teepot
, pkgs , pkgs
, stdenv
, bash , bash
, coreutils , coreutils
, container-name ? "teepot-key-preexec-dcap" , container-name ? "teepot-key-preexec-dcap"
@ -9,6 +10,7 @@
}: let }: let
entrypoint = "${bash}/bin/bash"; entrypoint = "${bash}/bin/bash";
in in
if (stdenv.hostPlatform.system != "x86_64-linux") then { } else
pkgs.lib.tee.sgxGramineContainer { pkgs.lib.tee.sgxGramineContainer {
name = container-name; name = container-name;
inherit tag entrypoint; inherit tag entrypoint;

View file

@ -2,10 +2,12 @@
# Copyright (c) 2024 Matter Labs # Copyright (c) 2024 Matter Labs
{ teepot { teepot
, pkgs , pkgs
, stdenv
, container-name ? "teepot-vault-admin-sgx-azure" , container-name ? "teepot-vault-admin-sgx-azure"
, tag ? null , tag ? null
, isAzure ? null , isAzure ? null
}: }:
if (stdenv.hostPlatform.system != "x86_64-linux") then { } else
pkgs.lib.tee.sgxGramineContainer { pkgs.lib.tee.sgxGramineContainer {
name = container-name; name = container-name;
inherit tag; inherit tag;

View file

@ -2,11 +2,13 @@
# Copyright (c) 2024 Matter Labs # Copyright (c) 2024 Matter Labs
{ dockerTools { dockerTools
, buildEnv , buildEnv
, stdenv
, teepot , teepot
, openssl , openssl
, curl , curl
, nixsgx , nixsgx
}: }:
if (stdenv.hostPlatform.system != "x86_64-linux") then { } else
dockerTools.buildLayeredImage { dockerTools.buildLayeredImage {
name = "vault-admin"; name = "vault-admin";

View file

@ -2,6 +2,7 @@
# Copyright (c) 2024 Matter Labs # Copyright (c) 2024 Matter Labs
{ teepot { teepot
, pkgs , pkgs
, stdenv
, vat , vat
, vault , vault
, container-name ? "teepot-vault-sgx-azure" , container-name ? "teepot-vault-sgx-azure"
@ -12,6 +13,7 @@ let
entrypoint = "${teepot.teepot.tee_ratls_preexec}/bin/tee-ratls-preexec"; entrypoint = "${teepot.teepot.tee_ratls_preexec}/bin/tee-ratls-preexec";
appDir = "/opt/vault"; appDir = "/opt/vault";
in in
if (stdenv.hostPlatform.system != "x86_64-linux") then { } else
pkgs.lib.tee.sgxGramineContainer { pkgs.lib.tee.sgxGramineContainer {
name = container-name; name = container-name;
inherit tag; inherit tag;
@ -86,5 +88,3 @@ pkgs.lib.tee.sgxGramineContainer {
sys.experimental__enable_flock = true; sys.experimental__enable_flock = true;
}; };
} }

View file

@ -2,11 +2,13 @@
# Copyright (c) 2024 Matter Labs # Copyright (c) 2024 Matter Labs
{ teepot { teepot
, pkgs , pkgs
, stdenv
, vat , vat
, container-name ? "teepot-vault-unseal-sgx-azure" , container-name ? "teepot-vault-unseal-sgx-azure"
, tag ? null , tag ? null
, isAzure ? true , isAzure ? true
}: }:
if (stdenv.hostPlatform.system != "x86_64-linux") then { } else
pkgs.lib.tee.sgxGramineContainer { pkgs.lib.tee.sgxGramineContainer {
name = container-name; name = container-name;
inherit tag isAzure; inherit tag isAzure;

View file

@ -1,30 +1,43 @@
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2024 Matter Labs # Copyright (c) 2024 Matter Labs
{ dockerTools { dockerTools
, lib
, stdenv
, buildEnv , buildEnv
, teepot , teepot
, openssl , openssl
, curl , curl
, nixsgx , nixsgx
}: }:
dockerTools.buildLayeredImage { if (stdenv.hostPlatform.isDarwin) then {
name = "vault-unseal"; # FIXME: dockerTools.buildLayeredImage seems to be broken on Darwin
} else
dockerTools.buildLayeredImage {
name = "vault-unseal";
config.Entrypoint = [ "${teepot.teepot.vault_unseal}/bin/vault-unseal" ]; config.Entrypoint = [ "${teepot.teepot.vault_unseal}/bin/vault-unseal" ];
contents = buildEnv { contents = buildEnv {
name = "image-root"; name = "image-root";
paths = with dockerTools; with nixsgx;[ paths =
openssl.out with dockerTools;
curl.out with nixsgx;
sgx-dcap.quote_verify [
sgx-dcap.default_qpl openssl.out
usrBinEnv curl.out
binSh usrBinEnv
caCertificates binSh
fakeNss caCertificates
teepot.teepot.vault_unseal fakeNss
]; teepot.teepot.vault_unseal
pathsToLink = [ "/bin" "/lib" "/etc" ]; ] ++ lib.optionals (stdenv.hostPlatform.system == "x86_64-linux") [
}; sgx-dcap.quote_verify
} sgx-dcap.default_qpl
];
pathsToLink = [
"/bin"
"/lib"
"/etc"
];
};
}

View file

@ -2,30 +2,44 @@
# Copyright (c) 2024 Matter Labs # Copyright (c) 2024 Matter Labs
{ dockerTools { dockerTools
, buildEnv , buildEnv
, lib
, stdenv
, teepot , teepot
, openssl , openssl
, curl , curl
, nixsgx , nixsgx
}: }:
dockerTools.buildLayeredImage { if (stdenv.hostPlatform.isDarwin) then {
name = "verify-attestation-sgx"; # FIXME: dockerTools.buildLayeredImage seems to be broken on Darwin
} else
dockerTools.buildLayeredImage {
name = "verify-attestation-sgx";
config.Entrypoint = [ "${teepot.teepot.verify_attestation}/bin/verify-attestation" ]; config.Entrypoint = [ "${teepot.teepot.verify_attestation}/bin/verify-attestation" ];
config.Env = [ "LD_LIBRARY_PATH=/lib" ]; config.Env = [ "LD_LIBRARY_PATH=/lib" ];
contents = buildEnv { contents = buildEnv {
name = "image-root"; name = "image-root";
paths = with dockerTools; with nixsgx;[ paths =
openssl.out with dockerTools;
curl.out with nixsgx;
sgx-dcap.quote_verify [
sgx-dcap.default_qpl openssl.out
teepot.teepot.verify_attestation curl.out
usrBinEnv teepot.teepot.verify_attestation
binSh usrBinEnv
caCertificates binSh
fakeNss caCertificates
]; fakeNss
pathsToLink = [ "/bin" "/lib" "/etc" "/share" ]; ] ++ lib.optionals (stdenv.hostPlatform.system == "x86_64-linux") [
}; sgx-dcap.quote_verify
} sgx-dcap.default_qpl
];
pathsToLink = [
"/bin"
"/lib"
"/etc"
"/share"
];
};
}

View file

@ -3,31 +3,45 @@
{ dockerTools { dockerTools
, buildEnv , buildEnv
, teepot , teepot
, stdenv
, openssl , openssl
, curl , curl
, nixsgx , nixsgx
, pkg-config , pkg-config
}: }:
dockerTools.buildLayeredImage { if (stdenv.hostPlatform.isDarwin) then {
name = "verify-era-proof-attestation"; # FIXME: dockerTools.buildLayeredImage seems to be broken on Darwin
} else
dockerTools.buildLayeredImage {
name = "verify-era-proof-attestation";
config.Entrypoint = [ "${teepot.teepot.verify_era_proof_attestation}/bin/verify-era-proof-attestation" ]; config.Entrypoint = [
config.Env = [ "LD_LIBRARY_PATH=/lib" ]; "${teepot.teepot.verify_era_proof_attestation}/bin/verify-era-proof-attestation"
contents = buildEnv {
name = "image-root";
paths = with dockerTools; with nixsgx;[
pkg-config
openssl.out
curl.out
sgx-dcap.quote_verify
sgx-dcap.default_qpl
teepot.teepot.verify_era_proof_attestation
usrBinEnv
binSh
caCertificates
fakeNss
]; ];
pathsToLink = [ "/bin" "/lib" "/etc" "/share" ]; config.Env = [ "LD_LIBRARY_PATH=/lib" ];
}; contents = buildEnv {
} name = "image-root";
paths =
with dockerTools;
with nixsgx;
[
pkg-config
openssl.out
curl.out
sgx-dcap.quote_verify
sgx-dcap.default_qpl
teepot.teepot.verify_era_proof_attestation
usrBinEnv
binSh
caCertificates
fakeNss
];
pathsToLink = [
"/bin"
"/lib"
"/etc"
"/share"
];
};
}

View file

@ -2,9 +2,12 @@
# Copyright (c) 2024 Matter Labs # Copyright (c) 2024 Matter Labs
{ lib { lib
, pkgs , pkgs
, stdenv
, system , system
, ... , ...
}: lib.teepot.nixosGenerate { }:
if (stdenv.hostPlatform.system != "x86_64-linux") then { } else
lib.teepot.nixosGenerate {
inherit (lib) nixosSystem; inherit (lib) nixosSystem;
inherit system pkgs; inherit system pkgs;
modules = [ modules = [

View file

@ -1,20 +1,30 @@
# SPDX-License-Identifier: Apache-2.0 # SPDX-License-Identifier: Apache-2.0
# Copyright (c) 2024 Matter Labs # Copyright (c) 2024 Matter Labs
{ lib, pkgs, makeWrapper, teepot }: { lib
let teepotCrate = teepot.teepotCrate; in , pkgs
, makeWrapper
, teepot
, stdenv
}:
let
teepotCrate = teepot.teepotCrate;
in
teepotCrate.craneLib.buildPackage ( teepotCrate.craneLib.buildPackage (
teepotCrate.commonArgs // { teepotCrate.commonArgs
// {
pname = "teepot"; pname = "teepot";
inherit (teepotCrate) cargoArtifacts; inherit (teepotCrate) cargoArtifacts;
nativeBuildInputs = teepotCrate.commonArgs.nativeBuildInputs ++ [ makeWrapper ]; nativeBuildInputs = teepotCrate.commonArgs.nativeBuildInputs ++ [ makeWrapper ];
passthru = { passthru = {
inherit (teepotCrate) rustPlatform inherit (teepotCrate)
rustPlatform
rustVersion rustVersion
commonArgs commonArgs
craneLib craneLib
cargoArtifacts; cargoArtifacts
;
}; };
outputs = [ outputs = [
@ -37,9 +47,10 @@ teepotCrate.craneLib.buildPackage (
"verify_era_proof_attestation" "verify_era_proof_attestation"
]; ];
postInstall = '' postInstall = lib.optionalString (stdenv.hostPlatform.system == "x86_64-linux") ''
removeReferencesToVendoredSources "$out" "$cargoVendorDir" removeReferencesToVendoredSources "$out" "$cargoVendorDir"
removeReferencesToVendoredSources "$out" "${teepotCrate.rustVersion}/lib/rustlib/" removeReferencesToVendoredSources "$out" "${teepotCrate.rustVersion}/lib/rustlib/"
'' + ''
mkdir -p $out/nix-support mkdir -p $out/nix-support
for i in $outputs; do for i in $outputs; do
[[ $i == "out" ]] && continue [[ $i == "out" ]] && continue
@ -47,14 +58,19 @@ teepotCrate.craneLib.buildPackage (
echo -n "''${!i} " >> $out/nix-support/propagated-user-env-packages echo -n "''${!i} " >> $out/nix-support/propagated-user-env-packages
binname=''${i//_/-} binname=''${i//_/-}
mv "$out/bin/$binname" "''${!i}/bin/" mv "$out/bin/$binname" "''${!i}/bin/"
'' + lib.optionalString (stdenv.hostPlatform.system == "x86_64-linux") ''
makeWrapper "''${!i}/bin/$binname" "''${!i}/bin/$binname-dcap" \ makeWrapper "''${!i}/bin/$binname" "''${!i}/bin/$binname-dcap" \
--prefix LD_LIBRARY_PATH : "${lib.makeLibraryPath [ pkgs.nixsgx.sgx-dcap.quote_verify pkgs.nixsgx.sgx-dcap.default_qpl pkgs.curl ]}" \ --prefix LD_LIBRARY_PATH : "${
--set-default QCNL_CONF_PATH "${pkgs.nixsgx.sgx-dcap.default_qpl}/etc/sgx_default_qcnl.conf" lib.makeLibraryPath [
pkgs.nixsgx.sgx-dcap.quote_verify
pkgs.nixsgx.sgx-dcap.default_qpl
pkgs.curl
]
}" \
--set-default QCNL_CONF_PATH "${pkgs.nixsgx.sgx-dcap.default_qpl}/etc/sgx_default_qcnl.conf"
'' + ''
done done
rmdir "$out/bin" rmdir "$out/bin"
''; '';
} }
) )

View file

@ -2,12 +2,14 @@
# Copyright (c) 2024 Matter Labs # Copyright (c) 2024 Matter Labs
{ lib { lib
, inputs , inputs
, stdenv
, makeRustPlatform , makeRustPlatform
, nixsgx , nixsgx ? null
, pkg-config , pkg-config
, rust-bin , rust-bin
, pkgs , pkgs
, openssl , openssl
, darwin
}: }:
let let
rustVersion = rust-bin.fromRustupToolchainFile (inputs.src + "/rust-toolchain.toml"); rustVersion = rust-bin.fromRustupToolchainFile (inputs.src + "/rust-toolchain.toml");
@ -23,11 +25,15 @@ let
]; ];
buildInputs = [ buildInputs = [
openssl openssl.dev
]
++ lib.optionals (stdenv.hostPlatform.system == "x86_64-linux") [
nixsgx.sgx-sdk nixsgx.sgx-sdk
nixsgx.sgx-dcap nixsgx.sgx-dcap
nixsgx.sgx-dcap.quote_verify nixsgx.sgx-dcap.quote_verify
nixsgx.sgx-dcap.libtdx_attest nixsgx.sgx-dcap.libtdx_attest
] ++ lib.optionals stdenv.isDarwin [
darwin.apple_sdk.frameworks.Security
]; ];
strictDeps = true; strictDeps = true;

View file

@ -1,3 +1,3 @@
[toolchain] [toolchain]
channel = "1.83" channel = "1.86"
components = ["rustfmt", "clippy", "rust-src"] components = ["rustfmt", "clippy", "rust-src"]

View file

@ -6,11 +6,18 @@
, teepot , teepot
, nixsgx , nixsgx
, stdenv , stdenv
,
}: }:
let let
toolchain_with_src = (teepot.teepot.passthru.rustVersion.override { toolchain_with_src = (
extensions = [ "rustfmt" "clippy" "rust-src" ]; teepot.teepot.passthru.rustVersion.override {
}); extensions = [
"rustfmt"
"clippy"
"rust-src"
];
}
);
in in
mkShell { mkShell {
inputsFrom = [ teepot.teepot ]; inputsFrom = [ teepot.teepot ];
@ -21,26 +28,34 @@ mkShell {
teepot.teepot.passthru.rustPlatform.bindgenHook teepot.teepot.passthru.rustPlatform.bindgenHook
]; ];
packages = with pkgs; [ packages =
dive with pkgs;
taplo [
vault dive
cargo-release taplo
google-cloud-sdk-gce vault
azure-cli cargo-release
kubectl azure-cli
kubectx kubectl
k9s kubectx
]; k9s
google-cloud-sdk
];
TEE_LD_LIBRARY_PATH = lib.makeLibraryPath [ TEE_LD_LIBRARY_PATH = lib.makeLibraryPath (
pkgs.curl lib.optionals (stdenv.hostPlatform.system == "x86_64-linux") [
nixsgx.sgx-dcap pkgs.curl
nixsgx.sgx-dcap.quote_verify nixsgx.sgx-dcap
nixsgx.sgx-dcap.default_qpl nixsgx.sgx-dcap.quote_verify
]; nixsgx.sgx-dcap.default_qpl
]
);
QCNL_CONF_PATH = "${nixsgx.sgx-dcap.default_qpl}/etc/sgx_default_qcnl.conf"; QCNL_CONF_PATH =
if (stdenv.hostPlatform.system != "x86_64-linux") then
""
else
"${nixsgx.sgx-dcap.default_qpl}/etc/sgx_default_qcnl.conf";
OPENSSL_NO_VENDOR = "1"; OPENSSL_NO_VENDOR = "1";
RUST_SRC_PATH = "${toolchain_with_src}/lib/rustlib/src/rust/library"; RUST_SRC_PATH = "${toolchain_with_src}/lib/rustlib/src/rust/library";