313 lines
10 KiB
Rust

use url::Url;
use x509_parser::prelude::{FromDer, X509Certificate};
use crate::data_model::oid::OID_CP_IPADDR_ASNUMBER;
use crate::data_model::rc::{
AsIdentifierChoice, IpAddressChoice, ResourceCertKind, ResourceCertificate,
ResourceCertificateParseError, ResourceCertificateParsed, ResourceCertificateProfileError,
ResourceCertificateRole,
};
use crate::data_model::tal::Tal;
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TaCertificate {
pub raw_der: Vec<u8>,
pub rc_ca: ResourceCertificate,
}
#[derive(Debug, thiserror::Error)]
pub enum TaCertificateParseError {
#[error("TA certificate parse error: {0} (RFC 5280 §4.1; RFC 6487 §4; RFC 8630 §2.3)")]
ResourceCertificate(#[from] ResourceCertificateParseError),
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TaCertificateParsed {
pub rc_parsed: ResourceCertificateParsed,
}
#[derive(Debug, thiserror::Error)]
pub enum TaCertificateProfileError {
#[error("resource certificate profile error: {0} (RFC 5280 §4; RFC 6487 §4)")]
ResourceCertificate(#[from] ResourceCertificateProfileError),
#[error("TA certificate must be a CA certificate (RFC 8630 §2.3; RFC 6487 §4.8.1)")]
NotCa,
#[error(
"TA certificate must be self-signed (issuer DN must equal subject DN) (RFC 8630 §2.3; RFC 5280 §4.1.2.4)"
)]
NotSelfSignedIssuerSubject,
#[error(
"TA certificate must contain certificatePolicies ipAddr-asNumber ({OID_CP_IPADDR_ASNUMBER}) (RFC 6487 §4.8.9; RFC 8630 §2.3)"
)]
MissingOrInvalidCertificatePolicies,
#[error("TA certificate must contain SubjectKeyIdentifier (RFC 6487 §4.8.2; RFC 8630 §2.3)")]
MissingSubjectKeyIdentifier,
#[error(
"TA certificate must contain at least one RFC 3779 resource extension (IP or AS) (RFC 6487 §4.8.10-§4.8.11; RFC 8630 §2.3)"
)]
ResourcesMissing,
#[error("TA certificate resources must be non-empty (RFC 8630 §2.3)")]
ResourcesEmpty,
#[error(
"TA certificate MUST NOT use inherit in IP resources (RFC 8630 §2.3; RFC 3779 §2.2.3.5)"
)]
IpResourcesInherit,
#[error(
"TA certificate MUST NOT use inherit in AS resources (RFC 8630 §2.3; RFC 3779 §3.2.3.3)"
)]
AsResourcesInherit,
}
#[derive(Debug, thiserror::Error)]
pub enum TaCertificateDecodeError {
#[error("{0}")]
Parse(#[from] TaCertificateParseError),
#[error("{0}")]
Validate(#[from] TaCertificateProfileError),
}
/// Backwards-compatible name: TA certificate errors from parse+validate.
pub type TaCertificateError = TaCertificateDecodeError;
#[derive(Debug, thiserror::Error)]
pub enum TaCertificateVerifyError {
#[error("TA certificate parse error: {0} (RFC 5280 §4.1; RFC 8630 §2.3)")]
Parse(String),
#[error("trailing bytes after TA certificate DER: {0} bytes (DER; RFC 5280 §4.1)")]
TrailingBytes(usize),
#[error(
"TA certificate self-signature verification failed: {0} (RFC 8630 §2.3; RFC 5280 §6.1)"
)]
InvalidSelfSignature(String),
}
impl TaCertificate {
/// Parse step of scheme A (`parse → validate → verify`).
pub fn parse_der(der: &[u8]) -> Result<TaCertificateParsed, TaCertificateParseError> {
Ok(TaCertificateParsed {
rc_parsed: ResourceCertificate::parse_der(der)?,
})
}
/// Profile validate step of scheme A (`parse → validate → verify`).
///
/// `TaCertificate` is already profile-validated when constructed via `decode_der()` /
/// `TaCertificateParsed::validate_profile()`.
pub fn validate_profile(&self) -> Result<(), TaCertificateProfileError> {
Ok(())
}
/// Decode a TA certificate (`parse + validate`).
pub fn decode_der(der: &[u8]) -> Result<Self, TaCertificateDecodeError> {
Ok(Self::parse_der(der)?.validate_profile()?)
}
pub fn decode_der_with_strict_name(der: &[u8]) -> Result<Self, TaCertificateDecodeError> {
let ta = Self::decode_der(der)?;
ta.rc_ca
.validate_strict_name_profile()
.map_err(TaCertificateProfileError::from)?;
Ok(ta)
}
/// Backwards-compatible helper (historical name).
pub fn from_der(der: &[u8]) -> Result<Self, TaCertificateError> {
Self::decode_der(der)
}
pub fn spki_der(&self) -> &[u8] {
&self.rc_ca.tbs.subject_public_key_info
}
/// Verify step of scheme A (`parse → validate → verify`).
pub fn verify_self_signature(&self) -> Result<(), TaCertificateVerifyError> {
let (rem, cert) = X509Certificate::from_der(&self.raw_der)
.map_err(|e| TaCertificateVerifyError::Parse(e.to_string()))?;
if !rem.is_empty() {
return Err(TaCertificateVerifyError::TrailingBytes(rem.len()));
}
cert.verify_signature(None)
.map_err(|e| TaCertificateVerifyError::InvalidSelfSignature(e.to_string()))?;
Ok(())
}
/// Validate TA-specific semantic constraints on a parsed Resource Certificate.
///
/// Note: this does not verify the X.509 signature; it is intended for higher-level logic and
/// for unit tests that exercise individual constraint branches.
pub fn validate_rc_constraints(
rc_ca: &ResourceCertificate,
) -> Result<(), TaCertificateProfileError> {
if rc_ca.kind != ResourceCertKind::Ca {
return Err(TaCertificateProfileError::NotCa);
}
if rc_ca.tbs.extensions.certificate_policies_oid.as_deref() != Some(OID_CP_IPADDR_ASNUMBER)
{
return Err(TaCertificateProfileError::MissingOrInvalidCertificatePolicies);
}
if rc_ca.tbs.extensions.subject_key_identifier.is_none() {
return Err(TaCertificateProfileError::MissingSubjectKeyIdentifier);
}
let ip = rc_ca.tbs.extensions.ip_resources.as_ref();
let asn = rc_ca.tbs.extensions.as_resources.as_ref();
if ip.is_none() && asn.is_none() {
return Err(TaCertificateProfileError::ResourcesMissing);
}
let mut has_any_resource = false;
if let Some(ip) = ip {
if ip.has_any_inherit() {
return Err(TaCertificateProfileError::IpResourcesInherit);
}
for fam in &ip.families {
match &fam.choice {
IpAddressChoice::Inherit => {
return Err(TaCertificateProfileError::IpResourcesInherit);
}
IpAddressChoice::AddressesOrRanges(items) => {
if !items.is_empty() {
has_any_resource = true;
}
}
}
}
}
if let Some(asn) = asn {
if matches!(asn.asnum, Some(AsIdentifierChoice::Inherit))
|| matches!(asn.rdi, Some(AsIdentifierChoice::Inherit))
{
return Err(TaCertificateProfileError::AsResourcesInherit);
}
if let Some(AsIdentifierChoice::AsIdsOrRanges(items)) = asn.asnum.as_ref() {
if !items.is_empty() {
has_any_resource = true;
}
}
if let Some(AsIdentifierChoice::AsIdsOrRanges(items)) = asn.rdi.as_ref() {
if !items.is_empty() {
has_any_resource = true;
}
}
}
if !has_any_resource {
return Err(TaCertificateProfileError::ResourcesEmpty);
}
Ok(())
}
}
impl TaCertificateParsed {
pub fn validate_profile(self) -> Result<TaCertificate, TaCertificateProfileError> {
let rc_ca = self.rc_parsed.validate_profile()?;
if rc_ca.kind != ResourceCertKind::Ca {
return Err(TaCertificateProfileError::NotCa);
}
rc_ca.validate_rfc6487_profile(ResourceCertificateRole::TrustAnchor)?;
if rc_ca.tbs.issuer_name != rc_ca.tbs.subject_name {
return Err(TaCertificateProfileError::NotSelfSignedIssuerSubject);
}
TaCertificate::validate_rc_constraints(&rc_ca)?;
Ok(TaCertificate {
raw_der: rc_ca.raw_der.clone(),
rc_ca,
})
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TrustAnchor {
pub tal: Tal,
pub ta_certificate: TaCertificate,
pub resolved_ta_uri: Option<Url>,
}
#[derive(Debug, thiserror::Error)]
pub enum TrustAnchorError {
#[error("TA certificate error: {0} (RFC 8630 §2.3)")]
TaCertificate(#[from] TaCertificateDecodeError),
#[error("TA certificate self-signature error: {0} (RFC 8630 §2.3)")]
TaSelfSignature(#[from] TaCertificateVerifyError),
#[error("{0}")]
Bind(#[from] TrustAnchorBindError),
}
#[derive(Debug, thiserror::Error)]
pub enum TrustAnchorBindError {
#[error("resolved TA URI not listed in TAL: {0} (RFC 8630 §2.2-§2.3)")]
ResolvedUriNotInTal(String),
#[error(
"TAL SPKI does not match TA certificate SubjectPublicKeyInfo (RFC 8630 §2.3; RFC 5280 §4.1.2.7)"
)]
TalSpkiMismatch,
}
impl TrustAnchor {
/// Bind a TAL and a downloaded TA certificate.
///
/// This does not download anything; it only validates the binding rules from RFC 8630 §2.3.
pub fn bind_der(
tal: Tal,
ta_der: &[u8],
resolved_uri: Option<&Url>,
) -> Result<Self, TrustAnchorError> {
let ta_certificate = TaCertificate::decode_der(ta_der)?;
ta_certificate.verify_self_signature()?;
Ok(Self::bind(tal, ta_certificate, resolved_uri)?)
}
pub fn bind_der_with_strict_name(
tal: Tal,
ta_der: &[u8],
resolved_uri: Option<&Url>,
) -> Result<Self, TrustAnchorError> {
let ta_certificate = TaCertificate::decode_der_with_strict_name(ta_der)?;
ta_certificate.verify_self_signature()?;
Ok(Self::bind(tal, ta_certificate, resolved_uri)?)
}
pub fn bind(
tal: Tal,
ta_certificate: TaCertificate,
resolved_uri: Option<&Url>,
) -> Result<Self, TrustAnchorBindError> {
if let Some(u) = resolved_uri {
if !tal.ta_uris.iter().any(|x| x == u) {
return Err(TrustAnchorBindError::ResolvedUriNotInTal(u.to_string()));
}
}
if tal.subject_public_key_info_der != ta_certificate.spki_der() {
return Err(TrustAnchorBindError::TalSpkiMismatch);
}
Ok(Self {
tal,
ta_certificate,
resolved_ta_uri: resolved_uri.cloned(),
})
}
}