use std::iter::once;
use ring::digest::{self, SHA256};
use ring::signature::{ECDSA_P384_SHA384_FIXED, UnparsedPublicKey};
use time::OffsetDateTime;
use x509_parser::certificate::X509Certificate;
use x509_parser::oid_registry::OID_SIG_ECDSA_WITH_SHA384;
use x509_parser::prelude::FromDer;
use x509_parser::validate::{Validator, X509StructureValidator};
use super::parse::{ParsedNitroQuote, parse_nitro_quote};
pub const AWS_NITRO_ROOT_G1_FINGERPRINT: [u8; 32] = [
0x64, 0x1a, 0x03, 0x21, 0xa3, 0xe2, 0x44, 0xef, 0xe4, 0x56, 0x46, 0x31, 0x95, 0xd6, 0x06, 0x31,
0x7e, 0xd7, 0xcd, 0xcc, 0x3c, 0x17, 0x56, 0xe0, 0x98, 0x93, 0xf3, 0xc6, 0x8f, 0x79, 0xbb, 0x5b,
];
pub const AWS_NITRO_ROOT_G1_PEM: &str = include_str!("aws_nitro_root_g1.pem");
#[derive(Debug, Clone, Copy)]
pub enum TrustAnchor {
AwsProduction,
RootFingerprint([u8; 32]),
}
impl TrustAnchor {
fn fingerprint(&self) -> [u8; 32] {
match self {
TrustAnchor::AwsProduction => AWS_NITRO_ROOT_G1_FINGERPRINT,
TrustAnchor::RootFingerprint(fp) => *fp,
}
}
}
#[derive(Debug, Clone, Copy)]
pub struct NitroVerifier {
pub anchor: TrustAnchor,
pub now: OffsetDateTime,
}
impl NitroVerifier {
pub fn aws_production(now: OffsetDateTime) -> Self {
Self {
anchor: TrustAnchor::AwsProduction,
now,
}
}
pub fn verify(&self, bytes: &[u8]) -> Result<ParsedNitroQuote, NitroVerifyError> {
let parsed = parse_nitro_quote(bytes)?;
let leaf_spki_der: Vec<u8> = {
let chain_der: Vec<&[u8]> = parsed
.cabundle_der
.iter()
.map(Vec::as_slice)
.chain(once(parsed.certificate_der.as_slice()))
.collect();
if chain_der.is_empty() {
return Err(NitroVerifyError::NoCertificates);
}
let mut parent: Option<X509Certificate> = None;
let mut leaf_spki: Option<Vec<u8>> = None;
for (idx, der) in chain_der.iter().enumerate() {
let cert = parse_cert(der, idx)?;
verify_cert(&cert, der, parent.as_ref(), self.anchor.fingerprint(), idx)?;
validate_window(&cert, self.now, idx)?;
leaf_spki = Some(cert.public_key().subject_public_key.as_ref().to_vec());
parent = Some(cert);
}
leaf_spki.expect("non-empty chain sets leaf SPKI")
};
parsed.cose.verify_signature(&[], |sig, data| {
UnparsedPublicKey::new(&ECDSA_P384_SHA384_FIXED, &leaf_spki_der)
.verify(data, sig)
.map_err(|_| NitroVerifyError::CoseSignatureInvalid)
})?;
Ok(parsed)
}
}
#[derive(Debug, thiserror::Error)]
pub enum NitroVerifyError {
#[error(transparent)]
Parse(#[from] super::parse::NitroParseError),
#[error("no certificates in attestation document")]
NoCertificates,
#[error("certificate {idx} malformed")]
CertMalformed { idx: usize },
#[error("certificate {idx} structure invalid")]
CertStructureInvalid { idx: usize },
#[error("certificate {idx} unexpected signature algorithm")]
UnexpectedAlgorithm { idx: usize },
#[error("certificate {idx} signature invalid")]
SignatureInvalid { idx: usize },
#[error("root fingerprint mismatch: have {have}, want {want}")]
RootFingerprintMismatch { have: String, want: String },
#[error("certificate {idx} expired")]
CertExpired { idx: usize },
#[error("certificate {idx} not yet valid")]
CertNotYetValid { idx: usize },
#[error("COSE signature verification failed")]
CoseSignatureInvalid,
}
fn parse_cert<'a>(der: &'a [u8], idx: usize) -> Result<X509Certificate<'a>, NitroVerifyError> {
let (_, cert) =
X509Certificate::from_der(der).map_err(|_| NitroVerifyError::CertMalformed { idx })?;
let mut logger = x509_parser::validate::VecLogger::default();
if !X509StructureValidator.validate(&cert, &mut logger) {
return Err(NitroVerifyError::CertStructureInvalid { idx });
}
Ok(cert)
}
fn verify_cert(
cert: &X509Certificate,
der: &[u8],
parent: Option<&X509Certificate>,
anchor_fp: [u8; 32],
idx: usize,
) -> Result<(), NitroVerifyError> {
if cert.signature_algorithm.oid() != &OID_SIG_ECDSA_WITH_SHA384 {
return Err(NitroVerifyError::UnexpectedAlgorithm { idx });
}
match parent {
Some(parent) => cert
.verify_signature(Some(parent.public_key()))
.map_err(|_| NitroVerifyError::SignatureInvalid { idx }),
None => {
let have = digest::digest(&SHA256, der);
if have.as_ref() != anchor_fp {
return Err(NitroVerifyError::RootFingerprintMismatch {
have: crate::hex::lower(have.as_ref()),
want: crate::hex::lower(&anchor_fp),
});
}
Ok(())
}
}
}
fn validate_window(
cert: &X509Certificate,
now: OffsetDateTime,
idx: usize,
) -> Result<(), NitroVerifyError> {
if now > cert.validity.not_after.to_datetime() {
return Err(NitroVerifyError::CertExpired { idx });
}
if now < cert.validity.not_before.to_datetime() {
return Err(NitroVerifyError::CertNotYetValid { idx });
}
Ok(())
}