pub mod dcap;
pub mod nitro;
pub mod sev_snp;
use crate::attestation::eat::EatClaimsSet;
use ciborium::Value;
use rustls_pki_types::{SignatureVerificationAlgorithm, UnixTime};
use std::collections::BTreeMap;
use std::fmt;
pub mod submod {
pub const AWS_NITRO: &str = "aws_nitro";
pub const SEV_SNP: &str = "sev_snp";
pub const TDX: &str = "tdx";
pub const SGX: &str = "sgx";
}
const AWS_NITRO_ROOT: &[u8] = include_bytes!("certs/aws_nitro_root_g1.der");
const MOCK_NITRO_ROOT: &[u8] = include_bytes!("certs/mock_nitro_root.der");
const INTEL_SGX_ROOT: &[u8] = include_bytes!("certs/intel_sgx_root_ca.der");
#[derive(Debug, Clone, Copy, PartialEq, Eq, Hash)]
pub enum TeeKind {
AwsNitro,
SevSnp,
Tdx,
Sgx,
}
impl fmt::Display for TeeKind {
fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
f.write_str(match self {
Self::AwsNitro => "AWS Nitro",
Self::SevSnp => "AMD SEV-SNP",
Self::Tdx => "Intel TDX",
Self::Sgx => "Intel SGX",
})
}
}
impl TeeKind {
pub fn from_submod(label: &str) -> Option<Self> {
match label {
submod::AWS_NITRO => Some(Self::AwsNitro),
submod::SEV_SNP => Some(Self::SevSnp),
submod::TDX => Some(Self::Tdx),
submod::SGX => Some(Self::Sgx),
_ => None,
}
}
}
#[derive(Debug, Clone)]
pub struct VerifiedEvidence {
pub tee: TeeKind,
pub report_data: Vec<u8>,
pub measurements: BTreeMap<String, Vec<u8>>,
pub debug: bool,
pub nitro: Option<nitro::AttestationDocument>,
}
#[derive(Debug, Clone)]
pub struct TrustStore {
pub aws_nitro_root: Vec<u8>,
pub mock_nitro_root: Vec<u8>,
pub intel_sgx_root: Vec<u8>,
pub amd: Vec<sev_snp::AmdRoots>,
}
impl TrustStore {
pub fn builtin() -> Self {
Self {
aws_nitro_root: AWS_NITRO_ROOT.to_vec(),
mock_nitro_root: MOCK_NITRO_ROOT.to_vec(),
intel_sgx_root: INTEL_SGX_ROOT.to_vec(),
amd: sev_snp::AmdRoots::builtin(),
}
}
}
impl Default for TrustStore {
fn default() -> Self {
Self::builtin()
}
}
#[derive(Debug, Clone, Copy, Default, PartialEq, Eq)]
pub struct Policy {
pub allow_mock: bool,
pub allow_debug: bool,
}
pub fn verify_evidence(
eat_bytes: &[u8],
binding: &[u8],
now: UnixTime,
trust: &TrustStore,
policy: Policy,
) -> Result<VerifiedEvidence, String> {
let claims =
EatClaimsSet::from_bytes(eat_bytes).map_err(|e| format!("invalid EAT token: {e}"))?;
let submods = claims.submods.ok_or("EAT token has no submods")?;
let entries = submods.into_map().map_err(|_| "EAT submods is not a map")?;
let mut known: Vec<(TeeKind, Value)> = entries
.into_iter()
.filter_map(|(label, value)| {
let tee = TeeKind::from_submod(label.as_text()?)?;
Some((tee, value))
})
.collect();
let (tee, value) = match known.len() {
0 => return Err("EAT token contains no supported TEE evidence".into()),
1 => known.remove(0),
_ => return Err("EAT token contains evidence from more than one TEE".into()),
};
let evidence = match tee {
TeeKind::AwsNitro => nitro::verify(&bytes_of(value, tee)?, now, trust, policy)?,
TeeKind::SevSnp => sev_snp::verify(&value, now, trust)?,
TeeKind::Tdx | TeeKind::Sgx => dcap::verify(&bytes_of(value, tee)?, tee, now, trust)?,
};
if evidence.debug && !(policy.allow_debug || policy.allow_mock) {
return Err(format!("{tee} evidence comes from a debug-mode TEE"));
}
if !is_bound_to(&evidence.report_data, binding) {
return Err(format!(
"{tee} report data does not match the certificate's public key"
));
}
Ok(evidence)
}
fn bytes_of(value: Value, tee: TeeKind) -> Result<Vec<u8>, String> {
value
.into_bytes()
.map_err(|_| format!("{tee} evidence is not a byte string"))
}
pub fn is_bound_to(report_data: &[u8], binding: &[u8]) -> bool {
report_data.len() >= binding.len()
&& report_data[..binding.len()] == *binding
&& report_data[binding.len()..].iter().all(|b| *b == 0)
}
fn chain_algorithms() -> &'static [&'static dyn SignatureVerificationAlgorithm] {
rustls::crypto::ring::default_provider()
.signature_verification_algorithms
.all
}
fn verify_ecdsa(
alg: &'static ring::signature::EcdsaVerificationAlgorithm,
public_key: &[u8],
message: &[u8],
signature: &[u8],
) -> Result<(), ()> {
ring::signature::UnparsedPublicKey::new(alg, public_key)
.verify(message, signature)
.map_err(|_| ())
}
fn field(buf: &[u8], offset: usize, len: usize) -> Vec<u8> {
buf[offset..offset + len].to_vec()
}
fn le_u64(buf: &[u8], offset: usize) -> u64 {
u64::from_le_bytes(buf[offset..offset + 8].try_into().expect("8-byte slice"))
}
struct AnyKeyUsage;
impl webpki::ExtendedKeyUsageValidator for AnyKeyUsage {
fn validate(&self, iter: webpki::KeyPurposeIdIter<'_, '_>) -> Result<(), webpki::Error> {
for eku in iter {
eku?;
}
Ok(())
}
}