run tree from tal pass
This commit is contained in:
parent
afc31c02ab
commit
6e135b9d7a
19
src/audit.rs
19
src/audit.rs
@ -52,6 +52,18 @@ impl From<&crate::report::Warning> for AuditWarning {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
|
||||||
pub struct PublicationPointAudit {
|
pub struct PublicationPointAudit {
|
||||||
|
/// Monotonic node ID assigned by the traversal engine.
|
||||||
|
///
|
||||||
|
/// Present when running via the Stage2 tree engine; may be absent in ad-hoc runs.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub node_id: Option<u64>,
|
||||||
|
/// Parent node ID in the traversal tree.
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub parent_node_id: Option<u64>,
|
||||||
|
/// Provenance metadata for non-root nodes (how this CA instance was discovered).
|
||||||
|
#[serde(skip_serializing_if = "Option::is_none")]
|
||||||
|
pub discovered_from: Option<DiscoveredFrom>,
|
||||||
|
|
||||||
pub rsync_base_uri: String,
|
pub rsync_base_uri: String,
|
||||||
pub manifest_rsync_uri: String,
|
pub manifest_rsync_uri: String,
|
||||||
pub publication_point_rsync_uri: String,
|
pub publication_point_rsync_uri: String,
|
||||||
@ -67,6 +79,13 @@ pub struct PublicationPointAudit {
|
|||||||
pub objects: Vec<ObjectAuditEntry>,
|
pub objects: Vec<ObjectAuditEntry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
||||||
|
pub struct DiscoveredFrom {
|
||||||
|
pub parent_manifest_rsync_uri: String,
|
||||||
|
pub child_ca_certificate_rsync_uri: String,
|
||||||
|
pub child_ca_certificate_sha256_hex: String,
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
||||||
pub struct TreeSummary {
|
pub struct TreeSummary {
|
||||||
pub instances_processed: usize,
|
pub instances_processed: usize,
|
||||||
|
|||||||
46
src/cli.rs
46
src/cli.rs
@ -205,7 +205,10 @@ fn unique_rrdp_repos(report: &AuditReportV1) -> usize {
|
|||||||
fn print_summary(report: &AuditReportV1) {
|
fn print_summary(report: &AuditReportV1) {
|
||||||
let rrdp_repos = unique_rrdp_repos(report);
|
let rrdp_repos = unique_rrdp_repos(report);
|
||||||
println!("RPKI stage2 serial run summary");
|
println!("RPKI stage2 serial run summary");
|
||||||
println!("validation_time={}", report.meta.validation_time_rfc3339_utc);
|
println!(
|
||||||
|
"validation_time={}",
|
||||||
|
report.meta.validation_time_rfc3339_utc
|
||||||
|
);
|
||||||
println!(
|
println!(
|
||||||
"publication_points_processed={} publication_points_failed={}",
|
"publication_points_processed={} publication_points_failed={}",
|
||||||
report.tree.instances_processed, report.tree.instances_failed
|
report.tree.instances_processed, report.tree.instances_failed
|
||||||
@ -213,7 +216,10 @@ fn print_summary(report: &AuditReportV1) {
|
|||||||
println!("rrdp_repos_unique={rrdp_repos}");
|
println!("rrdp_repos_unique={rrdp_repos}");
|
||||||
println!("vrps={}", report.vrps.len());
|
println!("vrps={}", report.vrps.len());
|
||||||
println!("aspas={}", report.aspas.len());
|
println!("aspas={}", report.aspas.len());
|
||||||
println!("audit_publication_points={}", report.publication_points.len());
|
println!(
|
||||||
|
"audit_publication_points={}",
|
||||||
|
report.publication_points.len()
|
||||||
|
);
|
||||||
println!(
|
println!(
|
||||||
"warnings_total={}",
|
"warnings_total={}",
|
||||||
report.tree.warnings.len()
|
report.tree.warnings.len()
|
||||||
@ -278,7 +284,9 @@ pub fn run(argv: &[String]) -> Result<(), String> {
|
|||||||
let args = parse_args(argv)?;
|
let args = parse_args(argv)?;
|
||||||
|
|
||||||
let policy = read_policy(args.policy_path.as_deref())?;
|
let policy = read_policy(args.policy_path.as_deref())?;
|
||||||
let validation_time = args.validation_time.unwrap_or_else(time::OffsetDateTime::now_utc);
|
let validation_time = args
|
||||||
|
.validation_time
|
||||||
|
.unwrap_or_else(time::OffsetDateTime::now_utc);
|
||||||
|
|
||||||
let store = RocksStore::open(&args.db_path).map_err(|e| e.to_string())?;
|
let store = RocksStore::open(&args.db_path).map_err(|e| e.to_string())?;
|
||||||
let http = BlockingHttpFetcher::new(HttpFetcherConfig::default()).map_err(|e| e.to_string())?;
|
let http = BlockingHttpFetcher::new(HttpFetcherConfig::default()).map_err(|e| e.to_string())?;
|
||||||
@ -290,7 +298,11 @@ pub fn run(argv: &[String]) -> Result<(), String> {
|
|||||||
|
|
||||||
let out = if let Some(dir) = args.rsync_local_dir.as_ref() {
|
let out = if let Some(dir) = args.rsync_local_dir.as_ref() {
|
||||||
let rsync = LocalDirRsyncFetcher::new(dir);
|
let rsync = LocalDirRsyncFetcher::new(dir);
|
||||||
match (args.tal_url.as_ref(), args.tal_path.as_ref(), args.ta_path.as_ref()) {
|
match (
|
||||||
|
args.tal_url.as_ref(),
|
||||||
|
args.tal_path.as_ref(),
|
||||||
|
args.ta_path.as_ref(),
|
||||||
|
) {
|
||||||
(Some(url), _, _) => run_tree_from_tal_url_serial_audit(
|
(Some(url), _, _) => run_tree_from_tal_url_serial_audit(
|
||||||
&store,
|
&store,
|
||||||
&policy,
|
&policy,
|
||||||
@ -323,7 +335,11 @@ pub fn run(argv: &[String]) -> Result<(), String> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
let rsync = SystemRsyncFetcher::new(SystemRsyncConfig::default());
|
let rsync = SystemRsyncFetcher::new(SystemRsyncConfig::default());
|
||||||
match (args.tal_url.as_ref(), args.tal_path.as_ref(), args.ta_path.as_ref()) {
|
match (
|
||||||
|
args.tal_url.as_ref(),
|
||||||
|
args.tal_path.as_ref(),
|
||||||
|
args.ta_path.as_ref(),
|
||||||
|
) {
|
||||||
(Some(url), _, _) => run_tree_from_tal_url_serial_audit(
|
(Some(url), _, _) => run_tree_from_tal_url_serial_audit(
|
||||||
&store,
|
&store,
|
||||||
&policy,
|
&policy,
|
||||||
@ -404,7 +420,10 @@ mod tests {
|
|||||||
"x.tal".to_string(),
|
"x.tal".to_string(),
|
||||||
];
|
];
|
||||||
let err = parse_args(&argv).unwrap_err();
|
let err = parse_args(&argv).unwrap_err();
|
||||||
assert!(err.contains("exactly one of --tal-url or --tal-path"), "{err}");
|
assert!(
|
||||||
|
err.contains("exactly one of --tal-url or --tal-path"),
|
||||||
|
"{err}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -482,7 +501,10 @@ mod tests {
|
|||||||
fn parse_rejects_missing_tal_mode() {
|
fn parse_rejects_missing_tal_mode() {
|
||||||
let argv = vec!["rpki".to_string(), "--db".to_string(), "db".to_string()];
|
let argv = vec!["rpki".to_string(), "--db".to_string(), "db".to_string()];
|
||||||
let err = parse_args(&argv).unwrap_err();
|
let err = parse_args(&argv).unwrap_err();
|
||||||
assert!(err.contains("--tal-url") || err.contains("--tal-path"), "{err}");
|
assert!(
|
||||||
|
err.contains("--tal-url") || err.contains("--tal-path"),
|
||||||
|
"{err}"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -573,18 +595,18 @@ mod tests {
|
|||||||
.expect("read ta fixture");
|
.expect("read ta fixture");
|
||||||
|
|
||||||
let discovery = crate::validation::from_tal::discover_root_ca_instance_from_tal_and_ta_der(
|
let discovery = crate::validation::from_tal::discover_root_ca_instance_from_tal_and_ta_der(
|
||||||
&tal_bytes,
|
&tal_bytes, &ta_der, None,
|
||||||
&ta_der,
|
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
.expect("discover root");
|
.expect("discover root");
|
||||||
|
|
||||||
let tree = crate::validation::tree::TreeRunOutput {
|
let tree = crate::validation::tree::TreeRunOutput {
|
||||||
instances_processed: 1,
|
instances_processed: 1,
|
||||||
instances_failed: 0,
|
instances_failed: 0,
|
||||||
warnings: vec![crate::report::Warning::new("synthetic warning")
|
warnings: vec![
|
||||||
|
crate::report::Warning::new("synthetic warning")
|
||||||
.with_rfc_refs(&[crate::report::RfcRef("RFC 6487 §4.8.8.1")])
|
.with_rfc_refs(&[crate::report::RfcRef("RFC 6487 §4.8.8.1")])
|
||||||
.with_context("rsync://example.test/repo/pp/")],
|
.with_context("rsync://example.test/repo/pp/"),
|
||||||
|
],
|
||||||
vrps: vec![crate::validation::objects::Vrp {
|
vrps: vec![crate::validation::objects::Vrp {
|
||||||
asn: 64496,
|
asn: 64496,
|
||||||
prefix: crate::data_model::roa::IpPrefix {
|
prefix: crate::data_model::roa::IpPrefix {
|
||||||
|
|||||||
@ -11,8 +11,8 @@ use crate::data_model::common::{
|
|||||||
};
|
};
|
||||||
use crate::data_model::oid::{
|
use crate::data_model::oid::{
|
||||||
OID_AD_CA_ISSUERS, OID_AD_SIGNED_OBJECT, OID_AUTHORITY_INFO_ACCESS,
|
OID_AD_CA_ISSUERS, OID_AD_SIGNED_OBJECT, OID_AUTHORITY_INFO_ACCESS,
|
||||||
OID_AUTHORITY_KEY_IDENTIFIER, OID_AUTONOMOUS_SYS_IDS, OID_CRL_DISTRIBUTION_POINTS,
|
OID_AUTHORITY_KEY_IDENTIFIER, OID_AUTONOMOUS_SYS_IDS, OID_CP_IPADDR_ASNUMBER,
|
||||||
OID_CP_IPADDR_ASNUMBER, OID_IP_ADDR_BLOCKS, OID_SHA256_WITH_RSA_ENCRYPTION,
|
OID_CRL_DISTRIBUTION_POINTS, OID_IP_ADDR_BLOCKS, OID_SHA256_WITH_RSA_ENCRYPTION,
|
||||||
OID_SUBJECT_INFO_ACCESS, OID_SUBJECT_KEY_IDENTIFIER,
|
OID_SUBJECT_INFO_ACCESS, OID_SUBJECT_KEY_IDENTIFIER,
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -451,19 +451,13 @@ pub enum ResourceCertificateProfileError {
|
|||||||
)]
|
)]
|
||||||
CrlDistributionPointsCriticality,
|
CrlDistributionPointsCriticality,
|
||||||
|
|
||||||
#[error(
|
#[error("CRLDistributionPoints MUST be omitted in self-signed certificates (RFC 6487 §4.8.6)")]
|
||||||
"CRLDistributionPoints MUST be omitted in self-signed certificates (RFC 6487 §4.8.6)"
|
|
||||||
)]
|
|
||||||
CrlDistributionPointsSelfSignedMustOmit,
|
CrlDistributionPointsSelfSignedMustOmit,
|
||||||
|
|
||||||
#[error(
|
#[error("CRLDistributionPoints must contain exactly one DistributionPoint (RFC 6487 §4.8.6)")]
|
||||||
"CRLDistributionPoints must contain exactly one DistributionPoint (RFC 6487 §4.8.6)"
|
|
||||||
)]
|
|
||||||
CrlDistributionPointsNotSingle,
|
CrlDistributionPointsNotSingle,
|
||||||
|
|
||||||
#[error(
|
#[error("CRLDistributionPoints distributionPoint field MUST be present (RFC 6487 §4.8.6)")]
|
||||||
"CRLDistributionPoints distributionPoint field MUST be present (RFC 6487 §4.8.6)"
|
|
||||||
)]
|
|
||||||
CrlDistributionPointsNoDistributionPoint,
|
CrlDistributionPointsNoDistributionPoint,
|
||||||
|
|
||||||
#[error("CRLDistributionPoints reasons field MUST be omitted (RFC 6487 §4.8.6)")]
|
#[error("CRLDistributionPoints reasons field MUST be omitted (RFC 6487 §4.8.6)")]
|
||||||
@ -495,9 +489,7 @@ pub enum ResourceCertificateProfileError {
|
|||||||
)]
|
)]
|
||||||
AuthorityInfoAccessCriticality,
|
AuthorityInfoAccessCriticality,
|
||||||
|
|
||||||
#[error(
|
#[error("authorityInfoAccess MUST be omitted in self-signed certificates (RFC 6487 §4.8.7)")]
|
||||||
"authorityInfoAccess MUST be omitted in self-signed certificates (RFC 6487 §4.8.7)"
|
|
||||||
)]
|
|
||||||
AuthorityInfoAccessSelfSignedMustOmit,
|
AuthorityInfoAccessSelfSignedMustOmit,
|
||||||
|
|
||||||
#[error(
|
#[error(
|
||||||
@ -505,9 +497,7 @@ pub enum ResourceCertificateProfileError {
|
|||||||
)]
|
)]
|
||||||
AuthorityInfoAccessCaIssuersNotUri,
|
AuthorityInfoAccessCaIssuersNotUri,
|
||||||
|
|
||||||
#[error(
|
#[error("authorityInfoAccess must include at least one id-ad-caIssuers URI (RFC 6487 §4.8.7)")]
|
||||||
"authorityInfoAccess must include at least one id-ad-caIssuers URI (RFC 6487 §4.8.7)"
|
|
||||||
)]
|
|
||||||
AuthorityInfoAccessMissingCaIssuers,
|
AuthorityInfoAccessMissingCaIssuers,
|
||||||
|
|
||||||
#[error("authorityInfoAccess must include at least one rsync:// URI (RFC 6487 §4.8.7)")]
|
#[error("authorityInfoAccess must include at least one rsync:// URI (RFC 6487 §4.8.7)")]
|
||||||
@ -674,7 +664,8 @@ impl RcExtensionsParsed {
|
|||||||
}
|
}
|
||||||
let keyid = aki.key_identifier.clone();
|
let keyid = aki.key_identifier.clone();
|
||||||
if is_self_signed {
|
if is_self_signed {
|
||||||
if let (Some(keyid), Some(ski)) = (keyid.as_ref(), subject_key_identifier.as_ref())
|
if let (Some(keyid), Some(ski)) =
|
||||||
|
(keyid.as_ref(), subject_key_identifier.as_ref())
|
||||||
{
|
{
|
||||||
if keyid != ski {
|
if keyid != ski {
|
||||||
return Err(ResourceCertificateProfileError::AkiSelfSignedNotEqualSki);
|
return Err(ResourceCertificateProfileError::AkiSelfSignedNotEqualSki);
|
||||||
@ -705,7 +696,9 @@ impl RcExtensionsParsed {
|
|||||||
return Err(ResourceCertificateProfileError::CrlDistributionPointsCriticality);
|
return Err(ResourceCertificateProfileError::CrlDistributionPointsCriticality);
|
||||||
}
|
}
|
||||||
if is_self_signed {
|
if is_self_signed {
|
||||||
return Err(ResourceCertificateProfileError::CrlDistributionPointsSelfSignedMustOmit);
|
return Err(
|
||||||
|
ResourceCertificateProfileError::CrlDistributionPointsSelfSignedMustOmit,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if crldp.distribution_points.len() != 1 {
|
if crldp.distribution_points.len() != 1 {
|
||||||
return Err(ResourceCertificateProfileError::CrlDistributionPointsNotSingle);
|
return Err(ResourceCertificateProfileError::CrlDistributionPointsNotSingle);
|
||||||
@ -718,13 +711,17 @@ impl RcExtensionsParsed {
|
|||||||
return Err(ResourceCertificateProfileError::CrlDistributionPointsHasCrlIssuer);
|
return Err(ResourceCertificateProfileError::CrlDistributionPointsHasCrlIssuer);
|
||||||
}
|
}
|
||||||
if !dp.distribution_point_present {
|
if !dp.distribution_point_present {
|
||||||
return Err(ResourceCertificateProfileError::CrlDistributionPointsNoDistributionPoint);
|
return Err(
|
||||||
|
ResourceCertificateProfileError::CrlDistributionPointsNoDistributionPoint,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if dp.name_relative_to_crl_issuer_present || !dp.full_name_present {
|
if dp.name_relative_to_crl_issuer_present || !dp.full_name_present {
|
||||||
return Err(ResourceCertificateProfileError::CrlDistributionPointsInvalidName);
|
return Err(ResourceCertificateProfileError::CrlDistributionPointsInvalidName);
|
||||||
}
|
}
|
||||||
if dp.full_name_not_uri {
|
if dp.full_name_not_uri {
|
||||||
return Err(ResourceCertificateProfileError::CrlDistributionPointsFullNameNotUri);
|
return Err(
|
||||||
|
ResourceCertificateProfileError::CrlDistributionPointsFullNameNotUri,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if !dp.full_name_uris.iter().any(|u| u.scheme() == "rsync") {
|
if !dp.full_name_uris.iter().any(|u| u.scheme() == "rsync") {
|
||||||
return Err(ResourceCertificateProfileError::CrlDistributionPointsNoRsync);
|
return Err(ResourceCertificateProfileError::CrlDistributionPointsNoRsync);
|
||||||
@ -751,13 +748,19 @@ impl RcExtensionsParsed {
|
|||||||
return Err(ResourceCertificateProfileError::AuthorityInfoAccessCriticality);
|
return Err(ResourceCertificateProfileError::AuthorityInfoAccessCriticality);
|
||||||
}
|
}
|
||||||
if is_self_signed {
|
if is_self_signed {
|
||||||
return Err(ResourceCertificateProfileError::AuthorityInfoAccessSelfSignedMustOmit);
|
return Err(
|
||||||
|
ResourceCertificateProfileError::AuthorityInfoAccessSelfSignedMustOmit,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if aia.ca_issuers_access_location_not_uri {
|
if aia.ca_issuers_access_location_not_uri {
|
||||||
return Err(ResourceCertificateProfileError::AuthorityInfoAccessCaIssuersNotUri);
|
return Err(
|
||||||
|
ResourceCertificateProfileError::AuthorityInfoAccessCaIssuersNotUri,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if aia.ca_issuers_uris.is_empty() {
|
if aia.ca_issuers_uris.is_empty() {
|
||||||
return Err(ResourceCertificateProfileError::AuthorityInfoAccessMissingCaIssuers);
|
return Err(
|
||||||
|
ResourceCertificateProfileError::AuthorityInfoAccessMissingCaIssuers,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
if !aia.ca_issuers_uris.iter().any(|u| u.scheme() == "rsync") {
|
if !aia.ca_issuers_uris.iter().any(|u| u.scheme() == "rsync") {
|
||||||
return Err(ResourceCertificateProfileError::AuthorityInfoAccessNoRsync);
|
return Err(ResourceCertificateProfileError::AuthorityInfoAccessNoRsync);
|
||||||
|
|||||||
@ -62,4 +62,3 @@ impl Fetcher for BlockingHttpFetcher {
|
|||||||
self.fetch_bytes(uri)
|
self.fetch_bytes(uri)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -50,7 +50,9 @@ impl SystemRsyncFetcher {
|
|||||||
.arg(src)
|
.arg(src)
|
||||||
.arg(dst);
|
.arg(dst);
|
||||||
|
|
||||||
let out = cmd.output().map_err(|e| format!("rsync spawn failed: {e}"))?;
|
let out = cmd
|
||||||
|
.output()
|
||||||
|
.map_err(|e| format!("rsync spawn failed: {e}"))?;
|
||||||
if !out.status.success() {
|
if !out.status.success() {
|
||||||
let stderr = String::from_utf8_lossy(&out.stderr);
|
let stderr = String::from_utf8_lossy(&out.stderr);
|
||||||
let stdout = String::from_utf8_lossy(&out.stdout);
|
let stdout = String::from_utf8_lossy(&out.stdout);
|
||||||
|
|||||||
@ -168,10 +168,7 @@ impl RocksStore {
|
|||||||
) -> StorageResult<impl Iterator<Item = (Box<[u8]>, Box<[u8]>)> + 'a> {
|
) -> StorageResult<impl Iterator<Item = (Box<[u8]>, Box<[u8]>)> + 'a> {
|
||||||
let cf = self.cf(CF_RAW_OBJECTS)?;
|
let cf = self.cf(CF_RAW_OBJECTS)?;
|
||||||
let mode = IteratorMode::Start;
|
let mode = IteratorMode::Start;
|
||||||
Ok(self
|
Ok(self.db.iterator_cf(cf, mode).filter_map(|res| res.ok()))
|
||||||
.db
|
|
||||||
.iterator_cf(cf, mode)
|
|
||||||
.filter_map(|res| res.ok()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
@ -180,10 +177,7 @@ impl RocksStore {
|
|||||||
) -> StorageResult<impl Iterator<Item = (Box<[u8]>, Box<[u8]>)> + 'a> {
|
) -> StorageResult<impl Iterator<Item = (Box<[u8]>, Box<[u8]>)> + 'a> {
|
||||||
let cf = self.cf(CF_VERIFIED_PUBLICATION_POINTS)?;
|
let cf = self.cf(CF_VERIFIED_PUBLICATION_POINTS)?;
|
||||||
let mode = IteratorMode::Start;
|
let mode = IteratorMode::Start;
|
||||||
Ok(self
|
Ok(self.db.iterator_cf(cf, mode).filter_map(|res| res.ok()))
|
||||||
.db
|
|
||||||
.iterator_cf(cf, mode)
|
|
||||||
.filter_map(|res| res.ok()))
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
|
|||||||
@ -98,7 +98,8 @@ pub fn ca_instance_uris_from_ca_certificate(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut publication_point_rsync_uri = ca_repo.ok_or(CaInstanceUrisError::MissingCaRepository)?;
|
let mut publication_point_rsync_uri =
|
||||||
|
ca_repo.ok_or(CaInstanceUrisError::MissingCaRepository)?;
|
||||||
if !publication_point_rsync_uri.ends_with('/') {
|
if !publication_point_rsync_uri.ends_with('/') {
|
||||||
publication_point_rsync_uri.push('/');
|
publication_point_rsync_uri.push('/');
|
||||||
}
|
}
|
||||||
@ -120,4 +121,3 @@ pub fn ca_instance_uris_from_ca_certificate(
|
|||||||
rrdp_notification_uri: notify,
|
rrdp_notification_uri: notify,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -51,17 +51,13 @@ pub enum CaPathError {
|
|||||||
#[error("certificate not valid at validation_time (RFC 5280 §4.1.2.5; RFC 5280 §6.1)")]
|
#[error("certificate not valid at validation_time (RFC 5280 §4.1.2.5; RFC 5280 §6.1)")]
|
||||||
CertificateNotValidAtTime,
|
CertificateNotValidAtTime,
|
||||||
|
|
||||||
#[error(
|
#[error("child CA KeyUsage extension missing (RFC 6487 §4.8.4; RFC 5280 §4.2.1.3)")]
|
||||||
"child CA KeyUsage extension missing (RFC 6487 §4.8.4; RFC 5280 §4.2.1.3)"
|
|
||||||
)]
|
|
||||||
KeyUsageMissing,
|
KeyUsageMissing,
|
||||||
|
|
||||||
#[error("child CA KeyUsage criticality must be critical (RFC 6487 §4.8.4; RFC 5280 §4.2.1.3)")]
|
#[error("child CA KeyUsage criticality must be critical (RFC 6487 §4.8.4; RFC 5280 §4.2.1.3)")]
|
||||||
KeyUsageNotCritical,
|
KeyUsageNotCritical,
|
||||||
|
|
||||||
#[error(
|
#[error("child CA KeyUsage must have only keyCertSign and cRLSign set (RFC 6487 §4.8.4)")]
|
||||||
"child CA KeyUsage must have only keyCertSign and cRLSign set (RFC 6487 §4.8.4)"
|
|
||||||
)]
|
|
||||||
KeyUsageInvalidBits,
|
KeyUsageInvalidBits,
|
||||||
|
|
||||||
#[error(
|
#[error(
|
||||||
@ -370,7 +366,8 @@ fn resolve_child_ip_resources(
|
|||||||
}
|
}
|
||||||
IpAddressChoice::AddressesOrRanges(items) => {
|
IpAddressChoice::AddressesOrRanges(items) => {
|
||||||
// Subset check against parent union for that AFI.
|
// Subset check against parent union for that AFI.
|
||||||
let parent_set = ip_resources_single_afi(parent, fam.afi, parent_by_afi.get(&fam.afi));
|
let parent_set =
|
||||||
|
ip_resources_single_afi(parent, fam.afi, parent_by_afi.get(&fam.afi));
|
||||||
if !ip_family_items_subset(items, &parent_set) {
|
if !ip_family_items_subset(items, &parent_set) {
|
||||||
return Err(CaPathError::ResourcesNotSubset);
|
return Err(CaPathError::ResourcesNotSubset);
|
||||||
}
|
}
|
||||||
@ -382,7 +379,9 @@ fn resolve_child_ip_resources(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(Some(IpResourceSet { families: out_families }))
|
Ok(Some(IpResourceSet {
|
||||||
|
families: out_families,
|
||||||
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn resolve_child_as_resources(
|
fn resolve_child_as_resources(
|
||||||
@ -403,7 +402,11 @@ fn resolve_child_as_resources(
|
|||||||
|
|
||||||
let asnum = match child_as.asnum.as_ref() {
|
let asnum = match child_as.asnum.as_ref() {
|
||||||
None => None,
|
None => None,
|
||||||
Some(AsIdentifierChoice::Inherit) => parent.asnum.clone().ok_or(CaPathError::InheritWithoutParentResources).map(Some)?,
|
Some(AsIdentifierChoice::Inherit) => parent
|
||||||
|
.asnum
|
||||||
|
.clone()
|
||||||
|
.ok_or(CaPathError::InheritWithoutParentResources)
|
||||||
|
.map(Some)?,
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
if !as_choice_subset(child_as.asnum.as_ref(), parent.asnum.as_ref()) {
|
if !as_choice_subset(child_as.asnum.as_ref(), parent.asnum.as_ref()) {
|
||||||
return Err(CaPathError::ResourcesNotSubset);
|
return Err(CaPathError::ResourcesNotSubset);
|
||||||
@ -414,7 +417,11 @@ fn resolve_child_as_resources(
|
|||||||
|
|
||||||
let rdi = match child_as.rdi.as_ref() {
|
let rdi = match child_as.rdi.as_ref() {
|
||||||
None => None,
|
None => None,
|
||||||
Some(AsIdentifierChoice::Inherit) => parent.rdi.clone().ok_or(CaPathError::InheritWithoutParentResources).map(Some)?,
|
Some(AsIdentifierChoice::Inherit) => parent
|
||||||
|
.rdi
|
||||||
|
.clone()
|
||||||
|
.ok_or(CaPathError::InheritWithoutParentResources)
|
||||||
|
.map(Some)?,
|
||||||
Some(_) => {
|
Some(_) => {
|
||||||
if !as_choice_subset(child_as.rdi.as_ref(), parent.rdi.as_ref()) {
|
if !as_choice_subset(child_as.rdi.as_ref(), parent.rdi.as_ref()) {
|
||||||
return Err(CaPathError::ResourcesNotSubset);
|
return Err(CaPathError::ResourcesNotSubset);
|
||||||
@ -508,9 +515,17 @@ enum AfiKey {
|
|||||||
|
|
||||||
fn ip_resources_by_afi_items(
|
fn ip_resources_by_afi_items(
|
||||||
set: &IpResourceSet,
|
set: &IpResourceSet,
|
||||||
) -> Result<std::collections::BTreeMap<crate::data_model::rc::Afi, Vec<crate::data_model::rc::IpAddressOrRange>>, CaPathError> {
|
) -> Result<
|
||||||
let mut m: std::collections::BTreeMap<crate::data_model::rc::Afi, Vec<crate::data_model::rc::IpAddressOrRange>> =
|
std::collections::BTreeMap<
|
||||||
std::collections::BTreeMap::new();
|
crate::data_model::rc::Afi,
|
||||||
|
Vec<crate::data_model::rc::IpAddressOrRange>,
|
||||||
|
>,
|
||||||
|
CaPathError,
|
||||||
|
> {
|
||||||
|
let mut m: std::collections::BTreeMap<
|
||||||
|
crate::data_model::rc::Afi,
|
||||||
|
Vec<crate::data_model::rc::IpAddressOrRange>,
|
||||||
|
> = std::collections::BTreeMap::new();
|
||||||
for fam in &set.families {
|
for fam in &set.families {
|
||||||
match &fam.choice {
|
match &fam.choice {
|
||||||
IpAddressChoice::Inherit => return Err(CaPathError::InheritWithoutParentResources),
|
IpAddressChoice::Inherit => return Err(CaPathError::InheritWithoutParentResources),
|
||||||
@ -555,8 +570,12 @@ fn ip_family_items_subset(
|
|||||||
let mut child_intervals: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
|
let mut child_intervals: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
|
||||||
for item in child_items {
|
for item in child_items {
|
||||||
match item {
|
match item {
|
||||||
crate::data_model::rc::IpAddressOrRange::Prefix(p) => child_intervals.push(prefix_to_range(p)),
|
crate::data_model::rc::IpAddressOrRange::Prefix(p) => {
|
||||||
crate::data_model::rc::IpAddressOrRange::Range(r) => child_intervals.push((r.min.clone(), r.max.clone())),
|
child_intervals.push(prefix_to_range(p))
|
||||||
|
}
|
||||||
|
crate::data_model::rc::IpAddressOrRange::Range(r) => {
|
||||||
|
child_intervals.push((r.min.clone(), r.max.clone()))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
child_intervals.sort_by(|(a, _), (b, _)| a.cmp(b));
|
child_intervals.sort_by(|(a, _), (b, _)| a.cmp(b));
|
||||||
@ -673,7 +692,9 @@ mod tests {
|
|||||||
use crate::data_model::rc::{
|
use crate::data_model::rc::{
|
||||||
Afi, AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressFamily, IpResourceSet,
|
Afi, AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressFamily, IpResourceSet,
|
||||||
};
|
};
|
||||||
use crate::data_model::rc::{RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate};
|
use crate::data_model::rc::{
|
||||||
|
RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate,
|
||||||
|
};
|
||||||
use der_parser::num_bigint::BigUint;
|
use der_parser::num_bigint::BigUint;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
@ -882,10 +903,8 @@ mod tests {
|
|||||||
Some(vec!["rsync://example.test/issuer.cer"]),
|
Some(vec!["rsync://example.test/issuer.cer"]),
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
let err = validate_child_crldp_contains_issuer_crl_uri(
|
let err =
|
||||||
&child,
|
validate_child_crldp_contains_issuer_crl_uri(&child, "rsync://example.test/issuer.crl")
|
||||||
"rsync://example.test/issuer.crl",
|
|
||||||
)
|
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
assert!(matches!(err, CaPathError::ChildCrlDpMissing), "{err}");
|
assert!(matches!(err, CaPathError::ChildCrlDpMissing), "{err}");
|
||||||
|
|
||||||
@ -898,15 +917,10 @@ mod tests {
|
|||||||
Some(vec!["rsync://example.test/issuer.cer"]),
|
Some(vec!["rsync://example.test/issuer.cer"]),
|
||||||
Some(vec!["rsync://example.test/other.crl"]),
|
Some(vec!["rsync://example.test/other.crl"]),
|
||||||
);
|
);
|
||||||
let err = validate_child_crldp_contains_issuer_crl_uri(
|
let err =
|
||||||
&child,
|
validate_child_crldp_contains_issuer_crl_uri(&child, "rsync://example.test/issuer.crl")
|
||||||
"rsync://example.test/issuer.crl",
|
|
||||||
)
|
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
assert!(
|
assert!(matches!(err, CaPathError::ChildCrlDpUriMismatch), "{err}");
|
||||||
matches!(err, CaPathError::ChildCrlDpUriMismatch),
|
|
||||||
"{err}"
|
|
||||||
);
|
|
||||||
|
|
||||||
// Cover child AKI missing.
|
// Cover child AKI missing.
|
||||||
let child_missing_aki = dummy_cert(
|
let child_missing_aki = dummy_cert(
|
||||||
|
|||||||
@ -42,17 +42,13 @@ pub enum CertPathError {
|
|||||||
#[error("EE certificate signature verification failed: {0} (RFC 5280 §6.1)")]
|
#[error("EE certificate signature verification failed: {0} (RFC 5280 §6.1)")]
|
||||||
EeSignatureInvalid(String),
|
EeSignatureInvalid(String),
|
||||||
|
|
||||||
#[error(
|
#[error("EE KeyUsage extension missing (RFC 6487 §4.8.4; RFC 5280 §4.2.1.3)")]
|
||||||
"EE KeyUsage extension missing (RFC 6487 §4.8.4; RFC 5280 §4.2.1.3)"
|
|
||||||
)]
|
|
||||||
KeyUsageMissing,
|
KeyUsageMissing,
|
||||||
|
|
||||||
#[error("EE KeyUsage criticality must be critical (RFC 6487 §4.8.4; RFC 5280 §4.2.1.3)")]
|
#[error("EE KeyUsage criticality must be critical (RFC 6487 §4.8.4; RFC 5280 §4.2.1.3)")]
|
||||||
KeyUsageNotCritical,
|
KeyUsageNotCritical,
|
||||||
|
|
||||||
#[error(
|
#[error("EE KeyUsage must have only digitalSignature set (RFC 6487 §4.8.4)")]
|
||||||
"EE KeyUsage must have only digitalSignature set (RFC 6487 §4.8.4)"
|
|
||||||
)]
|
|
||||||
KeyUsageInvalidBits,
|
KeyUsageInvalidBits,
|
||||||
|
|
||||||
#[error("issuer CA subjectKeyIdentifier missing (RFC 6487 §4.8.2)")]
|
#[error("issuer CA subjectKeyIdentifier missing (RFC 6487 §4.8.2)")]
|
||||||
@ -303,7 +299,9 @@ fn is_serial_revoked_by_crl(ee: &ResourceCertificate, crl: &RpkixCrl) -> bool {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::data_model::rc::{RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate};
|
use crate::data_model::rc::{
|
||||||
|
RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate,
|
||||||
|
};
|
||||||
use der_parser::num_bigint::BigUint;
|
use der_parser::num_bigint::BigUint;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
@ -410,8 +408,10 @@ mod tests {
|
|||||||
None,
|
None,
|
||||||
Some(vec!["rsync://example.test/issuer.crl"]),
|
Some(vec!["rsync://example.test/issuer.crl"]),
|
||||||
);
|
);
|
||||||
let err =
|
let err = validate_ee_aia_points_to_issuer_uri(
|
||||||
validate_ee_aia_points_to_issuer_uri(&ee_missing_aia, "rsync://example.test/issuer.cer")
|
&ee_missing_aia,
|
||||||
|
"rsync://example.test/issuer.cer",
|
||||||
|
)
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
assert!(matches!(err, CertPathError::EeAiaMissing), "{err}");
|
assert!(matches!(err, CertPathError::EeAiaMissing), "{err}");
|
||||||
|
|
||||||
@ -462,10 +462,7 @@ mod tests {
|
|||||||
"rsync://example.test/issuer.crl",
|
"rsync://example.test/issuer.crl",
|
||||||
)
|
)
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
assert!(
|
assert!(matches!(err, CertPathError::EeCrlDpUriMismatch), "{err}");
|
||||||
matches!(err, CertPathError::EeCrlDpUriMismatch),
|
|
||||||
"{err}"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -3,8 +3,9 @@ use url::Url;
|
|||||||
use crate::data_model::ta::{TrustAnchor, TrustAnchorError};
|
use crate::data_model::ta::{TrustAnchor, TrustAnchorError};
|
||||||
use crate::data_model::tal::{Tal, TalDecodeError};
|
use crate::data_model::tal::{Tal, TalDecodeError};
|
||||||
use crate::sync::rrdp::Fetcher;
|
use crate::sync::rrdp::Fetcher;
|
||||||
use crate::validation::ca_instance::{CaInstanceUris, CaInstanceUrisError, ca_instance_uris_from_ca_certificate};
|
use crate::validation::ca_instance::{
|
||||||
use crate::validation::objects::IssuerCaCertificateResolver;
|
CaInstanceUris, CaInstanceUrisError, ca_instance_uris_from_ca_certificate,
|
||||||
|
};
|
||||||
use crate::validation::run::{RunError, RunOutput, run_publication_point_once};
|
use crate::validation::run::{RunError, RunOutput, run_publication_point_once};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
@ -82,8 +83,8 @@ pub fn discover_root_ca_instance_from_tal(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let ca_instance = match ca_instance_uris_from_ca_certificate(&trust_anchor.ta_certificate.rc_ca)
|
let ca_instance =
|
||||||
{
|
match ca_instance_uris_from_ca_certificate(&trust_anchor.ta_certificate.rc_ca) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
last_err = Some(format!("CA instance discovery failed: {e}"));
|
last_err = Some(format!("CA instance discovery failed: {e}"));
|
||||||
@ -98,9 +99,9 @@ pub fn discover_root_ca_instance_from_tal(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Err(FromTalError::TaFetch(
|
Err(FromTalError::TaFetch(last_err.unwrap_or_else(|| {
|
||||||
last_err.unwrap_or_else(|| "unknown TA candidate error".to_string()),
|
"unknown TA candidate error".to_string()
|
||||||
))
|
})))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn discover_root_ca_instance_from_tal_and_ta_der(
|
pub fn discover_root_ca_instance_from_tal_and_ta_der(
|
||||||
@ -124,7 +125,6 @@ pub fn run_root_from_tal_url_once(
|
|||||||
tal_url: &str,
|
tal_url: &str,
|
||||||
http_fetcher: &dyn Fetcher,
|
http_fetcher: &dyn Fetcher,
|
||||||
rsync_fetcher: &dyn crate::fetch::rsync::RsyncFetcher,
|
rsync_fetcher: &dyn crate::fetch::rsync::RsyncFetcher,
|
||||||
issuer_resolver: &dyn IssuerCaCertificateResolver,
|
|
||||||
validation_time: time::OffsetDateTime,
|
validation_time: time::OffsetDateTime,
|
||||||
) -> Result<RunFromTalOutput, FromTalError> {
|
) -> Result<RunFromTalOutput, FromTalError> {
|
||||||
let discovery = discover_root_ca_instance_from_tal_url(http_fetcher, tal_url)?;
|
let discovery = discover_root_ca_instance_from_tal_url(http_fetcher, tal_url)?;
|
||||||
@ -138,7 +138,24 @@ pub fn run_root_from_tal_url_once(
|
|||||||
&discovery.ca_instance.publication_point_rsync_uri,
|
&discovery.ca_instance.publication_point_rsync_uri,
|
||||||
http_fetcher,
|
http_fetcher,
|
||||||
rsync_fetcher,
|
rsync_fetcher,
|
||||||
issuer_resolver,
|
&discovery.trust_anchor.ta_certificate.raw_der,
|
||||||
|
None,
|
||||||
|
discovery
|
||||||
|
.trust_anchor
|
||||||
|
.ta_certificate
|
||||||
|
.rc_ca
|
||||||
|
.tbs
|
||||||
|
.extensions
|
||||||
|
.ip_resources
|
||||||
|
.as_ref(),
|
||||||
|
discovery
|
||||||
|
.trust_anchor
|
||||||
|
.ta_certificate
|
||||||
|
.rc_ca
|
||||||
|
.tbs
|
||||||
|
.extensions
|
||||||
|
.as_resources
|
||||||
|
.as_ref(),
|
||||||
validation_time,
|
validation_time,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
|
|||||||
@ -1,10 +1,10 @@
|
|||||||
pub mod cert_path;
|
|
||||||
pub mod ca_instance;
|
pub mod ca_instance;
|
||||||
pub mod ca_path;
|
pub mod ca_path;
|
||||||
|
pub mod cert_path;
|
||||||
pub mod from_tal;
|
pub mod from_tal;
|
||||||
pub mod manifest;
|
pub mod manifest;
|
||||||
pub mod objects;
|
pub mod objects;
|
||||||
pub mod run;
|
pub mod run;
|
||||||
|
pub mod run_tree_from_tal;
|
||||||
pub mod tree;
|
pub mod tree;
|
||||||
pub mod tree_runner;
|
pub mod tree_runner;
|
||||||
pub mod run_tree_from_tal;
|
|
||||||
|
|||||||
@ -1,18 +1,29 @@
|
|||||||
|
use crate::audit::{AuditObjectKind, AuditObjectResult, ObjectAuditEntry, sha256_hex_from_32};
|
||||||
use crate::data_model::aspa::{AspaDecodeError, AspaObject, AspaValidateError};
|
use crate::data_model::aspa::{AspaDecodeError, AspaObject, AspaValidateError};
|
||||||
use crate::data_model::manifest::ManifestObject;
|
use crate::data_model::manifest::ManifestObject;
|
||||||
use crate::data_model::rc::{
|
use crate::data_model::rc::{
|
||||||
AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressOrRange, IpPrefix as RcIpPrefix,
|
AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressOrRange, IpPrefix as RcIpPrefix,
|
||||||
ResourceCertKind, ResourceCertificate,
|
ResourceCertificate,
|
||||||
};
|
};
|
||||||
use crate::data_model::roa::{IpPrefix, RoaAfi, RoaDecodeError, RoaObject, RoaValidateError};
|
use crate::data_model::roa::{IpPrefix, RoaAfi, RoaDecodeError, RoaObject, RoaValidateError};
|
||||||
use crate::data_model::signed_object::SignedObjectVerifyError;
|
use crate::data_model::signed_object::SignedObjectVerifyError;
|
||||||
use crate::audit::{AuditObjectKind, AuditObjectResult, ObjectAuditEntry, sha256_hex_from_32};
|
|
||||||
use crate::policy::{Policy, SignedObjectFailurePolicy};
|
use crate::policy::{Policy, SignedObjectFailurePolicy};
|
||||||
use crate::report::{RfcRef, Warning};
|
use crate::report::{RfcRef, Warning};
|
||||||
use crate::storage::{PackFile, VerifiedPublicationPointPack};
|
use crate::storage::{PackFile, VerifiedPublicationPointPack};
|
||||||
use crate::validation::cert_path::{CertPathError, validate_ee_cert_path};
|
use crate::validation::cert_path::{CertPathError, validate_ee_cert_path};
|
||||||
|
|
||||||
use std::collections::HashMap;
|
const RFC_NONE: &[RfcRef] = &[];
|
||||||
|
const RFC_CRLDP: &[RfcRef] = &[RfcRef("RFC 6487 §4.8.6")];
|
||||||
|
const RFC_CRLDP_AND_LOCKED_PACK: &[RfcRef] =
|
||||||
|
&[RfcRef("RFC 6487 §4.8.6"), RfcRef("RFC 9286 §4.2.1")];
|
||||||
|
|
||||||
|
fn extra_rfc_refs_for_crl_selection(e: &ObjectValidateError) -> &'static [RfcRef] {
|
||||||
|
match e {
|
||||||
|
ObjectValidateError::MissingCrlDpUris => RFC_CRLDP,
|
||||||
|
ObjectValidateError::CrlNotFound(_) => RFC_CRLDP_AND_LOCKED_PACK,
|
||||||
|
_ => RFC_NONE,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct Vrp {
|
pub struct Vrp {
|
||||||
@ -60,8 +71,16 @@ pub fn process_verified_publication_point_pack_for_issuer(
|
|||||||
) -> ObjectsOutput {
|
) -> ObjectsOutput {
|
||||||
let mut warnings: Vec<Warning> = Vec::new();
|
let mut warnings: Vec<Warning> = Vec::new();
|
||||||
let mut stats = ObjectsStats::default();
|
let mut stats = ObjectsStats::default();
|
||||||
stats.roa_total = pack.files.iter().filter(|f| f.rsync_uri.ends_with(".roa")).count();
|
stats.roa_total = pack
|
||||||
stats.aspa_total = pack.files.iter().filter(|f| f.rsync_uri.ends_with(".asa")).count();
|
.files
|
||||||
|
.iter()
|
||||||
|
.filter(|f| f.rsync_uri.ends_with(".roa"))
|
||||||
|
.count();
|
||||||
|
stats.aspa_total = pack
|
||||||
|
.files
|
||||||
|
.iter()
|
||||||
|
.filter(|f| f.rsync_uri.ends_with(".asa"))
|
||||||
|
.count();
|
||||||
let mut audit: Vec<ObjectAuditEntry> = Vec::new();
|
let mut audit: Vec<ObjectAuditEntry> = Vec::new();
|
||||||
|
|
||||||
// Enforce that `manifest_bytes` is actually a manifest object.
|
// Enforce that `manifest_bytes` is actually a manifest object.
|
||||||
@ -75,13 +94,13 @@ pub fn process_verified_publication_point_pack_for_issuer(
|
|||||||
.map(|f| (f.rsync_uri.clone(), f.bytes.clone()))
|
.map(|f| (f.rsync_uri.clone(), f.bytes.clone()))
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let (issuer_crl_uri, issuer_crl_der) = match choose_crl_for_issuer(issuer_ca_der, &crl_files) {
|
// If the pack has signed objects but no CRLs at all, we cannot validate any embedded EE
|
||||||
Ok((uri, der)) => (uri, der),
|
// certificate paths deterministically (EE CRLDP must reference an rsync URI in the pack).
|
||||||
Err(e) => {
|
if crl_files.is_empty() && (stats.roa_total > 0 || stats.aspa_total > 0) {
|
||||||
stats.publication_point_dropped = true;
|
stats.publication_point_dropped = true;
|
||||||
warnings.push(
|
warnings.push(
|
||||||
Warning::new(format!("dropping publication point: {e}"))
|
Warning::new("dropping publication point: no CRL files in verified pack")
|
||||||
.with_rfc_refs(&[RfcRef("RFC 6487 §5")])
|
.with_rfc_refs(&[RfcRef("RFC 6487 §4.8.6"), RfcRef("RFC 9286 §7")])
|
||||||
.with_context(&pack.manifest_rsync_uri),
|
.with_context(&pack.manifest_rsync_uri),
|
||||||
);
|
);
|
||||||
for f in &pack.files {
|
for f in &pack.files {
|
||||||
@ -91,7 +110,7 @@ pub fn process_verified_publication_point_pack_for_issuer(
|
|||||||
sha256_hex: sha256_hex_from_32(&f.sha256),
|
sha256_hex: sha256_hex_from_32(&f.sha256),
|
||||||
kind: AuditObjectKind::Roa,
|
kind: AuditObjectKind::Roa,
|
||||||
result: AuditObjectResult::Skipped,
|
result: AuditObjectResult::Skipped,
|
||||||
detail: Some("skipped due to missing issuer CRL".to_string()),
|
detail: Some("skipped due to missing CRL files in verified pack".to_string()),
|
||||||
});
|
});
|
||||||
} else if f.rsync_uri.ends_with(".asa") {
|
} else if f.rsync_uri.ends_with(".asa") {
|
||||||
audit.push(ObjectAuditEntry {
|
audit.push(ObjectAuditEntry {
|
||||||
@ -99,7 +118,7 @@ pub fn process_verified_publication_point_pack_for_issuer(
|
|||||||
sha256_hex: sha256_hex_from_32(&f.sha256),
|
sha256_hex: sha256_hex_from_32(&f.sha256),
|
||||||
kind: AuditObjectKind::Aspa,
|
kind: AuditObjectKind::Aspa,
|
||||||
result: AuditObjectResult::Skipped,
|
result: AuditObjectResult::Skipped,
|
||||||
detail: Some("skipped due to missing issuer CRL".to_string()),
|
detail: Some("skipped due to missing CRL files in verified pack".to_string()),
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -111,7 +130,6 @@ pub fn process_verified_publication_point_pack_for_issuer(
|
|||||||
audit,
|
audit,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
|
||||||
|
|
||||||
let mut vrps: Vec<Vrp> = Vec::new();
|
let mut vrps: Vec<Vrp> = Vec::new();
|
||||||
let mut aspas: Vec<AspaAttestation> = Vec::new();
|
let mut aspas: Vec<AspaAttestation> = Vec::new();
|
||||||
@ -121,9 +139,8 @@ pub fn process_verified_publication_point_pack_for_issuer(
|
|||||||
match process_roa_with_issuer(
|
match process_roa_with_issuer(
|
||||||
file,
|
file,
|
||||||
issuer_ca_der,
|
issuer_ca_der,
|
||||||
&issuer_crl_der,
|
|
||||||
issuer_ca_rsync_uri,
|
issuer_ca_rsync_uri,
|
||||||
Some(issuer_crl_uri.as_str()),
|
&crl_files,
|
||||||
issuer_effective_ip,
|
issuer_effective_ip,
|
||||||
issuer_effective_as,
|
issuer_effective_as,
|
||||||
validation_time,
|
validation_time,
|
||||||
@ -148,9 +165,11 @@ pub fn process_verified_publication_point_pack_for_issuer(
|
|||||||
result: AuditObjectResult::Error,
|
result: AuditObjectResult::Error,
|
||||||
detail: Some(e.to_string()),
|
detail: Some(e.to_string()),
|
||||||
});
|
});
|
||||||
|
let mut refs = vec![RfcRef("RFC 6488 §3"), RfcRef("RFC 9582 §4-§5")];
|
||||||
|
refs.extend_from_slice(extra_rfc_refs_for_crl_selection(&e));
|
||||||
warnings.push(
|
warnings.push(
|
||||||
Warning::new(format!("dropping invalid ROA: {}: {e}", file.rsync_uri))
|
Warning::new(format!("dropping invalid ROA: {}: {e}", file.rsync_uri))
|
||||||
.with_rfc_refs(&[RfcRef("RFC 6488 §3"), RfcRef("RFC 9582 §4-§5")])
|
.with_rfc_refs(&refs)
|
||||||
.with_context(&file.rsync_uri),
|
.with_context(&file.rsync_uri),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -188,12 +207,14 @@ pub fn process_verified_publication_point_pack_for_issuer(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let mut refs = vec![RfcRef("RFC 6488 §3"), RfcRef("RFC 9582 §4-§5")];
|
||||||
|
refs.extend_from_slice(extra_rfc_refs_for_crl_selection(&e));
|
||||||
warnings.push(
|
warnings.push(
|
||||||
Warning::new(format!(
|
Warning::new(format!(
|
||||||
"dropping publication point due to invalid ROA: {}: {e}",
|
"dropping publication point due to invalid ROA: {}: {e}",
|
||||||
file.rsync_uri
|
file.rsync_uri
|
||||||
))
|
))
|
||||||
.with_rfc_refs(&[RfcRef("RFC 6488 §3"), RfcRef("RFC 9582 §4-§5")])
|
.with_rfc_refs(&refs)
|
||||||
.with_context(&pack.manifest_rsync_uri),
|
.with_context(&pack.manifest_rsync_uri),
|
||||||
);
|
);
|
||||||
return ObjectsOutput {
|
return ObjectsOutput {
|
||||||
@ -210,9 +231,8 @@ pub fn process_verified_publication_point_pack_for_issuer(
|
|||||||
match process_aspa_with_issuer(
|
match process_aspa_with_issuer(
|
||||||
file,
|
file,
|
||||||
issuer_ca_der,
|
issuer_ca_der,
|
||||||
&issuer_crl_der,
|
|
||||||
issuer_ca_rsync_uri,
|
issuer_ca_rsync_uri,
|
||||||
Some(issuer_crl_uri.as_str()),
|
&crl_files,
|
||||||
issuer_effective_ip,
|
issuer_effective_ip,
|
||||||
issuer_effective_as,
|
issuer_effective_as,
|
||||||
validation_time,
|
validation_time,
|
||||||
@ -237,9 +257,11 @@ pub fn process_verified_publication_point_pack_for_issuer(
|
|||||||
result: AuditObjectResult::Error,
|
result: AuditObjectResult::Error,
|
||||||
detail: Some(e.to_string()),
|
detail: Some(e.to_string()),
|
||||||
});
|
});
|
||||||
|
let mut refs = vec![RfcRef("RFC 6488 §3")];
|
||||||
|
refs.extend_from_slice(extra_rfc_refs_for_crl_selection(&e));
|
||||||
warnings.push(
|
warnings.push(
|
||||||
Warning::new(format!("dropping invalid ASPA: {}: {e}", file.rsync_uri))
|
Warning::new(format!("dropping invalid ASPA: {}: {e}", file.rsync_uri))
|
||||||
.with_rfc_refs(&[RfcRef("RFC 6488 §3")])
|
.with_rfc_refs(&refs)
|
||||||
.with_context(&file.rsync_uri),
|
.with_context(&file.rsync_uri),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
@ -277,12 +299,14 @@ pub fn process_verified_publication_point_pack_for_issuer(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
let mut refs = vec![RfcRef("RFC 6488 §3")];
|
||||||
|
refs.extend_from_slice(extra_rfc_refs_for_crl_selection(&e));
|
||||||
warnings.push(
|
warnings.push(
|
||||||
Warning::new(format!(
|
Warning::new(format!(
|
||||||
"dropping publication point due to invalid ASPA: {}: {e}",
|
"dropping publication point due to invalid ASPA: {}: {e}",
|
||||||
file.rsync_uri
|
file.rsync_uri
|
||||||
))
|
))
|
||||||
.with_rfc_refs(&[RfcRef("RFC 6488 §3")])
|
.with_rfc_refs(&refs)
|
||||||
.with_context(&pack.manifest_rsync_uri),
|
.with_context(&pack.manifest_rsync_uri),
|
||||||
);
|
);
|
||||||
return ObjectsOutput {
|
return ObjectsOutput {
|
||||||
@ -307,221 +331,6 @@ pub fn process_verified_publication_point_pack_for_issuer(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
|
||||||
pub enum ObjectsProcessError {
|
|
||||||
#[error(
|
|
||||||
"publication point dropped due to invalid signed object: {rsync_uri}: {detail} (policy=signed_object_failure_policy=drop_publication_point)"
|
|
||||||
)]
|
|
||||||
PublicationPointDropped { rsync_uri: String, detail: String },
|
|
||||||
}
|
|
||||||
|
|
||||||
pub trait IssuerCaCertificateResolver {
|
|
||||||
fn resolve_by_subject_dn(&self, subject_dn: &str) -> Option<Vec<u8>>;
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn process_verified_publication_point_pack(
|
|
||||||
pack: &VerifiedPublicationPointPack,
|
|
||||||
policy: &Policy,
|
|
||||||
issuer_resolver: &dyn IssuerCaCertificateResolver,
|
|
||||||
validation_time: time::OffsetDateTime,
|
|
||||||
) -> Result<ObjectsOutput, ObjectsProcessError> {
|
|
||||||
let mut warnings = Vec::new();
|
|
||||||
let mut stats = ObjectsStats::default();
|
|
||||||
stats.roa_total = pack.files.iter().filter(|f| f.rsync_uri.ends_with(".roa")).count();
|
|
||||||
stats.aspa_total = pack.files.iter().filter(|f| f.rsync_uri.ends_with(".asa")).count();
|
|
||||||
let mut audit: Vec<ObjectAuditEntry> = Vec::new();
|
|
||||||
|
|
||||||
// Parse manifest once (primarily to enforce that `manifest_bytes` really is a manifest object).
|
|
||||||
let _manifest =
|
|
||||||
ManifestObject::decode_der(&pack.manifest_bytes).expect("verified pack manifest decodes");
|
|
||||||
|
|
||||||
// Index CA certs found in the pack by subject DN (best-effort; packs may be incomplete).
|
|
||||||
let mut ca_certs_by_subject: HashMap<String, Vec<u8>> = HashMap::new();
|
|
||||||
for f in &pack.files {
|
|
||||||
if !f.rsync_uri.ends_with(".cer") {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let Ok(cert) = ResourceCertificate::decode_der(&f.bytes) else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
if cert.kind != ResourceCertKind::Ca {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
ca_certs_by_subject.insert(cert.tbs.subject_dn.clone(), f.bytes.clone());
|
|
||||||
}
|
|
||||||
|
|
||||||
// Decode CRLs present in the pack (may be none in synthetic tests).
|
|
||||||
let crl_files = pack
|
|
||||||
.files
|
|
||||||
.iter()
|
|
||||||
.filter(|f| f.rsync_uri.ends_with(".crl"))
|
|
||||||
.map(|f| (f.rsync_uri.clone(), f.bytes.clone()))
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
|
|
||||||
let mut vrps = Vec::new();
|
|
||||||
let mut aspas = Vec::new();
|
|
||||||
|
|
||||||
for (idx, file) in pack.files.iter().enumerate() {
|
|
||||||
if file.rsync_uri.ends_with(".roa") {
|
|
||||||
match process_roa(
|
|
||||||
file,
|
|
||||||
&ca_certs_by_subject,
|
|
||||||
issuer_resolver,
|
|
||||||
&crl_files,
|
|
||||||
validation_time,
|
|
||||||
) {
|
|
||||||
Ok(mut out) => {
|
|
||||||
stats.roa_ok += 1;
|
|
||||||
vrps.append(&mut out);
|
|
||||||
audit.push(ObjectAuditEntry {
|
|
||||||
rsync_uri: file.rsync_uri.clone(),
|
|
||||||
sha256_hex: sha256_hex_from_32(&file.sha256),
|
|
||||||
kind: AuditObjectKind::Roa,
|
|
||||||
result: AuditObjectResult::Ok,
|
|
||||||
detail: None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Err(e) => match policy.signed_object_failure_policy {
|
|
||||||
SignedObjectFailurePolicy::DropObject => {
|
|
||||||
audit.push(ObjectAuditEntry {
|
|
||||||
rsync_uri: file.rsync_uri.clone(),
|
|
||||||
sha256_hex: sha256_hex_from_32(&file.sha256),
|
|
||||||
kind: AuditObjectKind::Roa,
|
|
||||||
result: AuditObjectResult::Error,
|
|
||||||
detail: Some(e.to_string()),
|
|
||||||
});
|
|
||||||
warnings.push(
|
|
||||||
Warning::new(format!("dropping invalid ROA: {}: {e}", file.rsync_uri))
|
|
||||||
.with_rfc_refs(&[RfcRef("RFC 6488 §3"), RfcRef("RFC 9582 §4-§5")])
|
|
||||||
.with_context(&file.rsync_uri),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
SignedObjectFailurePolicy::DropPublicationPoint => {
|
|
||||||
audit.push(ObjectAuditEntry {
|
|
||||||
rsync_uri: file.rsync_uri.clone(),
|
|
||||||
sha256_hex: sha256_hex_from_32(&file.sha256),
|
|
||||||
kind: AuditObjectKind::Roa,
|
|
||||||
result: AuditObjectResult::Error,
|
|
||||||
detail: Some(e.to_string()),
|
|
||||||
});
|
|
||||||
for f in pack.files.iter().skip(idx + 1) {
|
|
||||||
if f.rsync_uri.ends_with(".roa") {
|
|
||||||
audit.push(ObjectAuditEntry {
|
|
||||||
rsync_uri: f.rsync_uri.clone(),
|
|
||||||
sha256_hex: sha256_hex_from_32(&f.sha256),
|
|
||||||
kind: AuditObjectKind::Roa,
|
|
||||||
result: AuditObjectResult::Skipped,
|
|
||||||
detail: Some(
|
|
||||||
"skipped due to policy=signed_object_failure_policy=drop_publication_point"
|
|
||||||
.to_string(),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
} else if f.rsync_uri.ends_with(".asa") {
|
|
||||||
audit.push(ObjectAuditEntry {
|
|
||||||
rsync_uri: f.rsync_uri.clone(),
|
|
||||||
sha256_hex: sha256_hex_from_32(&f.sha256),
|
|
||||||
kind: AuditObjectKind::Aspa,
|
|
||||||
result: AuditObjectResult::Skipped,
|
|
||||||
detail: Some(
|
|
||||||
"skipped due to policy=signed_object_failure_policy=drop_publication_point"
|
|
||||||
.to_string(),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Err(ObjectsProcessError::PublicationPointDropped {
|
|
||||||
rsync_uri: file.rsync_uri.clone(),
|
|
||||||
detail: e.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
} else if file.rsync_uri.ends_with(".asa") {
|
|
||||||
match process_aspa(
|
|
||||||
file,
|
|
||||||
&ca_certs_by_subject,
|
|
||||||
issuer_resolver,
|
|
||||||
&crl_files,
|
|
||||||
validation_time,
|
|
||||||
) {
|
|
||||||
Ok(att) => {
|
|
||||||
stats.aspa_ok += 1;
|
|
||||||
aspas.push(att);
|
|
||||||
audit.push(ObjectAuditEntry {
|
|
||||||
rsync_uri: file.rsync_uri.clone(),
|
|
||||||
sha256_hex: sha256_hex_from_32(&file.sha256),
|
|
||||||
kind: AuditObjectKind::Aspa,
|
|
||||||
result: AuditObjectResult::Ok,
|
|
||||||
detail: None,
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Err(e) => match policy.signed_object_failure_policy {
|
|
||||||
SignedObjectFailurePolicy::DropObject => {
|
|
||||||
audit.push(ObjectAuditEntry {
|
|
||||||
rsync_uri: file.rsync_uri.clone(),
|
|
||||||
sha256_hex: sha256_hex_from_32(&file.sha256),
|
|
||||||
kind: AuditObjectKind::Aspa,
|
|
||||||
result: AuditObjectResult::Error,
|
|
||||||
detail: Some(e.to_string()),
|
|
||||||
});
|
|
||||||
warnings.push(
|
|
||||||
Warning::new(format!("dropping invalid ASPA: {}: {e}", file.rsync_uri))
|
|
||||||
.with_rfc_refs(&[RfcRef("RFC 6488 §3")])
|
|
||||||
.with_context(&file.rsync_uri),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
SignedObjectFailurePolicy::DropPublicationPoint => {
|
|
||||||
audit.push(ObjectAuditEntry {
|
|
||||||
rsync_uri: file.rsync_uri.clone(),
|
|
||||||
sha256_hex: sha256_hex_from_32(&file.sha256),
|
|
||||||
kind: AuditObjectKind::Aspa,
|
|
||||||
result: AuditObjectResult::Error,
|
|
||||||
detail: Some(e.to_string()),
|
|
||||||
});
|
|
||||||
for f in pack.files.iter().skip(idx + 1) {
|
|
||||||
if f.rsync_uri.ends_with(".roa") {
|
|
||||||
audit.push(ObjectAuditEntry {
|
|
||||||
rsync_uri: f.rsync_uri.clone(),
|
|
||||||
sha256_hex: sha256_hex_from_32(&f.sha256),
|
|
||||||
kind: AuditObjectKind::Roa,
|
|
||||||
result: AuditObjectResult::Skipped,
|
|
||||||
detail: Some(
|
|
||||||
"skipped due to policy=signed_object_failure_policy=drop_publication_point"
|
|
||||||
.to_string(),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
} else if f.rsync_uri.ends_with(".asa") {
|
|
||||||
audit.push(ObjectAuditEntry {
|
|
||||||
rsync_uri: f.rsync_uri.clone(),
|
|
||||||
sha256_hex: sha256_hex_from_32(&f.sha256),
|
|
||||||
kind: AuditObjectKind::Aspa,
|
|
||||||
result: AuditObjectResult::Skipped,
|
|
||||||
detail: Some(
|
|
||||||
"skipped due to policy=signed_object_failure_policy=drop_publication_point"
|
|
||||||
.to_string(),
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return Err(ObjectsProcessError::PublicationPointDropped {
|
|
||||||
rsync_uri: file.rsync_uri.clone(),
|
|
||||||
detail: e.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(ObjectsOutput {
|
|
||||||
vrps,
|
|
||||||
aspas,
|
|
||||||
warnings,
|
|
||||||
stats,
|
|
||||||
audit,
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
enum ObjectValidateError {
|
enum ObjectValidateError {
|
||||||
#[error("ROA decode failed: {0}")]
|
#[error("ROA decode failed: {0}")]
|
||||||
@ -542,11 +351,20 @@ enum ObjectValidateError {
|
|||||||
#[error("EE certificate path validation failed: {0}")]
|
#[error("EE certificate path validation failed: {0}")]
|
||||||
CertPath(#[from] CertPathError),
|
CertPath(#[from] CertPathError),
|
||||||
|
|
||||||
#[error("missing issuer CA certificate for subject DN: {0}")]
|
#[error(
|
||||||
MissingIssuerCaCert(String),
|
"certificate CRLDistributionPoints URIs missing (cannot select issuer CRL) (RFC 6487 §4.8.6)"
|
||||||
|
)]
|
||||||
|
MissingCrlDpUris,
|
||||||
|
|
||||||
#[error("no CRL available for issuer CA")]
|
#[error(
|
||||||
MissingCrl,
|
"no CRL available in verified pack (cannot validate certificates) (RFC 9286 §7; RFC 6487 §4.8.6)"
|
||||||
|
)]
|
||||||
|
MissingCrlInPack,
|
||||||
|
|
||||||
|
#[error(
|
||||||
|
"CRL referenced by CRLDistributionPoints not found in verified pack: {0} (RFC 6487 §4.8.6; RFC 9286 §4.2.1)"
|
||||||
|
)]
|
||||||
|
CrlNotFound(String),
|
||||||
|
|
||||||
#[error(
|
#[error(
|
||||||
"issuer effective IP resources missing (cannot validate EE IP resources subset) (RFC 6487 §7.2; RFC 3779 §2.3)"
|
"issuer effective IP resources missing (cannot validate EE IP resources subset) (RFC 6487 §7.2; RFC 3779 §2.3)"
|
||||||
@ -564,49 +382,11 @@ enum ObjectValidateError {
|
|||||||
EeResourcesNotSubset,
|
EeResourcesNotSubset,
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_roa(
|
|
||||||
file: &PackFile,
|
|
||||||
ca_certs_by_subject: &HashMap<String, Vec<u8>>,
|
|
||||||
issuer_resolver: &dyn IssuerCaCertificateResolver,
|
|
||||||
crl_files: &[(String, Vec<u8>)],
|
|
||||||
validation_time: time::OffsetDateTime,
|
|
||||||
) -> Result<Vec<Vrp>, ObjectValidateError> {
|
|
||||||
let roa = RoaObject::decode_der(&file.bytes)?;
|
|
||||||
roa.validate_embedded_ee_cert()?;
|
|
||||||
roa.signed_object.verify()?;
|
|
||||||
|
|
||||||
let ee_der = &roa.signed_object.signed_data.certificates[0].raw_der;
|
|
||||||
let ee_issuer_dn = roa.signed_object.signed_data.certificates[0]
|
|
||||||
.resource_cert
|
|
||||||
.tbs
|
|
||||||
.issuer_dn
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
let issuer_ca_der = ca_certs_by_subject
|
|
||||||
.get(&ee_issuer_dn)
|
|
||||||
.cloned()
|
|
||||||
.or_else(|| issuer_resolver.resolve_by_subject_dn(&ee_issuer_dn))
|
|
||||||
.ok_or_else(|| ObjectValidateError::MissingIssuerCaCert(ee_issuer_dn.clone()))?;
|
|
||||||
|
|
||||||
let (crl_uri, crl_der) = choose_crl_for_issuer(&issuer_ca_der, crl_files)?;
|
|
||||||
validate_ee_cert_path(
|
|
||||||
ee_der,
|
|
||||||
&issuer_ca_der,
|
|
||||||
&crl_der,
|
|
||||||
None,
|
|
||||||
Some(crl_uri.as_str()),
|
|
||||||
validation_time,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(roa_to_vrps(&roa))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_roa_with_issuer(
|
fn process_roa_with_issuer(
|
||||||
file: &PackFile,
|
file: &PackFile,
|
||||||
issuer_ca_der: &[u8],
|
issuer_ca_der: &[u8],
|
||||||
issuer_crl_der: &[u8],
|
|
||||||
issuer_ca_rsync_uri: Option<&str>,
|
issuer_ca_rsync_uri: Option<&str>,
|
||||||
issuer_crl_rsync_uri: Option<&str>,
|
crl_files: &[(String, Vec<u8>)],
|
||||||
issuer_effective_ip: Option<&crate::data_model::rc::IpResourceSet>,
|
issuer_effective_ip: Option<&crate::data_model::rc::IpResourceSet>,
|
||||||
issuer_effective_as: Option<&crate::data_model::rc::AsResourceSet>,
|
issuer_effective_as: Option<&crate::data_model::rc::AsResourceSet>,
|
||||||
validation_time: time::OffsetDateTime,
|
validation_time: time::OffsetDateTime,
|
||||||
@ -616,70 +396,33 @@ fn process_roa_with_issuer(
|
|||||||
roa.signed_object.verify()?;
|
roa.signed_object.verify()?;
|
||||||
|
|
||||||
let ee_der = &roa.signed_object.signed_data.certificates[0].raw_der;
|
let ee_der = &roa.signed_object.signed_data.certificates[0].raw_der;
|
||||||
|
let ee_crldp_uris = roa.signed_object.signed_data.certificates[0]
|
||||||
|
.resource_cert
|
||||||
|
.tbs
|
||||||
|
.extensions
|
||||||
|
.crl_distribution_points_uris
|
||||||
|
.as_ref();
|
||||||
|
let (issuer_crl_rsync_uri, issuer_crl_der) =
|
||||||
|
choose_crl_for_certificate(ee_crldp_uris, crl_files)?;
|
||||||
let validated = validate_ee_cert_path(
|
let validated = validate_ee_cert_path(
|
||||||
ee_der,
|
ee_der,
|
||||||
issuer_ca_der,
|
issuer_ca_der,
|
||||||
issuer_crl_der,
|
&issuer_crl_der,
|
||||||
issuer_ca_rsync_uri,
|
issuer_ca_rsync_uri,
|
||||||
issuer_crl_rsync_uri,
|
Some(issuer_crl_rsync_uri.as_str()),
|
||||||
validation_time,
|
validation_time,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
validate_ee_resources_subset(
|
validate_ee_resources_subset(&validated.ee, issuer_effective_ip, issuer_effective_as)?;
|
||||||
&validated.ee,
|
|
||||||
issuer_effective_ip,
|
|
||||||
issuer_effective_as,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(roa_to_vrps(&roa))
|
Ok(roa_to_vrps(&roa))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn process_aspa(
|
|
||||||
file: &PackFile,
|
|
||||||
ca_certs_by_subject: &HashMap<String, Vec<u8>>,
|
|
||||||
issuer_resolver: &dyn IssuerCaCertificateResolver,
|
|
||||||
crl_files: &[(String, Vec<u8>)],
|
|
||||||
validation_time: time::OffsetDateTime,
|
|
||||||
) -> Result<AspaAttestation, ObjectValidateError> {
|
|
||||||
let aspa = AspaObject::decode_der(&file.bytes)?;
|
|
||||||
aspa.validate_embedded_ee_cert()?;
|
|
||||||
aspa.signed_object.verify()?;
|
|
||||||
|
|
||||||
let ee_der = &aspa.signed_object.signed_data.certificates[0].raw_der;
|
|
||||||
let ee_issuer_dn = aspa.signed_object.signed_data.certificates[0]
|
|
||||||
.resource_cert
|
|
||||||
.tbs
|
|
||||||
.issuer_dn
|
|
||||||
.clone();
|
|
||||||
|
|
||||||
let issuer_ca_der = ca_certs_by_subject
|
|
||||||
.get(&ee_issuer_dn)
|
|
||||||
.cloned()
|
|
||||||
.or_else(|| issuer_resolver.resolve_by_subject_dn(&ee_issuer_dn))
|
|
||||||
.ok_or_else(|| ObjectValidateError::MissingIssuerCaCert(ee_issuer_dn.clone()))?;
|
|
||||||
|
|
||||||
let (crl_uri, crl_der) = choose_crl_for_issuer(&issuer_ca_der, crl_files)?;
|
|
||||||
validate_ee_cert_path(
|
|
||||||
ee_der,
|
|
||||||
&issuer_ca_der,
|
|
||||||
&crl_der,
|
|
||||||
None,
|
|
||||||
Some(crl_uri.as_str()),
|
|
||||||
validation_time,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(AspaAttestation {
|
|
||||||
customer_as_id: aspa.aspa.customer_as_id,
|
|
||||||
provider_as_ids: aspa.aspa.provider_as_ids.clone(),
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn process_aspa_with_issuer(
|
fn process_aspa_with_issuer(
|
||||||
file: &PackFile,
|
file: &PackFile,
|
||||||
issuer_ca_der: &[u8],
|
issuer_ca_der: &[u8],
|
||||||
issuer_crl_der: &[u8],
|
|
||||||
issuer_ca_rsync_uri: Option<&str>,
|
issuer_ca_rsync_uri: Option<&str>,
|
||||||
issuer_crl_rsync_uri: Option<&str>,
|
crl_files: &[(String, Vec<u8>)],
|
||||||
issuer_effective_ip: Option<&crate::data_model::rc::IpResourceSet>,
|
issuer_effective_ip: Option<&crate::data_model::rc::IpResourceSet>,
|
||||||
issuer_effective_as: Option<&crate::data_model::rc::AsResourceSet>,
|
issuer_effective_as: Option<&crate::data_model::rc::AsResourceSet>,
|
||||||
validation_time: time::OffsetDateTime,
|
validation_time: time::OffsetDateTime,
|
||||||
@ -689,20 +432,24 @@ fn process_aspa_with_issuer(
|
|||||||
aspa.signed_object.verify()?;
|
aspa.signed_object.verify()?;
|
||||||
|
|
||||||
let ee_der = &aspa.signed_object.signed_data.certificates[0].raw_der;
|
let ee_der = &aspa.signed_object.signed_data.certificates[0].raw_der;
|
||||||
|
let ee_crldp_uris = aspa.signed_object.signed_data.certificates[0]
|
||||||
|
.resource_cert
|
||||||
|
.tbs
|
||||||
|
.extensions
|
||||||
|
.crl_distribution_points_uris
|
||||||
|
.as_ref();
|
||||||
|
let (issuer_crl_rsync_uri, issuer_crl_der) =
|
||||||
|
choose_crl_for_certificate(ee_crldp_uris, crl_files)?;
|
||||||
let validated = validate_ee_cert_path(
|
let validated = validate_ee_cert_path(
|
||||||
ee_der,
|
ee_der,
|
||||||
issuer_ca_der,
|
issuer_ca_der,
|
||||||
issuer_crl_der,
|
&issuer_crl_der,
|
||||||
issuer_ca_rsync_uri,
|
issuer_ca_rsync_uri,
|
||||||
issuer_crl_rsync_uri,
|
Some(issuer_crl_rsync_uri.as_str()),
|
||||||
validation_time,
|
validation_time,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
validate_ee_resources_subset(
|
validate_ee_resources_subset(&validated.ee, issuer_effective_ip, issuer_effective_as)?;
|
||||||
&validated.ee,
|
|
||||||
issuer_effective_ip,
|
|
||||||
issuer_effective_as,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
Ok(AspaAttestation {
|
Ok(AspaAttestation {
|
||||||
customer_as_id: aspa.aspa.customer_as_id,
|
customer_as_id: aspa.aspa.customer_as_id,
|
||||||
@ -710,42 +457,31 @@ fn process_aspa_with_issuer(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn choose_crl_for_issuer(
|
fn choose_crl_for_certificate(
|
||||||
issuer_ca_der: &[u8],
|
crldp_uris: Option<&Vec<url::Url>>,
|
||||||
crl_files: &[(String, Vec<u8>)],
|
crl_files: &[(String, Vec<u8>)],
|
||||||
) -> Result<(String, Vec<u8>), ObjectValidateError> {
|
) -> Result<(String, Vec<u8>), ObjectValidateError> {
|
||||||
if crl_files.is_empty() {
|
if crl_files.is_empty() {
|
||||||
return Err(ObjectValidateError::MissingCrl);
|
return Err(ObjectValidateError::MissingCrlInPack);
|
||||||
}
|
}
|
||||||
|
|
||||||
let issuer_tbs = ResourceCertificate::decode_der(issuer_ca_der)
|
let Some(crldp_uris) = crldp_uris else {
|
||||||
.ok()
|
return Err(ObjectValidateError::MissingCrlDpUris);
|
||||||
.map(|c| c.tbs);
|
|
||||||
let Some(issuer_tbs) = issuer_tbs else {
|
|
||||||
return Ok(crl_files[0].clone());
|
|
||||||
};
|
};
|
||||||
|
|
||||||
if let Some(uris) = issuer_tbs.extensions.crl_distribution_points_uris.as_ref() {
|
for u in crldp_uris {
|
||||||
for u in uris {
|
|
||||||
let s = u.as_str();
|
let s = u.as_str();
|
||||||
if let Some((uri, bytes)) = crl_files.iter().find(|(uri, _)| uri.as_str() == s) {
|
if let Some((uri, bytes)) = crl_files.iter().find(|(uri, _)| uri.as_str() == s) {
|
||||||
return Ok((uri.clone(), bytes.clone()));
|
return Ok((uri.clone(), bytes.clone()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
Err(ObjectValidateError::CrlNotFound(
|
||||||
|
crldp_uris
|
||||||
for (uri, bytes) in crl_files {
|
.iter()
|
||||||
let Ok(crl) = crate::data_model::crl::RpkixCrl::decode_der(bytes) else {
|
.map(|u| u.as_str())
|
||||||
continue;
|
.collect::<Vec<_>>()
|
||||||
};
|
.join(", "),
|
||||||
if crl.issuer_dn == issuer_tbs.subject_dn {
|
))
|
||||||
return Ok((uri.clone(), bytes.clone()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Fall back to the first CRL when the pack is incomplete or uses a different DN string
|
|
||||||
// representation. Signature binding is still validated in `validate_ee_cert_path`.
|
|
||||||
Ok(crl_files[0].clone())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_ee_resources_subset(
|
fn validate_ee_resources_subset(
|
||||||
@ -1039,7 +775,10 @@ fn roa_afi_to_string(afi: RoaAfi) -> &'static str {
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::data_model::rc::{Afi, AsIdOrRange, AsIdentifierChoice, IpAddressFamily, IpAddressOrRange, IpAddressRange, IpPrefix, IpResourceSet};
|
use crate::data_model::rc::{
|
||||||
|
Afi, AsIdOrRange, AsIdentifierChoice, IpAddressFamily, IpAddressOrRange, IpAddressRange,
|
||||||
|
IpPrefix, IpResourceSet,
|
||||||
|
};
|
||||||
|
|
||||||
fn fixture_bytes(path: &str) -> Vec<u8> {
|
fn fixture_bytes(path: &str) -> Vec<u8> {
|
||||||
std::fs::read(std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(path))
|
std::fs::read(std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(path))
|
||||||
@ -1056,10 +795,9 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn as_choice_subset_rejects_inherit() {
|
fn as_choice_subset_rejects_inherit() {
|
||||||
let child = Some(&AsIdentifierChoice::Inherit);
|
let child = Some(&AsIdentifierChoice::Inherit);
|
||||||
let parent = Some(&AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Range {
|
let parent = Some(&AsIdentifierChoice::AsIdsOrRanges(vec![
|
||||||
min: 1,
|
AsIdOrRange::Range { min: 1, max: 10 },
|
||||||
max: 10,
|
]));
|
||||||
}]));
|
|
||||||
assert!(!as_choice_subset(child, parent));
|
assert!(!as_choice_subset(child, parent));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1069,10 +807,9 @@ mod tests {
|
|||||||
AsIdOrRange::Id(5),
|
AsIdOrRange::Id(5),
|
||||||
AsIdOrRange::Range { min: 7, max: 9 },
|
AsIdOrRange::Range { min: 7, max: 9 },
|
||||||
]));
|
]));
|
||||||
let parent = Some(&AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Range {
|
let parent = Some(&AsIdentifierChoice::AsIdsOrRanges(vec![
|
||||||
min: 1,
|
AsIdOrRange::Range { min: 1, max: 10 },
|
||||||
max: 10,
|
]));
|
||||||
}]));
|
|
||||||
assert!(as_choice_subset(child, parent));
|
assert!(as_choice_subset(child, parent));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1153,82 +890,84 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn choose_crl_for_issuer_reports_missing_crl() {
|
fn choose_crl_for_certificate_reports_missing_crl_in_pack() {
|
||||||
let issuer_ca_der = fixture_bytes(
|
let roa_der =
|
||||||
"tests/fixtures/repository/ca.rg.net/rpki/RGnet-OU/R-lVU1XGsAeqzV1Fv0HjOD6ZFkE.cer",
|
fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
|
||||||
);
|
let roa = RoaObject::decode_der(&roa_der).expect("decode roa");
|
||||||
let err = choose_crl_for_issuer(&issuer_ca_der, &[]).unwrap_err();
|
let ee_crldp_uris = roa.signed_object.signed_data.certificates[0]
|
||||||
assert!(matches!(err, ObjectValidateError::MissingCrl));
|
.resource_cert
|
||||||
|
.tbs
|
||||||
|
.extensions
|
||||||
|
.crl_distribution_points_uris
|
||||||
|
.as_ref();
|
||||||
|
let err = choose_crl_for_certificate(ee_crldp_uris, &[]).unwrap_err();
|
||||||
|
assert!(matches!(err, ObjectValidateError::MissingCrlInPack));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn choose_crl_for_issuer_falls_back_to_first_when_issuer_ca_is_not_decodable() {
|
fn choose_crl_for_certificate_reports_missing_crldp_uris() {
|
||||||
let invalid_issuer_ca_der = vec![0x01, 0x02, 0x03];
|
|
||||||
let crl_a = ("rsync://example.test/a.crl".to_string(), vec![0x01]);
|
let crl_a = ("rsync://example.test/a.crl".to_string(), vec![0x01]);
|
||||||
let crl_b = ("rsync://example.test/b.crl".to_string(), vec![0x02]);
|
let err = choose_crl_for_certificate(None, &[crl_a]).unwrap_err();
|
||||||
let (uri, bytes) =
|
assert!(matches!(err, ObjectValidateError::MissingCrlDpUris));
|
||||||
choose_crl_for_issuer(&invalid_issuer_ca_der, &[crl_a.clone(), crl_b]).unwrap();
|
|
||||||
assert_eq!(uri, crl_a.0);
|
|
||||||
assert_eq!(bytes, crl_a.1);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn choose_crl_for_issuer_prefers_matching_crldp_uri() {
|
fn choose_crl_for_certificate_prefers_matching_crldp_uri_in_order() {
|
||||||
let issuer_ca_der = fixture_bytes(
|
let roa_der =
|
||||||
"tests/fixtures/repository/ca.rg.net/rpki/RGnet-OU/R-lVU1XGsAeqzV1Fv0HjOD6ZFkE.cer",
|
fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
|
||||||
);
|
let roa = RoaObject::decode_der(&roa_der).expect("decode roa");
|
||||||
let matching_crl_der = fixture_bytes(
|
let ee_crldp_uris = roa.signed_object.signed_data.certificates[0]
|
||||||
"tests/fixtures/repository/ca.rg.net/rpki/RGnet-OU/bW-_qXU9uNhGQz21NR2ansB8lr0.crl",
|
.resource_cert
|
||||||
);
|
.tbs
|
||||||
|
.extensions
|
||||||
|
.crl_distribution_points_uris
|
||||||
|
.as_ref()
|
||||||
|
.expect("fixture ee has crldp");
|
||||||
|
|
||||||
|
// Use two CRLs, only one matches the first CRLDP URI.
|
||||||
let other_crl_der = fixture_bytes(
|
let other_crl_der = fixture_bytes(
|
||||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl",
|
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl",
|
||||||
);
|
);
|
||||||
|
let matching_uri = ee_crldp_uris[0].as_str().to_string();
|
||||||
|
let matching_crl_der = vec![0x01, 0x02, 0x03];
|
||||||
|
|
||||||
let (uri, bytes) = choose_crl_for_issuer(
|
let (uri, bytes) = choose_crl_for_certificate(
|
||||||
&issuer_ca_der,
|
Some(ee_crldp_uris),
|
||||||
&[
|
&[
|
||||||
(
|
("rsync://example.test/other.crl".to_string(), other_crl_der),
|
||||||
"rsync://example.test/other.crl".to_string(),
|
(matching_uri.clone(), matching_crl_der.clone()),
|
||||||
other_crl_der,
|
|
||||||
),
|
|
||||||
(
|
|
||||||
"rsync://ca.rg.net/rpki/RGnet-OU/bW-_qXU9uNhGQz21NR2ansB8lr0.crl".to_string(),
|
|
||||||
matching_crl_der.clone(),
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(uri, matching_uri);
|
||||||
uri,
|
|
||||||
"rsync://ca.rg.net/rpki/RGnet-OU/bW-_qXU9uNhGQz21NR2ansB8lr0.crl"
|
|
||||||
);
|
|
||||||
assert_eq!(bytes, matching_crl_der);
|
assert_eq!(bytes, matching_crl_der);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn choose_crl_for_issuer_falls_back_to_first_when_no_dn_match() {
|
fn choose_crl_for_certificate_reports_not_found_when_crldp_does_not_match_pack() {
|
||||||
let issuer_ca_der = fixture_bytes(
|
let roa_der =
|
||||||
"tests/fixtures/repository/ca.rg.net/rpki/RGnet-OU/R-lVU1XGsAeqzV1Fv0HjOD6ZFkE.cer",
|
fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
|
||||||
);
|
let roa = RoaObject::decode_der(&roa_der).expect("decode roa");
|
||||||
|
let ee_crldp_uris = roa.signed_object.signed_data.certificates[0]
|
||||||
|
.resource_cert
|
||||||
|
.tbs
|
||||||
|
.extensions
|
||||||
|
.crl_distribution_points_uris
|
||||||
|
.as_ref();
|
||||||
|
|
||||||
let (uri, bytes) = choose_crl_for_issuer(
|
let err = choose_crl_for_certificate(
|
||||||
&issuer_ca_der,
|
ee_crldp_uris,
|
||||||
&[
|
&[("rsync://example.test/other.crl".to_string(), vec![0x01])],
|
||||||
("rsync://example.test/a.crl".to_string(), vec![0x01]),
|
|
||||||
("rsync://example.test/b.crl".to_string(), vec![0x02]),
|
|
||||||
],
|
|
||||||
)
|
)
|
||||||
.unwrap();
|
.unwrap_err();
|
||||||
assert_eq!(uri, "rsync://example.test/a.crl");
|
assert!(matches!(err, ObjectValidateError::CrlNotFound(_)));
|
||||||
assert_eq!(bytes, vec![0x01]);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn validate_ee_resources_subset_reports_missing_issuer_effective_ip() {
|
fn validate_ee_resources_subset_reports_missing_issuer_effective_ip() {
|
||||||
let roa_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
let roa_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa",
|
.join("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
|
||||||
);
|
|
||||||
let roa_der = std::fs::read(roa_path).expect("read roa");
|
let roa_der = std::fs::read(roa_path).expect("read roa");
|
||||||
let roa = RoaObject::decode_der(&roa_der).expect("decode roa");
|
let roa = RoaObject::decode_der(&roa_der).expect("decode roa");
|
||||||
let ee = &roa.signed_object.signed_data.certificates[0].resource_cert;
|
let ee = &roa.signed_object.signed_data.certificates[0].resource_cert;
|
||||||
@ -1265,9 +1004,8 @@ mod tests {
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn validate_ee_resources_subset_reports_not_subset() {
|
fn validate_ee_resources_subset_reports_not_subset() {
|
||||||
let roa_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
let roa_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa",
|
.join("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
|
||||||
);
|
|
||||||
let roa_der = std::fs::read(roa_path).expect("read roa");
|
let roa_der = std::fs::read(roa_path).expect("read roa");
|
||||||
let roa = RoaObject::decode_der(&roa_der).expect("decode roa");
|
let roa = RoaObject::decode_der(&roa_der).expect("decode roa");
|
||||||
let ee = &roa.signed_object.signed_data.certificates[0].resource_cert;
|
let ee = &roa.signed_object.signed_data.certificates[0].resource_cert;
|
||||||
@ -1286,8 +1024,7 @@ mod tests {
|
|||||||
}],
|
}],
|
||||||
};
|
};
|
||||||
|
|
||||||
let err =
|
let err = validate_ee_resources_subset(ee, Some(&issuer_ip), None).unwrap_err();
|
||||||
validate_ee_resources_subset(ee, Some(&issuer_ip), None).unwrap_err();
|
|
||||||
assert!(matches!(err, ObjectValidateError::EeResourcesNotSubset));
|
assert!(matches!(err, ObjectValidateError::EeResourcesNotSubset));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
use crate::data_model::rc::{AsResourceSet, IpResourceSet};
|
||||||
use crate::fetch::rsync::RsyncFetcher;
|
use crate::fetch::rsync::RsyncFetcher;
|
||||||
use crate::policy::Policy;
|
use crate::policy::Policy;
|
||||||
use crate::storage::{RocksStore, VerifiedKey};
|
use crate::storage::{RocksStore, VerifiedKey};
|
||||||
@ -5,8 +6,7 @@ use crate::sync::repo::{RepoSyncResult, sync_publication_point};
|
|||||||
use crate::sync::rrdp::Fetcher as HttpFetcher;
|
use crate::sync::rrdp::Fetcher as HttpFetcher;
|
||||||
use crate::validation::manifest::{PublicationPointResult, process_manifest_publication_point};
|
use crate::validation::manifest::{PublicationPointResult, process_manifest_publication_point};
|
||||||
use crate::validation::objects::{
|
use crate::validation::objects::{
|
||||||
IssuerCaCertificateResolver, ObjectsOutput, ObjectsProcessError,
|
ObjectsOutput, process_verified_publication_point_pack_for_issuer,
|
||||||
process_verified_publication_point_pack,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
@ -23,9 +23,6 @@ pub enum RunError {
|
|||||||
|
|
||||||
#[error("manifest processing failed: {0}")]
|
#[error("manifest processing failed: {0}")]
|
||||||
Manifest(#[from] crate::validation::manifest::ManifestProcessError),
|
Manifest(#[from] crate::validation::manifest::ManifestProcessError),
|
||||||
|
|
||||||
#[error("objects processing failed: {0}")]
|
|
||||||
Objects(#[from] ObjectsProcessError),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// v1 serial offline-friendly end-to-end execution for a single publication point.
|
/// v1 serial offline-friendly end-to-end execution for a single publication point.
|
||||||
@ -43,7 +40,10 @@ pub fn run_publication_point_once(
|
|||||||
publication_point_rsync_uri: &str,
|
publication_point_rsync_uri: &str,
|
||||||
http_fetcher: &dyn HttpFetcher,
|
http_fetcher: &dyn HttpFetcher,
|
||||||
rsync_fetcher: &dyn RsyncFetcher,
|
rsync_fetcher: &dyn RsyncFetcher,
|
||||||
issuer_resolver: &dyn IssuerCaCertificateResolver,
|
issuer_ca_der: &[u8],
|
||||||
|
issuer_ca_rsync_uri: Option<&str>,
|
||||||
|
issuer_effective_ip: Option<&IpResourceSet>,
|
||||||
|
issuer_effective_as: Option<&AsResourceSet>,
|
||||||
validation_time: time::OffsetDateTime,
|
validation_time: time::OffsetDateTime,
|
||||||
) -> Result<RunOutput, RunError> {
|
) -> Result<RunOutput, RunError> {
|
||||||
let repo_sync = sync_publication_point(
|
let repo_sync = sync_publication_point(
|
||||||
@ -63,12 +63,15 @@ pub fn run_publication_point_once(
|
|||||||
validation_time,
|
validation_time,
|
||||||
)?;
|
)?;
|
||||||
|
|
||||||
let objects = process_verified_publication_point_pack(
|
let objects = process_verified_publication_point_pack_for_issuer(
|
||||||
&publication_point.pack,
|
&publication_point.pack,
|
||||||
policy,
|
policy,
|
||||||
issuer_resolver,
|
issuer_ca_der,
|
||||||
|
issuer_ca_rsync_uri,
|
||||||
|
issuer_effective_ip,
|
||||||
|
issuer_effective_as,
|
||||||
validation_time,
|
validation_time,
|
||||||
)?;
|
);
|
||||||
|
|
||||||
Ok(RunOutput {
|
Ok(RunOutput {
|
||||||
repo_sync,
|
repo_sync,
|
||||||
|
|||||||
@ -1,13 +1,16 @@
|
|||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
|
use crate::audit::PublicationPointAudit;
|
||||||
use crate::data_model::ta::TrustAnchor;
|
use crate::data_model::ta::TrustAnchor;
|
||||||
use crate::sync::rrdp::Fetcher;
|
use crate::sync::rrdp::Fetcher;
|
||||||
use crate::audit::PublicationPointAudit;
|
|
||||||
use crate::validation::from_tal::{
|
use crate::validation::from_tal::{
|
||||||
DiscoveredRootCaInstance, FromTalError, discover_root_ca_instance_from_tal_and_ta_der,
|
DiscoveredRootCaInstance, FromTalError, discover_root_ca_instance_from_tal_and_ta_der,
|
||||||
discover_root_ca_instance_from_tal_url,
|
discover_root_ca_instance_from_tal_url,
|
||||||
};
|
};
|
||||||
use crate::validation::tree::{CaInstanceHandle, TreeRunConfig, TreeRunError, TreeRunOutput, run_tree_serial};
|
use crate::validation::tree::{
|
||||||
|
CaInstanceHandle, TreeRunAuditOutput, TreeRunConfig, TreeRunError, TreeRunOutput,
|
||||||
|
run_tree_serial, run_tree_serial_audit,
|
||||||
|
};
|
||||||
use crate::validation::tree_runner::Rpkiv1PublicationPointRunner;
|
use crate::validation::tree_runner::Rpkiv1PublicationPointRunner;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
@ -95,26 +98,11 @@ pub fn run_tree_from_tal_url_serial_audit(
|
|||||||
validation_time,
|
validation_time,
|
||||||
};
|
};
|
||||||
|
|
||||||
let audits: std::cell::RefCell<Vec<PublicationPointAudit>> = std::cell::RefCell::new(Vec::new());
|
|
||||||
struct AuditingRunner<'a> {
|
|
||||||
inner: &'a Rpkiv1PublicationPointRunner<'a>,
|
|
||||||
audits: &'a std::cell::RefCell<Vec<PublicationPointAudit>>,
|
|
||||||
}
|
|
||||||
impl<'a> crate::validation::tree::PublicationPointRunner for AuditingRunner<'a> {
|
|
||||||
fn run_publication_point(
|
|
||||||
&self,
|
|
||||||
ca: &crate::validation::tree::CaInstanceHandle,
|
|
||||||
) -> Result<crate::validation::tree::PublicationPointRunResult, String> {
|
|
||||||
let res = self.inner.run_publication_point(ca)?;
|
|
||||||
self.audits.borrow_mut().push(res.audit.clone());
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let root = root_handle_from_trust_anchor(&discovery.trust_anchor, None, &discovery.ca_instance);
|
let root = root_handle_from_trust_anchor(&discovery.trust_anchor, None, &discovery.ca_instance);
|
||||||
let auditing_runner = AuditingRunner { inner: &runner, audits: &audits };
|
let TreeRunAuditOutput {
|
||||||
let tree = run_tree_serial(root, &auditing_runner, config)?;
|
tree,
|
||||||
let publication_points = audits.into_inner();
|
publication_points,
|
||||||
|
} = run_tree_serial_audit(root, &runner, config)?;
|
||||||
|
|
||||||
Ok(RunTreeFromTalAuditOutput {
|
Ok(RunTreeFromTalAuditOutput {
|
||||||
discovery,
|
discovery,
|
||||||
@ -173,26 +161,11 @@ pub fn run_tree_from_tal_and_ta_der_serial_audit(
|
|||||||
validation_time,
|
validation_time,
|
||||||
};
|
};
|
||||||
|
|
||||||
let audits: std::cell::RefCell<Vec<PublicationPointAudit>> = std::cell::RefCell::new(Vec::new());
|
|
||||||
struct AuditingRunner<'a> {
|
|
||||||
inner: &'a Rpkiv1PublicationPointRunner<'a>,
|
|
||||||
audits: &'a std::cell::RefCell<Vec<PublicationPointAudit>>,
|
|
||||||
}
|
|
||||||
impl<'a> crate::validation::tree::PublicationPointRunner for AuditingRunner<'a> {
|
|
||||||
fn run_publication_point(
|
|
||||||
&self,
|
|
||||||
ca: &crate::validation::tree::CaInstanceHandle,
|
|
||||||
) -> Result<crate::validation::tree::PublicationPointRunResult, String> {
|
|
||||||
let res = self.inner.run_publication_point(ca)?;
|
|
||||||
self.audits.borrow_mut().push(res.audit.clone());
|
|
||||||
Ok(res)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let root = root_handle_from_trust_anchor(&discovery.trust_anchor, None, &discovery.ca_instance);
|
let root = root_handle_from_trust_anchor(&discovery.trust_anchor, None, &discovery.ca_instance);
|
||||||
let auditing_runner = AuditingRunner { inner: &runner, audits: &audits };
|
let TreeRunAuditOutput {
|
||||||
let tree = run_tree_serial(root, &auditing_runner, config)?;
|
tree,
|
||||||
let publication_points = audits.into_inner();
|
publication_points,
|
||||||
|
} = run_tree_serial_audit(root, &runner, config)?;
|
||||||
|
|
||||||
Ok(RunTreeFromTalAuditOutput {
|
Ok(RunTreeFromTalAuditOutput {
|
||||||
discovery,
|
discovery,
|
||||||
|
|||||||
@ -1,8 +1,9 @@
|
|||||||
|
use crate::audit::DiscoveredFrom;
|
||||||
|
use crate::audit::PublicationPointAudit;
|
||||||
|
use crate::data_model::rc::{AsResourceSet, IpResourceSet};
|
||||||
use crate::report::{RfcRef, Warning};
|
use crate::report::{RfcRef, Warning};
|
||||||
use crate::storage::VerifiedPublicationPointPack;
|
use crate::storage::VerifiedPublicationPointPack;
|
||||||
use crate::validation::manifest::PublicationPointSource;
|
use crate::validation::manifest::PublicationPointSource;
|
||||||
use crate::audit::PublicationPointAudit;
|
|
||||||
use crate::data_model::rc::{AsResourceSet, IpResourceSet};
|
|
||||||
use crate::validation::objects::{AspaAttestation, ObjectsOutput, Vrp};
|
use crate::validation::objects::{AspaAttestation, ObjectsOutput, Vrp};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
@ -62,7 +63,13 @@ pub struct PublicationPointRunResult {
|
|||||||
/// RFC 9286 §6.6 restriction is enforced by the tree engine: if this
|
/// 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
|
/// publication point used verified cache due to failed fetch, children MUST NOT
|
||||||
/// be enqueued/processed in this run.
|
/// be enqueued/processed in this run.
|
||||||
pub discovered_children: Vec<CaInstanceHandle>,
|
pub discovered_children: Vec<DiscoveredChildCaInstance>,
|
||||||
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct DiscoveredChildCaInstance {
|
||||||
|
pub handle: CaInstanceHandle,
|
||||||
|
pub discovered_from: DiscoveredFrom,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
@ -87,22 +94,54 @@ pub trait PublicationPointRunner {
|
|||||||
) -> Result<PublicationPointRunResult, String>;
|
) -> Result<PublicationPointRunResult, String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
|
pub struct TreeRunAuditOutput {
|
||||||
|
pub tree: TreeRunOutput,
|
||||||
|
pub publication_points: Vec<PublicationPointAudit>,
|
||||||
|
}
|
||||||
|
|
||||||
pub fn run_tree_serial(
|
pub fn run_tree_serial(
|
||||||
root: CaInstanceHandle,
|
root: CaInstanceHandle,
|
||||||
runner: &dyn PublicationPointRunner,
|
runner: &dyn PublicationPointRunner,
|
||||||
config: &TreeRunConfig,
|
config: &TreeRunConfig,
|
||||||
) -> Result<TreeRunOutput, TreeRunError> {
|
) -> Result<TreeRunOutput, TreeRunError> {
|
||||||
let mut queue: std::collections::VecDeque<CaInstanceHandle> = std::collections::VecDeque::new();
|
Ok(run_tree_serial_audit(root, runner, config)?.tree)
|
||||||
queue.push_back(root);
|
}
|
||||||
|
|
||||||
let mut visited_manifest_uris: std::collections::HashSet<String> = std::collections::HashSet::new();
|
pub fn run_tree_serial_audit(
|
||||||
|
root: CaInstanceHandle,
|
||||||
|
runner: &dyn PublicationPointRunner,
|
||||||
|
config: &TreeRunConfig,
|
||||||
|
) -> Result<TreeRunAuditOutput, TreeRunError> {
|
||||||
|
#[derive(Clone, Debug)]
|
||||||
|
struct QueuedCaInstance {
|
||||||
|
id: u64,
|
||||||
|
handle: CaInstanceHandle,
|
||||||
|
parent_id: Option<u64>,
|
||||||
|
discovered_from: Option<DiscoveredFrom>,
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut next_id: u64 = 0;
|
||||||
|
let mut queue: std::collections::VecDeque<QueuedCaInstance> = std::collections::VecDeque::new();
|
||||||
|
queue.push_back(QueuedCaInstance {
|
||||||
|
id: next_id,
|
||||||
|
handle: root,
|
||||||
|
parent_id: None,
|
||||||
|
discovered_from: None,
|
||||||
|
});
|
||||||
|
next_id += 1;
|
||||||
|
|
||||||
|
let mut visited_manifest_uris: std::collections::HashSet<String> =
|
||||||
|
std::collections::HashSet::new();
|
||||||
let mut instances_processed = 0usize;
|
let mut instances_processed = 0usize;
|
||||||
let mut instances_failed = 0usize;
|
let mut instances_failed = 0usize;
|
||||||
let mut warnings: Vec<Warning> = Vec::new();
|
let mut warnings: Vec<Warning> = Vec::new();
|
||||||
let mut vrps: Vec<Vrp> = Vec::new();
|
let mut vrps: Vec<Vrp> = Vec::new();
|
||||||
let mut aspas: Vec<AspaAttestation> = Vec::new();
|
let mut aspas: Vec<AspaAttestation> = Vec::new();
|
||||||
|
let mut publication_points: Vec<PublicationPointAudit> = Vec::new();
|
||||||
|
|
||||||
while let Some(ca) = queue.pop_front() {
|
while let Some(node) = queue.pop_front() {
|
||||||
|
let ca = &node.handle;
|
||||||
if !visited_manifest_uris.insert(ca.manifest_rsync_uri.clone()) {
|
if !visited_manifest_uris.insert(ca.manifest_rsync_uri.clone()) {
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -119,7 +158,7 @@ pub fn run_tree_serial(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let res = match runner.run_publication_point(&ca) {
|
let res = match runner.run_publication_point(ca) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
instances_failed += 1;
|
instances_failed += 1;
|
||||||
@ -137,6 +176,12 @@ pub fn run_tree_serial(
|
|||||||
vrps.extend(res.objects.vrps.clone());
|
vrps.extend(res.objects.vrps.clone());
|
||||||
aspas.extend(res.objects.aspas.clone());
|
aspas.extend(res.objects.aspas.clone());
|
||||||
|
|
||||||
|
let mut audit = res.audit.clone();
|
||||||
|
audit.node_id = Some(node.id);
|
||||||
|
audit.parent_node_id = node.parent_id;
|
||||||
|
audit.discovered_from = node.discovered_from.clone();
|
||||||
|
publication_points.push(audit);
|
||||||
|
|
||||||
let enqueue_children = res.source == PublicationPointSource::Fresh;
|
let enqueue_children = res.source == PublicationPointSource::Fresh;
|
||||||
if !enqueue_children && !res.discovered_children.is_empty() {
|
if !enqueue_children && !res.discovered_children.is_empty() {
|
||||||
warnings.push(
|
warnings.push(
|
||||||
@ -147,17 +192,37 @@ pub fn run_tree_serial(
|
|||||||
}
|
}
|
||||||
|
|
||||||
if enqueue_children {
|
if enqueue_children {
|
||||||
for child in res.discovered_children {
|
let mut children = res.discovered_children;
|
||||||
queue.push_back(child.with_depth(ca.depth + 1));
|
children.sort_by(|a, b| {
|
||||||
|
a.handle
|
||||||
|
.manifest_rsync_uri
|
||||||
|
.cmp(&b.handle.manifest_rsync_uri)
|
||||||
|
.then_with(|| {
|
||||||
|
a.discovered_from
|
||||||
|
.child_ca_certificate_rsync_uri
|
||||||
|
.cmp(&b.discovered_from.child_ca_certificate_rsync_uri)
|
||||||
|
})
|
||||||
|
});
|
||||||
|
for child in children {
|
||||||
|
queue.push_back(QueuedCaInstance {
|
||||||
|
id: next_id,
|
||||||
|
handle: child.handle.with_depth(ca.depth + 1),
|
||||||
|
parent_id: Some(node.id),
|
||||||
|
discovered_from: Some(child.discovered_from),
|
||||||
|
});
|
||||||
|
next_id += 1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(TreeRunOutput {
|
Ok(TreeRunAuditOutput {
|
||||||
|
tree: TreeRunOutput {
|
||||||
instances_processed,
|
instances_processed,
|
||||||
instances_failed,
|
instances_failed,
|
||||||
warnings,
|
warnings,
|
||||||
vrps,
|
vrps,
|
||||||
aspas,
|
aspas,
|
||||||
|
},
|
||||||
|
publication_points,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,18 +1,20 @@
|
|||||||
|
use crate::audit::{
|
||||||
|
AuditObjectKind, AuditObjectResult, AuditWarning, ObjectAuditEntry, PublicationPointAudit,
|
||||||
|
sha256_hex, sha256_hex_from_32,
|
||||||
|
};
|
||||||
use crate::fetch::rsync::RsyncFetcher;
|
use crate::fetch::rsync::RsyncFetcher;
|
||||||
use crate::policy::Policy;
|
use crate::policy::Policy;
|
||||||
use crate::report::{RfcRef, Warning};
|
use crate::report::{RfcRef, Warning};
|
||||||
use crate::storage::RocksStore;
|
use crate::storage::RocksStore;
|
||||||
use crate::sync::repo::sync_publication_point;
|
use crate::sync::repo::sync_publication_point;
|
||||||
use crate::sync::rrdp::Fetcher;
|
use crate::sync::rrdp::Fetcher;
|
||||||
use crate::audit::{
|
|
||||||
AuditObjectKind, AuditObjectResult, AuditWarning, ObjectAuditEntry, PublicationPointAudit,
|
|
||||||
sha256_hex, sha256_hex_from_32,
|
|
||||||
};
|
|
||||||
use crate::validation::ca_instance::ca_instance_uris_from_ca_certificate;
|
use crate::validation::ca_instance::ca_instance_uris_from_ca_certificate;
|
||||||
use crate::validation::ca_path::{CaPathError, validate_subordinate_ca_cert};
|
use crate::validation::ca_path::{CaPathError, validate_subordinate_ca_cert};
|
||||||
use crate::validation::manifest::{PublicationPointSource, process_manifest_publication_point};
|
use crate::validation::manifest::{PublicationPointSource, process_manifest_publication_point};
|
||||||
use crate::validation::objects::process_verified_publication_point_pack_for_issuer;
|
use crate::validation::objects::process_verified_publication_point_pack_for_issuer;
|
||||||
use crate::validation::tree::{CaInstanceHandle, PublicationPointRunResult, PublicationPointRunner};
|
use crate::validation::tree::{
|
||||||
|
CaInstanceHandle, DiscoveredChildCaInstance, PublicationPointRunResult, PublicationPointRunner,
|
||||||
|
};
|
||||||
|
|
||||||
pub struct Rpkiv1PublicationPointRunner<'a> {
|
pub struct Rpkiv1PublicationPointRunner<'a> {
|
||||||
pub store: &'a RocksStore,
|
pub store: &'a RocksStore,
|
||||||
@ -38,7 +40,9 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
|
|||||||
self.rsync_fetcher,
|
self.rsync_fetcher,
|
||||||
) {
|
) {
|
||||||
warnings.push(
|
warnings.push(
|
||||||
Warning::new(format!("repo sync failed (continuing with cached/raw data): {e}"))
|
Warning::new(format!(
|
||||||
|
"repo sync failed (continuing with cached/raw data): {e}"
|
||||||
|
))
|
||||||
.with_rfc_refs(&[RfcRef("RFC 8182 §3.4.5"), RfcRef("RFC 9286 §6.6")])
|
.with_rfc_refs(&[RfcRef("RFC 8182 §3.4.5"), RfcRef("RFC 9286 §6.6")])
|
||||||
.with_context(&ca.rsync_base_uri),
|
.with_context(&ca.rsync_base_uri),
|
||||||
);
|
);
|
||||||
@ -97,7 +101,7 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
struct ChildDiscoveryOutput {
|
struct ChildDiscoveryOutput {
|
||||||
children: Vec<CaInstanceHandle>,
|
children: Vec<DiscoveredChildCaInstance>,
|
||||||
audits: Vec<ObjectAuditEntry>,
|
audits: Vec<ObjectAuditEntry>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -107,9 +111,8 @@ fn discover_children_from_fresh_pack_with_audit(
|
|||||||
validation_time: time::OffsetDateTime,
|
validation_time: time::OffsetDateTime,
|
||||||
) -> Result<ChildDiscoveryOutput, String> {
|
) -> Result<ChildDiscoveryOutput, String> {
|
||||||
let issuer_ca_der = issuer.ca_certificate_der.as_slice();
|
let issuer_ca_der = issuer.ca_certificate_der.as_slice();
|
||||||
let (issuer_crl_uri, issuer_crl_der) = select_issuer_crl_from_pack(issuer, pack)?;
|
|
||||||
|
|
||||||
let mut out: Vec<CaInstanceHandle> = Vec::new();
|
let mut out: Vec<DiscoveredChildCaInstance> = Vec::new();
|
||||||
let mut audits: Vec<ObjectAuditEntry> = Vec::new();
|
let mut audits: Vec<ObjectAuditEntry> = Vec::new();
|
||||||
for f in &pack.files {
|
for f in &pack.files {
|
||||||
if !f.rsync_uri.ends_with(".cer") {
|
if !f.rsync_uri.ends_with(".cer") {
|
||||||
@ -117,6 +120,22 @@ fn discover_children_from_fresh_pack_with_audit(
|
|||||||
}
|
}
|
||||||
let child_der = f.bytes.as_slice();
|
let child_der = f.bytes.as_slice();
|
||||||
|
|
||||||
|
let (issuer_crl_uri, issuer_crl_der) = match select_issuer_crl_from_pack(child_der, pack) {
|
||||||
|
Ok(v) => v,
|
||||||
|
Err(e) => {
|
||||||
|
audits.push(ObjectAuditEntry {
|
||||||
|
rsync_uri: f.rsync_uri.clone(),
|
||||||
|
sha256_hex: sha256_hex_from_32(&f.sha256),
|
||||||
|
kind: AuditObjectKind::Certificate,
|
||||||
|
result: AuditObjectResult::Error,
|
||||||
|
detail: Some(format!(
|
||||||
|
"cannot select issuer CRL for child certificate: {e}"
|
||||||
|
)),
|
||||||
|
});
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
let validated = match validate_subordinate_ca_cert(
|
let validated = match validate_subordinate_ca_cert(
|
||||||
child_der,
|
child_der,
|
||||||
issuer_ca_der,
|
issuer_ca_der,
|
||||||
@ -144,7 +163,7 @@ fn discover_children_from_fresh_pack_with_audit(
|
|||||||
sha256_hex: sha256_hex_from_32(&f.sha256),
|
sha256_hex: sha256_hex_from_32(&f.sha256),
|
||||||
kind: AuditObjectKind::Certificate,
|
kind: AuditObjectKind::Certificate,
|
||||||
result: AuditObjectResult::Error,
|
result: AuditObjectResult::Error,
|
||||||
detail: Some(e.to_string()),
|
detail: Some(format!("child CA validation failed: {e}")),
|
||||||
});
|
});
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
@ -164,7 +183,8 @@ fn discover_children_from_fresh_pack_with_audit(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
out.push(CaInstanceHandle {
|
out.push(DiscoveredChildCaInstance {
|
||||||
|
handle: CaInstanceHandle {
|
||||||
depth: 0,
|
depth: 0,
|
||||||
ca_certificate_der: child_der.to_vec(),
|
ca_certificate_der: child_der.to_vec(),
|
||||||
ca_certificate_rsync_uri: Some(f.rsync_uri.clone()),
|
ca_certificate_rsync_uri: Some(f.rsync_uri.clone()),
|
||||||
@ -174,6 +194,12 @@ fn discover_children_from_fresh_pack_with_audit(
|
|||||||
manifest_rsync_uri: uris.manifest_rsync_uri,
|
manifest_rsync_uri: uris.manifest_rsync_uri,
|
||||||
publication_point_rsync_uri: uris.publication_point_rsync_uri,
|
publication_point_rsync_uri: uris.publication_point_rsync_uri,
|
||||||
rrdp_notification_uri: uris.rrdp_notification_uri,
|
rrdp_notification_uri: uris.rrdp_notification_uri,
|
||||||
|
},
|
||||||
|
discovered_from: crate::audit::DiscoveredFrom {
|
||||||
|
parent_manifest_rsync_uri: issuer.manifest_rsync_uri.clone(),
|
||||||
|
child_ca_certificate_rsync_uri: f.rsync_uri.clone(),
|
||||||
|
child_ca_certificate_sha256_hex: sha256_hex_from_32(&f.sha256),
|
||||||
|
},
|
||||||
});
|
});
|
||||||
|
|
||||||
audits.push(ObjectAuditEntry {
|
audits.push(ObjectAuditEntry {
|
||||||
@ -185,38 +211,39 @@ fn discover_children_from_fresh_pack_with_audit(
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
Ok(ChildDiscoveryOutput { children: out, audits })
|
Ok(ChildDiscoveryOutput {
|
||||||
|
children: out,
|
||||||
|
audits,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn select_issuer_crl_from_pack<'a>(
|
fn select_issuer_crl_from_pack<'a>(
|
||||||
issuer: &CaInstanceHandle,
|
child_cert_der: &[u8],
|
||||||
pack: &'a crate::storage::VerifiedPublicationPointPack,
|
pack: &'a crate::storage::VerifiedPublicationPointPack,
|
||||||
) -> Result<(&'a str, &'a [u8]), String> {
|
) -> Result<(&'a str, &'a [u8]), String> {
|
||||||
let issuer_ca = crate::data_model::rc::ResourceCertificate::decode_der(&issuer.ca_certificate_der)
|
let child = crate::data_model::rc::ResourceCertificate::decode_der(child_cert_der)
|
||||||
.map_err(|e| e.to_string())?;
|
.map_err(|e| format!("child certificate decode failed: {e}"))?;
|
||||||
let subject_dn = issuer_ca.tbs.subject_dn;
|
let Some(crldp_uris) = child.tbs.extensions.crl_distribution_points_uris.as_ref() else {
|
||||||
|
return Err(
|
||||||
|
"child certificate CRLDistributionPoints missing (RFC 6487 §4.8.6)".to_string(),
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
if let Some(uris) = issuer_ca.tbs.extensions.crl_distribution_points_uris.as_ref() {
|
for u in crldp_uris {
|
||||||
for u in uris {
|
|
||||||
let s = u.as_str();
|
let s = u.as_str();
|
||||||
if let Some(f) = pack.files.iter().find(|f| f.rsync_uri == s) {
|
if let Some(f) = pack.files.iter().find(|f| f.rsync_uri == s) {
|
||||||
return Ok((f.rsync_uri.as_str(), f.bytes.as_slice()));
|
return Ok((f.rsync_uri.as_str(), f.bytes.as_slice()));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
for f in &pack.files {
|
Err(format!(
|
||||||
if !f.rsync_uri.ends_with(".crl") {
|
"CRL referenced by child certificate CRLDistributionPoints not found in verified pack: {} (RFC 6487 §4.8.6; RFC 9286 §4.2.1)",
|
||||||
continue;
|
crldp_uris
|
||||||
}
|
.iter()
|
||||||
let Ok(crl) = crate::data_model::crl::RpkixCrl::decode_der(&f.bytes) else {
|
.map(|u| u.as_str())
|
||||||
continue;
|
.collect::<Vec<_>>()
|
||||||
};
|
.join(", ")
|
||||||
if crl.issuer_dn == subject_dn {
|
))
|
||||||
return Ok((f.rsync_uri.as_str(), f.bytes.as_slice()));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
Err("issuer CRL not found in verified pack (RFC 9286 §7)".to_string())
|
|
||||||
}
|
}
|
||||||
|
|
||||||
fn kind_from_rsync_uri(uri: &str) -> AuditObjectKind {
|
fn kind_from_rsync_uri(uri: &str) -> AuditObjectKind {
|
||||||
@ -323,6 +350,9 @@ fn build_publication_point_audit(
|
|||||||
warnings.extend(objects.warnings.iter().map(AuditWarning::from));
|
warnings.extend(objects.warnings.iter().map(AuditWarning::from));
|
||||||
|
|
||||||
PublicationPointAudit {
|
PublicationPointAudit {
|
||||||
|
node_id: None,
|
||||||
|
parent_node_id: None,
|
||||||
|
discovered_from: None,
|
||||||
rsync_base_uri: ca.rsync_base_uri.clone(),
|
rsync_base_uri: ca.rsync_base_uri.clone(),
|
||||||
manifest_rsync_uri: ca.manifest_rsync_uri.clone(),
|
manifest_rsync_uri: ca.manifest_rsync_uri.clone(),
|
||||||
publication_point_rsync_uri: ca.publication_point_rsync_uri.clone(),
|
publication_point_rsync_uri: ca.publication_point_rsync_uri.clone(),
|
||||||
@ -455,15 +485,12 @@ authorityKeyIdentifier = keyid:always
|
|||||||
);
|
);
|
||||||
std::fs::write(dir.join("openssl.cnf"), cnf.as_bytes()).expect("write cnf");
|
std::fs::write(dir.join("openssl.cnf"), cnf.as_bytes()).expect("write cnf");
|
||||||
|
|
||||||
run(
|
run(Command::new("openssl")
|
||||||
Command::new("openssl")
|
|
||||||
.arg("genrsa")
|
.arg("genrsa")
|
||||||
.arg("-out")
|
.arg("-out")
|
||||||
.arg(dir.join("issuer.key"))
|
.arg(dir.join("issuer.key"))
|
||||||
.arg("2048"),
|
.arg("2048"));
|
||||||
);
|
run(Command::new("openssl")
|
||||||
run(
|
|
||||||
Command::new("openssl")
|
|
||||||
.arg("req")
|
.arg("req")
|
||||||
.arg("-new")
|
.arg("-new")
|
||||||
.arg("-x509")
|
.arg("-x509")
|
||||||
@ -477,18 +504,14 @@ authorityKeyIdentifier = keyid:always
|
|||||||
.arg("-extensions")
|
.arg("-extensions")
|
||||||
.arg("v3_issuer_ca")
|
.arg("v3_issuer_ca")
|
||||||
.arg("-out")
|
.arg("-out")
|
||||||
.arg(dir.join("issuer.pem")),
|
.arg(dir.join("issuer.pem")));
|
||||||
);
|
|
||||||
|
|
||||||
run(
|
run(Command::new("openssl")
|
||||||
Command::new("openssl")
|
|
||||||
.arg("genrsa")
|
.arg("genrsa")
|
||||||
.arg("-out")
|
.arg("-out")
|
||||||
.arg(dir.join("child.key"))
|
.arg(dir.join("child.key"))
|
||||||
.arg("2048"),
|
.arg("2048"));
|
||||||
);
|
run(Command::new("openssl")
|
||||||
run(
|
|
||||||
Command::new("openssl")
|
|
||||||
.arg("req")
|
.arg("req")
|
||||||
.arg("-new")
|
.arg("-new")
|
||||||
.arg("-key")
|
.arg("-key")
|
||||||
@ -496,11 +519,9 @@ authorityKeyIdentifier = keyid:always
|
|||||||
.arg("-subj")
|
.arg("-subj")
|
||||||
.arg("/CN=Test Child CA")
|
.arg("/CN=Test Child CA")
|
||||||
.arg("-out")
|
.arg("-out")
|
||||||
.arg(dir.join("child.csr")),
|
.arg(dir.join("child.csr")));
|
||||||
);
|
|
||||||
|
|
||||||
run(
|
run(Command::new("openssl")
|
||||||
Command::new("openssl")
|
|
||||||
.arg("ca")
|
.arg("ca")
|
||||||
.arg("-batch")
|
.arg("-batch")
|
||||||
.arg("-config")
|
.arg("-config")
|
||||||
@ -510,49 +531,40 @@ authorityKeyIdentifier = keyid:always
|
|||||||
.arg("-extensions")
|
.arg("-extensions")
|
||||||
.arg("v3_child_ca")
|
.arg("v3_child_ca")
|
||||||
.arg("-out")
|
.arg("-out")
|
||||||
.arg(dir.join("child.pem")),
|
.arg(dir.join("child.pem")));
|
||||||
);
|
|
||||||
|
|
||||||
run(
|
run(Command::new("openssl")
|
||||||
Command::new("openssl")
|
|
||||||
.arg("x509")
|
.arg("x509")
|
||||||
.arg("-in")
|
.arg("-in")
|
||||||
.arg(dir.join("issuer.pem"))
|
.arg(dir.join("issuer.pem"))
|
||||||
.arg("-outform")
|
.arg("-outform")
|
||||||
.arg("DER")
|
.arg("DER")
|
||||||
.arg("-out")
|
.arg("-out")
|
||||||
.arg(dir.join("issuer.cer")),
|
.arg(dir.join("issuer.cer")));
|
||||||
);
|
run(Command::new("openssl")
|
||||||
run(
|
|
||||||
Command::new("openssl")
|
|
||||||
.arg("x509")
|
.arg("x509")
|
||||||
.arg("-in")
|
.arg("-in")
|
||||||
.arg(dir.join("child.pem"))
|
.arg(dir.join("child.pem"))
|
||||||
.arg("-outform")
|
.arg("-outform")
|
||||||
.arg("DER")
|
.arg("DER")
|
||||||
.arg("-out")
|
.arg("-out")
|
||||||
.arg(dir.join("child.cer")),
|
.arg(dir.join("child.cer")));
|
||||||
);
|
|
||||||
|
|
||||||
run(
|
run(Command::new("openssl")
|
||||||
Command::new("openssl")
|
|
||||||
.arg("ca")
|
.arg("ca")
|
||||||
.arg("-gencrl")
|
.arg("-gencrl")
|
||||||
.arg("-config")
|
.arg("-config")
|
||||||
.arg(dir.join("openssl.cnf"))
|
.arg(dir.join("openssl.cnf"))
|
||||||
.arg("-out")
|
.arg("-out")
|
||||||
.arg(dir.join("issuer.crl.pem")),
|
.arg(dir.join("issuer.crl.pem")));
|
||||||
);
|
run(Command::new("openssl")
|
||||||
run(
|
|
||||||
Command::new("openssl")
|
|
||||||
.arg("crl")
|
.arg("crl")
|
||||||
.arg("-in")
|
.arg("-in")
|
||||||
.arg(dir.join("issuer.crl.pem"))
|
.arg(dir.join("issuer.crl.pem"))
|
||||||
.arg("-outform")
|
.arg("-outform")
|
||||||
.arg("DER")
|
.arg("DER")
|
||||||
.arg("-out")
|
.arg("-out")
|
||||||
.arg(dir.join("issuer.crl")),
|
.arg(dir.join("issuer.crl")));
|
||||||
);
|
|
||||||
|
|
||||||
Generated {
|
Generated {
|
||||||
issuer_ca_der: std::fs::read(dir.join("issuer.cer")).expect("read issuer der"),
|
issuer_ca_der: std::fs::read(dir.join("issuer.cer")).expect("read issuer der"),
|
||||||
@ -577,29 +589,29 @@ authorityKeyIdentifier = keyid:always
|
|||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn select_issuer_crl_from_pack_finds_matching_crl() {
|
fn select_issuer_crl_from_pack_finds_matching_crl() {
|
||||||
let g = generate_chain_and_crl();
|
// Use real fixtures to ensure child cert has CRLDP rsync URI and CRL exists.
|
||||||
|
let child_cert_der =
|
||||||
|
std::fs::read(std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
||||||
|
"tests/fixtures/repository/ca.rg.net/rpki/RGnet-OU/R-lVU1XGsAeqzV1Fv0HjOD6ZFkE.cer",
|
||||||
|
))
|
||||||
|
.expect("read child cert fixture");
|
||||||
|
let crl_der = std::fs::read(std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
||||||
|
"tests/fixtures/repository/ca.rg.net/rpki/RGnet-OU/bW-_qXU9uNhGQz21NR2ansB8lr0.crl",
|
||||||
|
))
|
||||||
|
.expect("read crl fixture");
|
||||||
|
|
||||||
let pack = dummy_pack_with_files(vec![PackFile::from_bytes_compute_sha256(
|
let pack = dummy_pack_with_files(vec![PackFile::from_bytes_compute_sha256(
|
||||||
"rsync://example.test/repo/issuer/issuer.crl",
|
"rsync://ca.rg.net/rpki/RGnet-OU/bW-_qXU9uNhGQz21NR2ansB8lr0.crl",
|
||||||
g.issuer_crl_der.clone(),
|
crl_der.clone(),
|
||||||
)]);
|
)]);
|
||||||
|
|
||||||
let issuer_ca = ResourceCertificate::decode_der(&g.issuer_ca_der).expect("decode issuer");
|
let (uri, found) =
|
||||||
let issuer = CaInstanceHandle {
|
select_issuer_crl_from_pack(child_cert_der.as_slice(), &pack).expect("find crl");
|
||||||
depth: 0,
|
assert_eq!(
|
||||||
ca_certificate_der: g.issuer_ca_der.clone(),
|
uri,
|
||||||
ca_certificate_rsync_uri: None,
|
"rsync://ca.rg.net/rpki/RGnet-OU/bW-_qXU9uNhGQz21NR2ansB8lr0.crl"
|
||||||
effective_ip_resources: issuer_ca.tbs.extensions.ip_resources.clone(),
|
);
|
||||||
effective_as_resources: issuer_ca.tbs.extensions.as_resources.clone(),
|
assert_eq!(found, crl_der.as_slice());
|
||||||
rsync_base_uri: "rsync://example.test/repo/issuer/".to_string(),
|
|
||||||
manifest_rsync_uri: "rsync://example.test/repo/issuer/issuer.mft".to_string(),
|
|
||||||
publication_point_rsync_uri: "rsync://example.test/repo/issuer/".to_string(),
|
|
||||||
rrdp_notification_uri: None,
|
|
||||||
};
|
|
||||||
|
|
||||||
let (uri, found) = select_issuer_crl_from_pack(&issuer, &pack).expect("find crl");
|
|
||||||
assert_eq!(uri, "rsync://example.test/repo/issuer/issuer.crl");
|
|
||||||
assert_eq!(found, g.issuer_crl_der.as_slice());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -635,26 +647,84 @@ authorityKeyIdentifier = keyid:always
|
|||||||
.expect("discover children")
|
.expect("discover children")
|
||||||
.children;
|
.children;
|
||||||
assert_eq!(children.len(), 1);
|
assert_eq!(children.len(), 1);
|
||||||
assert_eq!(children[0].rsync_base_uri, "rsync://example.test/repo/child/".to_string());
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
children[0].manifest_rsync_uri,
|
children[0].discovered_from.parent_manifest_rsync_uri,
|
||||||
"rsync://example.test/repo/child/child.mft".to_string()
|
issuer.manifest_rsync_uri
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
children[0].publication_point_rsync_uri,
|
children[0].discovered_from.child_ca_certificate_rsync_uri,
|
||||||
|
"rsync://example.test/repo/issuer/child.cer"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
children[0].handle.rsync_base_uri,
|
||||||
"rsync://example.test/repo/child/".to_string()
|
"rsync://example.test/repo/child/".to_string()
|
||||||
);
|
);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
children[0].rrdp_notification_uri.as_deref(),
|
children[0].handle.manifest_rsync_uri,
|
||||||
|
"rsync://example.test/repo/child/child.mft".to_string()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
children[0].handle.publication_point_rsync_uri,
|
||||||
|
"rsync://example.test/repo/child/".to_string()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
children[0].handle.rrdp_notification_uri.as_deref(),
|
||||||
Some("https://example.test/notification.xml")
|
Some("https://example.test/notification.xml")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn runner_offline_rsync_fixture_produces_pack_and_warnings() {
|
fn discover_children_with_audit_records_missing_crl_for_child_certificate() {
|
||||||
let fixture_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
let now = time::OffsetDateTime::now_utc();
|
||||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0",
|
|
||||||
|
let child_ca_der =
|
||||||
|
std::fs::read(std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
||||||
|
"tests/fixtures/repository/ca.rg.net/rpki/RGnet-OU/R-lVU1XGsAeqzV1Fv0HjOD6ZFkE.cer",
|
||||||
|
))
|
||||||
|
.expect("read child ca fixture");
|
||||||
|
|
||||||
|
// Pack contains the child CA cert but does not contain the CRL referenced by the child
|
||||||
|
// certificate CRLDistributionPoints extension.
|
||||||
|
let pack = dummy_pack_with_files(vec![PackFile::from_bytes_compute_sha256(
|
||||||
|
"rsync://ca.rg.net/rpki/RGnet-OU/R-lVU1XGsAeqzV1Fv0HjOD6ZFkE.cer",
|
||||||
|
child_ca_der,
|
||||||
|
)]);
|
||||||
|
|
||||||
|
let issuer = CaInstanceHandle {
|
||||||
|
depth: 0,
|
||||||
|
ca_certificate_der: vec![1],
|
||||||
|
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: pack.manifest_rsync_uri.clone(),
|
||||||
|
publication_point_rsync_uri: pack.publication_point_rsync_uri.clone(),
|
||||||
|
rrdp_notification_uri: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let out = discover_children_from_fresh_pack_with_audit(&issuer, &pack, now)
|
||||||
|
.expect("discovery should succeed with audit error");
|
||||||
|
assert_eq!(out.children.len(), 0);
|
||||||
|
assert_eq!(out.audits.len(), 1);
|
||||||
|
assert_eq!(
|
||||||
|
out.audits[0].rsync_uri,
|
||||||
|
"rsync://ca.rg.net/rpki/RGnet-OU/R-lVU1XGsAeqzV1Fv0HjOD6ZFkE.cer"
|
||||||
);
|
);
|
||||||
|
assert_eq!(out.audits[0].result, AuditObjectResult::Error);
|
||||||
|
assert!(
|
||||||
|
out.audits[0]
|
||||||
|
.detail
|
||||||
|
.as_deref()
|
||||||
|
.unwrap_or("")
|
||||||
|
.contains("cannot select issuer CRL"),
|
||||||
|
"expected deterministic CRL selection failure to be recorded"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn runner_offline_rsync_fixture_produces_pack_and_warnings() {
|
||||||
|
let fixture_dir = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
|
.join("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0");
|
||||||
assert!(fixture_dir.is_dir(), "fixture directory must exist");
|
assert!(fixture_dir.is_dir(), "fixture directory must exist");
|
||||||
|
|
||||||
let rsync_base_uri = "rsync://rpki.cernet.net/repo/cernet/0/".to_string();
|
let rsync_base_uri = "rsync://rpki.cernet.net/repo/cernet/0/".to_string();
|
||||||
|
|||||||
@ -1,5 +1,6 @@
|
|||||||
use std::cell::RefCell;
|
use std::cell::RefCell;
|
||||||
use std::collections::{BTreeMap, HashSet};
|
use std::collections::{BTreeMap, HashSet};
|
||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use rpki::data_model::crl::RpkixCrl;
|
use rpki::data_model::crl::RpkixCrl;
|
||||||
use rpki::fetch::http::{BlockingHttpFetcher, HttpFetcherConfig};
|
use rpki::fetch::http::{BlockingHttpFetcher, HttpFetcherConfig};
|
||||||
@ -59,7 +60,9 @@ impl LiveStats {
|
|||||||
fn record(&mut self, res: &PublicationPointRunResult) {
|
fn record(&mut self, res: &PublicationPointRunResult) {
|
||||||
self.publication_points_processed += 1;
|
self.publication_points_processed += 1;
|
||||||
match res.source {
|
match res.source {
|
||||||
rpki::validation::manifest::PublicationPointSource::Fresh => self.publication_points_fresh += 1,
|
rpki::validation::manifest::PublicationPointSource::Fresh => {
|
||||||
|
self.publication_points_fresh += 1
|
||||||
|
}
|
||||||
rpki::validation::manifest::PublicationPointSource::VerifiedCache => {
|
rpki::validation::manifest::PublicationPointSource::VerifiedCache => {
|
||||||
self.publication_points_cached += 1
|
self.publication_points_cached += 1
|
||||||
}
|
}
|
||||||
@ -139,8 +142,15 @@ impl<'a> PublicationPointRunner for CountingRunner<'a> {
|
|||||||
#[test]
|
#[test]
|
||||||
#[ignore = "live network + rsync full-tree stats (APNIC TAL); may take minutes"]
|
#[ignore = "live network + rsync full-tree stats (APNIC TAL); may take minutes"]
|
||||||
fn apnic_tree_full_stats_serial() {
|
fn apnic_tree_full_stats_serial() {
|
||||||
let http = BlockingHttpFetcher::new(HttpFetcherConfig::default()).expect("http fetcher");
|
let http = BlockingHttpFetcher::new(HttpFetcherConfig {
|
||||||
let rsync = SystemRsyncFetcher::new(SystemRsyncConfig::default());
|
timeout: Duration::from_secs(30 * 60),
|
||||||
|
..HttpFetcherConfig::default()
|
||||||
|
})
|
||||||
|
.expect("http fetcher");
|
||||||
|
let rsync = SystemRsyncFetcher::new(SystemRsyncConfig {
|
||||||
|
timeout: Duration::from_secs(30 * 60),
|
||||||
|
..SystemRsyncConfig::default()
|
||||||
|
});
|
||||||
let validation_time = time::OffsetDateTime::now_utc();
|
let validation_time = time::OffsetDateTime::now_utc();
|
||||||
|
|
||||||
let temp = tempfile::tempdir().expect("tempdir");
|
let temp = tempfile::tempdir().expect("tempdir");
|
||||||
@ -202,7 +212,12 @@ fn apnic_tree_full_stats_serial() {
|
|||||||
|
|
||||||
println!("APNIC Stage2 full-tree serial stats");
|
println!("APNIC Stage2 full-tree serial stats");
|
||||||
println!("tal_url={APNIC_TAL_URL}");
|
println!("tal_url={APNIC_TAL_URL}");
|
||||||
println!("validation_time={}", validation_time.format(&time::format_description::well_known::Rfc3339).unwrap());
|
println!(
|
||||||
|
"validation_time={}",
|
||||||
|
validation_time
|
||||||
|
.format(&time::format_description::well_known::Rfc3339)
|
||||||
|
.unwrap()
|
||||||
|
);
|
||||||
println!();
|
println!();
|
||||||
println!(
|
println!(
|
||||||
"publication_points_processed={} publication_points_failed={} fresh={} cached={}",
|
"publication_points_processed={} publication_points_failed={} fresh={} cached={}",
|
||||||
@ -212,7 +227,10 @@ fn apnic_tree_full_stats_serial() {
|
|||||||
stats.publication_points_cached
|
stats.publication_points_cached
|
||||||
);
|
);
|
||||||
println!("rrdp_repos_unique={}", stats.rrdp_repos_unique.len());
|
println!("rrdp_repos_unique={}", stats.rrdp_repos_unique.len());
|
||||||
println!("objects_dropped_publication_points={}", stats.objects_dropped_publication_points);
|
println!(
|
||||||
|
"objects_dropped_publication_points={}",
|
||||||
|
stats.objects_dropped_publication_points
|
||||||
|
);
|
||||||
println!();
|
println!();
|
||||||
println!(
|
println!(
|
||||||
"pack_uris_total={} pack_uris_unique={}",
|
"pack_uris_total={} pack_uris_unique={}",
|
||||||
@ -242,12 +260,24 @@ fn apnic_tree_full_stats_serial() {
|
|||||||
out.aspas.len()
|
out.aspas.len()
|
||||||
);
|
);
|
||||||
println!();
|
println!();
|
||||||
println!("rocksdb_raw_objects_total={} raw_by_ext={:?}", raw_total, raw_by_ext);
|
println!(
|
||||||
|
"rocksdb_raw_objects_total={} raw_by_ext={:?}",
|
||||||
|
raw_total, raw_by_ext
|
||||||
|
);
|
||||||
println!("rocksdb_verified_packs_total={}", verified_total);
|
println!("rocksdb_verified_packs_total={}", verified_total);
|
||||||
|
|
||||||
// Loose sanity assertions (avoid flakiness due to repository churn).
|
// Loose sanity assertions (avoid flakiness due to repository churn).
|
||||||
|
//
|
||||||
|
// This test supports bounded manual runs via env vars:
|
||||||
|
// - `RPKI_APNIC_MAX_DEPTH=0` or `RPKI_APNIC_MAX_INSTANCES=1` implies root-only.
|
||||||
|
// Otherwise we expect to reach at least one child.
|
||||||
|
let min_expected = if max_depth == Some(0) || max_instances.is_some_and(|n| n <= 1) {
|
||||||
|
1
|
||||||
|
} else {
|
||||||
|
2
|
||||||
|
};
|
||||||
assert!(
|
assert!(
|
||||||
out.instances_processed >= 2,
|
out.instances_processed >= min_expected,
|
||||||
"expected to process root + at least one child"
|
"expected to process at least {min_expected} publication point(s)"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -1,8 +1,11 @@
|
|||||||
|
use std::time::Duration;
|
||||||
|
|
||||||
use rpki::fetch::http::{BlockingHttpFetcher, HttpFetcherConfig};
|
use rpki::fetch::http::{BlockingHttpFetcher, HttpFetcherConfig};
|
||||||
use rpki::fetch::rsync_system::{SystemRsyncConfig, SystemRsyncFetcher};
|
use rpki::fetch::rsync_system::{SystemRsyncConfig, SystemRsyncFetcher};
|
||||||
use rpki::policy::Policy;
|
use rpki::policy::Policy;
|
||||||
use rpki::storage::RocksStore;
|
use rpki::storage::RocksStore;
|
||||||
use rpki::validation::run_tree_from_tal::run_tree_from_tal_url_serial;
|
use rpki::validation::run_tree_from_tal::run_tree_from_tal_url_serial;
|
||||||
|
use rpki::validation::run_tree_from_tal::run_tree_from_tal_url_serial_audit;
|
||||||
use rpki::validation::tree::TreeRunConfig;
|
use rpki::validation::tree::TreeRunConfig;
|
||||||
|
|
||||||
const APNIC_TAL_URL: &str = "https://tal.apnic.net/tal-archive/apnic-rfc7730-https.tal";
|
const APNIC_TAL_URL: &str = "https://tal.apnic.net/tal-archive/apnic-rfc7730-https.tal";
|
||||||
@ -10,8 +13,15 @@ const APNIC_TAL_URL: &str = "https://tal.apnic.net/tal-archive/apnic-rfc7730-htt
|
|||||||
#[test]
|
#[test]
|
||||||
#[ignore = "live network + rsync smoke test (APNIC TAL)"]
|
#[ignore = "live network + rsync smoke test (APNIC TAL)"]
|
||||||
fn apnic_tree_depth1_processes_more_than_root() {
|
fn apnic_tree_depth1_processes_more_than_root() {
|
||||||
let http = BlockingHttpFetcher::new(HttpFetcherConfig::default()).expect("http fetcher");
|
let http = BlockingHttpFetcher::new(HttpFetcherConfig {
|
||||||
let rsync = SystemRsyncFetcher::new(SystemRsyncConfig::default());
|
timeout: Duration::from_secs(30 * 60),
|
||||||
|
..HttpFetcherConfig::default()
|
||||||
|
})
|
||||||
|
.expect("http fetcher");
|
||||||
|
let rsync = SystemRsyncFetcher::new(SystemRsyncConfig {
|
||||||
|
timeout: Duration::from_secs(30 * 60),
|
||||||
|
..SystemRsyncConfig::default()
|
||||||
|
});
|
||||||
|
|
||||||
let temp = tempfile::tempdir().expect("tempdir");
|
let temp = tempfile::tempdir().expect("tempdir");
|
||||||
let store = RocksStore::open(temp.path()).expect("open rocksdb");
|
let store = RocksStore::open(temp.path()).expect("open rocksdb");
|
||||||
@ -35,3 +45,46 @@ fn apnic_tree_depth1_processes_more_than_root() {
|
|||||||
"expected to process root + at least one child"
|
"expected to process root + at least one child"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
#[ignore = "live network + rsync root-only smoke test (APNIC TAL); 30min timeouts"]
|
||||||
|
fn apnic_tree_root_only_processes_root_with_long_timeouts() {
|
||||||
|
let http = BlockingHttpFetcher::new(HttpFetcherConfig {
|
||||||
|
timeout: Duration::from_secs(30 * 60),
|
||||||
|
..HttpFetcherConfig::default()
|
||||||
|
})
|
||||||
|
.expect("http fetcher");
|
||||||
|
let rsync = SystemRsyncFetcher::new(SystemRsyncConfig {
|
||||||
|
timeout: Duration::from_secs(30 * 60),
|
||||||
|
..SystemRsyncConfig::default()
|
||||||
|
});
|
||||||
|
|
||||||
|
let temp = tempfile::tempdir().expect("tempdir");
|
||||||
|
let store = RocksStore::open(temp.path()).expect("open rocksdb");
|
||||||
|
let policy = Policy::default();
|
||||||
|
let validation_time = time::OffsetDateTime::now_utc();
|
||||||
|
|
||||||
|
let out = run_tree_from_tal_url_serial_audit(
|
||||||
|
&store,
|
||||||
|
&policy,
|
||||||
|
APNIC_TAL_URL,
|
||||||
|
&http,
|
||||||
|
&rsync,
|
||||||
|
validation_time,
|
||||||
|
&TreeRunConfig {
|
||||||
|
max_depth: Some(0),
|
||||||
|
max_instances: Some(1),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("run APNIC root-only");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
out.tree.instances_processed, 1,
|
||||||
|
"expected to process exactly the root publication point"
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
out.publication_points.len(),
|
||||||
|
out.tree.instances_processed,
|
||||||
|
"audit should include one publication point"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|||||||
@ -9,8 +9,7 @@ fn load_tal_and_ta_fixture(tal_name: &str, ta_name: &str) -> TrustAnchor {
|
|||||||
std::fs::read(format!("tests/fixtures/tal/{tal_name}")).expect("read TAL fixture");
|
std::fs::read(format!("tests/fixtures/tal/{tal_name}")).expect("read TAL fixture");
|
||||||
let tal = Tal::decode_bytes(&tal_bytes).expect("decode TAL");
|
let tal = Tal::decode_bytes(&tal_bytes).expect("decode TAL");
|
||||||
|
|
||||||
let ta_der =
|
let ta_der = std::fs::read(format!("tests/fixtures/ta/{ta_name}")).expect("read TA fixture");
|
||||||
std::fs::read(format!("tests/fixtures/ta/{ta_name}")).expect("read TA fixture");
|
|
||||||
let resolved = tal.ta_uris[0].clone();
|
let resolved = tal.ta_uris[0].clone();
|
||||||
|
|
||||||
TrustAnchor::bind_der(tal, &ta_der, Some(&resolved)).expect("bind TAL and TA")
|
TrustAnchor::bind_der(tal, &ta_der, Some(&resolved)).expect("bind TAL and TA")
|
||||||
|
|||||||
@ -106,15 +106,12 @@ authorityKeyIdentifier = keyid:always
|
|||||||
write(&dir.join("openssl.cnf"), &cnf);
|
write(&dir.join("openssl.cnf"), &cnf);
|
||||||
|
|
||||||
// Issuer CA key + self-signed CA cert (DER later).
|
// Issuer CA key + self-signed CA cert (DER later).
|
||||||
run(
|
run(Command::new("openssl")
|
||||||
Command::new("openssl")
|
|
||||||
.arg("genrsa")
|
.arg("genrsa")
|
||||||
.arg("-out")
|
.arg("-out")
|
||||||
.arg(dir.join("issuer.key"))
|
.arg(dir.join("issuer.key"))
|
||||||
.arg("2048"),
|
.arg("2048"));
|
||||||
);
|
run(Command::new("openssl")
|
||||||
run(
|
|
||||||
Command::new("openssl")
|
|
||||||
.arg("req")
|
.arg("req")
|
||||||
.arg("-new")
|
.arg("-new")
|
||||||
.arg("-x509")
|
.arg("-x509")
|
||||||
@ -128,19 +125,15 @@ authorityKeyIdentifier = keyid:always
|
|||||||
.arg("-config")
|
.arg("-config")
|
||||||
.arg(dir.join("openssl.cnf"))
|
.arg(dir.join("openssl.cnf"))
|
||||||
.arg("-extensions")
|
.arg("-extensions")
|
||||||
.arg("v3_issuer_ca"),
|
.arg("v3_issuer_ca"));
|
||||||
);
|
|
||||||
|
|
||||||
// Child CA key + CSR.
|
// Child CA key + CSR.
|
||||||
run(
|
run(Command::new("openssl")
|
||||||
Command::new("openssl")
|
|
||||||
.arg("genrsa")
|
.arg("genrsa")
|
||||||
.arg("-out")
|
.arg("-out")
|
||||||
.arg(dir.join("child.key"))
|
.arg(dir.join("child.key"))
|
||||||
.arg("2048"),
|
.arg("2048"));
|
||||||
);
|
run(Command::new("openssl")
|
||||||
run(
|
|
||||||
Command::new("openssl")
|
|
||||||
.arg("req")
|
.arg("req")
|
||||||
.arg("-new")
|
.arg("-new")
|
||||||
.arg("-key")
|
.arg("-key")
|
||||||
@ -150,12 +143,10 @@ authorityKeyIdentifier = keyid:always
|
|||||||
.arg("-out")
|
.arg("-out")
|
||||||
.arg(dir.join("child.csr"))
|
.arg(dir.join("child.csr"))
|
||||||
.arg("-config")
|
.arg("-config")
|
||||||
.arg(dir.join("openssl.cnf")),
|
.arg(dir.join("openssl.cnf")));
|
||||||
);
|
|
||||||
|
|
||||||
// Issue child CA cert using openssl ca (so it appears in the CA database for CRL).
|
// Issue child CA cert using openssl ca (so it appears in the CA database for CRL).
|
||||||
run(
|
run(Command::new("openssl")
|
||||||
Command::new("openssl")
|
|
||||||
.arg("ca")
|
.arg("ca")
|
||||||
.arg("-batch")
|
.arg("-batch")
|
||||||
.arg("-config")
|
.arg("-config")
|
||||||
@ -166,62 +157,51 @@ authorityKeyIdentifier = keyid:always
|
|||||||
.arg(dir.join("child.csr"))
|
.arg(dir.join("child.csr"))
|
||||||
.arg("-out")
|
.arg("-out")
|
||||||
.arg(dir.join("child.pem"))
|
.arg(dir.join("child.pem"))
|
||||||
.arg("-notext"),
|
.arg("-notext"));
|
||||||
);
|
|
||||||
|
|
||||||
if revoke_child {
|
if revoke_child {
|
||||||
run(
|
run(Command::new("openssl")
|
||||||
Command::new("openssl")
|
|
||||||
.arg("ca")
|
.arg("ca")
|
||||||
.arg("-config")
|
.arg("-config")
|
||||||
.arg(dir.join("openssl.cnf"))
|
.arg(dir.join("openssl.cnf"))
|
||||||
.arg("-revoke")
|
.arg("-revoke")
|
||||||
.arg(dir.join("child.pem")),
|
.arg(dir.join("child.pem")));
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Generate CRL.
|
// Generate CRL.
|
||||||
run(
|
run(Command::new("openssl")
|
||||||
Command::new("openssl")
|
|
||||||
.arg("ca")
|
.arg("ca")
|
||||||
.arg("-gencrl")
|
.arg("-gencrl")
|
||||||
.arg("-config")
|
.arg("-config")
|
||||||
.arg(dir.join("openssl.cnf"))
|
.arg(dir.join("openssl.cnf"))
|
||||||
.arg("-out")
|
.arg("-out")
|
||||||
.arg(dir.join("issuer.crl.pem")),
|
.arg(dir.join("issuer.crl.pem")));
|
||||||
);
|
|
||||||
|
|
||||||
// Convert to DER.
|
// Convert to DER.
|
||||||
run(
|
run(Command::new("openssl")
|
||||||
Command::new("openssl")
|
|
||||||
.arg("x509")
|
.arg("x509")
|
||||||
.arg("-in")
|
.arg("-in")
|
||||||
.arg(dir.join("issuer.pem"))
|
.arg(dir.join("issuer.pem"))
|
||||||
.arg("-outform")
|
.arg("-outform")
|
||||||
.arg("DER")
|
.arg("DER")
|
||||||
.arg("-out")
|
.arg("-out")
|
||||||
.arg(dir.join("issuer.cer")),
|
.arg(dir.join("issuer.cer")));
|
||||||
);
|
run(Command::new("openssl")
|
||||||
run(
|
|
||||||
Command::new("openssl")
|
|
||||||
.arg("x509")
|
.arg("x509")
|
||||||
.arg("-in")
|
.arg("-in")
|
||||||
.arg(dir.join("child.pem"))
|
.arg(dir.join("child.pem"))
|
||||||
.arg("-outform")
|
.arg("-outform")
|
||||||
.arg("DER")
|
.arg("DER")
|
||||||
.arg("-out")
|
.arg("-out")
|
||||||
.arg(dir.join("child.cer")),
|
.arg(dir.join("child.cer")));
|
||||||
);
|
run(Command::new("openssl")
|
||||||
run(
|
|
||||||
Command::new("openssl")
|
|
||||||
.arg("crl")
|
.arg("crl")
|
||||||
.arg("-in")
|
.arg("-in")
|
||||||
.arg(dir.join("issuer.crl.pem"))
|
.arg(dir.join("issuer.crl.pem"))
|
||||||
.arg("-outform")
|
.arg("-outform")
|
||||||
.arg("DER")
|
.arg("DER")
|
||||||
.arg("-out")
|
.arg("-out")
|
||||||
.arg(dir.join("issuer.crl")),
|
.arg(dir.join("issuer.crl")));
|
||||||
);
|
|
||||||
|
|
||||||
Generated {
|
Generated {
|
||||||
issuer_ca_der: std::fs::read(dir.join("issuer.cer")).expect("read issuer der"),
|
issuer_ca_der: std::fs::read(dir.join("issuer.cer")).expect("read issuer der"),
|
||||||
|
|||||||
@ -93,15 +93,12 @@ authorityKeyIdentifier = keyid:always
|
|||||||
);
|
);
|
||||||
std::fs::write(dir.join("openssl.cnf"), cnf.as_bytes()).expect("write cnf");
|
std::fs::write(dir.join("openssl.cnf"), cnf.as_bytes()).expect("write cnf");
|
||||||
|
|
||||||
run(
|
run(Command::new("openssl")
|
||||||
Command::new("openssl")
|
|
||||||
.arg("genrsa")
|
.arg("genrsa")
|
||||||
.arg("-out")
|
.arg("-out")
|
||||||
.arg(dir.join("issuer.key"))
|
.arg(dir.join("issuer.key"))
|
||||||
.arg("2048"),
|
.arg("2048"));
|
||||||
);
|
run(Command::new("openssl")
|
||||||
run(
|
|
||||||
Command::new("openssl")
|
|
||||||
.arg("req")
|
.arg("req")
|
||||||
.arg("-new")
|
.arg("-new")
|
||||||
.arg("-x509")
|
.arg("-x509")
|
||||||
@ -115,18 +112,14 @@ authorityKeyIdentifier = keyid:always
|
|||||||
.arg("-extensions")
|
.arg("-extensions")
|
||||||
.arg("v3_issuer_ca")
|
.arg("v3_issuer_ca")
|
||||||
.arg("-out")
|
.arg("-out")
|
||||||
.arg(dir.join("issuer.pem")),
|
.arg(dir.join("issuer.pem")));
|
||||||
);
|
|
||||||
|
|
||||||
run(
|
run(Command::new("openssl")
|
||||||
Command::new("openssl")
|
|
||||||
.arg("genrsa")
|
.arg("genrsa")
|
||||||
.arg("-out")
|
.arg("-out")
|
||||||
.arg(dir.join("ee.key"))
|
.arg(dir.join("ee.key"))
|
||||||
.arg("2048"),
|
.arg("2048"));
|
||||||
);
|
run(Command::new("openssl")
|
||||||
run(
|
|
||||||
Command::new("openssl")
|
|
||||||
.arg("req")
|
.arg("req")
|
||||||
.arg("-new")
|
.arg("-new")
|
||||||
.arg("-key")
|
.arg("-key")
|
||||||
@ -134,11 +127,9 @@ authorityKeyIdentifier = keyid:always
|
|||||||
.arg("-subj")
|
.arg("-subj")
|
||||||
.arg("/CN=Test EE")
|
.arg("/CN=Test EE")
|
||||||
.arg("-out")
|
.arg("-out")
|
||||||
.arg(dir.join("ee.csr")),
|
.arg(dir.join("ee.csr")));
|
||||||
);
|
|
||||||
|
|
||||||
run(
|
run(Command::new("openssl")
|
||||||
Command::new("openssl")
|
|
||||||
.arg("ca")
|
.arg("ca")
|
||||||
.arg("-batch")
|
.arg("-batch")
|
||||||
.arg("-config")
|
.arg("-config")
|
||||||
@ -148,49 +139,40 @@ authorityKeyIdentifier = keyid:always
|
|||||||
.arg("-extensions")
|
.arg("-extensions")
|
||||||
.arg("v3_ee")
|
.arg("v3_ee")
|
||||||
.arg("-out")
|
.arg("-out")
|
||||||
.arg(dir.join("ee.pem")),
|
.arg(dir.join("ee.pem")));
|
||||||
);
|
|
||||||
|
|
||||||
run(
|
run(Command::new("openssl")
|
||||||
Command::new("openssl")
|
|
||||||
.arg("x509")
|
.arg("x509")
|
||||||
.arg("-in")
|
.arg("-in")
|
||||||
.arg(dir.join("issuer.pem"))
|
.arg(dir.join("issuer.pem"))
|
||||||
.arg("-outform")
|
.arg("-outform")
|
||||||
.arg("DER")
|
.arg("DER")
|
||||||
.arg("-out")
|
.arg("-out")
|
||||||
.arg(dir.join("issuer.cer")),
|
.arg(dir.join("issuer.cer")));
|
||||||
);
|
run(Command::new("openssl")
|
||||||
run(
|
|
||||||
Command::new("openssl")
|
|
||||||
.arg("x509")
|
.arg("x509")
|
||||||
.arg("-in")
|
.arg("-in")
|
||||||
.arg(dir.join("ee.pem"))
|
.arg(dir.join("ee.pem"))
|
||||||
.arg("-outform")
|
.arg("-outform")
|
||||||
.arg("DER")
|
.arg("DER")
|
||||||
.arg("-out")
|
.arg("-out")
|
||||||
.arg(dir.join("ee.cer")),
|
.arg(dir.join("ee.cer")));
|
||||||
);
|
|
||||||
|
|
||||||
run(
|
run(Command::new("openssl")
|
||||||
Command::new("openssl")
|
|
||||||
.arg("ca")
|
.arg("ca")
|
||||||
.arg("-gencrl")
|
.arg("-gencrl")
|
||||||
.arg("-config")
|
.arg("-config")
|
||||||
.arg(dir.join("openssl.cnf"))
|
.arg(dir.join("openssl.cnf"))
|
||||||
.arg("-out")
|
.arg("-out")
|
||||||
.arg(dir.join("issuer.crl.pem")),
|
.arg(dir.join("issuer.crl.pem")));
|
||||||
);
|
run(Command::new("openssl")
|
||||||
run(
|
|
||||||
Command::new("openssl")
|
|
||||||
.arg("crl")
|
.arg("crl")
|
||||||
.arg("-in")
|
.arg("-in")
|
||||||
.arg(dir.join("issuer.crl.pem"))
|
.arg(dir.join("issuer.crl.pem"))
|
||||||
.arg("-outform")
|
.arg("-outform")
|
||||||
.arg("DER")
|
.arg("DER")
|
||||||
.arg("-out")
|
.arg("-out")
|
||||||
.arg(dir.join("issuer.crl")),
|
.arg(dir.join("issuer.crl")));
|
||||||
);
|
|
||||||
|
|
||||||
Generated {
|
Generated {
|
||||||
issuer_ca_der: std::fs::read(dir.join("issuer.cer")).expect("read issuer der"),
|
issuer_ca_der: std::fs::read(dir.join("issuer.cer")).expect("read issuer der"),
|
||||||
@ -203,7 +185,14 @@ authorityKeyIdentifier = keyid:always
|
|||||||
fn ee_key_usage_digital_signature_only_is_accepted() {
|
fn ee_key_usage_digital_signature_only_is_accepted() {
|
||||||
let g = generate_issuer_ca_ee_and_crl("keyUsage = critical, digitalSignature\n");
|
let g = generate_issuer_ca_ee_and_crl("keyUsage = critical, digitalSignature\n");
|
||||||
let now = time::OffsetDateTime::now_utc();
|
let now = time::OffsetDateTime::now_utc();
|
||||||
validate_ee_cert_path(&g.ee_der, &g.issuer_ca_der, &g.issuer_crl_der, None, None, now)
|
validate_ee_cert_path(
|
||||||
|
&g.ee_der,
|
||||||
|
&g.issuer_ca_der,
|
||||||
|
&g.issuer_crl_der,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
now,
|
||||||
|
)
|
||||||
.expect("valid EE path");
|
.expect("valid EE path");
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -211,7 +200,14 @@ fn ee_key_usage_digital_signature_only_is_accepted() {
|
|||||||
fn ee_key_usage_missing_is_rejected() {
|
fn ee_key_usage_missing_is_rejected() {
|
||||||
let g = generate_issuer_ca_ee_and_crl("");
|
let g = generate_issuer_ca_ee_and_crl("");
|
||||||
let now = time::OffsetDateTime::now_utc();
|
let now = time::OffsetDateTime::now_utc();
|
||||||
let err = validate_ee_cert_path(&g.ee_der, &g.issuer_ca_der, &g.issuer_crl_der, None, None, now)
|
let err = validate_ee_cert_path(
|
||||||
|
&g.ee_der,
|
||||||
|
&g.issuer_ca_der,
|
||||||
|
&g.issuer_crl_der,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
now,
|
||||||
|
)
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
assert!(matches!(err, CertPathError::KeyUsageMissing), "{err}");
|
assert!(matches!(err, CertPathError::KeyUsageMissing), "{err}");
|
||||||
}
|
}
|
||||||
@ -220,16 +216,31 @@ fn ee_key_usage_missing_is_rejected() {
|
|||||||
fn ee_key_usage_not_critical_is_rejected() {
|
fn ee_key_usage_not_critical_is_rejected() {
|
||||||
let g = generate_issuer_ca_ee_and_crl("keyUsage = digitalSignature\n");
|
let g = generate_issuer_ca_ee_and_crl("keyUsage = digitalSignature\n");
|
||||||
let now = time::OffsetDateTime::now_utc();
|
let now = time::OffsetDateTime::now_utc();
|
||||||
let err = validate_ee_cert_path(&g.ee_der, &g.issuer_ca_der, &g.issuer_crl_der, None, None, now)
|
let err = validate_ee_cert_path(
|
||||||
|
&g.ee_der,
|
||||||
|
&g.issuer_ca_der,
|
||||||
|
&g.issuer_crl_der,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
now,
|
||||||
|
)
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
assert!(matches!(err, CertPathError::KeyUsageNotCritical), "{err}");
|
assert!(matches!(err, CertPathError::KeyUsageNotCritical), "{err}");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn ee_key_usage_wrong_bits_is_rejected() {
|
fn ee_key_usage_wrong_bits_is_rejected() {
|
||||||
let g = generate_issuer_ca_ee_and_crl("keyUsage = critical, digitalSignature, keyEncipherment\n");
|
let g =
|
||||||
|
generate_issuer_ca_ee_and_crl("keyUsage = critical, digitalSignature, keyEncipherment\n");
|
||||||
let now = time::OffsetDateTime::now_utc();
|
let now = time::OffsetDateTime::now_utc();
|
||||||
let err = validate_ee_cert_path(&g.ee_der, &g.issuer_ca_der, &g.issuer_crl_der, None, None, now)
|
let err = validate_ee_cert_path(
|
||||||
|
&g.ee_der,
|
||||||
|
&g.issuer_ca_der,
|
||||||
|
&g.issuer_crl_der,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
now,
|
||||||
|
)
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
assert!(matches!(err, CertPathError::KeyUsageInvalidBits), "{err}");
|
assert!(matches!(err, CertPathError::KeyUsageInvalidBits), "{err}");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -10,8 +10,8 @@ fn cli_run_offline_mode_executes_and_writes_json() {
|
|||||||
|
|
||||||
let tal_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
let tal_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
.join("tests/fixtures/tal/apnic-rfc7730-https.tal");
|
.join("tests/fixtures/tal/apnic-rfc7730-https.tal");
|
||||||
let ta_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
let ta_path =
|
||||||
.join("tests/fixtures/ta/apnic-ta.cer");
|
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/ta/apnic-ta.cer");
|
||||||
|
|
||||||
let argv = vec![
|
let argv = vec![
|
||||||
"rpki".to_string(),
|
"rpki".to_string(),
|
||||||
@ -39,4 +39,3 @@ fn cli_run_offline_mode_executes_and_writes_json() {
|
|||||||
let v: serde_json::Value = serde_json::from_slice(&bytes).expect("parse report json");
|
let v: serde_json::Value = serde_json::from_slice(&bytes).expect("parse report json");
|
||||||
assert_eq!(v["format_version"], 1);
|
assert_eq!(v["format_version"], 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -11,8 +11,8 @@ fn cli_offline_smoke_writes_report_json() {
|
|||||||
|
|
||||||
let tal_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
let tal_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||||
.join("tests/fixtures/tal/apnic-rfc7730-https.tal");
|
.join("tests/fixtures/tal/apnic-rfc7730-https.tal");
|
||||||
let ta_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
let ta_path =
|
||||||
.join("tests/fixtures/ta/apnic-ta.cer");
|
std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join("tests/fixtures/ta/apnic-ta.cer");
|
||||||
|
|
||||||
let out = Command::new(bin)
|
let out = Command::new(bin)
|
||||||
.args([
|
.args([
|
||||||
@ -51,4 +51,3 @@ fn cli_offline_smoke_writes_report_json() {
|
|||||||
assert!(v.get("vrps").is_some());
|
assert!(v.get("vrps").is_some());
|
||||||
assert!(v.get("aspas").is_some());
|
assert!(v.get("aspas").is_some());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
136
tests/test_deterministic_semantics_m4.rs
Normal file
136
tests/test_deterministic_semantics_m4.rs
Normal file
@ -0,0 +1,136 @@
|
|||||||
|
use rpki::audit::PublicationPointAudit;
|
||||||
|
use rpki::policy::{Policy, SignedObjectFailurePolicy};
|
||||||
|
use rpki::storage::{PackFile, PackTime, VerifiedPublicationPointPack};
|
||||||
|
use rpki::validation::manifest::PublicationPointSource;
|
||||||
|
use rpki::validation::objects::process_verified_publication_point_pack_for_issuer;
|
||||||
|
use rpki::validation::tree::{
|
||||||
|
CaInstanceHandle, PublicationPointRunResult, PublicationPointRunner, TreeRunConfig,
|
||||||
|
run_tree_serial_audit,
|
||||||
|
};
|
||||||
|
|
||||||
|
fn fixture_bytes(path: &str) -> Vec<u8> {
|
||||||
|
std::fs::read(std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(path))
|
||||||
|
.unwrap_or_else(|e| panic!("read fixture {path}: {e}"))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn dummy_pack(files: Vec<PackFile>) -> VerifiedPublicationPointPack {
|
||||||
|
let now = time::OffsetDateTime::now_utc();
|
||||||
|
let manifest_rsync_uri =
|
||||||
|
"rsync://rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft";
|
||||||
|
VerifiedPublicationPointPack {
|
||||||
|
format_version: VerifiedPublicationPointPack::FORMAT_VERSION_V1,
|
||||||
|
manifest_rsync_uri: manifest_rsync_uri.to_string(),
|
||||||
|
publication_point_rsync_uri: "rsync://rpki.cernet.net/repo/cernet/0/".to_string(),
|
||||||
|
this_update: PackTime::from_utc_offset_datetime(now),
|
||||||
|
next_update: PackTime::from_utc_offset_datetime(now + time::Duration::hours(1)),
|
||||||
|
verified_at: PackTime::from_utc_offset_datetime(now),
|
||||||
|
manifest_bytes: fixture_bytes(
|
||||||
|
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
||||||
|
),
|
||||||
|
files,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
struct SinglePackRunner {
|
||||||
|
policy: Policy,
|
||||||
|
pack: VerifiedPublicationPointPack,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl PublicationPointRunner for SinglePackRunner {
|
||||||
|
fn run_publication_point(
|
||||||
|
&self,
|
||||||
|
ca: &CaInstanceHandle,
|
||||||
|
) -> Result<PublicationPointRunResult, String> {
|
||||||
|
let objects = process_verified_publication_point_pack_for_issuer(
|
||||||
|
&self.pack,
|
||||||
|
&self.policy,
|
||||||
|
&ca.ca_certificate_der,
|
||||||
|
ca.ca_certificate_rsync_uri.as_deref(),
|
||||||
|
ca.effective_ip_resources.as_ref(),
|
||||||
|
ca.effective_as_resources.as_ref(),
|
||||||
|
time::OffsetDateTime::now_utc(),
|
||||||
|
);
|
||||||
|
|
||||||
|
Ok(PublicationPointRunResult {
|
||||||
|
source: PublicationPointSource::Fresh,
|
||||||
|
pack: self.pack.clone(),
|
||||||
|
warnings: Vec::new(),
|
||||||
|
objects,
|
||||||
|
audit: PublicationPointAudit::default(),
|
||||||
|
discovered_children: Vec::new(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn crl_mismatch_drops_publication_point_and_cites_rfc_sections() {
|
||||||
|
let roa_bytes =
|
||||||
|
fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
|
||||||
|
|
||||||
|
// Include at least one CRL file but with a URI that does NOT match the EE certificate's CRLDP.
|
||||||
|
let pack = dummy_pack(vec![
|
||||||
|
PackFile::from_bytes_compute_sha256("rsync://example.test/repo/not-it.crl", vec![0x01]),
|
||||||
|
PackFile::from_bytes_compute_sha256("rsync://example.test/repo/a.roa", roa_bytes),
|
||||||
|
]);
|
||||||
|
|
||||||
|
let mut policy = Policy::default();
|
||||||
|
policy.signed_object_failure_policy = SignedObjectFailurePolicy::DropPublicationPoint;
|
||||||
|
|
||||||
|
let runner = SinglePackRunner { policy, pack };
|
||||||
|
|
||||||
|
let root = CaInstanceHandle {
|
||||||
|
depth: 0,
|
||||||
|
ca_certificate_der: vec![0x01, 0x02, 0x03],
|
||||||
|
ca_certificate_rsync_uri: None,
|
||||||
|
effective_ip_resources: None,
|
||||||
|
effective_as_resources: None,
|
||||||
|
rsync_base_uri: "rsync://example.test/repo/".to_string(),
|
||||||
|
manifest_rsync_uri:
|
||||||
|
"rsync://rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft"
|
||||||
|
.to_string(),
|
||||||
|
publication_point_rsync_uri: "rsync://example.test/repo/".to_string(),
|
||||||
|
rrdp_notification_uri: None,
|
||||||
|
};
|
||||||
|
|
||||||
|
let out = run_tree_serial_audit(
|
||||||
|
root,
|
||||||
|
&runner,
|
||||||
|
&TreeRunConfig {
|
||||||
|
max_depth: Some(0),
|
||||||
|
max_instances: Some(1),
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.expect("run tree audit");
|
||||||
|
|
||||||
|
assert_eq!(out.tree.instances_processed, 1);
|
||||||
|
assert_eq!(out.tree.instances_failed, 0);
|
||||||
|
|
||||||
|
assert!(
|
||||||
|
out.tree.warnings.iter().any(|w| w
|
||||||
|
.message
|
||||||
|
.contains("dropping publication point due to invalid ROA")),
|
||||||
|
"expected publication point drop warning"
|
||||||
|
);
|
||||||
|
let w = out
|
||||||
|
.tree
|
||||||
|
.warnings
|
||||||
|
.iter()
|
||||||
|
.find(|w| {
|
||||||
|
w.message
|
||||||
|
.contains("dropping publication point due to invalid ROA")
|
||||||
|
})
|
||||||
|
.expect("warning present");
|
||||||
|
let refs = w.rfc_refs.iter().map(|r| r.0).collect::<Vec<_>>();
|
||||||
|
assert!(
|
||||||
|
refs.contains(&"RFC 6487 §4.8.6"),
|
||||||
|
"expected CRLDP RFC reference in warning: {refs:?}"
|
||||||
|
);
|
||||||
|
assert!(
|
||||||
|
refs.contains(&"RFC 9286 §4.2.1"),
|
||||||
|
"expected manifest locked-pack RFC reference in warning: {refs:?}"
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(out.publication_points.len(), 1);
|
||||||
|
assert_eq!(out.publication_points[0].node_id, Some(0));
|
||||||
|
assert_eq!(out.publication_points[0].parent_node_id, None);
|
||||||
|
}
|
||||||
@ -22,4 +22,3 @@ fn local_dir_rsync_fetcher_normalizes_base_and_skips_non_files() {
|
|||||||
assert!(uris.contains(&"rsync://example.test/repo/a.txt"));
|
assert!(uris.contains(&"rsync://example.test/repo/a.txt"));
|
||||||
assert!(!uris.iter().any(|u| u.ends_with("/sock")));
|
assert!(!uris.iter().any(|u| u.ends_with("/sock")));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -6,10 +6,10 @@ use rpki::policy::{Policy, SyncPreference};
|
|||||||
use rpki::storage::RocksStore;
|
use rpki::storage::RocksStore;
|
||||||
use rpki::sync::rrdp::Fetcher;
|
use rpki::sync::rrdp::Fetcher;
|
||||||
use rpki::validation::from_tal::{
|
use rpki::validation::from_tal::{
|
||||||
FromTalError, discover_root_ca_instance_from_tal, discover_root_ca_instance_from_tal_and_ta_der,
|
FromTalError, discover_root_ca_instance_from_tal,
|
||||||
discover_root_ca_instance_from_tal_url, run_root_from_tal_url_once,
|
discover_root_ca_instance_from_tal_and_ta_der, discover_root_ca_instance_from_tal_url,
|
||||||
|
run_root_from_tal_url_once,
|
||||||
};
|
};
|
||||||
use rpki::validation::objects::IssuerCaCertificateResolver;
|
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
struct MapFetcher {
|
struct MapFetcher {
|
||||||
@ -34,19 +34,14 @@ impl Fetcher for MapFetcher {
|
|||||||
struct EmptyRsync;
|
struct EmptyRsync;
|
||||||
|
|
||||||
impl RsyncFetcher for EmptyRsync {
|
impl RsyncFetcher for EmptyRsync {
|
||||||
fn fetch_objects(&self, _rsync_base_uri: &str) -> Result<Vec<(String, Vec<u8>)>, RsyncFetchError> {
|
fn fetch_objects(
|
||||||
|
&self,
|
||||||
|
_rsync_base_uri: &str,
|
||||||
|
) -> Result<Vec<(String, Vec<u8>)>, RsyncFetchError> {
|
||||||
Ok(Vec::new())
|
Ok(Vec::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct NullResolver;
|
|
||||||
|
|
||||||
impl IssuerCaCertificateResolver for NullResolver {
|
|
||||||
fn resolve_by_subject_dn(&self, _subject_dn: &str) -> Option<Vec<u8>> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn apnic_tal_bytes() -> Vec<u8> {
|
fn apnic_tal_bytes() -> Vec<u8> {
|
||||||
std::fs::read("tests/fixtures/tal/apnic-rfc7730-https.tal").expect("read apnic TAL fixture")
|
std::fs::read("tests/fixtures/tal/apnic-rfc7730-https.tal").expect("read apnic TAL fixture")
|
||||||
}
|
}
|
||||||
@ -94,7 +89,8 @@ fn discover_root_tries_multiple_ta_uris_until_one_succeeds() {
|
|||||||
let tal_bytes = apnic_tal_bytes();
|
let tal_bytes = apnic_tal_bytes();
|
||||||
let mut tal = Tal::decode_bytes(&tal_bytes).expect("decode TAL");
|
let mut tal = Tal::decode_bytes(&tal_bytes).expect("decode TAL");
|
||||||
let good_uri = tal.ta_uris[0].clone();
|
let good_uri = tal.ta_uris[0].clone();
|
||||||
tal.ta_uris.insert(0, Url::parse("https://example.invalid/bad.cer").unwrap());
|
tal.ta_uris
|
||||||
|
.insert(0, Url::parse("https://example.invalid/bad.cer").unwrap());
|
||||||
|
|
||||||
let mut map = HashMap::new();
|
let mut map = HashMap::new();
|
||||||
map.insert(good_uri.as_str().to_string(), apnic_ta_der());
|
map.insert(good_uri.as_str().to_string(), apnic_ta_der());
|
||||||
@ -171,11 +167,9 @@ fn run_root_from_tal_url_once_propagates_run_error_when_repo_is_empty() {
|
|||||||
"https://example.test/apnic.tal",
|
"https://example.test/apnic.tal",
|
||||||
&fetcher,
|
&fetcher,
|
||||||
&EmptyRsync,
|
&EmptyRsync,
|
||||||
&NullResolver,
|
|
||||||
time::OffsetDateTime::now_utc(),
|
time::OffsetDateTime::now_utc(),
|
||||||
)
|
)
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
|
|
||||||
assert!(matches!(err, FromTalError::Run(_)));
|
assert!(matches!(err, FromTalError::Run(_)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use rpki::data_model::crl::RpkixCrl;
|
use rpki::data_model::crl::RpkixCrl;
|
||||||
@ -7,9 +6,7 @@ use rpki::data_model::rc::ResourceCertificate;
|
|||||||
use rpki::policy::{Policy, SignedObjectFailurePolicy};
|
use rpki::policy::{Policy, SignedObjectFailurePolicy};
|
||||||
use rpki::storage::{PackFile, RocksStore};
|
use rpki::storage::{PackFile, RocksStore};
|
||||||
use rpki::validation::manifest::process_manifest_publication_point;
|
use rpki::validation::manifest::process_manifest_publication_point;
|
||||||
use rpki::validation::objects::{
|
use rpki::validation::objects::process_verified_publication_point_pack_for_issuer;
|
||||||
IssuerCaCertificateResolver, process_verified_publication_point_pack,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn fixture_to_rsync_uri(path: &Path) -> String {
|
fn fixture_to_rsync_uri(path: &Path) -> String {
|
||||||
let rel = path
|
let rel = path
|
||||||
@ -33,28 +30,11 @@ fn fixture_dir_to_rsync_uri(dir: &Path) -> String {
|
|||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
struct EmptyResolver;
|
|
||||||
|
|
||||||
impl IssuerCaCertificateResolver for EmptyResolver {
|
|
||||||
fn resolve_by_subject_dn(&self, _subject_dn: &str) -> Option<Vec<u8>> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct MapResolver {
|
|
||||||
by_subject_dn: HashMap<String, Vec<u8>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IssuerCaCertificateResolver for MapResolver {
|
|
||||||
fn resolve_by_subject_dn(&self, subject_dn: &str) -> Option<Vec<u8>> {
|
|
||||||
self.by_subject_dn.get(subject_dn).cloned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_cernet_pack_and_validation_time() -> (
|
fn build_cernet_pack_and_validation_time() -> (
|
||||||
rpki::storage::VerifiedPublicationPointPack,
|
rpki::storage::VerifiedPublicationPointPack,
|
||||||
time::OffsetDateTime,
|
time::OffsetDateTime,
|
||||||
MapResolver,
|
Vec<u8>,
|
||||||
|
ResourceCertificate,
|
||||||
) {
|
) {
|
||||||
let manifest_path = Path::new(
|
let manifest_path = Path::new(
|
||||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
||||||
@ -112,44 +92,60 @@ fn build_cernet_pack_and_validation_time() -> (
|
|||||||
}
|
}
|
||||||
t += time::Duration::seconds(1);
|
t += time::Duration::seconds(1);
|
||||||
|
|
||||||
let resolver = MapResolver {
|
(out.pack, t, issuer_ca_der, issuer_ca)
|
||||||
by_subject_dn: HashMap::from([(issuer_ca.tbs.subject_dn, issuer_ca_der)]),
|
|
||||||
};
|
|
||||||
|
|
||||||
(out.pack, t, resolver)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_crl_causes_roas_to_be_dropped_under_drop_object_policy() {
|
fn missing_crl_causes_roas_to_be_dropped_under_drop_object_policy() {
|
||||||
let (mut pack, validation_time, resolver) = build_cernet_pack_and_validation_time();
|
let (mut pack, validation_time, issuer_ca_der, issuer_ca) =
|
||||||
|
build_cernet_pack_and_validation_time();
|
||||||
pack.files.retain(|f| !f.rsync_uri.ends_with(".crl"));
|
pack.files.retain(|f| !f.rsync_uri.ends_with(".crl"));
|
||||||
|
|
||||||
let mut policy = Policy::default();
|
let mut policy = Policy::default();
|
||||||
policy.signed_object_failure_policy = SignedObjectFailurePolicy::DropObject;
|
policy.signed_object_failure_policy = SignedObjectFailurePolicy::DropObject;
|
||||||
|
|
||||||
let out = process_verified_publication_point_pack(&pack, &policy, &resolver, validation_time)
|
let out = process_verified_publication_point_pack_for_issuer(
|
||||||
.expect("drop_object should not fail the publication point");
|
&pack,
|
||||||
|
&policy,
|
||||||
|
&issuer_ca_der,
|
||||||
|
None,
|
||||||
|
issuer_ca.tbs.extensions.ip_resources.as_ref(),
|
||||||
|
issuer_ca.tbs.extensions.as_resources.as_ref(),
|
||||||
|
validation_time,
|
||||||
|
);
|
||||||
assert!(out.vrps.is_empty());
|
assert!(out.vrps.is_empty());
|
||||||
assert!(!out.warnings.is_empty());
|
assert!(!out.warnings.is_empty());
|
||||||
|
assert!(out.stats.publication_point_dropped);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn missing_issuer_ca_cert_causes_roas_to_be_dropped_under_drop_object_policy() {
|
fn wrong_issuer_ca_cert_causes_roas_to_be_dropped_under_drop_object_policy() {
|
||||||
let (pack, validation_time, _resolver) = build_cernet_pack_and_validation_time();
|
let (pack, validation_time, _issuer_ca_der, _issuer_ca) =
|
||||||
|
build_cernet_pack_and_validation_time();
|
||||||
|
|
||||||
let mut policy = Policy::default();
|
let mut policy = Policy::default();
|
||||||
policy.signed_object_failure_policy = SignedObjectFailurePolicy::DropObject;
|
policy.signed_object_failure_policy = SignedObjectFailurePolicy::DropObject;
|
||||||
|
|
||||||
let out =
|
// Use an unrelated trust anchor certificate as the issuer to force EE cert path validation to fail.
|
||||||
process_verified_publication_point_pack(&pack, &policy, &EmptyResolver, validation_time)
|
let wrong_issuer_ca_der =
|
||||||
.expect("drop_object should not fail the publication point");
|
std::fs::read("tests/fixtures/ta/arin-ta.cer").expect("read wrong issuer ca");
|
||||||
|
let out = process_verified_publication_point_pack_for_issuer(
|
||||||
|
&pack,
|
||||||
|
&policy,
|
||||||
|
&wrong_issuer_ca_der,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
validation_time,
|
||||||
|
);
|
||||||
assert!(out.vrps.is_empty());
|
assert!(out.vrps.is_empty());
|
||||||
assert!(!out.warnings.is_empty());
|
assert!(!out.warnings.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn invalid_aspa_object_is_reported_as_warning_under_drop_object_policy() {
|
fn invalid_aspa_object_is_reported_as_warning_under_drop_object_policy() {
|
||||||
let (mut pack, validation_time, resolver) = build_cernet_pack_and_validation_time();
|
let (mut pack, validation_time, issuer_ca_der, issuer_ca) =
|
||||||
|
build_cernet_pack_and_validation_time();
|
||||||
|
|
||||||
let uri = "rsync://rpki.cernet.net/repo/cernet/0/INVALID.asa".to_string();
|
let uri = "rsync://rpki.cernet.net/repo/cernet/0/INVALID.asa".to_string();
|
||||||
pack.files.push(PackFile::from_bytes_compute_sha256(
|
pack.files.push(PackFile::from_bytes_compute_sha256(
|
||||||
@ -160,8 +156,15 @@ fn invalid_aspa_object_is_reported_as_warning_under_drop_object_policy() {
|
|||||||
let mut policy = Policy::default();
|
let mut policy = Policy::default();
|
||||||
policy.signed_object_failure_policy = SignedObjectFailurePolicy::DropObject;
|
policy.signed_object_failure_policy = SignedObjectFailurePolicy::DropObject;
|
||||||
|
|
||||||
let out = process_verified_publication_point_pack(&pack, &policy, &resolver, validation_time)
|
let out = process_verified_publication_point_pack_for_issuer(
|
||||||
.expect("drop_object should not fail");
|
&pack,
|
||||||
|
&policy,
|
||||||
|
&issuer_ca_der,
|
||||||
|
None,
|
||||||
|
issuer_ca.tbs.extensions.ip_resources.as_ref(),
|
||||||
|
issuer_ca.tbs.extensions.as_resources.as_ref(),
|
||||||
|
validation_time,
|
||||||
|
);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
out.warnings
|
out.warnings
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use rpki::data_model::crl::RpkixCrl;
|
use rpki::data_model::crl::RpkixCrl;
|
||||||
@ -7,9 +6,7 @@ use rpki::data_model::rc::ResourceCertificate;
|
|||||||
use rpki::policy::{Policy, SignedObjectFailurePolicy};
|
use rpki::policy::{Policy, SignedObjectFailurePolicy};
|
||||||
use rpki::storage::{PackFile, RocksStore};
|
use rpki::storage::{PackFile, RocksStore};
|
||||||
use rpki::validation::manifest::process_manifest_publication_point;
|
use rpki::validation::manifest::process_manifest_publication_point;
|
||||||
use rpki::validation::objects::{
|
use rpki::validation::objects::process_verified_publication_point_pack_for_issuer;
|
||||||
IssuerCaCertificateResolver, ObjectsProcessError, process_verified_publication_point_pack,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn fixture_to_rsync_uri(path: &Path) -> String {
|
fn fixture_to_rsync_uri(path: &Path) -> String {
|
||||||
let rel = path
|
let rel = path
|
||||||
@ -33,20 +30,11 @@ fn fixture_dir_to_rsync_uri(dir: &Path) -> String {
|
|||||||
s
|
s
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MapResolver {
|
|
||||||
by_subject_dn: HashMap<String, Vec<u8>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IssuerCaCertificateResolver for MapResolver {
|
|
||||||
fn resolve_by_subject_dn(&self, subject_dn: &str) -> Option<Vec<u8>> {
|
|
||||||
self.by_subject_dn.get(subject_dn).cloned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn build_cernet_pack_and_validation_time() -> (
|
fn build_cernet_pack_and_validation_time() -> (
|
||||||
rpki::storage::VerifiedPublicationPointPack,
|
rpki::storage::VerifiedPublicationPointPack,
|
||||||
time::OffsetDateTime,
|
time::OffsetDateTime,
|
||||||
MapResolver,
|
Vec<u8>,
|
||||||
|
ResourceCertificate,
|
||||||
) {
|
) {
|
||||||
let manifest_path = Path::new(
|
let manifest_path = Path::new(
|
||||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
||||||
@ -109,19 +97,13 @@ fn build_cernet_pack_and_validation_time() -> (
|
|||||||
}
|
}
|
||||||
t += time::Duration::seconds(1);
|
t += time::Duration::seconds(1);
|
||||||
|
|
||||||
let mut resolver = MapResolver {
|
(out.pack, t, issuer_ca_der, issuer_ca)
|
||||||
by_subject_dn: HashMap::new(),
|
|
||||||
};
|
|
||||||
resolver
|
|
||||||
.by_subject_dn
|
|
||||||
.insert(issuer_ca.tbs.subject_dn, issuer_ca_der);
|
|
||||||
|
|
||||||
(out.pack, t, resolver)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn drop_object_policy_drops_only_failing_object() {
|
fn drop_object_policy_drops_only_failing_object() {
|
||||||
let (mut pack, validation_time, resolver) = build_cernet_pack_and_validation_time();
|
let (mut pack, validation_time, issuer_ca_der, issuer_ca) =
|
||||||
|
build_cernet_pack_and_validation_time();
|
||||||
|
|
||||||
let valid_roa_uri = pack
|
let valid_roa_uri = pack
|
||||||
.files
|
.files
|
||||||
@ -145,8 +127,15 @@ fn drop_object_policy_drops_only_failing_object() {
|
|||||||
let mut policy = Policy::default();
|
let mut policy = Policy::default();
|
||||||
policy.signed_object_failure_policy = SignedObjectFailurePolicy::DropObject;
|
policy.signed_object_failure_policy = SignedObjectFailurePolicy::DropObject;
|
||||||
|
|
||||||
let out = process_verified_publication_point_pack(&pack, &policy, &resolver, validation_time)
|
let out = process_verified_publication_point_pack_for_issuer(
|
||||||
.expect("drop_object should succeed");
|
&pack,
|
||||||
|
&policy,
|
||||||
|
&issuer_ca_der,
|
||||||
|
None,
|
||||||
|
issuer_ca.tbs.extensions.ip_resources.as_ref(),
|
||||||
|
issuer_ca.tbs.extensions.as_resources.as_ref(),
|
||||||
|
validation_time,
|
||||||
|
);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
out.vrps.iter().any(|v| v.asn == 4538),
|
out.vrps.iter().any(|v| v.asn == 4538),
|
||||||
@ -161,8 +150,9 @@ fn drop_object_policy_drops_only_failing_object() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn drop_publication_point_policy_fails_the_publication_point() {
|
fn drop_publication_point_policy_drops_the_publication_point() {
|
||||||
let (mut pack, validation_time, resolver) = build_cernet_pack_and_validation_time();
|
let (mut pack, validation_time, issuer_ca_der, issuer_ca) =
|
||||||
|
build_cernet_pack_and_validation_time();
|
||||||
|
|
||||||
let tamper_idx = pack
|
let tamper_idx = pack
|
||||||
.files
|
.files
|
||||||
@ -179,11 +169,19 @@ fn drop_publication_point_policy_fails_the_publication_point() {
|
|||||||
let mut policy = Policy::default();
|
let mut policy = Policy::default();
|
||||||
policy.signed_object_failure_policy = SignedObjectFailurePolicy::DropPublicationPoint;
|
policy.signed_object_failure_policy = SignedObjectFailurePolicy::DropPublicationPoint;
|
||||||
|
|
||||||
let err = process_verified_publication_point_pack(&pack, &policy, &resolver, validation_time)
|
let out = process_verified_publication_point_pack_for_issuer(
|
||||||
.expect_err("drop_publication_point should fail");
|
&pack,
|
||||||
match err {
|
&policy,
|
||||||
ObjectsProcessError::PublicationPointDropped { rsync_uri, .. } => {
|
&issuer_ca_der,
|
||||||
assert_eq!(rsync_uri, victim_uri);
|
None,
|
||||||
}
|
issuer_ca.tbs.extensions.ip_resources.as_ref(),
|
||||||
}
|
issuer_ca.tbs.extensions.as_resources.as_ref(),
|
||||||
|
validation_time,
|
||||||
|
);
|
||||||
|
assert!(out.stats.publication_point_dropped);
|
||||||
|
assert!(out.vrps.is_empty(), "expected publication point dropped");
|
||||||
|
assert!(
|
||||||
|
out.audit.iter().any(|a| a.rsync_uri == victim_uri),
|
||||||
|
"expected audit entry for victim object"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -4,10 +4,7 @@ use rpki::storage::{PackFile, PackTime, RocksStore, VerifiedPublicationPointPack
|
|||||||
use rpki::sync::repo::sync_publication_point;
|
use rpki::sync::repo::sync_publication_point;
|
||||||
use rpki::sync::rrdp::Fetcher;
|
use rpki::sync::rrdp::Fetcher;
|
||||||
use rpki::validation::manifest::process_manifest_publication_point;
|
use rpki::validation::manifest::process_manifest_publication_point;
|
||||||
use rpki::validation::objects::{
|
use rpki::validation::objects::process_verified_publication_point_pack_for_issuer;
|
||||||
IssuerCaCertificateResolver, process_verified_publication_point_pack,
|
|
||||||
process_verified_publication_point_pack_for_issuer,
|
|
||||||
};
|
|
||||||
|
|
||||||
struct NoopHttpFetcher;
|
struct NoopHttpFetcher;
|
||||||
impl Fetcher for NoopHttpFetcher {
|
impl Fetcher for NoopHttpFetcher {
|
||||||
@ -24,13 +21,20 @@ fn cernet_fixture() -> (std::path::PathBuf, String, String) {
|
|||||||
(dir, rsync_base_uri, manifest_file)
|
(dir, rsync_base_uri, manifest_file)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validation_time_from_manifest_fixture(dir: &std::path::Path, manifest_file: &str) -> time::OffsetDateTime {
|
fn validation_time_from_manifest_fixture(
|
||||||
|
dir: &std::path::Path,
|
||||||
|
manifest_file: &str,
|
||||||
|
) -> time::OffsetDateTime {
|
||||||
let bytes = std::fs::read(dir.join(manifest_file)).expect("read manifest fixture");
|
let bytes = std::fs::read(dir.join(manifest_file)).expect("read manifest fixture");
|
||||||
let mft = rpki::data_model::manifest::ManifestObject::decode_der(&bytes).expect("decode mft");
|
let mft = rpki::data_model::manifest::ManifestObject::decode_der(&bytes).expect("decode mft");
|
||||||
let this_update = mft.manifest.this_update;
|
let this_update = mft.manifest.this_update;
|
||||||
let next_update = mft.manifest.next_update;
|
let next_update = mft.manifest.next_update;
|
||||||
let candidate = this_update + time::Duration::seconds(60);
|
let candidate = this_update + time::Duration::seconds(60);
|
||||||
if candidate < next_update { candidate } else { this_update }
|
if candidate < next_update {
|
||||||
|
candidate
|
||||||
|
} else {
|
||||||
|
this_update
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn issuer_ca_fixture() -> Vec<u8> {
|
fn issuer_ca_fixture() -> Vec<u8> {
|
||||||
@ -129,7 +133,11 @@ fn process_pack_for_issuer_extracts_vrps_from_real_cernet_fixture() {
|
|||||||
validation_time,
|
validation_time,
|
||||||
);
|
);
|
||||||
|
|
||||||
assert!(out.vrps.len() > 10, "expected many VRPs, got {}", out.vrps.len());
|
assert!(
|
||||||
|
out.vrps.len() > 10,
|
||||||
|
"expected many VRPs, got {}",
|
||||||
|
out.vrps.len()
|
||||||
|
);
|
||||||
assert!(out.aspas.is_empty());
|
assert!(out.aspas.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -187,7 +195,10 @@ fn signed_object_failure_policy_drop_object_drops_only_bad_object() {
|
|||||||
"expected one audit entry per ROA"
|
"expected one audit entry per ROA"
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
out.audit.iter().any(|e| e.rsync_uri == pack.files[bad_idx].rsync_uri && matches!(e.result, rpki::audit::AuditObjectResult::Error)),
|
out.audit
|
||||||
|
.iter()
|
||||||
|
.any(|e| e.rsync_uri == pack.files[bad_idx].rsync_uri
|
||||||
|
&& matches!(e.result, rpki::audit::AuditObjectResult::Error)),
|
||||||
"expected audit error for the corrupted ROA"
|
"expected audit error for the corrupted ROA"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -303,10 +314,8 @@ fn process_pack_for_issuer_handles_invalid_aspa_bytes() {
|
|||||||
let manifest_rsync_uri = format!("{rsync_base_uri}{manifest_file}");
|
let manifest_rsync_uri = format!("{rsync_base_uri}{manifest_file}");
|
||||||
let validation_time = validation_time_from_manifest_fixture(&dir, &manifest_file);
|
let validation_time = validation_time_from_manifest_fixture(&dir, &manifest_file);
|
||||||
let manifest_bytes = std::fs::read(dir.join(&manifest_file)).expect("read mft");
|
let manifest_bytes = std::fs::read(dir.join(&manifest_file)).expect("read mft");
|
||||||
let crl_bytes = std::fs::read(dir.join(
|
let crl_bytes =
|
||||||
"05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl",
|
std::fs::read(dir.join("05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl")).expect("read crl");
|
||||||
))
|
|
||||||
.expect("read crl");
|
|
||||||
|
|
||||||
let pack = minimal_pack(
|
let pack = minimal_pack(
|
||||||
&manifest_rsync_uri,
|
&manifest_rsync_uri,
|
||||||
@ -345,10 +354,8 @@ fn process_pack_for_issuer_drop_publication_point_on_invalid_aspa_bytes() {
|
|||||||
let manifest_rsync_uri = format!("{rsync_base_uri}{manifest_file}");
|
let manifest_rsync_uri = format!("{rsync_base_uri}{manifest_file}");
|
||||||
let validation_time = validation_time_from_manifest_fixture(&dir, &manifest_file);
|
let validation_time = validation_time_from_manifest_fixture(&dir, &manifest_file);
|
||||||
let manifest_bytes = std::fs::read(dir.join(&manifest_file)).expect("read mft");
|
let manifest_bytes = std::fs::read(dir.join(&manifest_file)).expect("read mft");
|
||||||
let crl_bytes = std::fs::read(dir.join(
|
let crl_bytes =
|
||||||
"05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl",
|
std::fs::read(dir.join("05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl")).expect("read crl");
|
||||||
))
|
|
||||||
.expect("read crl");
|
|
||||||
|
|
||||||
let pack = minimal_pack(
|
let pack = minimal_pack(
|
||||||
&manifest_rsync_uri,
|
&manifest_rsync_uri,
|
||||||
@ -384,80 +391,4 @@ fn process_pack_for_issuer_drop_publication_point_on_invalid_aspa_bytes() {
|
|||||||
assert!(!out.warnings.is_empty());
|
assert!(!out.warnings.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
struct NoIssuerResolver;
|
// NOTE: DN-based issuer resolution and pack-local CA indexing have been removed for determinism.
|
||||||
impl IssuerCaCertificateResolver for NoIssuerResolver {
|
|
||||||
fn resolve_by_subject_dn(&self, _subject_dn: &str) -> Option<Vec<u8>> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
struct AlwaysIssuerResolver {
|
|
||||||
issuer_ca_der: Vec<u8>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IssuerCaCertificateResolver for AlwaysIssuerResolver {
|
|
||||||
fn resolve_by_subject_dn(&self, _subject_dn: &str) -> Option<Vec<u8>> {
|
|
||||||
Some(self.issuer_ca_der.clone())
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn process_verified_pack_indexes_ca_certs_by_subject() {
|
|
||||||
let (dir, rsync_base_uri, manifest_file) = cernet_fixture();
|
|
||||||
let manifest_rsync_uri = format!("{rsync_base_uri}{manifest_file}");
|
|
||||||
let validation_time = validation_time_from_manifest_fixture(&dir, &manifest_file);
|
|
||||||
let manifest_bytes = std::fs::read(dir.join(&manifest_file)).expect("read mft");
|
|
||||||
|
|
||||||
// Add a real CA certificate to exercise CA indexing logic.
|
|
||||||
let ca_der = issuer_ca_fixture();
|
|
||||||
|
|
||||||
let pack = minimal_pack(
|
|
||||||
&manifest_rsync_uri,
|
|
||||||
&rsync_base_uri,
|
|
||||||
manifest_bytes,
|
|
||||||
vec![PackFile::from_bytes_compute_sha256(
|
|
||||||
format!("{rsync_base_uri}some-ca.cer"),
|
|
||||||
ca_der,
|
|
||||||
)],
|
|
||||||
validation_time,
|
|
||||||
);
|
|
||||||
|
|
||||||
let policy = Policy::default();
|
|
||||||
let out = process_verified_publication_point_pack(
|
|
||||||
&pack,
|
|
||||||
&policy,
|
|
||||||
&NoIssuerResolver,
|
|
||||||
validation_time,
|
|
||||||
)
|
|
||||||
.expect("process pack");
|
|
||||||
assert!(out.vrps.is_empty());
|
|
||||||
assert!(out.aspas.is_empty());
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn process_pack_with_resolver_extracts_vrps_from_real_cernet_fixture() {
|
|
||||||
let (dir, rsync_base_uri, manifest_file) = cernet_fixture();
|
|
||||||
let manifest_rsync_uri = format!("{rsync_base_uri}{manifest_file}");
|
|
||||||
let validation_time = validation_time_from_manifest_fixture(&dir, &manifest_file);
|
|
||||||
|
|
||||||
let pack = build_verified_pack_from_local_rsync_fixture(
|
|
||||||
&dir,
|
|
||||||
&rsync_base_uri,
|
|
||||||
&manifest_rsync_uri,
|
|
||||||
validation_time,
|
|
||||||
);
|
|
||||||
|
|
||||||
let policy = Policy::default();
|
|
||||||
let issuer_ca_der = issuer_ca_fixture();
|
|
||||||
let resolver = AlwaysIssuerResolver {
|
|
||||||
issuer_ca_der: issuer_ca_der.clone(),
|
|
||||||
};
|
|
||||||
|
|
||||||
let out = process_verified_publication_point_pack(&pack, &policy, &resolver, validation_time)
|
|
||||||
.expect("process pack");
|
|
||||||
assert!(out.vrps.len() > 10, "expected many VRPs, got {}", out.vrps.len());
|
|
||||||
assert!(
|
|
||||||
out.audit.len() >= pack.files.iter().filter(|f| f.rsync_uri.ends_with(".roa")).count(),
|
|
||||||
"expected ROA audit entries"
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|||||||
@ -1,9 +1,6 @@
|
|||||||
use rpki::policy::{Policy, SignedObjectFailurePolicy};
|
use rpki::policy::{Policy, SignedObjectFailurePolicy};
|
||||||
use rpki::storage::{PackFile, PackTime, VerifiedPublicationPointPack};
|
use rpki::storage::{PackFile, PackTime, VerifiedPublicationPointPack};
|
||||||
use rpki::validation::objects::{
|
use rpki::validation::objects::process_verified_publication_point_pack_for_issuer;
|
||||||
IssuerCaCertificateResolver, ObjectsProcessError, process_verified_publication_point_pack,
|
|
||||||
process_verified_publication_point_pack_for_issuer,
|
|
||||||
};
|
|
||||||
|
|
||||||
fn fixture_bytes(path: &str) -> Vec<u8> {
|
fn fixture_bytes(path: &str) -> Vec<u8> {
|
||||||
std::fs::read(std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(path))
|
std::fs::read(std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(path))
|
||||||
@ -24,28 +21,36 @@ fn dummy_pack(manifest_bytes: Vec<u8>, files: Vec<PackFile>) -> VerifiedPublicat
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct NoneResolver;
|
|
||||||
|
|
||||||
impl IssuerCaCertificateResolver for NoneResolver {
|
|
||||||
fn resolve_by_subject_dn(&self, _subject_dn: &str) -> Option<Vec<u8>> {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn process_pack_drop_object_on_missing_issuer_ca_for_roa() {
|
fn process_pack_drop_object_on_wrong_issuer_ca_for_roa() {
|
||||||
let manifest_bytes = fixture_bytes(
|
let manifest_bytes = fixture_bytes(
|
||||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
||||||
);
|
);
|
||||||
let roa_bytes =
|
let roa_bytes =
|
||||||
fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
|
fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
|
||||||
|
let roa = rpki::data_model::roa::RoaObject::decode_der(&roa_bytes).expect("decode roa");
|
||||||
|
let ee_crldp = roa.signed_object.signed_data.certificates[0]
|
||||||
|
.resource_cert
|
||||||
|
.tbs
|
||||||
|
.extensions
|
||||||
|
.crl_distribution_points_uris
|
||||||
|
.as_ref()
|
||||||
|
.expect("ee crldp")[0]
|
||||||
|
.as_str()
|
||||||
|
.to_string();
|
||||||
|
let crl_bytes = fixture_bytes(
|
||||||
|
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl",
|
||||||
|
);
|
||||||
|
|
||||||
let pack = dummy_pack(
|
let pack = dummy_pack(
|
||||||
manifest_bytes,
|
manifest_bytes,
|
||||||
vec![PackFile::from_bytes_compute_sha256(
|
vec![
|
||||||
|
PackFile::from_bytes_compute_sha256(ee_crldp, crl_bytes),
|
||||||
|
PackFile::from_bytes_compute_sha256(
|
||||||
"rsync://rpki.cernet.net/repo/cernet/0/AS4538.roa",
|
"rsync://rpki.cernet.net/repo/cernet/0/AS4538.roa",
|
||||||
roa_bytes,
|
roa_bytes,
|
||||||
)],
|
),
|
||||||
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
let policy = Policy {
|
let policy = Policy {
|
||||||
@ -53,9 +58,16 @@ fn process_pack_drop_object_on_missing_issuer_ca_for_roa() {
|
|||||||
..Policy::default()
|
..Policy::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let out =
|
let wrong_issuer_ca_der = fixture_bytes("tests/fixtures/ta/arin-ta.cer");
|
||||||
process_verified_publication_point_pack(&pack, &policy, &NoneResolver, time::OffsetDateTime::now_utc())
|
let out = process_verified_publication_point_pack_for_issuer(
|
||||||
.expect("drop_object should not error");
|
&pack,
|
||||||
|
&policy,
|
||||||
|
&wrong_issuer_ca_der,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
time::OffsetDateTime::now_utc(),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(out.stats.roa_total, 1);
|
assert_eq!(out.stats.roa_total, 1);
|
||||||
assert_eq!(out.stats.roa_ok, 0);
|
assert_eq!(out.stats.roa_ok, 0);
|
||||||
@ -64,12 +76,25 @@ fn process_pack_drop_object_on_missing_issuer_ca_for_roa() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn process_pack_drop_publication_point_on_missing_issuer_ca_for_roa_skips_rest() {
|
fn process_pack_drop_publication_point_on_wrong_issuer_ca_for_roa_skips_rest() {
|
||||||
let manifest_bytes = fixture_bytes(
|
let manifest_bytes = fixture_bytes(
|
||||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
||||||
);
|
);
|
||||||
let roa_bytes =
|
let roa_bytes =
|
||||||
fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
|
fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
|
||||||
|
let roa = rpki::data_model::roa::RoaObject::decode_der(&roa_bytes).expect("decode roa");
|
||||||
|
let ee_crldp = roa.signed_object.signed_data.certificates[0]
|
||||||
|
.resource_cert
|
||||||
|
.tbs
|
||||||
|
.extensions
|
||||||
|
.crl_distribution_points_uris
|
||||||
|
.as_ref()
|
||||||
|
.expect("ee crldp")[0]
|
||||||
|
.as_str()
|
||||||
|
.to_string();
|
||||||
|
let crl_bytes = fixture_bytes(
|
||||||
|
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl",
|
||||||
|
);
|
||||||
let aspa_bytes = fixture_bytes(
|
let aspa_bytes = fixture_bytes(
|
||||||
"tests/fixtures/repository/chloe.sobornost.net/rpki/RIPE-nljobsnijders/5m80fwYws_3FiFD7JiQjAqZ1RYQ.asa",
|
"tests/fixtures/repository/chloe.sobornost.net/rpki/RIPE-nljobsnijders/5m80fwYws_3FiFD7JiQjAqZ1RYQ.asa",
|
||||||
);
|
);
|
||||||
@ -77,6 +102,7 @@ fn process_pack_drop_publication_point_on_missing_issuer_ca_for_roa_skips_rest()
|
|||||||
let pack = dummy_pack(
|
let pack = dummy_pack(
|
||||||
manifest_bytes,
|
manifest_bytes,
|
||||||
vec![
|
vec![
|
||||||
|
PackFile::from_bytes_compute_sha256(ee_crldp, crl_bytes),
|
||||||
PackFile::from_bytes_compute_sha256(
|
PackFile::from_bytes_compute_sha256(
|
||||||
"rsync://example.test/repo/pp/first.roa",
|
"rsync://example.test/repo/pp/first.roa",
|
||||||
roa_bytes.clone(),
|
roa_bytes.clone(),
|
||||||
@ -85,10 +111,7 @@ fn process_pack_drop_publication_point_on_missing_issuer_ca_for_roa_skips_rest()
|
|||||||
"rsync://example.test/repo/pp/second.roa",
|
"rsync://example.test/repo/pp/second.roa",
|
||||||
roa_bytes,
|
roa_bytes,
|
||||||
),
|
),
|
||||||
PackFile::from_bytes_compute_sha256(
|
PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/x.asa", aspa_bytes),
|
||||||
"rsync://example.test/repo/pp/x.asa",
|
|
||||||
aspa_bytes,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -97,28 +120,48 @@ fn process_pack_drop_publication_point_on_missing_issuer_ca_for_roa_skips_rest()
|
|||||||
..Policy::default()
|
..Policy::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let err =
|
let wrong_issuer_ca_der = fixture_bytes("tests/fixtures/ta/arin-ta.cer");
|
||||||
process_verified_publication_point_pack(&pack, &policy, &NoneResolver, time::OffsetDateTime::now_utc())
|
let out = process_verified_publication_point_pack_for_issuer(
|
||||||
.unwrap_err();
|
&pack,
|
||||||
assert!(matches!(err, ObjectsProcessError::PublicationPointDropped { .. }));
|
&policy,
|
||||||
assert!(err.to_string().contains("drop_publication_point"));
|
&wrong_issuer_ca_der,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
time::OffsetDateTime::now_utc(),
|
||||||
|
);
|
||||||
|
assert!(out.stats.publication_point_dropped);
|
||||||
|
assert_eq!(out.warnings.len(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn process_pack_drop_object_on_missing_issuer_ca_for_aspa() {
|
fn process_pack_drop_object_on_wrong_issuer_ca_for_aspa() {
|
||||||
let manifest_bytes = fixture_bytes(
|
let manifest_bytes = fixture_bytes(
|
||||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
||||||
);
|
);
|
||||||
let aspa_bytes = fixture_bytes(
|
let aspa_bytes = fixture_bytes(
|
||||||
"tests/fixtures/repository/chloe.sobornost.net/rpki/RIPE-nljobsnijders/5m80fwYws_3FiFD7JiQjAqZ1RYQ.asa",
|
"tests/fixtures/repository/chloe.sobornost.net/rpki/RIPE-nljobsnijders/5m80fwYws_3FiFD7JiQjAqZ1RYQ.asa",
|
||||||
);
|
);
|
||||||
|
let aspa = rpki::data_model::aspa::AspaObject::decode_der(&aspa_bytes).expect("decode aspa");
|
||||||
|
let ee_crldp = aspa.signed_object.signed_data.certificates[0]
|
||||||
|
.resource_cert
|
||||||
|
.tbs
|
||||||
|
.extensions
|
||||||
|
.crl_distribution_points_uris
|
||||||
|
.as_ref()
|
||||||
|
.expect("ee crldp")[0]
|
||||||
|
.as_str()
|
||||||
|
.to_string();
|
||||||
|
let crl_bytes = fixture_bytes(
|
||||||
|
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl",
|
||||||
|
);
|
||||||
|
|
||||||
let pack = dummy_pack(
|
let pack = dummy_pack(
|
||||||
manifest_bytes,
|
manifest_bytes,
|
||||||
vec![PackFile::from_bytes_compute_sha256(
|
vec![
|
||||||
"rsync://example.test/repo/pp/x.asa",
|
PackFile::from_bytes_compute_sha256(ee_crldp, crl_bytes),
|
||||||
aspa_bytes,
|
PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/x.asa", aspa_bytes),
|
||||||
)],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
let policy = Policy {
|
let policy = Policy {
|
||||||
@ -126,9 +169,16 @@ fn process_pack_drop_object_on_missing_issuer_ca_for_aspa() {
|
|||||||
..Policy::default()
|
..Policy::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let out =
|
let wrong_issuer_ca_der = fixture_bytes("tests/fixtures/ta/arin-ta.cer");
|
||||||
process_verified_publication_point_pack(&pack, &policy, &NoneResolver, time::OffsetDateTime::now_utc())
|
let out = process_verified_publication_point_pack_for_issuer(
|
||||||
.expect("drop_object should not error");
|
&pack,
|
||||||
|
&policy,
|
||||||
|
&wrong_issuer_ca_der,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
time::OffsetDateTime::now_utc(),
|
||||||
|
);
|
||||||
|
|
||||||
assert_eq!(out.stats.aspa_total, 1);
|
assert_eq!(out.stats.aspa_total, 1);
|
||||||
assert_eq!(out.stats.aspa_ok, 0);
|
assert_eq!(out.stats.aspa_ok, 0);
|
||||||
@ -137,7 +187,7 @@ fn process_pack_drop_object_on_missing_issuer_ca_for_aspa() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn process_pack_drop_publication_point_on_missing_issuer_ca_for_aspa_skips_rest() {
|
fn process_pack_drop_publication_point_on_wrong_issuer_ca_for_aspa_skips_rest() {
|
||||||
let manifest_bytes = fixture_bytes(
|
let manifest_bytes = fixture_bytes(
|
||||||
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
||||||
);
|
);
|
||||||
@ -146,18 +196,26 @@ fn process_pack_drop_publication_point_on_missing_issuer_ca_for_aspa_skips_rest(
|
|||||||
let aspa_bytes = fixture_bytes(
|
let aspa_bytes = fixture_bytes(
|
||||||
"tests/fixtures/repository/chloe.sobornost.net/rpki/RIPE-nljobsnijders/5m80fwYws_3FiFD7JiQjAqZ1RYQ.asa",
|
"tests/fixtures/repository/chloe.sobornost.net/rpki/RIPE-nljobsnijders/5m80fwYws_3FiFD7JiQjAqZ1RYQ.asa",
|
||||||
);
|
);
|
||||||
|
let aspa = rpki::data_model::aspa::AspaObject::decode_der(&aspa_bytes).expect("decode aspa");
|
||||||
|
let ee_crldp = aspa.signed_object.signed_data.certificates[0]
|
||||||
|
.resource_cert
|
||||||
|
.tbs
|
||||||
|
.extensions
|
||||||
|
.crl_distribution_points_uris
|
||||||
|
.as_ref()
|
||||||
|
.expect("ee crldp")[0]
|
||||||
|
.as_str()
|
||||||
|
.to_string();
|
||||||
|
let crl_bytes = fixture_bytes(
|
||||||
|
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.crl",
|
||||||
|
);
|
||||||
|
|
||||||
let pack = dummy_pack(
|
let pack = dummy_pack(
|
||||||
manifest_bytes,
|
manifest_bytes,
|
||||||
vec![
|
vec![
|
||||||
PackFile::from_bytes_compute_sha256(
|
PackFile::from_bytes_compute_sha256(ee_crldp, crl_bytes),
|
||||||
"rsync://example.test/repo/pp/x.asa",
|
PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/x.asa", aspa_bytes),
|
||||||
aspa_bytes,
|
PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/y.roa", roa_bytes),
|
||||||
),
|
|
||||||
PackFile::from_bytes_compute_sha256(
|
|
||||||
"rsync://example.test/repo/pp/y.roa",
|
|
||||||
roa_bytes,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -166,10 +224,17 @@ fn process_pack_drop_publication_point_on_missing_issuer_ca_for_aspa_skips_rest(
|
|||||||
..Policy::default()
|
..Policy::default()
|
||||||
};
|
};
|
||||||
|
|
||||||
let err =
|
let wrong_issuer_ca_der = fixture_bytes("tests/fixtures/ta/arin-ta.cer");
|
||||||
process_verified_publication_point_pack(&pack, &policy, &NoneResolver, time::OffsetDateTime::now_utc())
|
let out = process_verified_publication_point_pack_for_issuer(
|
||||||
.unwrap_err();
|
&pack,
|
||||||
assert!(matches!(err, ObjectsProcessError::PublicationPointDropped { .. }));
|
&policy,
|
||||||
|
&wrong_issuer_ca_der,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
time::OffsetDateTime::now_utc(),
|
||||||
|
);
|
||||||
|
assert!(out.stats.publication_point_dropped);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -186,14 +251,8 @@ fn process_pack_for_issuer_marks_objects_skipped_when_missing_issuer_crl() {
|
|||||||
let pack = dummy_pack(
|
let pack = dummy_pack(
|
||||||
manifest_bytes,
|
manifest_bytes,
|
||||||
vec![
|
vec![
|
||||||
PackFile::from_bytes_compute_sha256(
|
PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/a.roa", roa_bytes),
|
||||||
"rsync://example.test/repo/pp/a.roa",
|
PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/a.asa", aspa_bytes),
|
||||||
roa_bytes,
|
|
||||||
),
|
|
||||||
PackFile::from_bytes_compute_sha256(
|
|
||||||
"rsync://example.test/repo/pp/a.asa",
|
|
||||||
aspa_bytes,
|
|
||||||
),
|
|
||||||
],
|
],
|
||||||
);
|
);
|
||||||
|
|
||||||
@ -302,3 +361,147 @@ fn process_pack_for_issuer_drop_publication_point_records_skips_for_rest() {
|
|||||||
assert_eq!(out.warnings.len(), 1);
|
assert_eq!(out.warnings.len(), 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn process_pack_for_issuer_selects_crl_by_ee_crldp_uri_roa() {
|
||||||
|
let manifest_bytes = fixture_bytes(
|
||||||
|
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
||||||
|
);
|
||||||
|
let roa_bytes =
|
||||||
|
fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
|
||||||
|
let roa = rpki::data_model::roa::RoaObject::decode_der(&roa_bytes).expect("decode roa");
|
||||||
|
let ee_crldp = roa.signed_object.signed_data.certificates[0]
|
||||||
|
.resource_cert
|
||||||
|
.tbs
|
||||||
|
.extensions
|
||||||
|
.crl_distribution_points_uris
|
||||||
|
.as_ref()
|
||||||
|
.expect("ee crldp")[0]
|
||||||
|
.as_str()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
// Provide a CRL file with the *exact* rsync URI referenced by the embedded EE certificate.
|
||||||
|
// Bytes need not be valid for this test: we just want to cover deterministic selection.
|
||||||
|
let pack = dummy_pack(
|
||||||
|
manifest_bytes,
|
||||||
|
vec![
|
||||||
|
PackFile::from_bytes_compute_sha256(ee_crldp, vec![0x01]),
|
||||||
|
PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/a.roa", roa_bytes),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
let policy = Policy {
|
||||||
|
signed_object_failure_policy: SignedObjectFailurePolicy::DropObject,
|
||||||
|
..Policy::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let out = process_verified_publication_point_pack_for_issuer(
|
||||||
|
&pack,
|
||||||
|
&policy,
|
||||||
|
&[0x01, 0x02, 0x03],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
time::OffsetDateTime::now_utc(),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(out.stats.roa_total, 1);
|
||||||
|
assert_eq!(out.stats.roa_ok, 0, "expected validation to fail later");
|
||||||
|
assert_eq!(out.audit.len(), 1);
|
||||||
|
assert_eq!(out.warnings.len(), 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn process_pack_for_issuer_rejects_roa_when_crldp_crl_missing() {
|
||||||
|
let manifest_bytes = fixture_bytes(
|
||||||
|
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
||||||
|
);
|
||||||
|
let roa_bytes =
|
||||||
|
fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
|
||||||
|
|
||||||
|
// Pack has a CRL, but its URI does not match the embedded EE certificate CRLDP.
|
||||||
|
let pack = dummy_pack(
|
||||||
|
manifest_bytes,
|
||||||
|
vec![
|
||||||
|
PackFile::from_bytes_compute_sha256(
|
||||||
|
"rsync://example.test/repo/pp/not-it.crl",
|
||||||
|
vec![0x01],
|
||||||
|
),
|
||||||
|
PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/a.roa", roa_bytes),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
let policy = Policy {
|
||||||
|
signed_object_failure_policy: SignedObjectFailurePolicy::DropObject,
|
||||||
|
..Policy::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let out = process_verified_publication_point_pack_for_issuer(
|
||||||
|
&pack,
|
||||||
|
&policy,
|
||||||
|
&[0x01, 0x02, 0x03],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
time::OffsetDateTime::now_utc(),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(out.stats.roa_total, 1);
|
||||||
|
assert_eq!(out.stats.roa_ok, 0);
|
||||||
|
assert_eq!(out.audit.len(), 1);
|
||||||
|
assert_eq!(out.warnings.len(), 1);
|
||||||
|
assert!(
|
||||||
|
out.warnings[0].message.contains("dropping invalid ROA")
|
||||||
|
|| out.warnings[0]
|
||||||
|
.message
|
||||||
|
.contains("dropping publication point"),
|
||||||
|
"expected deterministic CRL selection failure to surface as an invalid ROA warning"
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn process_pack_for_issuer_selects_crl_by_ee_crldp_uri_aspa() {
|
||||||
|
let manifest_bytes = fixture_bytes(
|
||||||
|
"tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft",
|
||||||
|
);
|
||||||
|
let aspa_bytes = fixture_bytes(
|
||||||
|
"tests/fixtures/repository/chloe.sobornost.net/rpki/RIPE-nljobsnijders/5m80fwYws_3FiFD7JiQjAqZ1RYQ.asa",
|
||||||
|
);
|
||||||
|
let aspa = rpki::data_model::aspa::AspaObject::decode_der(&aspa_bytes).expect("decode aspa");
|
||||||
|
let ee_crldp = aspa.signed_object.signed_data.certificates[0]
|
||||||
|
.resource_cert
|
||||||
|
.tbs
|
||||||
|
.extensions
|
||||||
|
.crl_distribution_points_uris
|
||||||
|
.as_ref()
|
||||||
|
.expect("ee crldp")[0]
|
||||||
|
.as_str()
|
||||||
|
.to_string();
|
||||||
|
|
||||||
|
let pack = dummy_pack(
|
||||||
|
manifest_bytes,
|
||||||
|
vec![
|
||||||
|
PackFile::from_bytes_compute_sha256(ee_crldp, vec![0x01]),
|
||||||
|
PackFile::from_bytes_compute_sha256("rsync://example.test/repo/pp/a.asa", aspa_bytes),
|
||||||
|
],
|
||||||
|
);
|
||||||
|
|
||||||
|
let policy = Policy {
|
||||||
|
signed_object_failure_policy: SignedObjectFailurePolicy::DropObject,
|
||||||
|
..Policy::default()
|
||||||
|
};
|
||||||
|
|
||||||
|
let out = process_verified_publication_point_pack_for_issuer(
|
||||||
|
&pack,
|
||||||
|
&policy,
|
||||||
|
&[0x01, 0x02, 0x03],
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
None,
|
||||||
|
time::OffsetDateTime::now_utc(),
|
||||||
|
);
|
||||||
|
|
||||||
|
assert_eq!(out.stats.aspa_total, 1);
|
||||||
|
assert_eq!(out.stats.aspa_ok, 0);
|
||||||
|
assert_eq!(out.audit.len(), 1);
|
||||||
|
assert_eq!(out.warnings.len(), 1);
|
||||||
|
}
|
||||||
|
|||||||
@ -1,4 +1,3 @@
|
|||||||
use std::collections::HashMap;
|
|
||||||
use std::path::Path;
|
use std::path::Path;
|
||||||
|
|
||||||
use rpki::data_model::crl::RpkixCrl;
|
use rpki::data_model::crl::RpkixCrl;
|
||||||
@ -8,7 +7,6 @@ use rpki::fetch::rsync::LocalDirRsyncFetcher;
|
|||||||
use rpki::policy::{Policy, SyncPreference};
|
use rpki::policy::{Policy, SyncPreference};
|
||||||
use rpki::storage::RocksStore;
|
use rpki::storage::RocksStore;
|
||||||
use rpki::sync::rrdp::Fetcher;
|
use rpki::sync::rrdp::Fetcher;
|
||||||
use rpki::validation::objects::IssuerCaCertificateResolver;
|
|
||||||
use rpki::validation::run::{run_publication_point_once, verified_pack_exists};
|
use rpki::validation::run::{run_publication_point_once, verified_pack_exists};
|
||||||
|
|
||||||
fn fixture_to_rsync_uri(path: &Path) -> String {
|
fn fixture_to_rsync_uri(path: &Path) -> String {
|
||||||
@ -41,16 +39,6 @@ impl Fetcher for NeverHttpFetcher {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
struct MapResolver {
|
|
||||||
by_subject_dn: HashMap<String, Vec<u8>>,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl IssuerCaCertificateResolver for MapResolver {
|
|
||||||
fn resolve_by_subject_dn(&self, subject_dn: &str) -> Option<Vec<u8>> {
|
|
||||||
self.by_subject_dn.get(subject_dn).cloned()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn e2e_offline_uses_rsync_then_writes_verified_pack_then_outputs_vrps() {
|
fn e2e_offline_uses_rsync_then_writes_verified_pack_then_outputs_vrps() {
|
||||||
let fixture_dir = Path::new("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0");
|
let fixture_dir = Path::new("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0");
|
||||||
@ -89,10 +77,6 @@ fn e2e_offline_uses_rsync_then_writes_verified_pack_then_outputs_vrps() {
|
|||||||
let rsync_fetcher = LocalDirRsyncFetcher::new(fixture_dir);
|
let rsync_fetcher = LocalDirRsyncFetcher::new(fixture_dir);
|
||||||
let http_fetcher = NeverHttpFetcher;
|
let http_fetcher = NeverHttpFetcher;
|
||||||
|
|
||||||
let resolver = MapResolver {
|
|
||||||
by_subject_dn: HashMap::from([(issuer_ca.tbs.subject_dn, issuer_ca_der)]),
|
|
||||||
};
|
|
||||||
|
|
||||||
let temp = tempfile::tempdir().expect("tempdir");
|
let temp = tempfile::tempdir().expect("tempdir");
|
||||||
let store = RocksStore::open(temp.path()).expect("open rocksdb");
|
let store = RocksStore::open(temp.path()).expect("open rocksdb");
|
||||||
|
|
||||||
@ -113,7 +97,10 @@ fn e2e_offline_uses_rsync_then_writes_verified_pack_then_outputs_vrps() {
|
|||||||
&publication_point_rsync_uri,
|
&publication_point_rsync_uri,
|
||||||
&http_fetcher,
|
&http_fetcher,
|
||||||
&rsync_fetcher,
|
&rsync_fetcher,
|
||||||
&resolver,
|
&issuer_ca_der,
|
||||||
|
None,
|
||||||
|
issuer_ca.tbs.extensions.ip_resources.as_ref(),
|
||||||
|
issuer_ca.tbs.extensions.as_resources.as_ref(),
|
||||||
t,
|
t,
|
||||||
)
|
)
|
||||||
.expect("run publication point once");
|
.expect("run publication point once");
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
use rpki::validation::from_tal::discover_root_ca_instance_from_tal_and_ta_der;
|
use rpki::validation::from_tal::discover_root_ca_instance_from_tal_and_ta_der;
|
||||||
use rpki::validation::run_tree_from_tal::root_handle_from_trust_anchor;
|
use rpki::validation::run_tree_from_tal::root_handle_from_trust_anchor;
|
||||||
use rpki::validation::run_tree_from_tal::{
|
use rpki::validation::run_tree_from_tal::{
|
||||||
run_tree_from_tal_and_ta_der_serial, run_tree_from_tal_url_serial,
|
run_tree_from_tal_and_ta_der_serial, run_tree_from_tal_and_ta_der_serial_audit,
|
||||||
run_tree_from_tal_and_ta_der_serial_audit, run_tree_from_tal_url_serial_audit,
|
run_tree_from_tal_url_serial, run_tree_from_tal_url_serial_audit,
|
||||||
};
|
};
|
||||||
use rpki::validation::tree::TreeRunConfig;
|
use rpki::validation::tree::TreeRunConfig;
|
||||||
|
|
||||||
@ -23,7 +23,10 @@ impl rpki::sync::rrdp::Fetcher for MapHttpFetcher {
|
|||||||
|
|
||||||
struct EmptyRsyncFetcher;
|
struct EmptyRsyncFetcher;
|
||||||
impl rpki::fetch::rsync::RsyncFetcher for EmptyRsyncFetcher {
|
impl rpki::fetch::rsync::RsyncFetcher for EmptyRsyncFetcher {
|
||||||
fn fetch_objects(&self, _rsync_base_uri: &str) -> Result<Vec<(String, Vec<u8>)>, rpki::fetch::rsync::RsyncFetchError> {
|
fn fetch_objects(
|
||||||
|
&self,
|
||||||
|
_rsync_base_uri: &str,
|
||||||
|
) -> Result<Vec<(String, Vec<u8>)>, rpki::fetch::rsync::RsyncFetchError> {
|
||||||
Ok(Vec::new())
|
Ok(Vec::new())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -37,13 +40,18 @@ fn root_handle_is_constructible_from_fixture_tal_and_ta() {
|
|||||||
let discovery =
|
let discovery =
|
||||||
discover_root_ca_instance_from_tal_and_ta_der(&tal_bytes, &ta_der, None).expect("discover");
|
discover_root_ca_instance_from_tal_and_ta_der(&tal_bytes, &ta_der, None).expect("discover");
|
||||||
|
|
||||||
let root =
|
let root = root_handle_from_trust_anchor(&discovery.trust_anchor, None, &discovery.ca_instance);
|
||||||
root_handle_from_trust_anchor(&discovery.trust_anchor, None, &discovery.ca_instance);
|
|
||||||
|
|
||||||
assert_eq!(root.depth, 0);
|
assert_eq!(root.depth, 0);
|
||||||
assert_eq!(root.manifest_rsync_uri, discovery.ca_instance.manifest_rsync_uri);
|
assert_eq!(
|
||||||
|
root.manifest_rsync_uri,
|
||||||
|
discovery.ca_instance.manifest_rsync_uri
|
||||||
|
);
|
||||||
assert_eq!(root.rsync_base_uri, discovery.ca_instance.rsync_base_uri);
|
assert_eq!(root.rsync_base_uri, discovery.ca_instance.rsync_base_uri);
|
||||||
assert!(root.ca_certificate_der.len() > 100, "TA der should be non-empty");
|
assert!(
|
||||||
|
root.ca_certificate_der.len() > 100,
|
||||||
|
"TA der should be non-empty"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -103,7 +111,9 @@ fn run_tree_from_tal_and_ta_der_entry_executes_and_records_failure_when_repo_emp
|
|||||||
.expect("read apnic tal fixture");
|
.expect("read apnic tal fixture");
|
||||||
let ta_der = std::fs::read("tests/fixtures/ta/apnic-ta.cer").expect("read apnic ta fixture");
|
let ta_der = std::fs::read("tests/fixtures/ta/apnic-ta.cer").expect("read apnic ta fixture");
|
||||||
|
|
||||||
let http = MapHttpFetcher { map: HashMap::new() };
|
let http = MapHttpFetcher {
|
||||||
|
map: HashMap::new(),
|
||||||
|
};
|
||||||
let rsync = EmptyRsyncFetcher;
|
let rsync = EmptyRsyncFetcher;
|
||||||
let temp = tempfile::tempdir().expect("tempdir");
|
let temp = tempfile::tempdir().expect("tempdir");
|
||||||
let store = rpki::storage::RocksStore::open(temp.path()).expect("open rocksdb");
|
let store = rpki::storage::RocksStore::open(temp.path()).expect("open rocksdb");
|
||||||
@ -190,7 +200,9 @@ fn run_tree_from_tal_and_ta_der_audit_entry_collects_no_publication_points_when_
|
|||||||
.expect("read apnic tal fixture");
|
.expect("read apnic tal fixture");
|
||||||
let ta_der = std::fs::read("tests/fixtures/ta/apnic-ta.cer").expect("read apnic ta fixture");
|
let ta_der = std::fs::read("tests/fixtures/ta/apnic-ta.cer").expect("read apnic ta fixture");
|
||||||
|
|
||||||
let http = MapHttpFetcher { map: HashMap::new() };
|
let http = MapHttpFetcher {
|
||||||
|
map: HashMap::new(),
|
||||||
|
};
|
||||||
let rsync = EmptyRsyncFetcher;
|
let rsync = EmptyRsyncFetcher;
|
||||||
|
|
||||||
let temp = tempfile::tempdir().expect("tempdir");
|
let temp = tempfile::tempdir().expect("tempdir");
|
||||||
|
|||||||
@ -31,4 +31,3 @@ fn storage_iter_all_lists_raw_and_verified_entries() {
|
|||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
assert_eq!(verified_keys, vec![key.as_str().to_string()]);
|
assert_eq!(verified_keys, vec![key.as_str().to_string()]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -100,4 +100,3 @@ fn ta_rc_constraints_reject_as_inherit() {
|
|||||||
Err(TaCertificateProfileError::AsResourcesInherit)
|
Err(TaCertificateProfileError::AsResourcesInherit)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -44,4 +44,3 @@ fn ta_verify_self_signature_rejects_tampered_signature() {
|
|||||||
TaCertificateVerifyError::InvalidSelfSignature(_) | TaCertificateVerifyError::Parse(_)
|
TaCertificateVerifyError::InvalidSelfSignature(_) | TaCertificateVerifyError::Parse(_)
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use rpki::audit::{DiscoveredFrom, PublicationPointAudit};
|
||||||
use rpki::report::Warning;
|
use rpki::report::Warning;
|
||||||
use rpki::storage::{PackTime, VerifiedPublicationPointPack};
|
use rpki::storage::{PackTime, VerifiedPublicationPointPack};
|
||||||
use rpki::validation::manifest::PublicationPointSource;
|
use rpki::validation::manifest::PublicationPointSource;
|
||||||
use rpki::validation::objects::{ObjectsOutput, ObjectsStats};
|
use rpki::validation::objects::{ObjectsOutput, ObjectsStats};
|
||||||
use rpki::validation::tree::{
|
use rpki::validation::tree::{
|
||||||
CaInstanceHandle, PublicationPointRunResult, PublicationPointRunner, TreeRunConfig,
|
CaInstanceHandle, DiscoveredChildCaInstance, PublicationPointRunResult, PublicationPointRunner,
|
||||||
run_tree_serial,
|
TreeRunConfig, run_tree_serial,
|
||||||
};
|
};
|
||||||
use rpki::audit::PublicationPointAudit;
|
|
||||||
|
|
||||||
fn empty_pack(manifest_uri: &str, pp_uri: &str) -> VerifiedPublicationPointPack {
|
fn empty_pack(manifest_uri: &str, pp_uri: &str) -> VerifiedPublicationPointPack {
|
||||||
VerifiedPublicationPointPack {
|
VerifiedPublicationPointPack {
|
||||||
@ -43,6 +43,25 @@ fn ca_handle(manifest_uri: &str) -> CaInstanceHandle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn discovered_child(
|
||||||
|
parent_manifest_uri: &str,
|
||||||
|
child_manifest_uri: &str,
|
||||||
|
) -> DiscoveredChildCaInstance {
|
||||||
|
let name = child_manifest_uri
|
||||||
|
.rsplit('/')
|
||||||
|
.next()
|
||||||
|
.unwrap_or("child.mft")
|
||||||
|
.trim_end_matches(".mft");
|
||||||
|
DiscoveredChildCaInstance {
|
||||||
|
handle: ca_handle(child_manifest_uri),
|
||||||
|
discovered_from: DiscoveredFrom {
|
||||||
|
parent_manifest_rsync_uri: parent_manifest_uri.to_string(),
|
||||||
|
child_ca_certificate_rsync_uri: format!("rsync://example.test/repo/{name}.cer"),
|
||||||
|
child_ca_certificate_sha256_hex: "00".repeat(32),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct ResultRunner {
|
struct ResultRunner {
|
||||||
by_manifest: HashMap<String, Result<PublicationPointRunResult, String>>,
|
by_manifest: HashMap<String, Result<PublicationPointRunResult, String>>,
|
||||||
@ -94,7 +113,10 @@ fn tree_continues_when_a_publication_point_fails() {
|
|||||||
audit: Vec::new(),
|
audit: Vec::new(),
|
||||||
},
|
},
|
||||||
audit: PublicationPointAudit::default(),
|
audit: PublicationPointAudit::default(),
|
||||||
discovered_children: vec![ca_handle(bad_child_manifest), ca_handle(ok_child_manifest)],
|
discovered_children: vec![
|
||||||
|
discovered_child(root_manifest, bad_child_manifest),
|
||||||
|
discovered_child(root_manifest, ok_child_manifest),
|
||||||
|
],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.with_err(bad_child_manifest, "synthetic failure")
|
.with_err(bad_child_manifest, "synthetic failure")
|
||||||
|
|||||||
@ -1,14 +1,14 @@
|
|||||||
use std::collections::HashMap;
|
use std::collections::HashMap;
|
||||||
|
|
||||||
|
use rpki::audit::{DiscoveredFrom, PublicationPointAudit};
|
||||||
use rpki::report::Warning;
|
use rpki::report::Warning;
|
||||||
use rpki::storage::{PackFile, PackTime, VerifiedPublicationPointPack};
|
use rpki::storage::{PackFile, PackTime, VerifiedPublicationPointPack};
|
||||||
use rpki::validation::manifest::PublicationPointSource;
|
use rpki::validation::manifest::PublicationPointSource;
|
||||||
use rpki::validation::tree::{
|
|
||||||
CaInstanceHandle, PublicationPointRunResult, PublicationPointRunner, TreeRunConfig,
|
|
||||||
run_tree_serial,
|
|
||||||
};
|
|
||||||
use rpki::validation::objects::{ObjectsOutput, ObjectsStats};
|
use rpki::validation::objects::{ObjectsOutput, ObjectsStats};
|
||||||
use rpki::audit::PublicationPointAudit;
|
use rpki::validation::tree::{
|
||||||
|
CaInstanceHandle, DiscoveredChildCaInstance, PublicationPointRunResult, PublicationPointRunner,
|
||||||
|
TreeRunConfig, run_tree_serial, run_tree_serial_audit,
|
||||||
|
};
|
||||||
|
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
struct MockRunner {
|
struct MockRunner {
|
||||||
@ -58,10 +58,7 @@ fn empty_pack(manifest_uri: &str, pp_uri: &str) -> VerifiedPublicationPointPack
|
|||||||
rfc3339_utc: "2026-02-06T00:00:00Z".to_string(),
|
rfc3339_utc: "2026-02-06T00:00:00Z".to_string(),
|
||||||
},
|
},
|
||||||
manifest_bytes: vec![1, 2, 3],
|
manifest_bytes: vec![1, 2, 3],
|
||||||
files: vec![PackFile::from_bytes_compute_sha256(
|
files: vec![PackFile::from_bytes_compute_sha256(manifest_uri, vec![1])],
|
||||||
manifest_uri,
|
|
||||||
vec![1],
|
|
||||||
)],
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -79,6 +76,25 @@ fn ca_handle(manifest_uri: &str) -> CaInstanceHandle {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn discovered_child(
|
||||||
|
parent_manifest_uri: &str,
|
||||||
|
child_manifest_uri: &str,
|
||||||
|
) -> DiscoveredChildCaInstance {
|
||||||
|
let name = child_manifest_uri
|
||||||
|
.rsplit('/')
|
||||||
|
.next()
|
||||||
|
.unwrap_or("child.mft")
|
||||||
|
.trim_end_matches(".mft");
|
||||||
|
DiscoveredChildCaInstance {
|
||||||
|
handle: ca_handle(child_manifest_uri),
|
||||||
|
discovered_from: DiscoveredFrom {
|
||||||
|
parent_manifest_rsync_uri: parent_manifest_uri.to_string(),
|
||||||
|
child_ca_certificate_rsync_uri: format!("rsync://example.test/repo/{name}.cer"),
|
||||||
|
child_ca_certificate_sha256_hex: "00".repeat(32),
|
||||||
|
},
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tree_enqueues_children_only_for_fresh_publication_points() {
|
fn tree_enqueues_children_only_for_fresh_publication_points() {
|
||||||
let root_manifest = "rsync://example.test/repo/root.mft";
|
let root_manifest = "rsync://example.test/repo/root.mft";
|
||||||
@ -86,8 +102,11 @@ fn tree_enqueues_children_only_for_fresh_publication_points() {
|
|||||||
let child2_manifest = "rsync://example.test/repo/child2.mft";
|
let child2_manifest = "rsync://example.test/repo/child2.mft";
|
||||||
let grandchild_manifest = "rsync://example.test/repo/grandchild.mft";
|
let grandchild_manifest = "rsync://example.test/repo/grandchild.mft";
|
||||||
|
|
||||||
let root_children = vec![ca_handle(child1_manifest), ca_handle(child2_manifest)];
|
let root_children = vec![
|
||||||
let child1_children = vec![ca_handle(grandchild_manifest)];
|
discovered_child(root_manifest, child1_manifest),
|
||||||
|
discovered_child(root_manifest, child2_manifest),
|
||||||
|
];
|
||||||
|
let child1_children = vec![discovered_child(child1_manifest, grandchild_manifest)];
|
||||||
|
|
||||||
let runner = MockRunner::default()
|
let runner = MockRunner::default()
|
||||||
.with(
|
.with(
|
||||||
@ -150,10 +169,15 @@ fn tree_enqueues_children_only_for_fresh_publication_points() {
|
|||||||
assert_eq!(out.instances_failed, 0);
|
assert_eq!(out.instances_failed, 0);
|
||||||
|
|
||||||
let called = runner.called();
|
let called = runner.called();
|
||||||
assert_eq!(called, vec![root_manifest, child1_manifest, child2_manifest]);
|
assert_eq!(
|
||||||
|
called,
|
||||||
|
vec![root_manifest, child1_manifest, child2_manifest]
|
||||||
|
);
|
||||||
|
|
||||||
assert!(
|
assert!(
|
||||||
out.warnings.iter().any(|w| w.message.contains("child1 warning")),
|
out.warnings
|
||||||
|
.iter()
|
||||||
|
.any(|w| w.message.contains("child1 warning")),
|
||||||
"expected child1 warning propagated"
|
"expected child1 warning propagated"
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
@ -184,7 +208,7 @@ fn tree_respects_max_depth_and_max_instances() {
|
|||||||
audit: Vec::new(),
|
audit: Vec::new(),
|
||||||
},
|
},
|
||||||
audit: PublicationPointAudit::default(),
|
audit: PublicationPointAudit::default(),
|
||||||
discovered_children: vec![ca_handle(child_manifest)],
|
discovered_children: vec![discovered_child(root_manifest, child_manifest)],
|
||||||
},
|
},
|
||||||
)
|
)
|
||||||
.with(
|
.with(
|
||||||
@ -229,3 +253,65 @@ fn tree_respects_max_depth_and_max_instances() {
|
|||||||
assert_eq!(out.instances_processed, 1);
|
assert_eq!(out.instances_processed, 1);
|
||||||
assert_eq!(out.instances_failed, 0);
|
assert_eq!(out.instances_failed, 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn tree_audit_includes_parent_and_discovered_from_for_non_root_nodes() {
|
||||||
|
let root_manifest = "rsync://example.test/repo/root.mft";
|
||||||
|
let child_manifest = "rsync://example.test/repo/child.mft";
|
||||||
|
|
||||||
|
let runner = MockRunner::default()
|
||||||
|
.with(
|
||||||
|
root_manifest,
|
||||||
|
PublicationPointRunResult {
|
||||||
|
source: PublicationPointSource::Fresh,
|
||||||
|
pack: empty_pack(root_manifest, "rsync://example.test/repo/"),
|
||||||
|
warnings: Vec::new(),
|
||||||
|
objects: ObjectsOutput {
|
||||||
|
vrps: Vec::new(),
|
||||||
|
aspas: Vec::new(),
|
||||||
|
warnings: Vec::new(),
|
||||||
|
stats: ObjectsStats::default(),
|
||||||
|
audit: Vec::new(),
|
||||||
|
},
|
||||||
|
audit: PublicationPointAudit::default(),
|
||||||
|
discovered_children: vec![discovered_child(root_manifest, child_manifest)],
|
||||||
|
},
|
||||||
|
)
|
||||||
|
.with(
|
||||||
|
child_manifest,
|
||||||
|
PublicationPointRunResult {
|
||||||
|
source: PublicationPointSource::Fresh,
|
||||||
|
pack: empty_pack(child_manifest, "rsync://example.test/repo/child/"),
|
||||||
|
warnings: Vec::new(),
|
||||||
|
objects: ObjectsOutput {
|
||||||
|
vrps: Vec::new(),
|
||||||
|
aspas: Vec::new(),
|
||||||
|
warnings: Vec::new(),
|
||||||
|
stats: ObjectsStats::default(),
|
||||||
|
audit: Vec::new(),
|
||||||
|
},
|
||||||
|
audit: PublicationPointAudit::default(),
|
||||||
|
discovered_children: Vec::new(),
|
||||||
|
},
|
||||||
|
);
|
||||||
|
|
||||||
|
let out = run_tree_serial_audit(ca_handle(root_manifest), &runner, &TreeRunConfig::default())
|
||||||
|
.expect("run tree audit");
|
||||||
|
|
||||||
|
assert_eq!(out.tree.instances_processed, 2);
|
||||||
|
assert_eq!(out.publication_points.len(), 2);
|
||||||
|
|
||||||
|
let root_audit = &out.publication_points[0];
|
||||||
|
assert_eq!(root_audit.node_id, Some(0));
|
||||||
|
assert_eq!(root_audit.parent_node_id, None);
|
||||||
|
assert!(root_audit.discovered_from.is_none());
|
||||||
|
|
||||||
|
let child_audit = &out.publication_points[1];
|
||||||
|
assert_eq!(child_audit.node_id, Some(1));
|
||||||
|
assert_eq!(child_audit.parent_node_id, Some(0));
|
||||||
|
let df = child_audit
|
||||||
|
.discovered_from
|
||||||
|
.as_ref()
|
||||||
|
.expect("child discovered_from");
|
||||||
|
assert_eq!(df.parent_manifest_rsync_uri, root_manifest);
|
||||||
|
}
|
||||||
|
|||||||
@ -1,8 +1,8 @@
|
|||||||
use rpki::data_model::manifest::ManifestObject;
|
use rpki::data_model::manifest::ManifestObject;
|
||||||
use rpki::data_model::rc::{AsIdOrRange, AsIdentifierChoice, AsResourceSet, ResourceCertificate};
|
use rpki::data_model::rc::{AsIdOrRange, AsIdentifierChoice, AsResourceSet, ResourceCertificate};
|
||||||
|
use rpki::data_model::roa::{IpPrefix, RoaAfi};
|
||||||
use rpki::data_model::ta::{TaCertificate, TaCertificateParsed, TaCertificateProfileError};
|
use rpki::data_model::ta::{TaCertificate, TaCertificateParsed, TaCertificateProfileError};
|
||||||
use rpki::data_model::tal::{Tal, TalDecodeError, TalProfileError};
|
use rpki::data_model::tal::{Tal, TalDecodeError, TalProfileError};
|
||||||
use rpki::data_model::roa::{IpPrefix, RoaAfi};
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn tal_validate_profile_noop_is_callable() {
|
fn tal_validate_profile_noop_is_callable() {
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user