rpki/tests/test_ta_certificate.rs
2026-02-04 17:02:17 +08:00

178 lines
6.1 KiB
Rust

use der_parser::num_bigint::BigUint;
use rpki::data_model::oid::OID_CP_IPADDR_ASNUMBER;
use rpki::data_model::rc::{
Afi, AsIdOrRange, AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressFamily,
IpAddressOrRange, IpPrefix, RcExtensions, ResourceCertKind, ResourceCertificate,
RpkixTbsCertificate,
};
use rpki::data_model::ta::{TaCertificate, TaCertificateDecodeError, TaCertificateProfileError};
use time::OffsetDateTime;
fn dummy_rc_ca(ext: RcExtensions) -> ResourceCertificate {
let t = OffsetDateTime::from_unix_timestamp(0).unwrap();
ResourceCertificate {
raw_der: Vec::new(),
kind: ResourceCertKind::Ca,
tbs: RpkixTbsCertificate {
version: 2,
serial_number: BigUint::from(1u32),
signature_algorithm: "1.2.840.113549.1.1.11".into(),
issuer_dn: "CN=TA".into(),
subject_dn: "CN=TA".into(),
validity_not_before: t,
validity_not_after: t,
subject_public_key_info: Vec::new(),
extensions: ext,
},
}
}
#[test]
fn ta_certificate_from_der_parses_downloaded_fixtures() {
let fixtures = [
"tests/fixtures/ta/afrinic-ta.cer",
"tests/fixtures/ta/apnic-ta.cer",
"tests/fixtures/ta/arin-ta.cer",
"tests/fixtures/ta/lacnic-ta.cer",
"tests/fixtures/ta/ripe-ncc-ta.cer",
];
for path in fixtures {
let der = std::fs::read(path).expect("read TA fixture");
let ta = TaCertificate::from_der(&der).expect("parse TA fixture");
assert_eq!(ta.rc_ca.kind, ResourceCertKind::Ca);
assert_eq!(
ta.rc_ca.tbs.extensions.certificate_policies_oid.as_deref(),
Some(OID_CP_IPADDR_ASNUMBER)
);
assert!(ta.rc_ca.tbs.extensions.subject_key_identifier.is_some());
}
}
#[test]
fn ta_certificate_rejects_non_self_signed_ca() {
// This is a CA cert fixture, but not self-signed (issuer != subject).
let der = std::fs::read(
"tests/fixtures/repository/rpki.apnic.net/repository/B527EF581D6611E2BB468F7C72FD1FF2/BfycW4hQb3wNP4YsiJW-1n6fjro.cer",
)
.expect("read CA cert fixture");
assert!(matches!(
TaCertificate::from_der(&der),
Err(TaCertificateDecodeError::Validate(
TaCertificateProfileError::NotSelfSignedIssuerSubject
))
));
}
#[test]
fn ta_constraints_require_policies_and_ski() {
let rc = dummy_rc_ca(RcExtensions {
basic_constraints_ca: true,
subject_key_identifier: None,
subject_info_access: None,
certificate_policies_oid: None,
ip_resources: Some(rpki::data_model::rc::IpResourceSet { families: vec![] }),
as_resources: None,
});
assert!(matches!(
TaCertificate::validate_rc_constraints(&rc),
Err(TaCertificateProfileError::MissingOrInvalidCertificatePolicies)
));
let rc = dummy_rc_ca(RcExtensions {
certificate_policies_oid: Some(OID_CP_IPADDR_ASNUMBER.to_string()),
..rc.tbs.extensions.clone()
});
assert!(matches!(
TaCertificate::validate_rc_constraints(&rc),
Err(TaCertificateProfileError::MissingSubjectKeyIdentifier)
));
}
#[test]
fn ta_constraints_require_non_empty_resources_and_no_inherit() {
// Missing both IP and AS resources.
let rc = dummy_rc_ca(RcExtensions {
basic_constraints_ca: true,
subject_key_identifier: Some(vec![1]),
subject_info_access: None,
certificate_policies_oid: Some(OID_CP_IPADDR_ASNUMBER.to_string()),
ip_resources: None,
as_resources: None,
});
assert!(matches!(
TaCertificate::validate_rc_constraints(&rc),
Err(TaCertificateProfileError::ResourcesMissing)
));
// IP resources present but empty => resources empty.
let rc = dummy_rc_ca(RcExtensions {
ip_resources: Some(rpki::data_model::rc::IpResourceSet { families: vec![] }),
..rc.tbs.extensions.clone()
});
assert!(matches!(
TaCertificate::validate_rc_constraints(&rc),
Err(TaCertificateProfileError::ResourcesEmpty)
));
// IP resources inherit is rejected.
let rc = dummy_rc_ca(RcExtensions {
ip_resources: Some(rpki::data_model::rc::IpResourceSet {
families: vec![IpAddressFamily {
afi: Afi::Ipv4,
choice: IpAddressChoice::Inherit,
}],
}),
..rc.tbs.extensions.clone()
});
assert!(matches!(
TaCertificate::validate_rc_constraints(&rc),
Err(TaCertificateProfileError::IpResourcesInherit)
));
// AS resources inherit is rejected.
let rc = dummy_rc_ca(RcExtensions {
ip_resources: None,
as_resources: Some(AsResourceSet {
asnum: Some(AsIdentifierChoice::Inherit),
rdi: None,
}),
..rc.tbs.extensions.clone()
});
assert!(matches!(
TaCertificate::validate_rc_constraints(&rc),
Err(TaCertificateProfileError::AsResourcesInherit)
));
// Valid non-empty explicit IP resources => OK.
let rc = dummy_rc_ca(RcExtensions {
ip_resources: Some(rpki::data_model::rc::IpResourceSet {
families: vec![IpAddressFamily {
afi: Afi::Ipv6,
choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
IpPrefix {
afi: Afi::Ipv6,
prefix_len: 32,
addr: vec![0x20, 0x01, 0x0d, 0xb8, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
},
)]),
}],
}),
as_resources: None,
..rc.tbs.extensions.clone()
});
TaCertificate::validate_rc_constraints(&rc).expect("valid explicit resources");
// Valid non-empty explicit AS resources => OK.
let rc = dummy_rc_ca(RcExtensions {
ip_resources: None,
as_resources: Some(AsResourceSet {
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(
64512,
)])),
rdi: None,
}),
..rc.tbs.extensions.clone()
});
TaCertificate::validate_rc_constraints(&rc).expect("valid explicit AS resources");
}