重构error code
This commit is contained in:
@@ -2,7 +2,10 @@ 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};
|
||||
use crate::data_model::rc::{
|
||||
AsIdentifierChoice, IpAddressChoice, ResourceCertKind, ResourceCertificate,
|
||||
ResourceCertificateParseError, ResourceCertificateParsed, ResourceCertificateProfileError,
|
||||
};
|
||||
use crate::data_model::tal::Tal;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
@@ -12,103 +15,160 @@ pub struct TaCertificate {
|
||||
}
|
||||
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum TaCertificateError {
|
||||
pub enum TaCertificateParseError {
|
||||
#[error("TA certificate parse error: {0} (RFC 5280 §4.1; RFC 6487 §4; RFC 8630 §2.3)")]
|
||||
Parse(String),
|
||||
ResourceCertificate(#[from] ResourceCertificateParseError),
|
||||
}
|
||||
|
||||
#[error("trailing bytes after TA certificate DER: {0} bytes (DER; RFC 5280 §4.1; RFC 6487 §4)")]
|
||||
TrailingBytes(usize),
|
||||
#[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)")]
|
||||
#[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 self-signature verification failed: {0} (RFC 8630 §2.3; RFC 5280 §6.1)")]
|
||||
InvalidSelfSignature(String),
|
||||
|
||||
#[error("TA certificate must contain certificatePolicies ipAddr-asNumber ({OID_CP_IPADDR_ASNUMBER}) (RFC 6487 §4.8.9; RFC 8630 §2.3)")]
|
||||
#[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)")]
|
||||
#[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)")]
|
||||
#[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)")]
|
||||
#[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 {
|
||||
pub fn from_der(der: &[u8]) -> Result<Self, TaCertificateError> {
|
||||
let rc_ca = ResourceCertificate::from_der(der).map_err(|e| TaCertificateError::Parse(e.to_string()))?;
|
||||
if rc_ca.kind != ResourceCertKind::Ca {
|
||||
return Err(TaCertificateError::NotCa);
|
||||
}
|
||||
|
||||
// Strong self-signed check: issuer==subject AND signature verifies with its own SPKI.
|
||||
let (rem, cert) =
|
||||
X509Certificate::from_der(der).map_err(|e| TaCertificateError::Parse(e.to_string()))?;
|
||||
if !rem.is_empty() {
|
||||
return Err(TaCertificateError::TrailingBytes(rem.len()));
|
||||
}
|
||||
if cert.issuer().to_string() != cert.subject().to_string() {
|
||||
return Err(TaCertificateError::NotSelfSignedIssuerSubject);
|
||||
}
|
||||
cert.verify_signature(None)
|
||||
.map_err(|e| TaCertificateError::InvalidSelfSignature(e.to_string()))?;
|
||||
|
||||
Self::validate_rc_constraints(&rc_ca)?;
|
||||
|
||||
Ok(Self {
|
||||
raw_der: der.to_vec(),
|
||||
rc_ca,
|
||||
/// 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()?)
|
||||
}
|
||||
|
||||
/// 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<(), TaCertificateError> {
|
||||
pub fn validate_rc_constraints(
|
||||
rc_ca: &ResourceCertificate,
|
||||
) -> Result<(), TaCertificateProfileError> {
|
||||
if rc_ca.kind != ResourceCertKind::Ca {
|
||||
return Err(TaCertificateError::NotCa);
|
||||
return Err(TaCertificateProfileError::NotCa);
|
||||
}
|
||||
|
||||
if rc_ca.tbs.extensions.certificate_policies_oid.as_deref() != Some(OID_CP_IPADDR_ASNUMBER) {
|
||||
return Err(TaCertificateError::MissingOrInvalidCertificatePolicies);
|
||||
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(TaCertificateError::MissingSubjectKeyIdentifier);
|
||||
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(TaCertificateError::ResourcesMissing);
|
||||
return Err(TaCertificateProfileError::ResourcesMissing);
|
||||
}
|
||||
|
||||
let mut has_any_resource = false;
|
||||
|
||||
if let Some(ip) = ip {
|
||||
if ip.has_any_inherit() {
|
||||
return Err(TaCertificateError::IpResourcesInherit);
|
||||
return Err(TaCertificateProfileError::IpResourcesInherit);
|
||||
}
|
||||
for fam in &ip.families {
|
||||
match &fam.choice {
|
||||
IpAddressChoice::Inherit => return Err(TaCertificateError::IpResourcesInherit),
|
||||
IpAddressChoice::Inherit => {
|
||||
return Err(TaCertificateProfileError::IpResourcesInherit);
|
||||
}
|
||||
IpAddressChoice::AddressesOrRanges(items) => {
|
||||
if !items.is_empty() {
|
||||
has_any_resource = true;
|
||||
@@ -122,7 +182,7 @@ impl TaCertificate {
|
||||
if matches!(asn.asnum, Some(AsIdentifierChoice::Inherit))
|
||||
|| matches!(asn.rdi, Some(AsIdentifierChoice::Inherit))
|
||||
{
|
||||
return Err(TaCertificateError::AsResourcesInherit);
|
||||
return Err(TaCertificateProfileError::AsResourcesInherit);
|
||||
}
|
||||
if let Some(AsIdentifierChoice::AsIdsOrRanges(items)) = asn.asnum.as_ref() {
|
||||
if !items.is_empty() {
|
||||
@@ -137,13 +197,33 @@ impl TaCertificate {
|
||||
}
|
||||
|
||||
if !has_any_resource {
|
||||
return Err(TaCertificateError::ResourcesEmpty);
|
||||
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);
|
||||
}
|
||||
|
||||
if rc_ca.tbs.issuer_dn != rc_ca.tbs.subject_dn {
|
||||
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,
|
||||
@@ -154,12 +234,20 @@ pub struct TrustAnchor {
|
||||
#[derive(Debug, thiserror::Error)]
|
||||
pub enum TrustAnchorError {
|
||||
#[error("TA certificate error: {0} (RFC 8630 §2.3)")]
|
||||
TaCertificate(#[from] TaCertificateError),
|
||||
TaCertificate(#[from] TaCertificateDecodeError),
|
||||
|
||||
#[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)")]
|
||||
#[error(
|
||||
"TAL SPKI does not match TA certificate SubjectPublicKeyInfo (RFC 8630 §2.3; RFC 5280 §4.1.2.7)"
|
||||
)]
|
||||
TalSpkiMismatch,
|
||||
}
|
||||
|
||||
@@ -167,16 +255,28 @@ 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(tal: Tal, ta_der: &[u8], resolved_uri: Option<&Url>) -> Result<Self, TrustAnchorError> {
|
||||
pub fn bind_der(
|
||||
tal: Tal,
|
||||
ta_der: &[u8],
|
||||
resolved_uri: Option<&Url>,
|
||||
) -> Result<Self, TrustAnchorError> {
|
||||
let ta_certificate = TaCertificate::decode_der(ta_der)?;
|
||||
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(TrustAnchorError::ResolvedUriNotInTal(u.to_string()));
|
||||
return Err(TrustAnchorBindError::ResolvedUriNotInTal(u.to_string()));
|
||||
}
|
||||
}
|
||||
|
||||
let ta_certificate = TaCertificate::from_der(ta_der)?;
|
||||
if tal.subject_public_key_info_der != ta_certificate.spki_der() {
|
||||
return Err(TrustAnchorError::TalSpkiMismatch);
|
||||
return Err(TrustAnchorBindError::TalSpkiMismatch);
|
||||
}
|
||||
|
||||
Ok(Self {
|
||||
|
||||
Reference in New Issue
Block a user