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, 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 { 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 { Ok(Self::parse_der(der)?.validate_profile()?) } pub fn decode_der_with_strict_name(der: &[u8]) -> Result { 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::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 { 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, } #[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 { 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 { 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 { 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(), }) } }