use std::process::Command; use rpki::data_model::rc::ResourceCertificate; use rpki::validation::ca_path::{CaPathError, validate_subordinate_ca_cert}; fn openssl_available() -> bool { Command::new("openssl") .arg("version") .output() .map(|o| o.status.success()) .unwrap_or(false) } struct Generated { issuer_ca_der: Vec, child_ca_der: Vec, issuer_crl_der: Vec, } fn write(path: &std::path::Path, s: &str) { std::fs::write(path, s.as_bytes()).expect("write file"); } fn run(cmd: &mut Command) { let out = cmd.output().expect("run command"); if !out.status.success() { panic!( "command failed: {:?}\nstdout={}\nstderr={}", cmd, String::from_utf8_lossy(&out.stdout), String::from_utf8_lossy(&out.stderr) ); } } fn generate_chain_and_crl(child_ext: &str, revoke_child: bool) -> Generated { assert!(openssl_available(), "openssl is required for this test"); let td = tempfile::tempdir().expect("tempdir"); let dir = td.path(); // Minimal CA database layout required by `openssl ca`. std::fs::create_dir_all(dir.join("newcerts")).expect("newcerts"); std::fs::write(dir.join("index.txt"), b"").expect("index"); std::fs::write(dir.join("serial"), b"1000\n").expect("serial"); std::fs::write(dir.join("crlnumber"), b"1000\n").expect("crlnumber"); let cnf = format!( r#" [ ca ] default_ca = CA_default [ CA_default ] dir = {dir} database = $dir/index.txt new_certs_dir = $dir/newcerts certificate = $dir/issuer.pem private_key = $dir/issuer.key serial = $dir/serial crlnumber = $dir/crlnumber default_md = sha256 default_days = 365 default_crl_days = 1 policy = policy_any x509_extensions = v3_issuer_ca crl_extensions = crl_ext unique_subject = no copy_extensions = none [ policy_any ] commonName = supplied [ req ] prompt = no distinguished_name = dn [ dn ] CN = Test Issuer CA [ v3_issuer_ca ] basicConstraints = critical,CA:true keyUsage = critical, keyCertSign, cRLSign subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always certificatePolicies = critical, 1.3.6.1.5.5.7.14.2 subjectInfoAccess = caRepository;URI:rsync://example.test/repo/issuer/, rpkiManifest;URI:rsync://example.test/repo/issuer/issuer.mft, rpkiNotify;URI:https://example.test/notification.xml sbgp-ipAddrBlock = critical, IPv4:10.0.0.0/8 sbgp-autonomousSysNum = critical, AS:64496-64511 [ v3_child_ca ] basicConstraints = critical,CA:true subjectKeyIdentifier = hash authorityKeyIdentifier = keyid:always crlDistributionPoints = URI:rsync://example.test/repo/issuer/issuer.crl authorityInfoAccess = caIssuers;URI:rsync://example.test/repo/issuer/issuer.cer certificatePolicies = critical, 1.3.6.1.5.5.7.14.2 subjectInfoAccess = caRepository;URI:rsync://example.test/repo/child/, rpkiManifest;URI:rsync://example.test/repo/child/child.mft, rpkiNotify;URI:https://example.test/notification.xml {child_ext} [ crl_ext ] authorityKeyIdentifier = keyid:always "#, dir = dir.display(), child_ext = child_ext ); write(&dir.join("openssl.cnf"), &cnf); // Issuer CA key + self-signed CA cert (DER later). run(Command::new("openssl") .arg("genrsa") .arg("-out") .arg(dir.join("issuer.key")) .arg("2048")); run(Command::new("openssl") .arg("req") .arg("-new") .arg("-x509") .arg("-sha256") .arg("-days") .arg("365") .arg("-key") .arg(dir.join("issuer.key")) .arg("-out") .arg(dir.join("issuer.pem")) .arg("-config") .arg(dir.join("openssl.cnf")) .arg("-extensions") .arg("v3_issuer_ca")); // Child CA key + CSR. run(Command::new("openssl") .arg("genrsa") .arg("-out") .arg(dir.join("child.key")) .arg("2048")); run(Command::new("openssl") .arg("req") .arg("-new") .arg("-key") .arg(dir.join("child.key")) .arg("-subj") .arg("/CN=Child CA") .arg("-out") .arg(dir.join("child.csr")) .arg("-config") .arg(dir.join("openssl.cnf"))); // Issue child CA cert using openssl ca (so it appears in the CA database for CRL). run(Command::new("openssl") .arg("ca") .arg("-batch") .arg("-config") .arg(dir.join("openssl.cnf")) .arg("-extensions") .arg("v3_child_ca") .arg("-in") .arg(dir.join("child.csr")) .arg("-out") .arg(dir.join("child.pem")) .arg("-notext")); if revoke_child { run(Command::new("openssl") .arg("ca") .arg("-config") .arg(dir.join("openssl.cnf")) .arg("-revoke") .arg(dir.join("child.pem"))); } // Generate CRL. run(Command::new("openssl") .arg("ca") .arg("-gencrl") .arg("-config") .arg(dir.join("openssl.cnf")) .arg("-out") .arg(dir.join("issuer.crl.pem"))); // Convert to DER. run(Command::new("openssl") .arg("x509") .arg("-in") .arg(dir.join("issuer.pem")) .arg("-outform") .arg("DER") .arg("-out") .arg(dir.join("issuer.cer"))); run(Command::new("openssl") .arg("x509") .arg("-in") .arg(dir.join("child.pem")) .arg("-outform") .arg("DER") .arg("-out") .arg(dir.join("child.cer"))); run(Command::new("openssl") .arg("crl") .arg("-in") .arg(dir.join("issuer.crl.pem")) .arg("-outform") .arg("DER") .arg("-out") .arg(dir.join("issuer.crl"))); Generated { issuer_ca_der: std::fs::read(dir.join("issuer.cer")).expect("read issuer der"), child_ca_der: std::fs::read(dir.join("child.cer")).expect("read child der"), issuer_crl_der: std::fs::read(dir.join("issuer.crl")).expect("read crl der"), } } #[test] fn validate_subordinate_ca_succeeds_for_valid_child_and_subset_resources() { let generated = generate_chain_and_crl( "keyUsage = critical, keyCertSign, cRLSign\nsbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16\nsbgp-autonomousSysNum = critical, AS:64496\n", false, ); let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer"); let now = time::OffsetDateTime::now_utc(); let validated = validate_subordinate_ca_cert( &generated.child_ca_der, &generated.issuer_ca_der, &generated.issuer_crl_der, Some("rsync://example.test/repo/issuer/issuer.cer"), "rsync://example.test/repo/issuer/issuer.crl", issuer.tbs.extensions.ip_resources.as_ref(), issuer.tbs.extensions.as_resources.as_ref(), now, ) .expect("validate subordinate"); assert!(validated.effective_ip_resources.is_some()); assert!(validated.effective_as_resources.is_some()); } #[test] fn validate_subordinate_ca_rejects_wrong_key_usage_bits() { let generated = generate_chain_and_crl( "keyUsage = critical, digitalSignature\nsbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16\n", false, ); let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer"); let now = time::OffsetDateTime::now_utc(); let err = validate_subordinate_ca_cert( &generated.child_ca_der, &generated.issuer_ca_der, &generated.issuer_crl_der, Some("rsync://example.test/repo/issuer/issuer.cer"), "rsync://example.test/repo/issuer/issuer.crl", issuer.tbs.extensions.ip_resources.as_ref(), issuer.tbs.extensions.as_resources.as_ref(), now, ) .unwrap_err(); assert!(matches!(err, CaPathError::KeyUsageInvalidBits)); } #[test] fn validate_subordinate_ca_rejects_out_of_scope_resources() { let generated = generate_chain_and_crl( "keyUsage = critical, keyCertSign, cRLSign\nsbgp-ipAddrBlock = critical, IPv4:11.0.0.0/8\n", false, ); let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer"); let now = time::OffsetDateTime::now_utc(); let err = validate_subordinate_ca_cert( &generated.child_ca_der, &generated.issuer_ca_der, &generated.issuer_crl_der, Some("rsync://example.test/repo/issuer/issuer.cer"), "rsync://example.test/repo/issuer/issuer.crl", issuer.tbs.extensions.ip_resources.as_ref(), issuer.tbs.extensions.as_resources.as_ref(), now, ) .unwrap_err(); assert!(matches!(err, CaPathError::ResourcesNotSubset)); } #[test] fn validate_subordinate_ca_rejects_revoked_child() { let generated = generate_chain_and_crl( "keyUsage = critical, keyCertSign, cRLSign\nsbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16\n", true, ); let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer"); let now = time::OffsetDateTime::now_utc(); let err = validate_subordinate_ca_cert( &generated.child_ca_der, &generated.issuer_ca_der, &generated.issuer_crl_der, Some("rsync://example.test/repo/issuer/issuer.cer"), "rsync://example.test/repo/issuer/issuer.crl", issuer.tbs.extensions.ip_resources.as_ref(), issuer.tbs.extensions.as_resources.as_ref(), now, ) .unwrap_err(); assert!(matches!(err, CaPathError::ChildRevoked)); } #[test] fn validate_subordinate_ca_rejects_missing_key_usage_extension() { let generated = generate_chain_and_crl( "sbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16\nsbgp-autonomousSysNum = critical, AS:64496\n", false, ); let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer"); let now = time::OffsetDateTime::now_utc(); let err = validate_subordinate_ca_cert( &generated.child_ca_der, &generated.issuer_ca_der, &generated.issuer_crl_der, Some("rsync://example.test/repo/issuer/issuer.cer"), "rsync://example.test/repo/issuer/issuer.crl", issuer.tbs.extensions.ip_resources.as_ref(), issuer.tbs.extensions.as_resources.as_ref(), now, ) .unwrap_err(); assert!(matches!(err, CaPathError::KeyUsageMissing)); } #[test] fn validate_subordinate_ca_rejects_non_critical_key_usage() { let generated = generate_chain_and_crl( "keyUsage = keyCertSign, cRLSign\nsbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16\n", false, ); let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer"); let now = time::OffsetDateTime::now_utc(); let err = validate_subordinate_ca_cert( &generated.child_ca_der, &generated.issuer_ca_der, &generated.issuer_crl_der, Some("rsync://example.test/repo/issuer/issuer.cer"), "rsync://example.test/repo/issuer/issuer.crl", issuer.tbs.extensions.ip_resources.as_ref(), issuer.tbs.extensions.as_resources.as_ref(), now, ) .unwrap_err(); assert!(matches!(err, CaPathError::KeyUsageNotCritical)); } #[test] fn validate_subordinate_ca_rejects_when_child_has_no_resources() { let generated = generate_chain_and_crl("keyUsage = critical, keyCertSign, cRLSign\n", false); let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer"); let now = time::OffsetDateTime::now_utc(); let err = validate_subordinate_ca_cert( &generated.child_ca_der, &generated.issuer_ca_der, &generated.issuer_crl_der, Some("rsync://example.test/repo/issuer/issuer.cer"), "rsync://example.test/repo/issuer/issuer.crl", issuer.tbs.extensions.ip_resources.as_ref(), issuer.tbs.extensions.as_resources.as_ref(), now, ) .unwrap_err(); assert!(matches!(err, CaPathError::ResourcesMissing)); } #[test] fn validate_subordinate_ca_rejects_when_cert_not_valid_at_validation_time() { let generated = generate_chain_and_crl( "keyUsage = critical, keyCertSign, cRLSign\nsbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16\n", false, ); let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer"); let validation_time = time::OffsetDateTime::now_utc() + time::Duration::days(400); let err = validate_subordinate_ca_cert( &generated.child_ca_der, &generated.issuer_ca_der, &generated.issuer_crl_der, Some("rsync://example.test/repo/issuer/issuer.cer"), "rsync://example.test/repo/issuer/issuer.crl", issuer.tbs.extensions.ip_resources.as_ref(), issuer.tbs.extensions.as_resources.as_ref(), validation_time, ) .unwrap_err(); assert!(matches!(err, CaPathError::CertificateNotValidAtTime)); } #[test] fn validate_subordinate_ca_rejects_when_crl_not_valid_at_validation_time() { let generated = generate_chain_and_crl( "keyUsage = critical, keyCertSign, cRLSign\nsbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16\n", false, ); let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer"); let validation_time = time::OffsetDateTime::now_utc() + time::Duration::days(2); let err = validate_subordinate_ca_cert( &generated.child_ca_der, &generated.issuer_ca_der, &generated.issuer_crl_der, Some("rsync://example.test/repo/issuer/issuer.cer"), "rsync://example.test/repo/issuer/issuer.crl", issuer.tbs.extensions.ip_resources.as_ref(), issuer.tbs.extensions.as_resources.as_ref(), validation_time, ) .unwrap_err(); assert!(matches!(err, CaPathError::CrlNotValidAtTime)); } #[test] fn validate_subordinate_ca_rejects_tampered_child_signature() { let generated = generate_chain_and_crl( "keyUsage = critical, keyCertSign, cRLSign\nsbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16\n", false, ); let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer"); let now = time::OffsetDateTime::now_utc(); let mut tampered = generated.child_ca_der.clone(); if let Some(last) = tampered.last_mut() { *last ^= 0x01; } let err = validate_subordinate_ca_cert( &tampered, &generated.issuer_ca_der, &generated.issuer_crl_der, Some("rsync://example.test/repo/issuer/issuer.cer"), "rsync://example.test/repo/issuer/issuer.crl", issuer.tbs.extensions.ip_resources.as_ref(), issuer.tbs.extensions.as_resources.as_ref(), now, ) .unwrap_err(); assert!(matches!(err, CaPathError::ChildSignatureInvalid(_))); } #[test] fn validate_subordinate_ca_rejects_tampered_crl_signature() { let generated = generate_chain_and_crl( "keyUsage = critical, keyCertSign, cRLSign\nsbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16\n", false, ); let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer"); let now = time::OffsetDateTime::now_utc(); let mut tampered = generated.issuer_crl_der.clone(); if let Some(last) = tampered.last_mut() { *last ^= 0x01; } let err = validate_subordinate_ca_cert( &generated.child_ca_der, &generated.issuer_ca_der, &tampered, Some("rsync://example.test/repo/issuer/issuer.cer"), "rsync://example.test/repo/issuer/issuer.crl", issuer.tbs.extensions.ip_resources.as_ref(), issuer.tbs.extensions.as_resources.as_ref(), now, ) .unwrap_err(); assert!(matches!(err, CaPathError::CrlVerify(_))); } #[test] fn validate_subordinate_ca_rejects_non_ca_child_and_non_ca_issuer() { let generated = generate_chain_and_crl( "keyUsage = critical, keyCertSign, cRLSign sbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16 ", false, ); let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer"); let ee_child_der = std::fs::read("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa") .expect("read roa fixture"); let roa = rpki::data_model::roa::RoaObject::decode_der(&ee_child_der).expect("decode roa"); let ee_der = roa.signed_object.signed_data.certificates[0] .raw_der .clone(); let err = validate_subordinate_ca_cert( &ee_der, &generated.issuer_ca_der, &generated.issuer_crl_der, Some("rsync://example.test/repo/issuer/issuer.cer"), "rsync://example.test/repo/issuer/issuer.crl", issuer.tbs.extensions.ip_resources.as_ref(), issuer.tbs.extensions.as_resources.as_ref(), time::OffsetDateTime::now_utc(), ) .unwrap_err(); assert!(matches!(err, CaPathError::ChildNotCa), "{err}"); let err = validate_subordinate_ca_cert( &generated.child_ca_der, &ee_der, &generated.issuer_crl_der, Some("rsync://example.test/repo/issuer/issuer.cer"), "rsync://example.test/repo/issuer/issuer.crl", issuer.tbs.extensions.ip_resources.as_ref(), issuer.tbs.extensions.as_resources.as_ref(), time::OffsetDateTime::now_utc(), ) .unwrap_err(); assert!(matches!(err, CaPathError::IssuerNotCa), "{err}"); } #[test] fn validate_subordinate_ca_rejects_mismatched_issuer_subject_and_missing_resources() { let with_resources = generate_chain_and_crl( "keyUsage = critical, keyCertSign, cRLSign sbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16 sbgp-autonomousSysNum = critical, AS:64496 ", false, ); let issuer = ResourceCertificate::decode_der(&with_resources.issuer_ca_der).expect("decode issuer"); let wrong_issuer_der = std::fs::read( "tests/fixtures/repository/ca.rg.net/rpki/RGnet-OU/R-lVU1XGsAeqzV1Fv0HjOD6ZFkE.cer", ) .expect("read wrong issuer fixture"); let wrong_issuer = ResourceCertificate::decode_der(&wrong_issuer_der).expect("decode wrong issuer"); let err = validate_subordinate_ca_cert( &with_resources.child_ca_der, &wrong_issuer_der, &with_resources.issuer_crl_der, Some("rsync://example.test/repo/issuer/issuer.cer"), "rsync://example.test/repo/issuer/issuer.crl", wrong_issuer.tbs.extensions.ip_resources.as_ref(), wrong_issuer.tbs.extensions.as_resources.as_ref(), time::OffsetDateTime::now_utc(), ) .unwrap_err(); assert!( matches!(err, CaPathError::IssuerSubjectMismatch { .. }), "{err}" ); let no_resources = generate_chain_and_crl( "keyUsage = critical, keyCertSign, cRLSign ", false, ); let err = validate_subordinate_ca_cert( &no_resources.child_ca_der, &no_resources.issuer_ca_der, &no_resources.issuer_crl_der, Some("rsync://example.test/repo/issuer/issuer.cer"), "rsync://example.test/repo/issuer/issuer.crl", issuer.tbs.extensions.ip_resources.as_ref(), issuer.tbs.extensions.as_resources.as_ref(), time::OffsetDateTime::now_utc(), ) .unwrap_err(); assert!(matches!(err, CaPathError::ResourcesMissing), "{err}"); } #[test] fn validate_subordinate_ca_with_prevalidated_issuer_covers_success_and_error_paths() { use rpki::data_model::common::BigUnsigned; use rpki::data_model::crl::RpkixCrl; use rpki::validation::ca_path::validate_subordinate_ca_cert_with_prevalidated_issuer; use std::collections::HashSet; use x509_parser::prelude::FromDer; use x509_parser::x509::SubjectPublicKeyInfo; let generated = generate_chain_and_crl( "keyUsage = critical, keyCertSign, cRLSign sbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16 sbgp-autonomousSysNum = critical, AS:64496 ", false, ); let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer"); let child = ResourceCertificate::decode_der(&generated.child_ca_der).expect("decode child"); let issuer_crl = RpkixCrl::decode_der(&generated.issuer_crl_der).expect("decode crl"); let (rem, issuer_spki) = SubjectPublicKeyInfo::from_der(&issuer.tbs.subject_public_key_info) .expect("parse issuer spki"); assert!(rem.is_empty()); let now = time::OffsetDateTime::now_utc(); let validated = validate_subordinate_ca_cert_with_prevalidated_issuer( &generated.child_ca_der, child.clone(), &issuer, &issuer_spki, &issuer_crl, &HashSet::new(), Some("rsync://example.test/repo/issuer/issuer.cer"), "rsync://example.test/repo/issuer/issuer.crl", issuer.tbs.extensions.ip_resources.as_ref(), issuer.tbs.extensions.as_resources.as_ref(), now, ) .expect("validate subordinate with prevalidated issuer"); assert!(validated.effective_ip_resources.is_some()); assert!(validated.effective_as_resources.is_some()); let mut revoked = HashSet::new(); revoked.insert(BigUnsigned::from_biguint(&child.tbs.serial_number).bytes_be); let err = validate_subordinate_ca_cert_with_prevalidated_issuer( &generated.child_ca_der, child, &issuer, &issuer_spki, &issuer_crl, &revoked, Some("rsync://example.test/repo/issuer/issuer.cer"), "rsync://example.test/repo/issuer/issuer.crl", issuer.tbs.extensions.ip_resources.as_ref(), issuer.tbs.extensions.as_resources.as_ref(), now, ) .unwrap_err(); assert!(matches!(err, CaPathError::ChildRevoked), "{err}"); } #[test] fn validate_subordinate_ca_with_prevalidated_issuer_rejects_non_ca_inputs_and_invalid_times() { use rpki::data_model::crl::RpkixCrl; use rpki::validation::ca_path::validate_subordinate_ca_cert_with_prevalidated_issuer; use std::collections::HashSet; use x509_parser::prelude::FromDer; use x509_parser::x509::SubjectPublicKeyInfo; let generated = generate_chain_and_crl( "keyUsage = critical, keyCertSign, cRLSign sbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16 sbgp-autonomousSysNum = critical, AS:64496 ", false, ); let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer"); let child = ResourceCertificate::decode_der(&generated.child_ca_der).expect("decode child"); let issuer_crl = RpkixCrl::decode_der(&generated.issuer_crl_der).expect("decode crl"); let (rem, issuer_spki) = SubjectPublicKeyInfo::from_der(&issuer.tbs.subject_public_key_info) .expect("parse issuer spki"); assert!(rem.is_empty()); let roa_der = std::fs::read("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa") .expect("read roa fixture"); let roa = rpki::data_model::roa::RoaObject::decode_der(&roa_der).expect("decode roa"); let ee_rc = ResourceCertificate::decode_der(&roa.signed_object.signed_data.certificates[0].raw_der) .expect("decode ee rc"); let (rem, ee_spki) = SubjectPublicKeyInfo::from_der(&ee_rc.tbs.subject_public_key_info).expect("parse ee spki"); assert!(rem.is_empty()); let err = validate_subordinate_ca_cert_with_prevalidated_issuer( &generated.child_ca_der, child.clone(), &ee_rc, &ee_spki, &issuer_crl, &HashSet::new(), Some("rsync://example.test/repo/issuer/issuer.cer"), "rsync://example.test/repo/issuer/issuer.crl", issuer.tbs.extensions.ip_resources.as_ref(), issuer.tbs.extensions.as_resources.as_ref(), time::OffsetDateTime::now_utc(), ) .unwrap_err(); assert!(matches!(err, CaPathError::IssuerNotCa), "{err}"); let err = validate_subordinate_ca_cert_with_prevalidated_issuer( &generated.child_ca_der, ee_rc, &issuer, &issuer_spki, &issuer_crl, &HashSet::new(), Some("rsync://example.test/repo/issuer/issuer.cer"), "rsync://example.test/repo/issuer/issuer.crl", issuer.tbs.extensions.ip_resources.as_ref(), issuer.tbs.extensions.as_resources.as_ref(), time::OffsetDateTime::now_utc(), ) .unwrap_err(); assert!(matches!(err, CaPathError::ChildNotCa), "{err}"); let err = validate_subordinate_ca_cert_with_prevalidated_issuer( &generated.child_ca_der, child.clone(), &issuer, &issuer_spki, &issuer_crl, &HashSet::new(), Some("rsync://example.test/repo/issuer/issuer.cer"), "rsync://example.test/repo/issuer/issuer.crl", issuer.tbs.extensions.ip_resources.as_ref(), issuer.tbs.extensions.as_resources.as_ref(), child.tbs.validity_not_before - time::Duration::seconds(1), ) .unwrap_err(); assert!( matches!(err, CaPathError::CertificateNotValidAtTime), "{err}" ); let err = validate_subordinate_ca_cert_with_prevalidated_issuer( &generated.child_ca_der, child, &issuer, &issuer_spki, &issuer_crl, &HashSet::new(), Some("rsync://example.test/repo/issuer/issuer.cer"), "rsync://example.test/repo/issuer/issuer.crl", issuer.tbs.extensions.ip_resources.as_ref(), issuer.tbs.extensions.as_resources.as_ref(), issuer_crl.next_update.utc + time::Duration::seconds(1), ) .unwrap_err(); assert!(matches!(err, CaPathError::CrlNotValidAtTime), "{err}"); } #[test] fn validate_subordinate_ca_with_prevalidated_issuer_rejects_mismatch_and_missing_resources() { use rpki::data_model::crl::RpkixCrl; use rpki::validation::ca_path::validate_subordinate_ca_cert_with_prevalidated_issuer; use std::collections::HashSet; use x509_parser::prelude::FromDer; use x509_parser::x509::SubjectPublicKeyInfo; let generated = generate_chain_and_crl( "keyUsage = critical, keyCertSign, cRLSign sbgp-ipAddrBlock = critical, IPv4:10.0.0.0/16 sbgp-autonomousSysNum = critical, AS:64496 ", false, ); let issuer = ResourceCertificate::decode_der(&generated.issuer_ca_der).expect("decode issuer"); let child = ResourceCertificate::decode_der(&generated.child_ca_der).expect("decode child"); let issuer_crl = RpkixCrl::decode_der(&generated.issuer_crl_der).expect("decode crl"); let (rem, issuer_spki) = SubjectPublicKeyInfo::from_der(&issuer.tbs.subject_public_key_info) .expect("parse issuer spki"); assert!(rem.is_empty()); let mut mismatched_child = child.clone(); mismatched_child.tbs.issuer_name = mismatched_child.tbs.subject_name.clone(); let err = validate_subordinate_ca_cert_with_prevalidated_issuer( &generated.child_ca_der, mismatched_child, &issuer, &issuer_spki, &issuer_crl, &HashSet::new(), Some("rsync://example.test/repo/issuer/issuer.cer"), "rsync://example.test/repo/issuer/issuer.crl", issuer.tbs.extensions.ip_resources.as_ref(), issuer.tbs.extensions.as_resources.as_ref(), time::OffsetDateTime::now_utc(), ) .unwrap_err(); assert!( matches!(err, CaPathError::IssuerSubjectMismatch { .. }), "{err}" ); let no_resources = generate_chain_and_crl( "keyUsage = critical, keyCertSign, cRLSign ", false, ); let no_resources_child = ResourceCertificate::decode_der(&no_resources.child_ca_der) .expect("decode no resources child"); let no_resources_issuer = ResourceCertificate::decode_der(&no_resources.issuer_ca_der) .expect("decode no resources issuer"); let no_resources_crl = RpkixCrl::decode_der(&no_resources.issuer_crl_der).expect("decode no resources crl"); let (rem, no_resources_spki) = SubjectPublicKeyInfo::from_der(&no_resources_issuer.tbs.subject_public_key_info) .expect("parse no resources issuer spki"); assert!(rem.is_empty()); let err = validate_subordinate_ca_cert_with_prevalidated_issuer( &no_resources.child_ca_der, no_resources_child, &no_resources_issuer, &no_resources_spki, &no_resources_crl, &HashSet::new(), Some("rsync://example.test/repo/issuer/issuer.cer"), "rsync://example.test/repo/issuer/issuer.crl", no_resources_issuer.tbs.extensions.ip_resources.as_ref(), no_resources_issuer.tbs.extensions.as_resources.as_ref(), time::OffsetDateTime::now_utc(), ) .unwrap_err(); assert!(matches!(err, CaPathError::ResourcesMissing), "{err}"); }