use thiserror::Error;
use crate::attestation::extensions::VendorId;
#[derive(Debug, Error)]
#[non_exhaustive]
pub enum AttestationError {
#[error("X.509 parse failure")]
Parse(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
#[error("certificate chain validation failed")]
InvalidChain(#[source] Box<dyn std::error::Error + Send + Sync + 'static>),
#[error("certificate expired or not yet valid")]
TimeBoundsViolation,
#[error("BasicConstraints violation")]
BasicConstraintsViolation,
#[error("PAA not in trust store")]
UntrustedRoot,
#[error("VID mismatch: DAC={dac:?} PAI={pai:?}")]
VidMismatch {
dac: VendorId,
pai: VendorId,
},
#[error("PAI is not authorized for DAC's product")]
PaiVidNotAuthorized,
#[error("VID-scoped PAA scope mismatch: PAA={paa_vid:?} DAC/PAI={dac_vid:?}")]
PaaVidScopeMismatch {
paa_vid: VendorId,
dac_vid: VendorId,
},
#[error("attestation_elements malformed or missing required fields")]
ResponseElementsMalformed,
#[error("certification declaration has invalid CMS structure")]
CertificationDeclarationMalformed,
#[error("certification declaration signature does not verify against any trusted root")]
CertificationDeclarationSignatureInvalid,
#[error("certification declaration inner TLV malformed")]
CertificationDeclarationTlvMalformed,
#[error(
"certification declaration VID mismatch: declared {declared:?}, expected {expected:?}"
)]
CertificationDeclarationVidMismatch {
declared: crate::attestation::VendorId,
expected: crate::attestation::VendorId,
},
#[error("certification declaration PID list does not contain expected {0:?}")]
CertificationDeclarationPidMismatch(crate::attestation::ProductId),
#[error("AttestationResponse signature verification failed")]
BadResponseSignature,
}
pub(crate) fn map_webpki_error(err: webpki::Error) -> AttestationError {
use webpki::Error as W;
match err {
W::CertExpired { .. } | W::CertNotValidYet { .. } | W::InvalidCertValidity => {
AttestationError::TimeBoundsViolation
}
W::PathLenConstraintViolated | W::EndEntityUsedAsCa | W::CaUsedAsEndEntity => {
AttestationError::BasicConstraintsViolation
}
W::UnknownIssuer => AttestationError::UntrustedRoot,
other => AttestationError::InvalidChain(Box::new(other)),
}
}
#[cfg(test)]
mod tests {
use super::*;
use core::time::Duration;
use rustls_pki_types::UnixTime;
fn epoch() -> UnixTime {
UnixTime::since_unix_epoch(Duration::from_secs(0))
}
#[test]
fn maps_cert_expired_to_time_bounds_violation() {
let err = map_webpki_error(webpki::Error::CertExpired {
time: epoch(),
not_after: epoch(),
});
assert!(matches!(err, AttestationError::TimeBoundsViolation));
}
#[test]
fn maps_cert_not_valid_yet_to_time_bounds_violation() {
let err = map_webpki_error(webpki::Error::CertNotValidYet {
time: epoch(),
not_before: epoch(),
});
assert!(matches!(err, AttestationError::TimeBoundsViolation));
}
#[test]
fn maps_invalid_cert_validity_to_time_bounds_violation() {
let err = map_webpki_error(webpki::Error::InvalidCertValidity);
assert!(matches!(err, AttestationError::TimeBoundsViolation));
}
#[test]
fn maps_path_len_constraint_violated_to_basic_constraints_violation() {
let err = map_webpki_error(webpki::Error::PathLenConstraintViolated);
assert!(matches!(err, AttestationError::BasicConstraintsViolation));
}
#[test]
fn maps_end_entity_used_as_ca_to_basic_constraints_violation() {
let err = map_webpki_error(webpki::Error::EndEntityUsedAsCa);
assert!(matches!(err, AttestationError::BasicConstraintsViolation));
}
#[test]
fn maps_ca_used_as_end_entity_to_basic_constraints_violation() {
let err = map_webpki_error(webpki::Error::CaUsedAsEndEntity);
assert!(matches!(err, AttestationError::BasicConstraintsViolation));
}
#[test]
fn maps_unknown_issuer_to_untrusted_root() {
let err = map_webpki_error(webpki::Error::UnknownIssuer);
assert!(matches!(err, AttestationError::UntrustedRoot));
}
#[test]
fn maps_long_tail_to_invalid_chain() {
let err = map_webpki_error(webpki::Error::InvalidSignatureForPublicKey);
assert!(matches!(err, AttestationError::InvalidChain(_)));
}
#[test]
fn bad_response_signature_variant_exists() {
let err = AttestationError::BadResponseSignature;
assert!(matches!(err, AttestationError::BadResponseSignature));
assert_eq!(
format!("{err}"),
"AttestationResponse signature verification failed"
);
}
}