Skip to main content

ttk_server/attestation/
mod.rs

1//! Hardware-agnostic attestation providers.
2//!
3//! Each supported TEE (AWS Nitro, AMD SEV-SNP, Intel TDX, ...) implements
4//! [`AttestationProvider`]. [`detect`] probes the machine at runtime and returns the provider
5//! matching the hardware we are running on; [`by_name`] selects one explicitly.
6//!
7//! Backends are gated by additive Cargo features (`nitro`, `sev-snp`, `tdx`, `mock`), so a single
8//! binary can support several of them.
9
10use crate::{AttestationParams, EatClaimsSet};
11use std::fmt;
12
13pub mod eat;
14
15#[cfg(any(feature = "nitro", feature = "mock"))]
16pub mod nitro_doc;
17
18#[cfg(feature = "nitro")]
19pub mod nitro;
20#[cfg(feature = "nitro")]
21pub use nitro::NsmSession;
22
23#[cfg(any(feature = "sev-snp", feature = "tdx"))]
24pub mod tsm;
25
26#[cfg(feature = "sev-snp")]
27pub mod sev_snp;
28
29#[cfg(feature = "tdx")]
30pub mod tdx;
31
32#[cfg(feature = "mock")]
33pub mod mock;
34#[cfg(feature = "mock")]
35pub use mock::MockSession;
36
37/// Environment variable that forces a specific provider (see [`by_name`]).
38pub const PROVIDER_ENV: &str = "TTK_ATTESTATION";
39
40/// Errors produced by any attestation provider.
41#[derive(Debug)]
42pub enum AttestationError {
43    /// The TEE device could not be opened (e.g. `/dev/nsm`).
44    DeviceOpenFailed(String),
45    /// The TEE driver returned an error.
46    Driver(String),
47    /// The TEE driver returned an unexpected response.
48    UnexpectedResponse(String),
49    /// Input parameter validation failed.
50    InvalidInput(String),
51    /// Failed to decode or parse an attestation document.
52    DocumentDecodingFailed(String),
53    /// The requested provider is unknown, not compiled in, or not implemented.
54    Unsupported(String),
55    /// No provider matches the current hardware.
56    NoProvider,
57    /// An I/O error occurred.
58    Io(std::io::Error),
59}
60
61/// Human-readable messages for each [`AttestationError`] variant.
62impl fmt::Display for AttestationError {
63    /// Formats the error as a single-line message.
64    fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
65        match self {
66            Self::DeviceOpenFailed(msg) => write!(f, "Failed to open TEE device: {msg}"),
67            Self::Driver(msg) => write!(f, "TEE driver returned an error: {msg}"),
68            Self::UnexpectedResponse(msg) => {
69                write!(f, "Unexpected response from TEE driver: {msg}")
70            }
71            Self::InvalidInput(msg) => write!(f, "Invalid attestation input: {msg}"),
72            Self::DocumentDecodingFailed(msg) => write!(f, "Document decoding failure: {msg}"),
73            Self::Unsupported(msg) => write!(f, "Unsupported attestation provider: {msg}"),
74            Self::NoProvider => write!(f, "No attestation provider matches this hardware"),
75            Self::Io(err) => write!(f, "I/O error: {err}"),
76        }
77    }
78}
79
80/// Allows [`AttestationError`] to be used as a standard error type.
81impl std::error::Error for AttestationError {}
82
83/// Wraps I/O errors as [`AttestationError::Io`].
84impl From<std::io::Error> for AttestationError {
85    /// Converts an I/O error into [`AttestationError::Io`].
86    fn from(err: std::io::Error) -> Self {
87        Self::Io(err)
88    }
89}
90
91/// A source of attestation evidence backed by a specific TEE.
92pub trait AttestationProvider: Send + Sync {
93    /// Short stable identifier, e.g. `"aws-nitro"`, `"sev-snp"`, `"tdx"`, `"mock"`.
94    fn name(&self) -> &'static str;
95
96    /// Cheap probe: is this provider's hardware present on this machine?
97    fn is_available() -> bool
98    where
99        Self: Sized;
100
101    /// Produces an EAT claims-set carrying this TEE's evidence for `params`.
102    fn generate_document(
103        &self,
104        params: &AttestationParams,
105    ) -> Result<EatClaimsSet, AttestationError>;
106}
107
108/// Picks the provider matching the current hardware.
109///
110/// `TTK_ATTESTATION=<name>` overrides probing. Otherwise real hardware backends are probed in a
111/// fixed order, and the mock provider (if compiled in) is used only as a last resort.
112pub fn detect() -> Result<Box<dyn AttestationProvider>, AttestationError> {
113    if let Ok(name) = std::env::var(PROVIDER_ENV) {
114        return by_name(&name);
115    }
116
117    #[cfg(feature = "nitro")]
118    if nitro::NsmSession::is_available() {
119        return by_name("aws-nitro");
120    }
121    #[cfg(feature = "sev-snp")]
122    if sev_snp::SevSnpSession::is_available() {
123        return by_name("sev-snp");
124    }
125    #[cfg(feature = "tdx")]
126    if tdx::TdxSession::is_available() {
127        return by_name("tdx");
128    }
129
130    fallback()
131}
132
133/// Last-resort provider when no hardware was detected: the mock provider.
134#[cfg(feature = "mock")]
135fn fallback() -> Result<Box<dyn AttestationProvider>, AttestationError> {
136    log::warn!("No TEE hardware detected; using MOCK attestation. Evidence is NOT trustworthy.");
137    by_name("mock")
138}
139
140/// Without the `mock` feature there is no fallback, so detection fails with `NoProvider`.
141#[cfg(not(feature = "mock"))]
142fn fallback() -> Result<Box<dyn AttestationProvider>, AttestationError> {
143    Err(AttestationError::NoProvider)
144}
145
146/// Opens the provider called `name`.
147pub fn by_name(name: &str) -> Result<Box<dyn AttestationProvider>, AttestationError> {
148    match name {
149        #[cfg(feature = "nitro")]
150        "aws-nitro" => Ok(Box::new(nitro::NsmSession::open()?)),
151        #[cfg(feature = "sev-snp")]
152        "sev-snp" => Ok(Box::new(sev_snp::SevSnpSession::open()?)),
153        #[cfg(feature = "tdx")]
154        "tdx" => Ok(Box::new(tdx::TdxSession::open()?)),
155        #[cfg(feature = "mock")]
156        "mock" => Ok(Box::new(mock::MockSession)),
157        other => Err(AttestationError::Unsupported(format!(
158            "'{other}' is unknown or not compiled into this build"
159        ))),
160    }
161}