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