open-standards/sandbox/sandbox-rs/crates/sandbox-tee/src/provider.rs
Transaction Science ba1212361f Add aac, map, sandbox standards; catalog the standards family
Commit the previously-untracked standard directories and catalog them in the
root README alongside proof:

- aac/    — communication cargo class: portable boards + a ten-level
            offline-first cascade (Apache-2.0 code / CC-BY-4.0 spec).
- map/    — geospatial cargo class: range-requestable signed tilesets +
            an F0–F4 fidelity cascade (Apache-2.0 code / CC-BY-4.0 spec).
- sandbox/— isolated, metered, attested execution across the heterogeneous
            compute fabric (Apache-2.0 code / CC-BY-4.0 spec).
- jouleclaw-otel-authz, jouleclaw-trust — new jouleclaw reference crates.

Root README now lists aac, map, sandbox, and proof in the catalog, the tree,
and the contribution guide.

Co-Authored-By: Claude Opus 4.8 <[email protected]>
2026-06-07 17:04:36 -04:00

163 lines
5.7 KiB
Rust

//! TEE report providers (SPEC §7.6).
//!
//! A provider obtains an attestation report bound to a 64-byte `report_data`
//! challenge. The real [`ConfigfsTsmProvider`] requests one from the Linux
//! `configfs-tsm` interface — the vendor-agnostic kernel path
//! (`/sys/kernel/config/tsm/report/`, Linux 6.7+) that fronts SEV-SNP, TDX, and
//! Arm CCA — and returns [`TeeError::Unavailable`] where that interface or the
//! confidential hardware is absent, exactly as the microVM driver does without
//! KVM. The [`LocalSnpProvider`] mints a **software-grounded** report for wiring
//! and tests; it is tagged [`Grounding::Software`] so nothing mistakes it for a
//! hardware root of trust.
use crate::report::SnpReport;
use crate::sig::{dev_signing_key, sign_body, P384Sig};
use crate::{Grounding, TeeError};
use p384::ecdsa::{SigningKey, VerifyingKey};
use sandbox_attest::TeeKind;
use std::path::Path;
/// Obtains an attestation report bound to a challenge.
pub trait AttestationProvider {
/// The TEE kind this provider speaks.
fn kind(&self) -> TeeKind;
/// Whether reports are hardware-rooted or software stand-ins (SPEC §9).
fn grounding(&self) -> Grounding;
/// Request a report bound to `report_data` (64 bytes). Returns the raw report.
fn report(&self, report_data: &[u8; 64]) -> Result<Vec<u8>, TeeError>;
}
/// The real Linux `configfs-tsm` provider. Hardware-rooted; available only on a
/// confidential-computing guest with the interface mounted.
pub struct ConfigfsTsmProvider {
base: String,
}
impl Default for ConfigfsTsmProvider {
fn default() -> Self {
ConfigfsTsmProvider {
base: "/sys/kernel/config/tsm/report".into(),
}
}
}
impl AttestationProvider for ConfigfsTsmProvider {
fn kind(&self) -> TeeKind {
TeeKind::SevSnp
}
fn grounding(&self) -> Grounding {
Grounding::Hardware
}
fn report(&self, report_data: &[u8; 64]) -> Result<Vec<u8>, TeeError> {
if !Path::new(&self.base).exists() {
return Err(TeeError::Unavailable(format!(
"{} not present (needs Linux 6.7+ on confidential hardware)",
self.base
)));
}
// configfs-tsm flow: create an entry, write the challenge to `inblob`,
// read the report from `outblob`, then remove the entry.
let dir = format!("{}/sandbox-{}", self.base, std::process::id());
std::fs::create_dir(&dir)
.map_err(|e| TeeError::Unavailable(format!("create {dir}: {e}")))?;
let result = (|| {
std::fs::write(format!("{dir}/inblob"), report_data)
.map_err(|e| TeeError::Unavailable(format!("write inblob: {e}")))?;
std::fs::read(format!("{dir}/outblob"))
.map_err(|e| TeeError::Unavailable(format!("read outblob: {e}")))
})();
let _ = std::fs::remove_dir(&dir);
result
}
}
/// A software-grounded SEV-SNP provider for wiring and tests. It mints a report
/// with a configured launch measurement and TCB, binds the requested
/// `report_data`, and signs it with a local key — a real ECDSA-P384 signature,
/// but **not** a hardware root of trust ([`Grounding::Software`]).
pub struct LocalSnpProvider {
signing_key: SigningKey,
measurement: [u8; 48],
current_tcb: u64,
}
impl LocalSnpProvider {
/// A provider with the given launch measurement and TCB.
pub fn new(measurement: [u8; 48], current_tcb: u64) -> Self {
LocalSnpProvider {
signing_key: dev_signing_key(),
measurement,
current_tcb,
}
}
/// The VCEK public key a verifier checks reports against. Because this is the
/// local dev key (not AMD's KDS-rooted VCEK), trusting it tells a verifier
/// the report is software-grounded.
pub fn vcek(&self) -> VerifyingKey {
*self.signing_key.verifying_key()
}
}
impl AttestationProvider for LocalSnpProvider {
fn kind(&self) -> TeeKind {
TeeKind::SevSnp
}
fn grounding(&self) -> Grounding {
Grounding::Software
}
fn report(&self, report_data: &[u8; 64]) -> Result<Vec<u8>, TeeError> {
let mut report = SnpReport {
version: 2,
guest_svn: 1,
policy: 0, // debug disabled
current_tcb: self.current_tcb,
report_data: *report_data,
measurement: self.measurement,
host_data: [0; 32],
chip_id: [0; 64],
signature: P384Sig {
r: [0; 48],
s: [0; 48],
},
};
let body = report.to_bytes();
let signed = &body[0..crate::report::SIG_OFFSET];
report.signature = sign_body(&self.signing_key, signed);
Ok(report.to_bytes().to_vec())
}
}
#[cfg(test)]
mod tests {
use super::*;
use crate::policy::{verify_snp, TeePolicy};
#[test]
fn local_provider_report_verifies_end_to_end() {
let provider = LocalSnpProvider::new([0x11; 48], 7);
let challenge = [0x22u8; 64];
let bytes = provider.report(&challenge).unwrap();
assert_eq!(provider.grounding(), Grounding::Software);
let policy = TeePolicy::sev_snp_baseline()
.with_measurement([0x11; 48])
.with_report_data(challenge);
let verified = verify_snp(&bytes, &policy, &provider.vcek()).unwrap();
assert_eq!(verified.current_tcb, 7);
assert_eq!(verified.report_data, challenge);
}
#[test]
fn configfs_provider_is_unavailable_off_hardware() {
// On a host without the configfs-tsm interface, honest Unavailable.
let p = ConfigfsTsmProvider {
base: "/nonexistent/tsm/report".into(),
};
assert!(matches!(p.report(&[0; 64]), Err(TeeError::Unavailable(_))));
}
}