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]>
163 lines
5.7 KiB
Rust
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(_))));
|
|
}
|
|
}
|