86 lines
3.3 KiB
Rust
86 lines
3.3 KiB
Rust
use crate::data_model::common::X509NameDer;
|
|
use x509_parser::prelude::FromDer;
|
|
|
|
fn canonicalize(name: &X509NameDer) -> Option<String> {
|
|
let (rem, parsed) = x509_parser::x509::X509Name::from_der(name.as_raw()).ok()?;
|
|
if !rem.is_empty() {
|
|
return None;
|
|
}
|
|
Some(parsed.to_string())
|
|
}
|
|
|
|
/// Compare two X.509 distinguished names using a tolerant semantic comparison.
|
|
///
|
|
/// RPKI repositories in the wild sometimes encode the same name using different
|
|
/// ASN.1 string types (e.g., PrintableString vs UTF8String) while remaining
|
|
/// semantically equivalent. RFC 5280 path validation requires name matching, but
|
|
/// DER byte equality is too strict for interoperability.
|
|
pub fn x509_names_equivalent(a: &X509NameDer, b: &X509NameDer) -> bool {
|
|
let Some(ca) = canonicalize(a) else {
|
|
return a == b;
|
|
};
|
|
let Some(cb) = canonicalize(b) else {
|
|
return a == b;
|
|
};
|
|
ca == cb
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
|
|
#[test]
|
|
fn x509_names_equivalent_falls_back_to_der_equality_when_parse_fails() {
|
|
// Invalid tag (not a SEQUENCE) makes x509-parser fail and forces DER equality fallback.
|
|
let a = X509NameDer(vec![0x01, 0x00]);
|
|
let b = X509NameDer(vec![0x01, 0x00]);
|
|
assert!(x509_names_equivalent(&a, &b));
|
|
}
|
|
|
|
#[test]
|
|
fn x509_names_equivalent_compares_semantic_names_when_parse_succeeds() {
|
|
let cert_der = std::fs::read(
|
|
"tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer",
|
|
)
|
|
.expect("read certificate fixture");
|
|
|
|
let (_rem, cert) =
|
|
x509_parser::parse_x509_certificate(&cert_der).expect("parse certificate fixture");
|
|
|
|
let subject = X509NameDer(cert.tbs_certificate.subject.as_raw().to_vec());
|
|
let issuer = X509NameDer(cert.tbs_certificate.issuer.as_raw().to_vec());
|
|
|
|
assert!(x509_names_equivalent(&subject, &subject));
|
|
assert!(!x509_names_equivalent(&subject, &issuer));
|
|
}
|
|
|
|
#[test]
|
|
fn x509_names_equivalent_falls_back_when_name_has_trailing_bytes() {
|
|
// Use a real name DER and append a trailing byte so parsing yields leftover `rem`.
|
|
let cert_der = std::fs::read(
|
|
"tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer",
|
|
)
|
|
.expect("read certificate fixture");
|
|
let (_rem, cert) =
|
|
x509_parser::parse_x509_certificate(&cert_der).expect("parse certificate fixture");
|
|
let mut name = cert.tbs_certificate.subject.as_raw().to_vec();
|
|
name.push(0x00);
|
|
let a = X509NameDer(name.clone());
|
|
let b = X509NameDer(name);
|
|
assert!(x509_names_equivalent(&a, &b));
|
|
}
|
|
|
|
#[test]
|
|
fn x509_names_equivalent_falls_back_when_one_side_fails_to_parse() {
|
|
let cert_der = std::fs::read(
|
|
"tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer",
|
|
)
|
|
.expect("read certificate fixture");
|
|
let (_rem, cert) =
|
|
x509_parser::parse_x509_certificate(&cert_der).expect("parse certificate fixture");
|
|
let good = X509NameDer(cert.tbs_certificate.subject.as_raw().to_vec());
|
|
let bad = X509NameDer(vec![0x01, 0x00]);
|
|
assert!(!x509_names_equivalent(&good, &bad));
|
|
}
|
|
}
|