extern crate alloc;
use alloc::vec::Vec;
use crate::{counter::CounterEvidence, digest::TypedDigest, pcr::TypedPcrBank};
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash, serde::Serialize, serde::Deserialize)]
#[repr(u8)]
pub enum HardwareBackendType {
Tpm2 = 0x01,
Dice = 0x02,
IntelTdx = 0x03,
AmdSevSnp = 0x04,
NitroEnclave = 0x05,
#[cfg(feature = "unsafe-test-backend")]
TestOnly = 0xFF,
}
impl HardwareBackendType {
#[must_use]
pub fn is_hardware_rooted(self) -> bool {
match self {
Self::Tpm2 | Self::Dice | Self::IntelTdx | Self::AmdSevSnp | Self::NitroEnclave => true,
#[cfg(feature = "unsafe-test-backend")]
Self::TestOnly => false,
}
}
#[must_use]
pub const fn name(self) -> &'static str {
match self {
Self::Tpm2 => "TPM 2.0",
Self::Dice => "DICE",
Self::IntelTdx => "Intel TDX",
Self::AmdSevSnp => "AMD SEV-SNP",
Self::NitroEnclave => "AWS Nitro Enclave",
#[cfg(feature = "unsafe-test-backend")]
Self::TestOnly => "TestOnly (UNSAFE — not for production)",
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
#[allow(clippy::struct_excessive_bools)]
pub struct HardwareCapabilities {
pub has_monotonic_counter: bool,
pub has_ek_certificate: bool,
pub supports_measured_boot: bool,
pub supports_nonce_binding: bool,
pub provides_signed_quote: bool,
}
impl HardwareCapabilities {
#[must_use]
pub const fn tpm2_full() -> Self {
Self {
has_monotonic_counter: true,
has_ek_certificate: true,
supports_measured_boot: true,
supports_nonce_binding: true,
provides_signed_quote: true,
}
}
#[must_use]
pub const fn dice() -> Self {
Self {
has_monotonic_counter: false,
has_ek_certificate: false,
supports_measured_boot: true,
supports_nonce_binding: false,
provides_signed_quote: false,
}
}
#[cfg(feature = "unsafe-test-backend")]
#[must_use]
pub const fn test_only() -> Self {
Self {
has_monotonic_counter: false,
has_ek_certificate: false,
supports_measured_boot: false,
supports_nonce_binding: false,
provides_signed_quote: false,
}
}
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct HardwareEvidence {
pub backend_type: HardwareBackendType,
pub capabilities: HardwareCapabilities,
pub pcr_bank: TypedPcrBank,
pub firmware_digest: TypedDigest,
pub secondary_digest: Option<TypedDigest>,
pub counter: CounterEvidence,
pub backend_evidence: BackendSpecificEvidence,
}
impl HardwareEvidence {
#[must_use]
pub fn pcrs_are_normalized(&self) -> bool {
self.pcr_bank.all_normalized()
}
#[must_use]
pub fn is_hardware_rooted(&self) -> bool {
self.backend_type.is_hardware_rooted()
}
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub enum BackendSpecificEvidence {
Tpm2(TpmQuoteEvidence),
Dice(DiceEvidence),
IntelTdx(TdxEvidence),
AmdSevSnp(SevSnpEvidence),
NitroEnclave(NitroEnclaveEvidence),
#[cfg(feature = "unsafe-test-backend")]
TestOnly,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct TpmQuoteEvidence {
#[serde(with = "serde_bytes")]
pub quote_blob: Vec<u8>,
#[serde(with = "serde_bytes")]
pub quote_signature: Vec<u8>,
pub identity: TpmIdentity,
pub clock_info: TpmClockInfo,
pub qualifying_data: [u8; 32],
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct TpmIdentity {
#[serde(with = "serde_bytes")]
pub ak_pub: Vec<u8>,
#[serde(with = "serde_bytes")]
pub ek_pub: Option<Vec<u8>>,
pub manufacturer: Option<alloc::string::String>,
pub firmware_version: Option<alloc::string::String>,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq, serde::Serialize, serde::Deserialize)]
pub struct TpmClockInfo {
pub clock_ms: u64,
pub reset_count: u32,
pub restart_count: u32,
pub safe: bool,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct DiceEvidence {
pub cdi_attest: TypedDigest,
pub layered_measurements: Vec<TypedDigest>,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct TdxEvidence {
#[serde(with = "serde_bytes")]
pub raw_quote: Vec<u8>,
#[serde(with = "serde_bytes")]
pub mrtd: Vec<u8>,
pub rtmr: [alloc::vec::Vec<u8>; 4],
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct SevSnpEvidence {
#[serde(with = "serde_bytes")]
pub raw_report: Vec<u8>,
#[serde(with = "serde_bytes")]
pub measurement: Vec<u8>,
}
#[derive(Clone, Debug, serde::Serialize, serde::Deserialize)]
pub struct NitroEnclaveEvidence {
#[serde(with = "serde_bytes")]
pub attestation_doc: Vec<u8>,
#[serde(with = "serde_bytes")]
pub pcr0: Vec<u8>,
}
pub trait HardwareRootOfTrust {
fn collect_evidence(&self, nonce: &[u8; 32]) -> Result<HardwareEvidence, HardwareError>;
fn backend_type(&self) -> HardwareBackendType;
fn capabilities(&self) -> HardwareCapabilities;
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum HardwareError {
DeviceUnavailable,
PcrReadFailed,
QuoteFailed,
NonceBidingFailed,
UnsupportedAlgorithm,
InconsistentEvidence,
BackendNotCompiled,
}
impl core::fmt::Display for HardwareError {
fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result {
match self {
Self::DeviceUnavailable => f.write_str("hardware device unavailable"),
Self::PcrReadFailed => f.write_str("PCR read failed"),
Self::QuoteFailed => f.write_str("TPM quote operation failed"),
Self::NonceBidingFailed => f.write_str("nonce binding failed"),
Self::UnsupportedAlgorithm => f.write_str("unsupported hash algorithm"),
Self::InconsistentEvidence => f.write_str("evidence is internally inconsistent"),
Self::BackendNotCompiled => f.write_str("backend not compiled in"),
}
}
}