串行验证通过

This commit is contained in:
2026-02-09 19:35:54 +08:00
parent 7be865d7f1
commit afc31c02ab
66 changed files with 12049 additions and 4 deletions

163
src/validation/tree.rs Normal file
View File

@@ -0,0 +1,163 @@
use crate::report::{RfcRef, Warning};
use crate::storage::VerifiedPublicationPointPack;
use crate::validation::manifest::PublicationPointSource;
use crate::audit::PublicationPointAudit;
use crate::data_model::rc::{AsResourceSet, IpResourceSet};
use crate::validation::objects::{AspaAttestation, ObjectsOutput, Vrp};
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TreeRunConfig {
/// Max CA instance depth to process (0 = root only).
pub max_depth: Option<usize>,
/// Max number of CA instances to process.
pub max_instances: Option<usize>,
}
impl Default for TreeRunConfig {
fn default() -> Self {
Self {
max_depth: None,
max_instances: None,
}
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct CaInstanceHandle {
pub depth: usize,
/// DER bytes of the CA certificate for this CA instance.
pub ca_certificate_der: Vec<u8>,
/// rsync URI of this CA certificate object (where it is published).
///
/// This is used for strict AIA binding checks (RFC 6487 §4.8.7) when validating
/// subordinate CA and EE certificates. Trust anchor certificates downloaded from
/// TAL URIs may not have an rsync publication URI, so this can be `None`.
pub ca_certificate_rsync_uri: Option<String>,
/// Effective (fully resolved) resources for this CA instance, used for resource-path
/// validation of subordinate CA certificates.
pub effective_ip_resources: Option<IpResourceSet>,
pub effective_as_resources: Option<AsResourceSet>,
pub rsync_base_uri: String,
pub manifest_rsync_uri: String,
pub publication_point_rsync_uri: String,
pub rrdp_notification_uri: Option<String>,
}
impl CaInstanceHandle {
pub fn with_depth(mut self, depth: usize) -> Self {
self.depth = depth;
self
}
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct PublicationPointRunResult {
pub source: PublicationPointSource,
pub pack: VerifiedPublicationPointPack,
pub warnings: Vec<Warning>,
pub objects: ObjectsOutput,
pub audit: PublicationPointAudit,
/// Candidate child CA instances discovered from this publication point.
///
/// RFC 9286 §6.6 restriction is enforced by the tree engine: if this
/// publication point used verified cache due to failed fetch, children MUST NOT
/// be enqueued/processed in this run.
pub discovered_children: Vec<CaInstanceHandle>,
}
#[derive(Clone, Debug, PartialEq, Eq)]
pub struct TreeRunOutput {
pub instances_processed: usize,
pub instances_failed: usize,
pub warnings: Vec<Warning>,
pub vrps: Vec<Vrp>,
pub aspas: Vec<AspaAttestation>,
}
#[derive(Debug, thiserror::Error)]
pub enum TreeRunError {
#[error("publication point runner failed: {0}")]
Runner(String),
}
pub trait PublicationPointRunner {
fn run_publication_point(
&self,
ca: &CaInstanceHandle,
) -> Result<PublicationPointRunResult, String>;
}
pub fn run_tree_serial(
root: CaInstanceHandle,
runner: &dyn PublicationPointRunner,
config: &TreeRunConfig,
) -> Result<TreeRunOutput, TreeRunError> {
let mut queue: std::collections::VecDeque<CaInstanceHandle> = std::collections::VecDeque::new();
queue.push_back(root);
let mut visited_manifest_uris: std::collections::HashSet<String> = std::collections::HashSet::new();
let mut instances_processed = 0usize;
let mut instances_failed = 0usize;
let mut warnings: Vec<Warning> = Vec::new();
let mut vrps: Vec<Vrp> = Vec::new();
let mut aspas: Vec<AspaAttestation> = Vec::new();
while let Some(ca) = queue.pop_front() {
if !visited_manifest_uris.insert(ca.manifest_rsync_uri.clone()) {
continue;
}
if let Some(max_instances) = config.max_instances {
if instances_processed >= max_instances {
break;
}
}
if let Some(max_depth) = config.max_depth {
if ca.depth > max_depth {
continue;
}
}
let res = match runner.run_publication_point(&ca) {
Ok(v) => v,
Err(e) => {
instances_failed += 1;
warnings.push(
Warning::new(format!("publication point failed: {e}"))
.with_context(&ca.manifest_rsync_uri),
);
continue;
}
};
instances_processed += 1;
warnings.extend(res.warnings.clone());
warnings.extend(res.objects.warnings.clone());
vrps.extend(res.objects.vrps.clone());
aspas.extend(res.objects.aspas.clone());
let enqueue_children = res.source == PublicationPointSource::Fresh;
if !enqueue_children && !res.discovered_children.is_empty() {
warnings.push(
Warning::new("skipping child CA discovery due to failed fetch cache use")
.with_rfc_refs(&[RfcRef("RFC 9286 §6.6")])
.with_context(&ca.manifest_rsync_uri),
);
}
if enqueue_children {
for child in res.discovered_children {
queue.push_back(child.with_depth(ca.depth + 1));
}
}
}
Ok(TreeRunOutput {
instances_processed,
instances_failed,
warnings,
vrps,
aspas,
})
}