20260711 补充P1缓存与证书profile回归

This commit is contained in:
yuyr 2026-07-12 07:15:32 +08:00
parent 1ce897dbff
commit 4a64fa7900
3 changed files with 272 additions and 0 deletions

View File

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

View File

@ -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();

View File

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