rpki/src/validation/x509_name.rs

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));
}
}