From 4a64fa7900e9e20cc56a19cde66910d209e66573 Mon Sep 17 00:00:00 2001 From: yuyr Date: Sun, 12 Jul 2026 07:15:32 +0800 Subject: [PATCH] =?UTF-8?q?20260711=20=E8=A1=A5=E5=85=85P1=E7=BC=93?= =?UTF-8?q?=E5=AD=98=E4=B8=8E=E8=AF=81=E4=B9=A6profile=E5=9B=9E=E5=BD=92?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src/validation/objects.rs | 52 ++++++++ src/validation/tree_runner/tests.rs | 89 ++++++++++++++ tests/test_resource_certificate_profile.rs | 131 +++++++++++++++++++++ 3 files changed, 272 insertions(+) create mode 100644 tests/test_resource_certificate_profile.rs diff --git a/src/validation/objects.rs b/src/validation/objects.rs index e94c606..ff449d7 100644 --- a/src/validation/objects.rs +++ b/src/validation/objects.rs @@ -4346,6 +4346,58 @@ mod tests { )); } + #[test] + fn roa_validation_cache_view_blocks_before_safe_reuse_time() { + let validation_time = fixed_time("2026-06-05T00:00:00Z"); + let issuer_der = b"issuer-ca"; + let crl_bytes = b"current-crl".to_vec(); + let crl_hash = sha256_32(&crl_bytes); + let roa_hash = [0x11; 32]; + let vcir = sample_roa_cache_vcir( + issuer_der, + crl_hash, + roa_hash, + fixed_time("2026-06-07T00:00:00Z"), + fixed_time("2026-06-08T00:00:00Z"), + ); + let mut projection = sample_roa_cache_projection(&vcir, roa_hash); + projection.entries[0].earliest_safe_reuse_time_unix = + Some(fixed_time("2026-06-06T00:00:00Z").unix_timestamp()); + let view = RoaValidationCacheView::from_projection(&projection, validation_time); + let file = PackFile::from_bytes_with_sha256(TEST_ROA_URI, vec![0x01], roa_hash); + let mut crl_cache = sample_crl_cache(crl_bytes); + + assert!(matches!( + view.lookup(&file, &mut crl_cache, issuer_der, validation_time), + RoaCacheLookupResult::ExpiredBlocked + )); + } + + #[test] + fn roa_validation_cache_view_treats_legacy_entry_without_lower_bound_as_miss() { + let validation_time = fixed_time("2026-06-05T00:00:00Z"); + let issuer_der = b"issuer-ca"; + let crl_hash = sha256_32(b"current-crl"); + let roa_hash = [0x11; 32]; + let vcir = sample_roa_cache_vcir( + issuer_der, + crl_hash, + roa_hash, + fixed_time("2026-06-07T00:00:00Z"), + fixed_time("2026-06-08T00:00:00Z"), + ); + let mut projection = sample_roa_cache_projection(&vcir, roa_hash); + projection.entries[0].earliest_safe_reuse_time_unix = None; + let view = RoaValidationCacheView::from_projection(&projection, validation_time); + let file = PackFile::from_bytes_with_sha256(TEST_ROA_URI, vec![0x01], roa_hash); + let mut crl_cache = sample_crl_cache(b"current-crl".to_vec()); + + assert!(matches!( + view.lookup(&file, &mut crl_cache, issuer_der, validation_time), + RoaCacheLookupResult::Miss + )); + } + #[test] fn roa_validation_cache_stats_records_vcir_miss_to_timing() { let stats = RoaValidationCacheStats::for_input(RoaValidationCacheInput::enabled(None), 3); diff --git a/src/validation/tree_runner/tests.rs b/src/validation/tree_runner/tests.rs index 4ea534b..a57b9aa 100644 --- a/src/validation/tree_runner/tests.rs +++ b/src/validation/tree_runner/tests.rs @@ -775,6 +775,22 @@ fn put_vcir_for_failed_fetch_reuse( .expect("put reusable VCIR"); } +fn sample_ca_for_failed_fetch_reuse(vcir: &ValidatedCaInstanceResult) -> CaInstanceHandle { + CaInstanceHandle { + depth: 0, + tal_id: "test-tal".to_string(), + parent_manifest_rsync_uri: None, + ca_certificate: CaCertificateRef::inline_der(Vec::new()), + ca_certificate_rsync_uri: None, + effective_ip_resources: None, + effective_as_resources: None, + rsync_base_uri: "rsync://example.test/repo/issuer/".to_string(), + manifest_rsync_uri: vcir.manifest_rsync_uri.clone(), + publication_point_rsync_uri: "rsync://example.test/repo/issuer/".to_string(), + rrdp_notification_uri: Some("https://example.test/notify.xml".to_string()), + } +} + #[test] fn never_http_fetcher_returns_error() { let f = NeverHttpFetcher; @@ -3937,6 +3953,79 @@ fn project_current_instance_vcir_reuses_local_outputs_and_restores_children() { ); } +#[test] +fn project_current_instance_vcir_does_not_reuse_without_identity() { + let now = time::OffsetDateTime::now_utc(); + let child_cert_hash = sha256_hex(b"child-cert"); + let vcir = sample_vcir_for_projection(now, &child_cert_hash); + let store_dir = tempfile::tempdir().expect("store dir"); + let store = RocksStore::open(store_dir.path()).expect("open rocksdb"); + store.put_vcir(&vcir).expect("put legacy-style VCIR"); + let ca = sample_ca_for_failed_fetch_reuse(&vcir); + + let projection = project_current_instance_vcir_on_failed_fetch( + &store, + &ca, + &ManifestFreshError::RepoSyncFailed { + detail: "synthetic".to_string(), + }, + &Policy::default(), + now, + ) + .expect("project VCIR"); + + assert_eq!( + projection.source, + PublicationPointSource::FailedFetchNoCache + ); + assert!(projection.objects.vrps.is_empty()); + assert!(projection.discovered_children.is_empty()); + assert!( + projection + .warnings + .iter() + .any(|warning| warning.message.contains("reuse identity is missing")) + ); +} + +#[test] +fn project_current_instance_vcir_does_not_reuse_when_ta_context_changes() { + let now = time::OffsetDateTime::now_utc(); + let child_cert_hash = sha256_hex(b"child-cert"); + let vcir = sample_vcir_for_projection(now, &child_cert_hash); + let store_dir = tempfile::tempdir().expect("store dir"); + let store = RocksStore::open(store_dir.path()).expect("open rocksdb"); + let policy = Policy::default(); + let original_ca = sample_ca_for_failed_fetch_reuse(&vcir); + put_vcir_for_failed_fetch_reuse(&store, &original_ca, &policy, &vcir); + let mut changed_ca = original_ca; + changed_ca.tal_id = "different-tal".to_string(); + + let projection = project_current_instance_vcir_on_failed_fetch( + &store, + &changed_ca, + &ManifestFreshError::RepoSyncFailed { + detail: "synthetic".to_string(), + }, + &policy, + now, + ) + .expect("project VCIR"); + + assert_eq!( + projection.source, + PublicationPointSource::FailedFetchNoCache + ); + assert!(projection.objects.vrps.is_empty()); + assert!(projection.discovered_children.is_empty()); + assert!( + projection + .warnings + .iter() + .any(|warning| warning.message.contains("identity does not match")) + ); +} + #[test] fn project_current_instance_vcir_returns_no_output_when_instance_gate_expired() { let now = time::OffsetDateTime::now_utc(); diff --git a/tests/test_resource_certificate_profile.rs b/tests/test_resource_certificate_profile.rs new file mode 100644 index 0000000..b331f59 --- /dev/null +++ b/tests/test_resource_certificate_profile.rs @@ -0,0 +1,131 @@ +use der_parser::num_bigint::BigUint; +use rpki::data_model::common::X509NameDer; +use rpki::data_model::oid::{ + OID_AUTONOMOUS_SYS_IDS, OID_BASIC_CONSTRAINTS, OID_CERTIFICATE_POLICIES, OID_CP_IPADDR_ASNUMBER, +}; +use rpki::data_model::rc::{ + AsIdOrRange, AsIdentifierChoice, AsResourceSet, BasicConstraintsProfile, + CertificatePoliciesProfile, RcExtensions, ResourceCertKind, ResourceCertificate, + ResourceCertificateProfileError, ResourceCertificateRole, RpkixTbsCertificate, +}; +use time::OffsetDateTime; + +fn valid_ca_certificate() -> ResourceCertificate { + let validation_time = OffsetDateTime::from_unix_timestamp(0).expect("valid epoch"); + 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".to_string(), + issuer_name: X509NameDer(b"CN=issuer".to_vec()), + subject_name: X509NameDer(b"CN=subject".to_vec()), + validity_not_before: validation_time, + validity_not_after: validation_time, + subject_public_key_info: Vec::new(), + extensions: RcExtensions { + basic_constraints_ca: true, + basic_constraints: Some(BasicConstraintsProfile { + ca: true, + critical: true, + path_len_constraint: None, + }), + subject_key_identifier: None, + authority_key_identifier: None, + crl_distribution_points_uris: None, + ca_issuers_uris: None, + subject_info_access: None, + certificate_policies_oid: Some(OID_CP_IPADDR_ASNUMBER.to_string()), + certificate_policies: Some(CertificatePoliciesProfile { + policy_oid: OID_CP_IPADDR_ASNUMBER.to_string(), + qualifier_oids: Vec::new(), + }), + extension_oids: vec![ + OID_BASIC_CONSTRAINTS.to_string(), + OID_CERTIFICATE_POLICIES.to_string(), + ], + ip_resources: None, + as_resources: None, + }, + }, + } +} + +#[test] +fn ca_profile_accepts_required_basic_constraints_and_policy() { + valid_ca_certificate() + .validate_rfc6487_profile(ResourceCertificateRole::Ca) + .expect("valid CA profile"); +} + +#[test] +fn ca_profile_rejects_noncritical_basic_constraints() { + let mut certificate = valid_ca_certificate(); + certificate + .tbs + .extensions + .basic_constraints + .as_mut() + .expect("constraints") + .critical = false; + + assert!(matches!( + certificate.validate_rfc6487_profile(ResourceCertificateRole::Ca), + Err(ResourceCertificateProfileError::BasicConstraintsCriticality) + )); +} + +#[test] +fn ca_profile_rejects_unknown_extension() { + let mut certificate = valid_ca_certificate(); + certificate + .tbs + .extensions + .extension_oids + .push("1.2.3.4".to_string()); + + assert!(matches!( + certificate.validate_rfc6487_profile(ResourceCertificateRole::Ca), + Err(ResourceCertificateProfileError::DisallowedExtension { .. }) + )); +} + +#[test] +fn ca_profile_rejects_non_cps_policy_qualifier() { + let mut certificate = valid_ca_certificate(); + certificate + .tbs + .extensions + .certificate_policies + .as_mut() + .expect("certificate policies") + .qualifier_oids + .push("1.2.3.4".to_string()); + + assert!(matches!( + certificate.validate_rfc6487_profile(ResourceCertificateRole::Ca), + Err(ResourceCertificateProfileError::CertificatePoliciesInvalidQualifier(_)) + )); +} + +#[test] +fn ca_profile_rejects_autonomous_system_rdi() { + let mut certificate = valid_ca_certificate(); + certificate.tbs.extensions.as_resources = Some(AsResourceSet { + asnum: None, + rdi: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id( + 64512, + )])), + }); + certificate + .tbs + .extensions + .extension_oids + .push(OID_AUTONOMOUS_SYS_IDS.to_string()); + + assert!(matches!( + certificate.validate_rfc6487_profile(ResourceCertificateRole::Ca), + Err(ResourceCertificateProfileError::AsResourcesRdiPresent) + )); +}