20260510 增加strict策略模式并隔离多TA失败
This commit is contained in:
parent
265b6f65d0
commit
51e483d924
191
src/cli.rs
191
src/cli.rs
@ -16,7 +16,7 @@ use crate::fetch::rsync::LocalDirRsyncFetcher;
|
|||||||
use crate::fetch::rsync_system::{SystemRsyncConfig, SystemRsyncFetcher};
|
use crate::fetch::rsync_system::{SystemRsyncConfig, SystemRsyncFetcher};
|
||||||
use crate::parallel::config::{ParallelPhase1Config, ParallelPhase2Config};
|
use crate::parallel::config::{ParallelPhase1Config, ParallelPhase2Config};
|
||||||
use crate::parallel::types::TalInputSpec;
|
use crate::parallel::types::TalInputSpec;
|
||||||
use crate::policy::Policy;
|
use crate::policy::{Policy, StrictPolicy};
|
||||||
use crate::storage::RocksStore;
|
use crate::storage::RocksStore;
|
||||||
use crate::validation::run_tree_from_tal::{
|
use crate::validation::run_tree_from_tal::{
|
||||||
RunTreeFromTalAuditOutput, run_tree_from_multiple_tals_parallel_phase2_audit,
|
RunTreeFromTalAuditOutput, run_tree_from_multiple_tals_parallel_phase2_audit,
|
||||||
@ -70,6 +70,7 @@ pub struct CliArgs {
|
|||||||
pub raw_store_db: Option<PathBuf>,
|
pub raw_store_db: Option<PathBuf>,
|
||||||
pub repo_bytes_db: Option<PathBuf>,
|
pub repo_bytes_db: Option<PathBuf>,
|
||||||
pub policy_path: Option<PathBuf>,
|
pub policy_path: Option<PathBuf>,
|
||||||
|
pub strict_policy: Option<StrictPolicy>,
|
||||||
pub report_json_path: Option<PathBuf>,
|
pub report_json_path: Option<PathBuf>,
|
||||||
pub report_json_compact: bool,
|
pub report_json_compact: bool,
|
||||||
pub skip_report_build: bool,
|
pub skip_report_build: bool,
|
||||||
@ -120,6 +121,7 @@ Options:
|
|||||||
--raw-store-db <path> External raw-by-hash store DB path (optional)
|
--raw-store-db <path> External raw-by-hash store DB path (optional)
|
||||||
--repo-bytes-db <path> External repo object bytes DB path (optional)
|
--repo-bytes-db <path> External repo object bytes DB path (optional)
|
||||||
--policy <path> Policy TOML path (optional)
|
--policy <path> Policy TOML path (optional)
|
||||||
|
--strict [policies] Enable strict policies (default all; comma list: name,cms-der,signed-attrs; none disables)
|
||||||
--report-json <path> Write full audit report as JSON (optional)
|
--report-json <path> Write full audit report as JSON (optional)
|
||||||
--report-json-compact Write report JSON without pretty-printing (requires --report-json)
|
--report-json-compact Write report JSON without pretty-printing (requires --report-json)
|
||||||
--skip-report-build Skip full audit report construction when --report-json is not requested
|
--skip-report-build Skip full audit report construction when --report-json is not requested
|
||||||
@ -195,6 +197,7 @@ pub fn parse_args(argv: &[String]) -> Result<CliArgs, String> {
|
|||||||
let mut raw_store_db: Option<PathBuf> = None;
|
let mut raw_store_db: Option<PathBuf> = None;
|
||||||
let mut repo_bytes_db: Option<PathBuf> = None;
|
let mut repo_bytes_db: Option<PathBuf> = None;
|
||||||
let mut policy_path: Option<PathBuf> = None;
|
let mut policy_path: Option<PathBuf> = None;
|
||||||
|
let mut strict_policy: Option<StrictPolicy> = None;
|
||||||
let mut report_json_path: Option<PathBuf> = None;
|
let mut report_json_path: Option<PathBuf> = None;
|
||||||
let mut report_json_compact: bool = false;
|
let mut report_json_compact: bool = false;
|
||||||
let mut skip_report_build: bool = false;
|
let mut skip_report_build: bool = false;
|
||||||
@ -372,6 +375,18 @@ pub fn parse_args(argv: &[String]) -> Result<CliArgs, String> {
|
|||||||
let v = argv.get(i).ok_or("--policy requires a value")?;
|
let v = argv.get(i).ok_or("--policy requires a value")?;
|
||||||
policy_path = Some(PathBuf::from(v));
|
policy_path = Some(PathBuf::from(v));
|
||||||
}
|
}
|
||||||
|
"--strict" => {
|
||||||
|
let next = argv.get(i + 1).map(String::as_str);
|
||||||
|
let spec = next.filter(|v| !v.starts_with("--"));
|
||||||
|
if spec.is_some() {
|
||||||
|
i += 1;
|
||||||
|
}
|
||||||
|
strict_policy = Some(StrictPolicy::parse_cli_spec(spec)?);
|
||||||
|
}
|
||||||
|
_ if arg.starts_with("--strict=") => {
|
||||||
|
let spec = arg.strip_prefix("--strict=").expect("prefix checked");
|
||||||
|
strict_policy = Some(StrictPolicy::parse_cli_spec(Some(spec))?);
|
||||||
|
}
|
||||||
"--report-json" => {
|
"--report-json" => {
|
||||||
i += 1;
|
i += 1;
|
||||||
let v = argv.get(i).ok_or("--report-json requires a value")?;
|
let v = argv.get(i).ok_or("--report-json requires a value")?;
|
||||||
@ -796,6 +811,7 @@ pub fn parse_args(argv: &[String]) -> Result<CliArgs, String> {
|
|||||||
raw_store_db,
|
raw_store_db,
|
||||||
repo_bytes_db,
|
repo_bytes_db,
|
||||||
policy_path,
|
policy_path,
|
||||||
|
strict_policy,
|
||||||
report_json_path,
|
report_json_path,
|
||||||
report_json_compact,
|
report_json_compact,
|
||||||
skip_report_build,
|
skip_report_build,
|
||||||
@ -940,6 +956,7 @@ fn print_summary_from_shared(validation_time: time::OffsetDateTime, shared: &Pos
|
|||||||
struct PostValidationShared {
|
struct PostValidationShared {
|
||||||
discovery: crate::validation::from_tal::DiscoveredRootCaInstance,
|
discovery: crate::validation::from_tal::DiscoveredRootCaInstance,
|
||||||
discoveries: Arc<[crate::validation::from_tal::DiscoveredRootCaInstance]>,
|
discoveries: Arc<[crate::validation::from_tal::DiscoveredRootCaInstance]>,
|
||||||
|
successful_tal_inputs: Arc<[TalInputSpec]>,
|
||||||
instances_processed: usize,
|
instances_processed: usize,
|
||||||
instances_failed: usize,
|
instances_failed: usize,
|
||||||
tree_warnings: Arc<[crate::report::Warning]>,
|
tree_warnings: Arc<[crate::report::Warning]>,
|
||||||
@ -958,6 +975,7 @@ impl PostValidationShared {
|
|||||||
let RunTreeFromTalAuditOutput {
|
let RunTreeFromTalAuditOutput {
|
||||||
discovery,
|
discovery,
|
||||||
discoveries,
|
discoveries,
|
||||||
|
successful_tal_inputs,
|
||||||
tree,
|
tree,
|
||||||
publication_points,
|
publication_points,
|
||||||
downloads,
|
downloads,
|
||||||
@ -977,6 +995,7 @@ impl PostValidationShared {
|
|||||||
Self {
|
Self {
|
||||||
discovery,
|
discovery,
|
||||||
discoveries: discoveries.into(),
|
discoveries: discoveries.into(),
|
||||||
|
successful_tal_inputs: successful_tal_inputs.into(),
|
||||||
instances_processed,
|
instances_processed,
|
||||||
instances_failed,
|
instances_failed,
|
||||||
tree_warnings: warnings.into(),
|
tree_warnings: warnings.into(),
|
||||||
@ -1285,6 +1304,38 @@ fn resolve_cir_export_tal_uris(args: &CliArgs) -> Result<Vec<String>, String> {
|
|||||||
Err("CIR export requires TAL URI source(s)".to_string())
|
Err("CIR export requires TAL URI source(s)".to_string())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn effective_cir_tal_uris_for_discoveries(
|
||||||
|
args: &CliArgs,
|
||||||
|
shared: &PostValidationShared,
|
||||||
|
cir_tal_uris: Vec<String>,
|
||||||
|
) -> Result<Vec<String>, String> {
|
||||||
|
if shared.successful_tal_inputs.is_empty() {
|
||||||
|
return Ok(cir_tal_uris);
|
||||||
|
}
|
||||||
|
if cir_tal_uris.len() == shared.discoveries.len() {
|
||||||
|
return Ok(cir_tal_uris);
|
||||||
|
}
|
||||||
|
if cir_tal_uris.len() != args.tal_inputs.len() {
|
||||||
|
return Ok(cir_tal_uris);
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut mapped = Vec::with_capacity(shared.successful_tal_inputs.len());
|
||||||
|
for successful in shared.successful_tal_inputs.iter() {
|
||||||
|
let input_index = args
|
||||||
|
.tal_inputs
|
||||||
|
.iter()
|
||||||
|
.position(|candidate| candidate == successful)
|
||||||
|
.ok_or_else(|| {
|
||||||
|
format!(
|
||||||
|
"successful TAL '{}' was not found in original TAL input list",
|
||||||
|
successful.tal_id
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
mapped.push(cir_tal_uris[input_index].clone());
|
||||||
|
}
|
||||||
|
Ok(mapped)
|
||||||
|
}
|
||||||
|
|
||||||
fn build_repo_sync_stats(
|
fn build_repo_sync_stats(
|
||||||
publication_points: &[crate::audit::PublicationPointAudit],
|
publication_points: &[crate::audit::PublicationPointAudit],
|
||||||
) -> AuditRepoSyncStats {
|
) -> AuditRepoSyncStats {
|
||||||
@ -1420,6 +1471,9 @@ pub fn run(argv: &[String]) -> Result<(), String> {
|
|||||||
let args = parse_args(argv)?;
|
let args = parse_args(argv)?;
|
||||||
|
|
||||||
let mut policy = read_policy(args.policy_path.as_deref())?;
|
let mut policy = read_policy(args.policy_path.as_deref())?;
|
||||||
|
if let Some(strict_policy) = args.strict_policy {
|
||||||
|
policy.strict = strict_policy;
|
||||||
|
}
|
||||||
if args.disable_rrdp {
|
if args.disable_rrdp {
|
||||||
policy.sync_preference = crate::policy::SyncPreference::RsyncOnly;
|
policy.sync_preference = crate::policy::SyncPreference::RsyncOnly;
|
||||||
}
|
}
|
||||||
@ -1796,7 +1850,11 @@ pub fn run(argv: &[String]) -> Result<(), String> {
|
|||||||
let mut cir_write_cir_ms = None;
|
let mut cir_write_cir_ms = None;
|
||||||
let mut cir_total_ms = None;
|
let mut cir_total_ms = None;
|
||||||
if args.cir_enabled {
|
if args.cir_enabled {
|
||||||
let cir_tal_uris = resolve_cir_export_tal_uris(&args)?;
|
let cir_tal_uris = effective_cir_tal_uris_for_discoveries(
|
||||||
|
&args,
|
||||||
|
&shared,
|
||||||
|
resolve_cir_export_tal_uris(&args)?,
|
||||||
|
)?;
|
||||||
if cir_tal_uris.len() != shared.discoveries.len() {
|
if cir_tal_uris.len() != shared.discoveries.len() {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"CIR export TAL URI count ({}) does not match discovery count ({})",
|
"CIR export TAL URI count ({}) does not match discovery count ({})",
|
||||||
@ -2026,6 +2084,108 @@ mod tests {
|
|||||||
assert!(args.report_json_compact);
|
assert!(args.report_json_compact);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_accepts_strict_policy_list() {
|
||||||
|
let argv = vec![
|
||||||
|
"rpki".to_string(),
|
||||||
|
"--db".to_string(),
|
||||||
|
"db".to_string(),
|
||||||
|
"--tal-url".to_string(),
|
||||||
|
"https://example.test/x.tal".to_string(),
|
||||||
|
"--strict".to_string(),
|
||||||
|
"name,cms-der".to_string(),
|
||||||
|
];
|
||||||
|
let args = parse_args(&argv).expect("parse args");
|
||||||
|
assert_eq!(
|
||||||
|
args.strict_policy,
|
||||||
|
Some(StrictPolicy {
|
||||||
|
name: true,
|
||||||
|
cms_der: true,
|
||||||
|
signed_attrs: false,
|
||||||
|
})
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_accepts_strict_without_value_as_all() {
|
||||||
|
let argv = vec![
|
||||||
|
"rpki".to_string(),
|
||||||
|
"--db".to_string(),
|
||||||
|
"db".to_string(),
|
||||||
|
"--tal-url".to_string(),
|
||||||
|
"https://example.test/x.tal".to_string(),
|
||||||
|
"--strict".to_string(),
|
||||||
|
"--report-json".to_string(),
|
||||||
|
"out/report.json".to_string(),
|
||||||
|
];
|
||||||
|
let args = parse_args(&argv).expect("parse args");
|
||||||
|
assert_eq!(args.strict_policy, Some(StrictPolicy::all()));
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn parse_rejects_unknown_strict_policy() {
|
||||||
|
let argv = vec![
|
||||||
|
"rpki".to_string(),
|
||||||
|
"--db".to_string(),
|
||||||
|
"db".to_string(),
|
||||||
|
"--tal-url".to_string(),
|
||||||
|
"https://example.test/x.tal".to_string(),
|
||||||
|
"--strict=unknown".to_string(),
|
||||||
|
];
|
||||||
|
let err = parse_args(&argv).expect_err("unknown strict policy should fail");
|
||||||
|
assert!(err.contains("unknown strict policy"), "{err}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn effective_cir_tal_uris_filters_skipped_multi_tal_inputs() {
|
||||||
|
let argv = vec![
|
||||||
|
"rpki".to_string(),
|
||||||
|
"--db".to_string(),
|
||||||
|
"db".to_string(),
|
||||||
|
"--tal-path".to_string(),
|
||||||
|
"afrinic.tal".to_string(),
|
||||||
|
"--ta-path".to_string(),
|
||||||
|
"afrinic.cer".to_string(),
|
||||||
|
"--tal-path".to_string(),
|
||||||
|
"apnic.tal".to_string(),
|
||||||
|
"--ta-path".to_string(),
|
||||||
|
"apnic.cer".to_string(),
|
||||||
|
"--tal-path".to_string(),
|
||||||
|
"arin.tal".to_string(),
|
||||||
|
"--ta-path".to_string(),
|
||||||
|
"arin.cer".to_string(),
|
||||||
|
"--cir-enable".to_string(),
|
||||||
|
"--cir-out".to_string(),
|
||||||
|
"out.cir".to_string(),
|
||||||
|
"--cir-tal-uri".to_string(),
|
||||||
|
"https://example.test/afrinic.cer".to_string(),
|
||||||
|
"--cir-tal-uri".to_string(),
|
||||||
|
"https://example.test/apnic.cer".to_string(),
|
||||||
|
"--cir-tal-uri".to_string(),
|
||||||
|
"https://example.test/arin.cer".to_string(),
|
||||||
|
];
|
||||||
|
let args = parse_args(&argv).expect("parse args");
|
||||||
|
let mut shared = synthetic_post_validation_shared();
|
||||||
|
shared.discoveries = vec![shared.discovery.clone(), shared.discovery.clone()].into();
|
||||||
|
shared.successful_tal_inputs =
|
||||||
|
vec![args.tal_inputs[0].clone(), args.tal_inputs[2].clone()].into();
|
||||||
|
|
||||||
|
let effective = effective_cir_tal_uris_for_discoveries(
|
||||||
|
&args,
|
||||||
|
&shared,
|
||||||
|
resolve_cir_export_tal_uris(&args).expect("resolve cir tal uris"),
|
||||||
|
)
|
||||||
|
.expect("map effective cir tal uris");
|
||||||
|
|
||||||
|
assert_eq!(
|
||||||
|
effective,
|
||||||
|
vec![
|
||||||
|
"https://example.test/afrinic.cer".to_string(),
|
||||||
|
"https://example.test/arin.cer".to_string(),
|
||||||
|
]
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn parse_rejects_report_json_compact_without_report_json() {
|
fn parse_rejects_report_json_compact_without_report_json() {
|
||||||
let argv = vec![
|
let argv = vec![
|
||||||
@ -3024,6 +3184,32 @@ mod tests {
|
|||||||
policy.signed_object_failure_policy,
|
policy.signed_object_failure_policy,
|
||||||
crate::policy::SignedObjectFailurePolicy::DropPublicationPoint
|
crate::policy::SignedObjectFailurePolicy::DropPublicationPoint
|
||||||
);
|
);
|
||||||
|
assert_eq!(policy.strict, StrictPolicy::default());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn read_policy_accepts_strict_table() {
|
||||||
|
let dir = tempfile::tempdir().expect("tmpdir");
|
||||||
|
let p = dir.path().join("policy.toml");
|
||||||
|
std::fs::write(
|
||||||
|
&p,
|
||||||
|
r#"
|
||||||
|
[strict]
|
||||||
|
name = true
|
||||||
|
cms_der = true
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.expect("write policy");
|
||||||
|
|
||||||
|
let policy = read_policy(Some(&p)).expect("parse policy");
|
||||||
|
assert_eq!(
|
||||||
|
policy.strict,
|
||||||
|
StrictPolicy {
|
||||||
|
name: true,
|
||||||
|
cms_der: true,
|
||||||
|
signed_attrs: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -3099,6 +3285,7 @@ mod tests {
|
|||||||
let out = crate::validation::run_tree_from_tal::RunTreeFromTalAuditOutput {
|
let out = crate::validation::run_tree_from_tal::RunTreeFromTalAuditOutput {
|
||||||
discovery: discovery.clone(),
|
discovery: discovery.clone(),
|
||||||
discoveries: vec![discovery],
|
discoveries: vec![discovery],
|
||||||
|
successful_tal_inputs: Vec::new(),
|
||||||
tree,
|
tree,
|
||||||
publication_points: vec![pp1, pp2, pp3],
|
publication_points: vec![pp1, pp2, pp3],
|
||||||
downloads: Vec::new(),
|
downloads: Vec::new(),
|
||||||
|
|||||||
@ -2,7 +2,8 @@ use crate::data_model::common::{DerReader, der_take_tlv};
|
|||||||
use crate::data_model::oid::OID_CT_ASPA;
|
use crate::data_model::oid::OID_CT_ASPA;
|
||||||
use crate::data_model::rc::ResourceCertificate;
|
use crate::data_model::rc::ResourceCertificate;
|
||||||
use crate::data_model::signed_object::{
|
use crate::data_model::signed_object::{
|
||||||
RpkiSignedObject, RpkiSignedObjectParsed, SignedObjectParseError, SignedObjectValidateError,
|
RpkiSignedObject, RpkiSignedObjectParsed, SignedObjectDecodeError, SignedObjectParseError,
|
||||||
|
SignedObjectValidateError,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
@ -91,6 +92,15 @@ pub enum AspaProfileError {
|
|||||||
ProvidersContainCustomer(u32),
|
ProvidersContainCustomer(u32),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<SignedObjectDecodeError> for AspaProfileError {
|
||||||
|
fn from(value: SignedObjectDecodeError) -> Self {
|
||||||
|
match value {
|
||||||
|
SignedObjectDecodeError::Parse(e) => AspaProfileError::ProfileDecode(e.to_string()),
|
||||||
|
SignedObjectDecodeError::Validate(e) => AspaProfileError::SignedObject(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum AspaDecodeError {
|
pub enum AspaDecodeError {
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
@ -173,6 +183,17 @@ impl AspaObject {
|
|||||||
Ok(Self::parse_der(der)?.validate_profile()?)
|
Ok(Self::parse_der(der)?.validate_profile()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn decode_der_with_strict_options(
|
||||||
|
der: &[u8],
|
||||||
|
strict_cms_der: bool,
|
||||||
|
strict_name: bool,
|
||||||
|
) -> Result<Self, AspaDecodeError> {
|
||||||
|
let signed_object =
|
||||||
|
RpkiSignedObject::decode_der_with_strict_options(der, strict_cms_der, strict_name)
|
||||||
|
.map_err(AspaProfileError::from)?;
|
||||||
|
Self::from_signed_object(signed_object)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn from_signed_object(signed_object: RpkiSignedObject) -> Result<Self, AspaDecodeError> {
|
pub fn from_signed_object(signed_object: RpkiSignedObject) -> Result<Self, AspaDecodeError> {
|
||||||
let econtent_type = signed_object
|
let econtent_type = signed_object
|
||||||
.signed_data
|
.signed_data
|
||||||
|
|||||||
@ -3,7 +3,8 @@ use crate::data_model::common::{BigUnsigned, UtcTime};
|
|||||||
use crate::data_model::oid::{OID_CT_RPKI_MANIFEST, OID_SHA256};
|
use crate::data_model::oid::{OID_CT_RPKI_MANIFEST, OID_SHA256};
|
||||||
use crate::data_model::rc::ResourceCertificate;
|
use crate::data_model::rc::ResourceCertificate;
|
||||||
use crate::data_model::signed_object::{
|
use crate::data_model::signed_object::{
|
||||||
RpkiSignedObject, RpkiSignedObjectParsed, SignedObjectParseError, SignedObjectValidateError,
|
RpkiSignedObject, RpkiSignedObjectParsed, SignedObjectDecodeError, SignedObjectParseError,
|
||||||
|
SignedObjectValidateError,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
@ -119,6 +120,15 @@ pub enum ManifestProfileError {
|
|||||||
InvalidHashLength(usize),
|
InvalidHashLength(usize),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<SignedObjectDecodeError> for ManifestProfileError {
|
||||||
|
fn from(value: SignedObjectDecodeError) -> Self {
|
||||||
|
match value {
|
||||||
|
SignedObjectDecodeError::Parse(e) => ManifestProfileError::ProfileDecode(e.to_string()),
|
||||||
|
SignedObjectDecodeError::Validate(e) => ManifestProfileError::SignedObject(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum ManifestDecodeError {
|
pub enum ManifestDecodeError {
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
@ -186,6 +196,17 @@ impl ManifestObject {
|
|||||||
Ok(Self::parse_der(der)?.validate_profile()?)
|
Ok(Self::parse_der(der)?.validate_profile()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn decode_der_with_strict_options(
|
||||||
|
der: &[u8],
|
||||||
|
strict_cms_der: bool,
|
||||||
|
strict_name: bool,
|
||||||
|
) -> Result<Self, ManifestDecodeError> {
|
||||||
|
let signed_object =
|
||||||
|
RpkiSignedObject::decode_der_with_strict_options(der, strict_cms_der, strict_name)
|
||||||
|
.map_err(ManifestProfileError::from)?;
|
||||||
|
Self::from_signed_object(signed_object)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn from_signed_object(
|
pub fn from_signed_object(
|
||||||
signed_object: RpkiSignedObject,
|
signed_object: RpkiSignedObject,
|
||||||
) -> Result<Self, ManifestDecodeError> {
|
) -> Result<Self, ManifestDecodeError> {
|
||||||
|
|||||||
@ -8,7 +8,7 @@ use x509_parser::prelude::{FromDer, X509Certificate, X509Extension, X509Version}
|
|||||||
use serde::{Deserialize, Serialize};
|
use serde::{Deserialize, Serialize};
|
||||||
|
|
||||||
use crate::data_model::common::{
|
use crate::data_model::common::{
|
||||||
Asn1TimeUtc, InvalidTimeEncodingError, UtcTime, X509NameDer, asn1_time_to_model,
|
Asn1TimeUtc, DerReader, InvalidTimeEncodingError, UtcTime, X509NameDer, asn1_time_to_model,
|
||||||
};
|
};
|
||||||
use crate::data_model::oid::{
|
use crate::data_model::oid::{
|
||||||
OID_AD_CA_ISSUERS_RAW, OID_AD_CA_REPOSITORY, OID_AD_CA_REPOSITORY_RAW, OID_AD_RPKI_MANIFEST,
|
OID_AD_CA_ISSUERS_RAW, OID_AD_CA_REPOSITORY, OID_AD_CA_REPOSITORY_RAW, OID_AD_RPKI_MANIFEST,
|
||||||
@ -389,6 +389,11 @@ pub enum ResourceCertificateProfileError {
|
|||||||
#[error("invalid signature algorithm parameters (RFC 5280 §4.1.1.2)")]
|
#[error("invalid signature algorithm parameters (RFC 5280 §4.1.1.2)")]
|
||||||
InvalidSignatureAlgorithmParameters,
|
InvalidSignatureAlgorithmParameters,
|
||||||
|
|
||||||
|
#[error(
|
||||||
|
"{role} Name strict validation failed: {detail} (RFC 6487 §4.4; RFC 5280 §4.1.2.4/§4.1.2.6)"
|
||||||
|
)]
|
||||||
|
StrictName { role: &'static str, detail: String },
|
||||||
|
|
||||||
#[error("duplicate extension: {0} (RFC 5280 §4.2; RFC 6487 §4.8)")]
|
#[error("duplicate extension: {0} (RFC 5280 §4.2; RFC 6487 §4.8)")]
|
||||||
DuplicateExtension(&'static str),
|
DuplicateExtension(&'static str),
|
||||||
|
|
||||||
@ -562,17 +567,181 @@ impl ResourceCertificate {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn validate_strict_name_profile(&self) -> Result<(), ResourceCertificateProfileError> {
|
||||||
|
validate_strict_rpki_name(&self.tbs.issuer_name, "issuer")?;
|
||||||
|
validate_strict_rpki_name(&self.tbs.subject_name, "subject")?;
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
/// Decode a resource certificate (`parse + validate`).
|
/// Decode a resource certificate (`parse + validate`).
|
||||||
pub fn decode_der(der: &[u8]) -> Result<Self, ResourceCertificateDecodeError> {
|
pub fn decode_der(der: &[u8]) -> Result<Self, ResourceCertificateDecodeError> {
|
||||||
Ok(Self::parse_der(der)?.validate_profile()?)
|
Ok(Self::parse_der(der)?.validate_profile()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn decode_der_with_strict_name(der: &[u8]) -> Result<Self, ResourceCertificateDecodeError> {
|
||||||
|
let cert = Self::decode_der(der)?;
|
||||||
|
cert.validate_strict_name_profile()?;
|
||||||
|
Ok(cert)
|
||||||
|
}
|
||||||
|
|
||||||
/// Backwards-compatible helper (historical name).
|
/// Backwards-compatible helper (historical name).
|
||||||
pub fn from_der(der: &[u8]) -> Result<Self, ResourceCertificateError> {
|
pub fn from_der(der: &[u8]) -> Result<Self, ResourceCertificateError> {
|
||||||
Self::decode_der(der)
|
Self::decode_der(der)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn validate_strict_rpki_name(
|
||||||
|
name: &X509NameDer,
|
||||||
|
role: &'static str,
|
||||||
|
) -> Result<(), ResourceCertificateProfileError> {
|
||||||
|
let mut name_seq = DerReader::new(name.as_raw())
|
||||||
|
.take_sequence()
|
||||||
|
.map_err(|e| ResourceCertificateProfileError::StrictName { role, detail: e })?;
|
||||||
|
|
||||||
|
let mut common_name_count = 0usize;
|
||||||
|
let mut serial_number_count = 0usize;
|
||||||
|
|
||||||
|
while !name_seq.is_empty() {
|
||||||
|
let set_bytes = name_seq
|
||||||
|
.take_tag(0x31)
|
||||||
|
.map_err(|e| ResourceCertificateProfileError::StrictName { role, detail: e })?;
|
||||||
|
let mut rdn_set = DerReader::new(set_bytes);
|
||||||
|
if rdn_set.is_empty() {
|
||||||
|
return Err(ResourceCertificateProfileError::StrictName {
|
||||||
|
role,
|
||||||
|
detail: "RelativeDistinguishedName SET is empty".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
while !rdn_set.is_empty() {
|
||||||
|
let mut attr = rdn_set
|
||||||
|
.take_sequence()
|
||||||
|
.map_err(|e| ResourceCertificateProfileError::StrictName { role, detail: e })?;
|
||||||
|
let oid = attr
|
||||||
|
.take_tag(0x06)
|
||||||
|
.map_err(|e| ResourceCertificateProfileError::StrictName { role, detail: e })?;
|
||||||
|
let (value_tag, _value) = attr
|
||||||
|
.take_any()
|
||||||
|
.map_err(|e| ResourceCertificateProfileError::StrictName { role, detail: e })?;
|
||||||
|
if !attr.is_empty() {
|
||||||
|
return Err(ResourceCertificateProfileError::StrictName {
|
||||||
|
role,
|
||||||
|
detail: "AttributeTypeAndValue must be SEQUENCE of 2".to_string(),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
match oid {
|
||||||
|
// 2.5.4.3 commonName
|
||||||
|
&[0x55, 0x04, 0x03] => {
|
||||||
|
common_name_count += 1;
|
||||||
|
if value_tag != 0x13 {
|
||||||
|
return Err(ResourceCertificateProfileError::StrictName {
|
||||||
|
role,
|
||||||
|
detail: format!(
|
||||||
|
"commonName must be PrintableString, got tag 0x{value_tag:02X}"
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
// 2.5.4.5 serialNumber
|
||||||
|
&[0x55, 0x04, 0x05] => {
|
||||||
|
serial_number_count += 1;
|
||||||
|
if value_tag != 0x13 {
|
||||||
|
return Err(ResourceCertificateProfileError::StrictName {
|
||||||
|
role,
|
||||||
|
detail: format!(
|
||||||
|
"serialNumber must be PrintableString, got tag 0x{value_tag:02X}"
|
||||||
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if common_name_count != 1 {
|
||||||
|
return Err(ResourceCertificateProfileError::StrictName {
|
||||||
|
role,
|
||||||
|
detail: format!("commonName must appear exactly once, got {common_name_count}"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
if serial_number_count > 1 {
|
||||||
|
return Err(ResourceCertificateProfileError::StrictName {
|
||||||
|
role,
|
||||||
|
detail: format!("serialNumber must appear at most once, got {serial_number_count}"),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
Ok(())
|
||||||
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod strict_name_tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
fn name_with_attrs(attrs: &[(&[u8], u8, &[u8])]) -> X509NameDer {
|
||||||
|
let mut rdns = Vec::new();
|
||||||
|
for (oid, tag, value) in attrs {
|
||||||
|
let mut attr = Vec::new();
|
||||||
|
attr.extend(der_tlv(0x06, oid));
|
||||||
|
attr.extend(der_tlv(*tag, value));
|
||||||
|
let attr = der_tlv(0x30, &attr);
|
||||||
|
let rdn = der_tlv(0x31, &attr);
|
||||||
|
rdns.extend(rdn);
|
||||||
|
}
|
||||||
|
X509NameDer(der_tlv(0x30, &rdns))
|
||||||
|
}
|
||||||
|
|
||||||
|
fn der_tlv(tag: u8, value: &[u8]) -> Vec<u8> {
|
||||||
|
let mut out = vec![tag];
|
||||||
|
encode_len(value.len(), &mut out);
|
||||||
|
out.extend_from_slice(value);
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_len(len: usize, out: &mut Vec<u8>) {
|
||||||
|
if len < 0x80 {
|
||||||
|
out.push(len as u8);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mut bytes = Vec::new();
|
||||||
|
let mut value = len;
|
||||||
|
while value > 0 {
|
||||||
|
bytes.push((value & 0xFF) as u8);
|
||||||
|
value >>= 8;
|
||||||
|
}
|
||||||
|
bytes.reverse();
|
||||||
|
out.push(0x80 | bytes.len() as u8);
|
||||||
|
out.extend(bytes);
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn strict_name_accepts_printable_common_name_and_serial_number() {
|
||||||
|
let name = name_with_attrs(&[
|
||||||
|
(&[0x55, 0x04, 0x03], 0x13, b"CN1"),
|
||||||
|
(&[0x55, 0x04, 0x05], 0x13, b"SN1"),
|
||||||
|
]);
|
||||||
|
validate_strict_rpki_name(&name, "subject").expect("strict name");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn strict_name_rejects_utf8_common_name() {
|
||||||
|
let name = name_with_attrs(&[(&[0x55, 0x04, 0x03], 0x0C, b"CN1")]);
|
||||||
|
let err = validate_strict_rpki_name(&name, "subject").expect_err("strict name fails");
|
||||||
|
assert!(err.to_string().contains("PrintableString"), "{err}");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn strict_name_rejects_duplicate_common_name() {
|
||||||
|
let name = name_with_attrs(&[
|
||||||
|
(&[0x55, 0x04, 0x03], 0x13, b"CN1"),
|
||||||
|
(&[0x55, 0x04, 0x03], 0x13, b"CN2"),
|
||||||
|
]);
|
||||||
|
let err = validate_strict_rpki_name(&name, "subject").expect_err("strict name fails");
|
||||||
|
assert!(err.to_string().contains("exactly once"), "{err}");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
impl ResourceCertificateParsed {
|
impl ResourceCertificateParsed {
|
||||||
pub fn validate_profile(self) -> Result<ResourceCertificate, ResourceCertificateProfileError> {
|
pub fn validate_profile(self) -> Result<ResourceCertificate, ResourceCertificateProfileError> {
|
||||||
let version = match self.version {
|
let version = match self.version {
|
||||||
|
|||||||
@ -2,7 +2,8 @@ use crate::data_model::common::{DerReader, der_take_tlv};
|
|||||||
use crate::data_model::oid::OID_CT_ROUTE_ORIGIN_AUTHZ;
|
use crate::data_model::oid::OID_CT_ROUTE_ORIGIN_AUTHZ;
|
||||||
use crate::data_model::rc::{Afi as RcAfi, IpPrefix as RcIpPrefix, ResourceCertificate};
|
use crate::data_model::rc::{Afi as RcAfi, IpPrefix as RcIpPrefix, ResourceCertificate};
|
||||||
use crate::data_model::signed_object::{
|
use crate::data_model::signed_object::{
|
||||||
RpkiSignedObject, RpkiSignedObjectParsed, SignedObjectParseError, SignedObjectValidateError,
|
RpkiSignedObject, RpkiSignedObjectParsed, SignedObjectDecodeError, SignedObjectParseError,
|
||||||
|
SignedObjectValidateError,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
@ -106,6 +107,15 @@ pub enum RoaProfileError {
|
|||||||
},
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
|
impl From<SignedObjectDecodeError> for RoaProfileError {
|
||||||
|
fn from(value: SignedObjectDecodeError) -> Self {
|
||||||
|
match value {
|
||||||
|
SignedObjectDecodeError::Parse(e) => RoaProfileError::ProfileDecode(e.to_string()),
|
||||||
|
SignedObjectDecodeError::Validate(e) => RoaProfileError::SignedObject(e),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum RoaDecodeError {
|
pub enum RoaDecodeError {
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
@ -171,6 +181,17 @@ impl RoaObject {
|
|||||||
Ok(Self::parse_der(der)?.validate_profile()?)
|
Ok(Self::parse_der(der)?.validate_profile()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn decode_der_with_strict_options(
|
||||||
|
der: &[u8],
|
||||||
|
strict_cms_der: bool,
|
||||||
|
strict_name: bool,
|
||||||
|
) -> Result<Self, RoaDecodeError> {
|
||||||
|
let signed_object =
|
||||||
|
RpkiSignedObject::decode_der_with_strict_options(der, strict_cms_der, strict_name)
|
||||||
|
.map_err(RoaProfileError::from)?;
|
||||||
|
Self::from_signed_object(signed_object)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn from_signed_object(signed_object: RpkiSignedObject) -> Result<Self, RoaDecodeError> {
|
pub fn from_signed_object(signed_object: RpkiSignedObject) -> Result<Self, RoaDecodeError> {
|
||||||
let econtent_type = signed_object
|
let econtent_type = signed_object
|
||||||
.signed_data
|
.signed_data
|
||||||
|
|||||||
@ -9,10 +9,10 @@ use crate::data_model::oid::{
|
|||||||
OID_SIGNED_DATA_RAW, OID_SUBJECT_INFO_ACCESS,
|
OID_SIGNED_DATA_RAW, OID_SUBJECT_INFO_ACCESS,
|
||||||
};
|
};
|
||||||
use crate::data_model::rc::{ResourceCertificate, SubjectInfoAccess};
|
use crate::data_model::rc::{ResourceCertificate, SubjectInfoAccess};
|
||||||
use asn1_rs::{Any, Class, FromBer, Header, Tag};
|
use asn1_rs::{Any, Class, FromBer, FromDer as Asn1FromDer, Header, Tag};
|
||||||
use ring::digest;
|
use ring::digest;
|
||||||
use x509_parser::extensions::ParsedExtension;
|
use x509_parser::extensions::ParsedExtension;
|
||||||
use x509_parser::prelude::{FromDer, X509Certificate};
|
use x509_parser::prelude::X509Certificate;
|
||||||
use x509_parser::public_key::PublicKey;
|
use x509_parser::public_key::PublicKey;
|
||||||
use x509_parser::x509::SubjectPublicKeyInfo;
|
use x509_parser::x509::SubjectPublicKeyInfo;
|
||||||
|
|
||||||
@ -321,7 +321,13 @@ impl RpkiSignedObject {
|
|||||||
/// This performs encoding/structure parsing only. Profile constraints are enforced by
|
/// This performs encoding/structure parsing only. Profile constraints are enforced by
|
||||||
/// `RpkiSignedObjectParsed::validate_profile`.
|
/// `RpkiSignedObjectParsed::validate_profile`.
|
||||||
pub fn parse_der(der: &[u8]) -> Result<RpkiSignedObjectParsed, SignedObjectParseError> {
|
pub fn parse_der(der: &[u8]) -> Result<RpkiSignedObjectParsed, SignedObjectParseError> {
|
||||||
parse_signed_object_content_info(der, der)
|
parse_signed_object_content_info(der, der, CmsParseMode::BerCompatible)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_der_strict_cms(
|
||||||
|
der: &[u8],
|
||||||
|
) -> Result<RpkiSignedObjectParsed, SignedObjectParseError> {
|
||||||
|
parse_signed_object_content_info(der, der, CmsParseMode::DerStrict)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Decode a DER-encoded RPKI Signed Object (CMS ContentInfo wrapping SignedData) and enforce
|
/// Decode a DER-encoded RPKI Signed Object (CMS ContentInfo wrapping SignedData) and enforce
|
||||||
@ -331,6 +337,19 @@ impl RpkiSignedObject {
|
|||||||
Ok(parsed.validate_profile()?)
|
Ok(parsed.validate_profile()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn decode_der_with_strict_options(
|
||||||
|
der: &[u8],
|
||||||
|
strict_cms_der: bool,
|
||||||
|
strict_name: bool,
|
||||||
|
) -> Result<Self, SignedObjectDecodeError> {
|
||||||
|
let parsed = if strict_cms_der {
|
||||||
|
Self::parse_der_strict_cms(der)?
|
||||||
|
} else {
|
||||||
|
Self::parse_der(der)?
|
||||||
|
};
|
||||||
|
Ok(parsed.validate_profile_with_strict_name(strict_name)?)
|
||||||
|
}
|
||||||
|
|
||||||
/// Scheme-A naming for signature verification.
|
/// Scheme-A naming for signature verification.
|
||||||
pub fn verify(&self) -> Result<(), SignedObjectVerifyError> {
|
pub fn verify(&self) -> Result<(), SignedObjectVerifyError> {
|
||||||
self.verify_signature()
|
self.verify_signature()
|
||||||
@ -399,13 +418,20 @@ impl RpkiSignedObject {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||||
|
enum CmsParseMode {
|
||||||
|
BerCompatible,
|
||||||
|
DerStrict,
|
||||||
|
}
|
||||||
|
|
||||||
struct CmsReader<'a> {
|
struct CmsReader<'a> {
|
||||||
buf: &'a [u8],
|
buf: &'a [u8],
|
||||||
|
mode: CmsParseMode,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl<'a> CmsReader<'a> {
|
impl<'a> CmsReader<'a> {
|
||||||
fn new(buf: &'a [u8]) -> Self {
|
fn new(buf: &'a [u8], mode: CmsParseMode) -> Self {
|
||||||
Self { buf }
|
Self { buf, mode }
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_empty(&self) -> bool {
|
fn is_empty(&self) -> bool {
|
||||||
@ -417,19 +443,19 @@ impl<'a> CmsReader<'a> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
fn peek_tag(&self) -> Result<u8, String> {
|
fn peek_tag(&self) -> Result<u8, String> {
|
||||||
let (_rem, any) = Any::from_ber(self.buf).map_err(|e| format!("BER parse error: {e}"))?;
|
let (_rem, any) = parse_any(self.buf, self.mode)?;
|
||||||
header_to_single_byte_tag(&any.header)
|
header_to_single_byte_tag(&any.header)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn take_any(&mut self) -> Result<(u8, &'a [u8]), String> {
|
fn take_any(&mut self) -> Result<(u8, &'a [u8]), String> {
|
||||||
let (rem, any) = Any::from_ber(self.buf).map_err(|e| format!("BER parse error: {e}"))?;
|
let (rem, any) = parse_any(self.buf, self.mode)?;
|
||||||
let tag = header_to_single_byte_tag(&any.header)?;
|
let tag = header_to_single_byte_tag(&any.header)?;
|
||||||
self.buf = rem;
|
self.buf = rem;
|
||||||
Ok((tag, any.data))
|
Ok((tag, any.data))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn take_any_full(&mut self) -> Result<(u8, &'a [u8], &'a [u8]), String> {
|
fn take_any_full(&mut self) -> Result<(u8, &'a [u8], &'a [u8]), String> {
|
||||||
let (rem, any) = Any::from_ber(self.buf).map_err(|e| format!("BER parse error: {e}"))?;
|
let (rem, any) = parse_any(self.buf, self.mode)?;
|
||||||
let consumed = self.buf.len() - rem.len();
|
let consumed = self.buf.len() - rem.len();
|
||||||
let full = &self.buf[..consumed];
|
let full = &self.buf[..consumed];
|
||||||
let tag = header_to_single_byte_tag(&any.header)?;
|
let tag = header_to_single_byte_tag(&any.header)?;
|
||||||
@ -454,16 +480,21 @@ impl<'a> CmsReader<'a> {
|
|||||||
|
|
||||||
fn take_sequence(&mut self) -> Result<CmsReader<'a>, String> {
|
fn take_sequence(&mut self) -> Result<CmsReader<'a>, String> {
|
||||||
let value = self.take_tag(0x30)?;
|
let value = self.take_tag(0x30)?;
|
||||||
Ok(CmsReader::new(value))
|
Ok(CmsReader::new(value, self.mode))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn take_octet_string(&mut self) -> Result<Vec<u8>, String> {
|
fn take_octet_string(&mut self) -> Result<Vec<u8>, String> {
|
||||||
let (rem, any) = Any::from_ber(self.buf).map_err(|e| format!("BER parse error: {e}"))?;
|
let (rem, any) = parse_any(self.buf, self.mode)?;
|
||||||
let tag = header_to_single_byte_tag(&any.header)?;
|
let tag = header_to_single_byte_tag(&any.header)?;
|
||||||
|
if self.mode == CmsParseMode::DerStrict && tag != 0x04 {
|
||||||
|
return Err(format!(
|
||||||
|
"unexpected tag in DER strict mode: got 0x{tag:02X}, expected 0x04"
|
||||||
|
));
|
||||||
|
}
|
||||||
if tag != 0x04 && tag != 0x24 {
|
if tag != 0x04 && tag != 0x24 {
|
||||||
return Err(format!("unexpected tag: got 0x{tag:02X}, expected 0x04"));
|
return Err(format!("unexpected tag: got 0x{tag:02X}, expected 0x04"));
|
||||||
}
|
}
|
||||||
let octets = flatten_octet_string(any)?;
|
let octets = flatten_octet_string(any, self.mode)?;
|
||||||
self.buf = rem;
|
self.buf = rem;
|
||||||
Ok(octets)
|
Ok(octets)
|
||||||
}
|
}
|
||||||
@ -475,7 +506,7 @@ impl<'a> CmsReader<'a> {
|
|||||||
|
|
||||||
fn take_explicit(&mut self, expected_outer_tag: u8) -> Result<(u8, &'a [u8]), String> {
|
fn take_explicit(&mut self, expected_outer_tag: u8) -> Result<(u8, &'a [u8]), String> {
|
||||||
let inner_der = self.take_tag(expected_outer_tag)?;
|
let inner_der = self.take_tag(expected_outer_tag)?;
|
||||||
let (tag, value, rem) = cms_take_tlv(inner_der)?;
|
let (tag, value, rem) = cms_take_tlv(inner_der, self.mode)?;
|
||||||
if !rem.is_empty() {
|
if !rem.is_empty() {
|
||||||
return Err("trailing bytes inside EXPLICIT value".into());
|
return Err("trailing bytes inside EXPLICIT value".into());
|
||||||
}
|
}
|
||||||
@ -484,7 +515,7 @@ impl<'a> CmsReader<'a> {
|
|||||||
|
|
||||||
fn take_explicit_der(&mut self, expected_outer_tag: u8) -> Result<&'a [u8], String> {
|
fn take_explicit_der(&mut self, expected_outer_tag: u8) -> Result<&'a [u8], String> {
|
||||||
let inner_der = self.take_tag(expected_outer_tag)?;
|
let inner_der = self.take_tag(expected_outer_tag)?;
|
||||||
let (_tag, _value, rem) = cms_take_tlv(inner_der)?;
|
let (_tag, _value, rem) = cms_take_tlv(inner_der, self.mode)?;
|
||||||
if !rem.is_empty() {
|
if !rem.is_empty() {
|
||||||
return Err("trailing bytes inside EXPLICIT value".into());
|
return Err("trailing bytes inside EXPLICIT value".into());
|
||||||
}
|
}
|
||||||
@ -495,8 +526,9 @@ impl<'a> CmsReader<'a> {
|
|||||||
fn parse_signed_object_content_info(
|
fn parse_signed_object_content_info(
|
||||||
raw_der: &[u8],
|
raw_der: &[u8],
|
||||||
parse_der: &[u8],
|
parse_der: &[u8],
|
||||||
|
mode: CmsParseMode,
|
||||||
) -> Result<RpkiSignedObjectParsed, SignedObjectParseError> {
|
) -> Result<RpkiSignedObjectParsed, SignedObjectParseError> {
|
||||||
let mut r = CmsReader::new(parse_der);
|
let mut r = CmsReader::new(parse_der, mode);
|
||||||
let mut content_info_seq = r
|
let mut content_info_seq = r
|
||||||
.take_sequence()
|
.take_sequence()
|
||||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||||
@ -519,6 +551,17 @@ fn parse_signed_object_content_info(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn parse_any<'a>(input: &'a [u8], mode: CmsParseMode) -> Result<(&'a [u8], Any<'a>), String> {
|
||||||
|
match mode {
|
||||||
|
CmsParseMode::BerCompatible => {
|
||||||
|
Any::from_ber(input).map_err(|e| format!("BER parse error: {e}"))
|
||||||
|
}
|
||||||
|
CmsParseMode::DerStrict => {
|
||||||
|
Any::from_der(input).map_err(|e| format!("DER parse error: {e}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn header_to_single_byte_tag(header: &Header<'_>) -> Result<u8, String> {
|
fn header_to_single_byte_tag(header: &Header<'_>) -> Result<u8, String> {
|
||||||
let tag_no = header.tag().0;
|
let tag_no = header.tag().0;
|
||||||
if tag_no > 30 {
|
if tag_no > 30 {
|
||||||
@ -529,24 +572,27 @@ fn header_to_single_byte_tag(header: &Header<'_>) -> Result<u8, String> {
|
|||||||
| tag_no as u8)
|
| tag_no as u8)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn cms_take_tlv(input: &[u8]) -> Result<(u8, &[u8], &[u8]), String> {
|
fn cms_take_tlv(input: &[u8], mode: CmsParseMode) -> Result<(u8, &[u8], &[u8]), String> {
|
||||||
let (rem, any) = Any::from_ber(input).map_err(|e| format!("BER parse error: {e}"))?;
|
let (rem, any) = parse_any(input, mode)?;
|
||||||
let tag = header_to_single_byte_tag(&any.header)?;
|
let tag = header_to_single_byte_tag(&any.header)?;
|
||||||
Ok((tag, any.data, rem))
|
Ok((tag, any.data, rem))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn flatten_octet_string(any: Any<'_>) -> Result<Vec<u8>, String> {
|
fn flatten_octet_string(any: Any<'_>, mode: CmsParseMode) -> Result<Vec<u8>, String> {
|
||||||
if any.class() != Class::Universal || any.tag() != Tag::OctetString {
|
if any.class() != Class::Universal || any.tag() != Tag::OctetString {
|
||||||
return Err("expected OCTET STRING".into());
|
return Err("expected OCTET STRING".into());
|
||||||
}
|
}
|
||||||
if !any.header.constructed() {
|
if !any.header.constructed() {
|
||||||
return Ok(any.data.to_vec());
|
return Ok(any.data.to_vec());
|
||||||
}
|
}
|
||||||
|
if mode == CmsParseMode::DerStrict {
|
||||||
|
return Err("constructed OCTET STRING is not allowed in DER strict mode".into());
|
||||||
|
}
|
||||||
let mut out = Vec::new();
|
let mut out = Vec::new();
|
||||||
let mut input = any.data;
|
let mut input = any.data;
|
||||||
while !input.is_empty() {
|
while !input.is_empty() {
|
||||||
let (rem, child) = Any::from_ber(input).map_err(|e| format!("BER parse error: {e}"))?;
|
let (rem, child) = Any::from_ber(input).map_err(|e| format!("BER parse error: {e}"))?;
|
||||||
out.extend(flatten_octet_string(child)?);
|
out.extend(flatten_octet_string(child, mode)?);
|
||||||
input = rem;
|
input = rem;
|
||||||
}
|
}
|
||||||
Ok(out)
|
Ok(out)
|
||||||
@ -554,13 +600,20 @@ fn flatten_octet_string(any: Any<'_>) -> Result<Vec<u8>, String> {
|
|||||||
|
|
||||||
impl RpkiSignedObjectParsed {
|
impl RpkiSignedObjectParsed {
|
||||||
pub fn validate_profile(self) -> Result<RpkiSignedObject, SignedObjectValidateError> {
|
pub fn validate_profile(self) -> Result<RpkiSignedObject, SignedObjectValidateError> {
|
||||||
|
self.validate_profile_with_strict_name(false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn validate_profile_with_strict_name(
|
||||||
|
self,
|
||||||
|
strict_name: bool,
|
||||||
|
) -> Result<RpkiSignedObject, SignedObjectValidateError> {
|
||||||
if self.content_info_content_type != OID_SIGNED_DATA {
|
if self.content_info_content_type != OID_SIGNED_DATA {
|
||||||
return Err(SignedObjectValidateError::InvalidContentInfoContentType(
|
return Err(SignedObjectValidateError::InvalidContentInfoContentType(
|
||||||
self.content_info_content_type,
|
self.content_info_content_type,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let signed_data = validate_signed_data_profile(self.signed_data)?;
|
let signed_data = validate_signed_data_profile(self.signed_data, strict_name)?;
|
||||||
|
|
||||||
Ok(RpkiSignedObject {
|
Ok(RpkiSignedObject {
|
||||||
raw_der: self.raw_der,
|
raw_der: self.raw_der,
|
||||||
@ -576,7 +629,7 @@ fn parse_signed_data_from_contentinfo_cursor(
|
|||||||
let inner_der = seq.take_explicit_der(0xA0).map_err(|_e| {
|
let inner_der = seq.take_explicit_der(0xA0).map_err(|_e| {
|
||||||
SignedObjectParseError::Parse("ContentInfo.content must be [0] EXPLICIT".into())
|
SignedObjectParseError::Parse("ContentInfo.content must be [0] EXPLICIT".into())
|
||||||
})?;
|
})?;
|
||||||
let mut r = CmsReader::new(inner_der);
|
let mut r = CmsReader::new(inner_der, seq.mode);
|
||||||
let signed_data_seq = r
|
let signed_data_seq = r
|
||||||
.take_sequence()
|
.take_sequence()
|
||||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||||
@ -598,7 +651,7 @@ fn parse_signed_data_cursor(
|
|||||||
let digest_set_bytes = seq
|
let digest_set_bytes = seq
|
||||||
.take_tag(0x31)
|
.take_tag(0x31)
|
||||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||||
let mut digest_set = CmsReader::new(digest_set_bytes);
|
let mut digest_set = CmsReader::new(digest_set_bytes, seq.mode);
|
||||||
let mut digest_algorithms: Vec<AlgorithmIdentifierParsed> = Vec::new();
|
let mut digest_algorithms: Vec<AlgorithmIdentifierParsed> = Vec::new();
|
||||||
while !digest_set.is_empty() {
|
while !digest_set.is_empty() {
|
||||||
let alg = digest_set
|
let alg = digest_set
|
||||||
@ -631,7 +684,7 @@ fn parse_signed_data_cursor(
|
|||||||
let content = seq
|
let content = seq
|
||||||
.take_tag(0xA0)
|
.take_tag(0xA0)
|
||||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||||
certificates = Some(split_der_objects(content)?);
|
certificates = Some(split_der_objects(content, seq.mode)?);
|
||||||
}
|
}
|
||||||
0xA1 => {
|
0xA1 => {
|
||||||
crls_present = true;
|
crls_present = true;
|
||||||
@ -647,7 +700,7 @@ fn parse_signed_data_cursor(
|
|||||||
let set_bytes = seq
|
let set_bytes = seq
|
||||||
.take_tag(0x31)
|
.take_tag(0x31)
|
||||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||||
signer_infos = Some(parse_signer_infos_set_cursor(set_bytes)?);
|
signer_infos = Some(parse_signer_infos_set_cursor(set_bytes, seq.mode)?);
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(SignedObjectParseError::Parse(
|
return Err(SignedObjectParseError::Parse(
|
||||||
@ -689,7 +742,7 @@ fn parse_encapsulated_content_info_cursor(
|
|||||||
"EncapsulatedContentInfo.eContent must be [0] EXPLICIT".into(),
|
"EncapsulatedContentInfo.eContent must be [0] EXPLICIT".into(),
|
||||||
)
|
)
|
||||||
})?;
|
})?;
|
||||||
let mut inner = CmsReader::new(inner_der);
|
let mut inner = CmsReader::new(inner_der, seq.mode);
|
||||||
let octets = inner
|
let octets = inner
|
||||||
.take_octet_string()
|
.take_octet_string()
|
||||||
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
.map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||||
@ -712,11 +765,14 @@ fn parse_encapsulated_content_info_cursor(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn split_der_objects(mut input: &[u8]) -> Result<Vec<Vec<u8>>, SignedObjectParseError> {
|
fn split_der_objects(
|
||||||
|
mut input: &[u8],
|
||||||
|
mode: CmsParseMode,
|
||||||
|
) -> Result<Vec<Vec<u8>>, SignedObjectParseError> {
|
||||||
let mut out: Vec<Vec<u8>> = Vec::new();
|
let mut out: Vec<Vec<u8>> = Vec::new();
|
||||||
while !input.is_empty() {
|
while !input.is_empty() {
|
||||||
let (_tag, _value, rem) =
|
let (_tag, _value, rem) =
|
||||||
cms_take_tlv(input).map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
cms_take_tlv(input, mode).map_err(|e| SignedObjectParseError::Parse(e.to_string()))?;
|
||||||
let consumed = input.len() - rem.len();
|
let consumed = input.len() - rem.len();
|
||||||
out.push(input[..consumed].to_vec());
|
out.push(input[..consumed].to_vec());
|
||||||
input = rem;
|
input = rem;
|
||||||
@ -726,8 +782,9 @@ fn split_der_objects(mut input: &[u8]) -> Result<Vec<Vec<u8>>, SignedObjectParse
|
|||||||
|
|
||||||
fn parse_signer_infos_set_cursor(
|
fn parse_signer_infos_set_cursor(
|
||||||
set_bytes: &[u8],
|
set_bytes: &[u8],
|
||||||
|
mode: CmsParseMode,
|
||||||
) -> Result<Vec<SignerInfoParsed>, SignedObjectParseError> {
|
) -> Result<Vec<SignerInfoParsed>, SignedObjectParseError> {
|
||||||
let mut set = CmsReader::new(set_bytes);
|
let mut set = CmsReader::new(set_bytes, mode);
|
||||||
let mut out: Vec<SignerInfoParsed> = Vec::new();
|
let mut out: Vec<SignerInfoParsed> = Vec::new();
|
||||||
while !set.is_empty() {
|
while !set.is_empty() {
|
||||||
let si = set
|
let si = set
|
||||||
@ -738,7 +795,10 @@ fn parse_signer_infos_set_cursor(
|
|||||||
Ok(out)
|
Ok(out)
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_ee_certificate(der: &[u8]) -> Result<ResourceEeCertificate, SignedObjectValidateError> {
|
fn validate_ee_certificate(
|
||||||
|
der: &[u8],
|
||||||
|
strict_name: bool,
|
||||||
|
) -> Result<ResourceEeCertificate, SignedObjectValidateError> {
|
||||||
let (rem, cert) = X509Certificate::from_der(der)
|
let (rem, cert) = X509Certificate::from_der(der)
|
||||||
.map_err(|e| SignedObjectValidateError::EeCertificateParse(e.to_string()))?;
|
.map_err(|e| SignedObjectValidateError::EeCertificateParse(e.to_string()))?;
|
||||||
if !rem.is_empty() {
|
if !rem.is_empty() {
|
||||||
@ -762,6 +822,10 @@ fn validate_ee_certificate(der: &[u8]) -> Result<ResourceEeCertificate, SignedOb
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
if strict_name {
|
||||||
|
rc.validate_strict_name_profile()
|
||||||
|
.map_err(|e| SignedObjectValidateError::EeCertificateParse(e.to_string()))?;
|
||||||
|
}
|
||||||
|
|
||||||
let ski = rc
|
let ski = rc
|
||||||
.tbs
|
.tbs
|
||||||
@ -940,6 +1004,7 @@ fn parse_signer_info_cursor(
|
|||||||
|
|
||||||
fn validate_signed_data_profile(
|
fn validate_signed_data_profile(
|
||||||
signed_data: SignedDataParsed,
|
signed_data: SignedDataParsed,
|
||||||
|
strict_name: bool,
|
||||||
) -> Result<SignedDataProfiled, SignedObjectValidateError> {
|
) -> Result<SignedDataProfiled, SignedObjectValidateError> {
|
||||||
if signed_data.version != 3 {
|
if signed_data.version != 3 {
|
||||||
return Err(SignedObjectValidateError::InvalidSignedDataVersion(
|
return Err(SignedObjectValidateError::InvalidSignedDataVersion(
|
||||||
@ -985,7 +1050,7 @@ fn validate_signed_data_profile(
|
|||||||
certs.len(),
|
certs.len(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let ee = validate_ee_certificate(&certs[0])?;
|
let ee = validate_ee_certificate(&certs[0], strict_name)?;
|
||||||
|
|
||||||
if signed_data.signer_infos.len() != 1 {
|
if signed_data.signer_infos.len() != 1 {
|
||||||
return Err(SignedObjectValidateError::InvalidSignerInfosCount(
|
return Err(SignedObjectValidateError::InvalidSignerInfosCount(
|
||||||
@ -1414,3 +1479,116 @@ fn strip_leading_zeros(bytes: &[u8]) -> &[u8] {
|
|||||||
&bytes[idx..]
|
&bytes[idx..]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn strict_cms_der_rejects_constructed_octet_string_fixture() {
|
||||||
|
let der = std::fs::read(std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR")).join(
|
||||||
|
"tests/fixtures/repository/ca.rg.net/rpki/RGnet-OU/bW-_qXU9uNhGQz21NR2ansB8lr0.mft",
|
||||||
|
))
|
||||||
|
.expect("read manifest fixture");
|
||||||
|
let parsed = RpkiSignedObject::parse_der(&der).expect("parse fixture");
|
||||||
|
let econtent = parsed
|
||||||
|
.signed_data
|
||||||
|
.encap_content_info
|
||||||
|
.econtent
|
||||||
|
.clone()
|
||||||
|
.expect("fixture eContent");
|
||||||
|
assert_eq!(
|
||||||
|
parsed.signed_data.encap_content_info.econtent.as_deref(),
|
||||||
|
Some(econtent.as_slice())
|
||||||
|
);
|
||||||
|
|
||||||
|
let primitive_octets = der_tlv(0x04, &econtent);
|
||||||
|
let constructed_octets = same_size_constructed_octet_string(&primitive_octets, &econtent);
|
||||||
|
let mutated = replace_first_subslice(&der, &primitive_octets, &constructed_octets)
|
||||||
|
.expect("replace eContent OCTET STRING");
|
||||||
|
let compatible = RpkiSignedObject::parse_der(&mutated).expect("BER-compatible parse");
|
||||||
|
assert!(
|
||||||
|
econtent.starts_with(
|
||||||
|
compatible
|
||||||
|
.signed_data
|
||||||
|
.encap_content_info
|
||||||
|
.econtent
|
||||||
|
.as_deref()
|
||||||
|
.expect("compatible eContent")
|
||||||
|
)
|
||||||
|
);
|
||||||
|
let err = RpkiSignedObject::parse_der_strict_cms(&mutated)
|
||||||
|
.expect_err("DER strict rejects constructed OCTET STRING");
|
||||||
|
assert!(err.to_string().contains("DER"), "{err}");
|
||||||
|
}
|
||||||
|
|
||||||
|
fn replace_first_subslice(input: &[u8], from: &[u8], to: &[u8]) -> Option<Vec<u8>> {
|
||||||
|
let pos = input
|
||||||
|
.windows(from.len())
|
||||||
|
.position(|candidate| candidate == from)?;
|
||||||
|
let mut out = Vec::with_capacity(input.len() - from.len() + to.len());
|
||||||
|
out.extend_from_slice(&input[..pos]);
|
||||||
|
out.extend_from_slice(to);
|
||||||
|
out.extend_from_slice(&input[pos + from.len()..]);
|
||||||
|
Some(out)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn same_size_constructed_octet_string(primitive: &[u8], content: &[u8]) -> Vec<u8> {
|
||||||
|
assert_eq!(primitive[0], 0x04);
|
||||||
|
let header_len = tlv_header_len(primitive);
|
||||||
|
let outer_value_len = primitive.len() - header_len;
|
||||||
|
let child_len = (0..=outer_value_len)
|
||||||
|
.rev()
|
||||||
|
.find(|candidate| 1 + len_len(*candidate) + *candidate == outer_value_len)
|
||||||
|
.expect("find child length");
|
||||||
|
let mut out = primitive[..header_len].to_vec();
|
||||||
|
out[0] = 0x24;
|
||||||
|
out.extend(der_tlv(0x04, &content[..child_len]));
|
||||||
|
assert_eq!(out.len(), primitive.len());
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn tlv_header_len(tlv: &[u8]) -> usize {
|
||||||
|
if tlv[1] & 0x80 == 0 {
|
||||||
|
2
|
||||||
|
} else {
|
||||||
|
2 + (tlv[1] & 0x7F) as usize
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn len_len(len: usize) -> usize {
|
||||||
|
if len < 0x80 {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
let mut value = len;
|
||||||
|
let mut n = 0usize;
|
||||||
|
while value > 0 {
|
||||||
|
n += 1;
|
||||||
|
value >>= 8;
|
||||||
|
}
|
||||||
|
1 + n
|
||||||
|
}
|
||||||
|
|
||||||
|
fn der_tlv(tag: u8, value: &[u8]) -> Vec<u8> {
|
||||||
|
let mut out = vec![tag];
|
||||||
|
encode_len(value.len(), &mut out);
|
||||||
|
out.extend_from_slice(value);
|
||||||
|
out
|
||||||
|
}
|
||||||
|
|
||||||
|
fn encode_len(len: usize, out: &mut Vec<u8>) {
|
||||||
|
if len < 0x80 {
|
||||||
|
out.push(len as u8);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
let mut bytes = Vec::new();
|
||||||
|
let mut value = len;
|
||||||
|
while value > 0 {
|
||||||
|
bytes.push((value & 0xFF) as u8);
|
||||||
|
value >>= 8;
|
||||||
|
}
|
||||||
|
bytes.reverse();
|
||||||
|
out.push(0x80 | bytes.len() as u8);
|
||||||
|
out.extend(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -112,6 +112,14 @@ impl TaCertificate {
|
|||||||
Ok(Self::parse_der(der)?.validate_profile()?)
|
Ok(Self::parse_der(der)?.validate_profile()?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn decode_der_with_strict_name(der: &[u8]) -> Result<Self, TaCertificateDecodeError> {
|
||||||
|
let ta = Self::decode_der(der)?;
|
||||||
|
ta.rc_ca
|
||||||
|
.validate_strict_name_profile()
|
||||||
|
.map_err(TaCertificateProfileError::from)?;
|
||||||
|
Ok(ta)
|
||||||
|
}
|
||||||
|
|
||||||
/// Backwards-compatible helper (historical name).
|
/// Backwards-compatible helper (historical name).
|
||||||
pub fn from_der(der: &[u8]) -> Result<Self, TaCertificateError> {
|
pub fn from_der(der: &[u8]) -> Result<Self, TaCertificateError> {
|
||||||
Self::decode_der(der)
|
Self::decode_der(der)
|
||||||
@ -264,6 +272,15 @@ impl TrustAnchor {
|
|||||||
Ok(Self::bind(tal, ta_certificate, resolved_uri)?)
|
Ok(Self::bind(tal, ta_certificate, resolved_uri)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn bind_der_with_strict_name(
|
||||||
|
tal: Tal,
|
||||||
|
ta_der: &[u8],
|
||||||
|
resolved_uri: Option<&Url>,
|
||||||
|
) -> Result<Self, TrustAnchorError> {
|
||||||
|
let ta_certificate = TaCertificate::decode_der_with_strict_name(ta_der)?;
|
||||||
|
Ok(Self::bind(tal, ta_certificate, resolved_uri)?)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn bind(
|
pub fn bind(
|
||||||
tal: Tal,
|
tal: Tal,
|
||||||
ta_certificate: TaCertificate,
|
ta_certificate: TaCertificate,
|
||||||
|
|||||||
103
src/policy.rs
103
src/policy.rs
@ -39,12 +39,67 @@ impl Default for SignedObjectFailurePolicy {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
|
#[serde(default)]
|
||||||
|
pub struct StrictPolicy {
|
||||||
|
pub name: bool,
|
||||||
|
pub cms_der: bool,
|
||||||
|
pub signed_attrs: bool,
|
||||||
|
}
|
||||||
|
|
||||||
|
impl StrictPolicy {
|
||||||
|
pub fn none() -> Self {
|
||||||
|
Self::default()
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn all() -> Self {
|
||||||
|
Self {
|
||||||
|
name: true,
|
||||||
|
cms_der: true,
|
||||||
|
signed_attrs: true,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn parse_cli_spec(spec: Option<&str>) -> Result<Self, String> {
|
||||||
|
let Some(spec) = spec else {
|
||||||
|
return Ok(Self::all());
|
||||||
|
};
|
||||||
|
let spec = spec.trim();
|
||||||
|
if spec.is_empty() || spec == "all" {
|
||||||
|
return Ok(Self::all());
|
||||||
|
}
|
||||||
|
if spec == "none" {
|
||||||
|
return Ok(Self::none());
|
||||||
|
}
|
||||||
|
|
||||||
|
let mut out = Self::none();
|
||||||
|
for raw in spec.split(',') {
|
||||||
|
let item = raw.trim();
|
||||||
|
match item {
|
||||||
|
"name" => out.name = true,
|
||||||
|
"cms-der" | "cms_der" => out.cms_der = true,
|
||||||
|
"signed-attrs" | "signed_attrs" => out.signed_attrs = true,
|
||||||
|
"all" => out = Self::all(),
|
||||||
|
"none" => out = Self::none(),
|
||||||
|
"" => return Err("empty strict policy name".to_string()),
|
||||||
|
_ => {
|
||||||
|
return Err(format!(
|
||||||
|
"unknown strict policy: {item}; supported: name,cms-der,signed-attrs,all,none"
|
||||||
|
));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Ok(out)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
#[serde(default)]
|
#[serde(default)]
|
||||||
pub struct Policy {
|
pub struct Policy {
|
||||||
pub sync_preference: SyncPreference,
|
pub sync_preference: SyncPreference,
|
||||||
pub ca_failed_fetch_policy: CaFailedFetchPolicy,
|
pub ca_failed_fetch_policy: CaFailedFetchPolicy,
|
||||||
pub signed_object_failure_policy: SignedObjectFailurePolicy,
|
pub signed_object_failure_policy: SignedObjectFailurePolicy,
|
||||||
|
pub strict: StrictPolicy,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Default for Policy {
|
impl Default for Policy {
|
||||||
@ -53,6 +108,7 @@ impl Default for Policy {
|
|||||||
sync_preference: SyncPreference::default(),
|
sync_preference: SyncPreference::default(),
|
||||||
ca_failed_fetch_policy: CaFailedFetchPolicy::default(),
|
ca_failed_fetch_policy: CaFailedFetchPolicy::default(),
|
||||||
signed_object_failure_policy: SignedObjectFailurePolicy::default(),
|
signed_object_failure_policy: SignedObjectFailurePolicy::default(),
|
||||||
|
strict: StrictPolicy::default(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -68,3 +124,50 @@ impl Policy {
|
|||||||
toml::from_str(s).map_err(|e| PolicyParseError::Toml(e.to_string()))
|
toml::from_str(s).map_err(|e| PolicyParseError::Toml(e.to_string()))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg(test)]
|
||||||
|
mod tests {
|
||||||
|
use super::*;
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn strict_policy_parses_cli_specs() {
|
||||||
|
assert_eq!(
|
||||||
|
StrictPolicy::parse_cli_spec(None).unwrap(),
|
||||||
|
StrictPolicy::all()
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
StrictPolicy::parse_cli_spec(Some("name,signed-attrs")).unwrap(),
|
||||||
|
StrictPolicy {
|
||||||
|
name: true,
|
||||||
|
cms_der: false,
|
||||||
|
signed_attrs: true,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
assert_eq!(
|
||||||
|
StrictPolicy::parse_cli_spec(Some("none")).unwrap(),
|
||||||
|
StrictPolicy::none()
|
||||||
|
);
|
||||||
|
assert!(StrictPolicy::parse_cli_spec(Some("bogus")).is_err());
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn policy_toml_accepts_strict_table() {
|
||||||
|
let policy = Policy::from_toml_str(
|
||||||
|
r#"
|
||||||
|
[strict]
|
||||||
|
name = true
|
||||||
|
cms_der = true
|
||||||
|
signed_attrs = false
|
||||||
|
"#,
|
||||||
|
)
|
||||||
|
.expect("parse policy");
|
||||||
|
assert_eq!(
|
||||||
|
policy.strict,
|
||||||
|
StrictPolicy {
|
||||||
|
name: true,
|
||||||
|
cms_der: true,
|
||||||
|
signed_attrs: false,
|
||||||
|
}
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -57,10 +57,42 @@ pub fn discover_root_ca_instance_from_tal_url(
|
|||||||
discover_root_ca_instance_from_tal(http_fetcher, tal, Some(tal_url.to_string()))
|
discover_root_ca_instance_from_tal(http_fetcher, tal, Some(tal_url.to_string()))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn discover_root_ca_instance_from_tal_url_with_strict_name(
|
||||||
|
http_fetcher: &dyn Fetcher,
|
||||||
|
tal_url: &str,
|
||||||
|
) -> Result<DiscoveredRootCaInstance, FromTalError> {
|
||||||
|
let tal_bytes = http_fetcher
|
||||||
|
.fetch(tal_url)
|
||||||
|
.map_err(FromTalError::TalFetch)?;
|
||||||
|
let tal = Tal::decode_bytes(&tal_bytes)?;
|
||||||
|
discover_root_ca_instance_from_tal_with_strict_name(
|
||||||
|
http_fetcher,
|
||||||
|
tal,
|
||||||
|
Some(tal_url.to_string()),
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn discover_root_ca_instance_from_tal(
|
pub fn discover_root_ca_instance_from_tal(
|
||||||
http_fetcher: &dyn Fetcher,
|
http_fetcher: &dyn Fetcher,
|
||||||
tal: Tal,
|
tal: Tal,
|
||||||
tal_url: Option<String>,
|
tal_url: Option<String>,
|
||||||
|
) -> Result<DiscoveredRootCaInstance, FromTalError> {
|
||||||
|
discover_root_ca_instance_from_tal_impl(http_fetcher, tal, tal_url, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn discover_root_ca_instance_from_tal_with_strict_name(
|
||||||
|
http_fetcher: &dyn Fetcher,
|
||||||
|
tal: Tal,
|
||||||
|
tal_url: Option<String>,
|
||||||
|
) -> Result<DiscoveredRootCaInstance, FromTalError> {
|
||||||
|
discover_root_ca_instance_from_tal_impl(http_fetcher, tal, tal_url, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn discover_root_ca_instance_from_tal_impl(
|
||||||
|
http_fetcher: &dyn Fetcher,
|
||||||
|
tal: Tal,
|
||||||
|
tal_url: Option<String>,
|
||||||
|
strict_name: bool,
|
||||||
) -> Result<DiscoveredRootCaInstance, FromTalError> {
|
) -> Result<DiscoveredRootCaInstance, FromTalError> {
|
||||||
if tal.ta_uris.is_empty() {
|
if tal.ta_uris.is_empty() {
|
||||||
return Err(FromTalError::NoTaUris);
|
return Err(FromTalError::NoTaUris);
|
||||||
@ -76,7 +108,8 @@ pub fn discover_root_ca_instance_from_tal(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let trust_anchor = match TrustAnchor::bind_der(tal.clone(), &ta_der, Some(ta_uri)) {
|
let trust_anchor =
|
||||||
|
match bind_trust_anchor_der(tal.clone(), &ta_der, Some(ta_uri), strict_name) {
|
||||||
Ok(ta) => ta,
|
Ok(ta) => ta,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
last_err = Some(format!("bind {ta_uri} failed: {e}"));
|
last_err = Some(format!("bind {ta_uri} failed: {e}"));
|
||||||
@ -110,6 +143,37 @@ pub fn discover_root_ca_instance_from_tal_with_fetchers(
|
|||||||
rsync_fetcher: &dyn RsyncFetcher,
|
rsync_fetcher: &dyn RsyncFetcher,
|
||||||
tal: Tal,
|
tal: Tal,
|
||||||
tal_url: Option<String>,
|
tal_url: Option<String>,
|
||||||
|
) -> Result<DiscoveredRootCaInstance, FromTalError> {
|
||||||
|
discover_root_ca_instance_from_tal_with_fetchers_impl(
|
||||||
|
http_fetcher,
|
||||||
|
rsync_fetcher,
|
||||||
|
tal,
|
||||||
|
tal_url,
|
||||||
|
false,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn discover_root_ca_instance_from_tal_with_fetchers_strict_name(
|
||||||
|
http_fetcher: &dyn Fetcher,
|
||||||
|
rsync_fetcher: &dyn RsyncFetcher,
|
||||||
|
tal: Tal,
|
||||||
|
tal_url: Option<String>,
|
||||||
|
) -> Result<DiscoveredRootCaInstance, FromTalError> {
|
||||||
|
discover_root_ca_instance_from_tal_with_fetchers_impl(
|
||||||
|
http_fetcher,
|
||||||
|
rsync_fetcher,
|
||||||
|
tal,
|
||||||
|
tal_url,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn discover_root_ca_instance_from_tal_with_fetchers_impl(
|
||||||
|
http_fetcher: &dyn Fetcher,
|
||||||
|
rsync_fetcher: &dyn RsyncFetcher,
|
||||||
|
tal: Tal,
|
||||||
|
tal_url: Option<String>,
|
||||||
|
strict_name: bool,
|
||||||
) -> Result<DiscoveredRootCaInstance, FromTalError> {
|
) -> Result<DiscoveredRootCaInstance, FromTalError> {
|
||||||
if tal.ta_uris.is_empty() {
|
if tal.ta_uris.is_empty() {
|
||||||
return Err(FromTalError::NoTaUris);
|
return Err(FromTalError::NoTaUris);
|
||||||
@ -127,7 +191,8 @@ pub fn discover_root_ca_instance_from_tal_with_fetchers(
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let trust_anchor = match TrustAnchor::bind_der(tal.clone(), &ta_der, Some(ta_uri)) {
|
let trust_anchor =
|
||||||
|
match bind_trust_anchor_der(tal.clone(), &ta_der, Some(ta_uri), strict_name) {
|
||||||
Ok(ta) => ta,
|
Ok(ta) => ta,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
last_err = Some(format!("bind {ta_uri} failed: {e}"));
|
last_err = Some(format!("bind {ta_uri} failed: {e}"));
|
||||||
@ -168,6 +233,19 @@ fn fetch_ta_der(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn bind_trust_anchor_der(
|
||||||
|
tal: Tal,
|
||||||
|
ta_der: &[u8],
|
||||||
|
resolved_uri: Option<&Url>,
|
||||||
|
strict_name: bool,
|
||||||
|
) -> Result<TrustAnchor, TrustAnchorError> {
|
||||||
|
if strict_name {
|
||||||
|
TrustAnchor::bind_der_with_strict_name(tal, ta_der, resolved_uri)
|
||||||
|
} else {
|
||||||
|
TrustAnchor::bind_der(tal, ta_der, resolved_uri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn fetch_ta_der_via_rsync(
|
fn fetch_ta_der_via_rsync(
|
||||||
rsync_fetcher: &dyn RsyncFetcher,
|
rsync_fetcher: &dyn RsyncFetcher,
|
||||||
ta_rsync_uri: &str,
|
ta_rsync_uri: &str,
|
||||||
@ -213,9 +291,26 @@ pub fn discover_root_ca_instance_from_tal_and_ta_der(
|
|||||||
tal_bytes: &[u8],
|
tal_bytes: &[u8],
|
||||||
ta_der: &[u8],
|
ta_der: &[u8],
|
||||||
resolved_ta_uri: Option<&Url>,
|
resolved_ta_uri: Option<&Url>,
|
||||||
|
) -> Result<DiscoveredRootCaInstance, FromTalError> {
|
||||||
|
discover_root_ca_instance_from_tal_and_ta_der_impl(tal_bytes, ta_der, resolved_ta_uri, false)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn discover_root_ca_instance_from_tal_and_ta_der_with_strict_name(
|
||||||
|
tal_bytes: &[u8],
|
||||||
|
ta_der: &[u8],
|
||||||
|
resolved_ta_uri: Option<&Url>,
|
||||||
|
) -> Result<DiscoveredRootCaInstance, FromTalError> {
|
||||||
|
discover_root_ca_instance_from_tal_and_ta_der_impl(tal_bytes, ta_der, resolved_ta_uri, true)
|
||||||
|
}
|
||||||
|
|
||||||
|
fn discover_root_ca_instance_from_tal_and_ta_der_impl(
|
||||||
|
tal_bytes: &[u8],
|
||||||
|
ta_der: &[u8],
|
||||||
|
resolved_ta_uri: Option<&Url>,
|
||||||
|
strict_name: bool,
|
||||||
) -> Result<DiscoveredRootCaInstance, FromTalError> {
|
) -> Result<DiscoveredRootCaInstance, FromTalError> {
|
||||||
let tal = Tal::decode_bytes(tal_bytes)?;
|
let tal = Tal::decode_bytes(tal_bytes)?;
|
||||||
let trust_anchor = TrustAnchor::bind_der(tal, ta_der, resolved_ta_uri)?;
|
let trust_anchor = bind_trust_anchor_der(tal, ta_der, resolved_ta_uri, strict_name)?;
|
||||||
let ca_instance = ca_instance_uris_from_ca_certificate(&trust_anchor.ta_certificate.rc_ca)?;
|
let ca_instance = ca_instance_uris_from_ca_certificate(&trust_anchor.ta_certificate.rc_ca)?;
|
||||||
Ok(DiscoveredRootCaInstance {
|
Ok(DiscoveredRootCaInstance {
|
||||||
tal_url: None,
|
tal_url: None,
|
||||||
|
|||||||
@ -28,6 +28,17 @@ const RFC_CRLDP: &[RfcRef] = &[RfcRef("RFC 6487 §4.8.6")];
|
|||||||
const RFC_CRLDP_AND_LOCKED_PACK: &[RfcRef] =
|
const RFC_CRLDP_AND_LOCKED_PACK: &[RfcRef] =
|
||||||
&[RfcRef("RFC 6487 §4.8.6"), RfcRef("RFC 9286 §4.2.1")];
|
&[RfcRef("RFC 6487 §4.8.6"), RfcRef("RFC 9286 §4.2.1")];
|
||||||
|
|
||||||
|
fn decode_resource_certificate_with_policy(
|
||||||
|
der: &[u8],
|
||||||
|
policy: &Policy,
|
||||||
|
) -> Result<ResourceCertificate, crate::data_model::rc::ResourceCertificateDecodeError> {
|
||||||
|
if policy.strict.name {
|
||||||
|
ResourceCertificate::decode_der_with_strict_name(der)
|
||||||
|
} else {
|
||||||
|
ResourceCertificate::decode_der(der)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug)]
|
#[derive(Clone, Debug)]
|
||||||
pub(crate) struct VerifiedIssuerCrl {
|
pub(crate) struct VerifiedIssuerCrl {
|
||||||
crl: crate::data_model::crl::RpkixCrl,
|
crl: crate::data_model::crl::RpkixCrl,
|
||||||
@ -178,11 +189,15 @@ pub fn process_publication_point_for_issuer_with_options<P: PublicationPointData
|
|||||||
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.
|
||||||
let _manifest = ManifestObject::decode_der(manifest_bytes)
|
let _manifest = ManifestObject::decode_der_with_strict_options(
|
||||||
|
manifest_bytes,
|
||||||
|
policy.strict.cms_der,
|
||||||
|
policy.strict.name,
|
||||||
|
)
|
||||||
.expect("publication point snapshot manifest decodes");
|
.expect("publication point snapshot manifest decodes");
|
||||||
|
|
||||||
// Decode issuer CA once; if it fails we cannot validate ROA/ASPA EE certificates.
|
// Decode issuer CA once; if it fails we cannot validate ROA/ASPA EE certificates.
|
||||||
let issuer_ca = match ResourceCertificate::decode_der(issuer_ca_der) {
|
let issuer_ca = match decode_resource_certificate_with_policy(issuer_ca_der, policy) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
stats.publication_point_dropped = true;
|
stats.publication_point_dropped = true;
|
||||||
@ -349,6 +364,8 @@ pub fn process_publication_point_for_issuer_with_options<P: PublicationPointData
|
|||||||
validation_time,
|
validation_time,
|
||||||
timing,
|
timing,
|
||||||
collect_vcir_local_outputs,
|
collect_vcir_local_outputs,
|
||||||
|
policy.strict.cms_der,
|
||||||
|
policy.strict.name,
|
||||||
);
|
);
|
||||||
match result.outcome {
|
match result.outcome {
|
||||||
Ok(mut ok) => {
|
Ok(mut ok) => {
|
||||||
@ -457,6 +474,8 @@ pub fn process_publication_point_for_issuer_with_options<P: PublicationPointData
|
|||||||
validation_time,
|
validation_time,
|
||||||
timing,
|
timing,
|
||||||
collect_vcir_local_outputs,
|
collect_vcir_local_outputs,
|
||||||
|
policy.strict.cms_der,
|
||||||
|
policy.strict.name,
|
||||||
) {
|
) {
|
||||||
Ok((att, local_output)) => {
|
Ok((att, local_output)) => {
|
||||||
stats.aspa_ok += 1;
|
stats.aspa_ok += 1;
|
||||||
@ -739,6 +758,8 @@ pub(crate) struct OwnedRoaTask {
|
|||||||
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,
|
||||||
collect_vcir_local_outputs: bool,
|
collect_vcir_local_outputs: bool,
|
||||||
|
strict_cms_der: bool,
|
||||||
|
strict_name: bool,
|
||||||
pub(crate) submitted_at: Option<Instant>,
|
pub(crate) submitted_at: Option<Instant>,
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -844,6 +865,8 @@ fn validate_owned_roa_task(worker_index: usize, task: OwnedRoaTask) -> RoaTaskRe
|
|||||||
task.validation_time,
|
task.validation_time,
|
||||||
None,
|
None,
|
||||||
task.collect_vcir_local_outputs,
|
task.collect_vcir_local_outputs,
|
||||||
|
task.strict_cms_der,
|
||||||
|
task.strict_name,
|
||||||
)
|
)
|
||||||
.map(|(vrps, local_outputs)| RoaTaskOk {
|
.map(|(vrps, local_outputs)| RoaTaskOk {
|
||||||
vrps,
|
vrps,
|
||||||
@ -881,6 +904,8 @@ pub(crate) struct ParallelObjectsStage {
|
|||||||
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,
|
||||||
collect_vcir_local_outputs: bool,
|
collect_vcir_local_outputs: bool,
|
||||||
|
strict_cms_der: bool,
|
||||||
|
strict_name: bool,
|
||||||
warnings: Vec<Warning>,
|
warnings: Vec<Warning>,
|
||||||
stats: ObjectsStats,
|
stats: ObjectsStats,
|
||||||
audit: Vec<ObjectAuditEntry>,
|
audit: Vec<ObjectAuditEntry>,
|
||||||
@ -907,6 +932,8 @@ impl ParallelObjectsStage {
|
|||||||
issuer_effective_as: self.issuer_effective_as.clone(),
|
issuer_effective_as: self.issuer_effective_as.clone(),
|
||||||
validation_time: self.validation_time,
|
validation_time: self.validation_time,
|
||||||
collect_vcir_local_outputs: self.collect_vcir_local_outputs,
|
collect_vcir_local_outputs: self.collect_vcir_local_outputs,
|
||||||
|
strict_cms_der: self.strict_cms_der,
|
||||||
|
strict_name: self.strict_name,
|
||||||
submitted_at: None,
|
submitted_at: None,
|
||||||
})
|
})
|
||||||
.collect()
|
.collect()
|
||||||
@ -928,6 +955,7 @@ impl ParallelObjectsStage {
|
|||||||
pub(crate) fn prepare_publication_point_for_parallel_roa<P: PublicationPointData>(
|
pub(crate) fn prepare_publication_point_for_parallel_roa<P: PublicationPointData>(
|
||||||
publication_point_id: u64,
|
publication_point_id: u64,
|
||||||
publication_point: &P,
|
publication_point: &P,
|
||||||
|
policy: &Policy,
|
||||||
issuer_ca_der: &[u8],
|
issuer_ca_der: &[u8],
|
||||||
issuer_ca_rsync_uri: Option<&str>,
|
issuer_ca_rsync_uri: Option<&str>,
|
||||||
issuer_effective_ip: Option<&crate::data_model::rc::IpResourceSet>,
|
issuer_effective_ip: Option<&crate::data_model::rc::IpResourceSet>,
|
||||||
@ -950,10 +978,14 @@ pub(crate) fn prepare_publication_point_for_parallel_roa<P: PublicationPointData
|
|||||||
.count();
|
.count();
|
||||||
let mut audit: Vec<ObjectAuditEntry> = Vec::new();
|
let mut audit: Vec<ObjectAuditEntry> = Vec::new();
|
||||||
|
|
||||||
let _manifest = ManifestObject::decode_der(manifest_bytes)
|
let _manifest = ManifestObject::decode_der_with_strict_options(
|
||||||
|
manifest_bytes,
|
||||||
|
policy.strict.cms_der,
|
||||||
|
policy.strict.name,
|
||||||
|
)
|
||||||
.expect("publication point snapshot manifest decodes");
|
.expect("publication point snapshot manifest decodes");
|
||||||
|
|
||||||
let issuer_ca = match ResourceCertificate::decode_der(issuer_ca_der) {
|
let issuer_ca = match decode_resource_certificate_with_policy(issuer_ca_der, policy) {
|
||||||
Ok(v) => v,
|
Ok(v) => v,
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
stats.publication_point_dropped = true;
|
stats.publication_point_dropped = true;
|
||||||
@ -1109,6 +1141,8 @@ pub(crate) fn prepare_publication_point_for_parallel_roa<P: PublicationPointData
|
|||||||
issuer_effective_as: issuer_effective_as.cloned(),
|
issuer_effective_as: issuer_effective_as.cloned(),
|
||||||
validation_time,
|
validation_time,
|
||||||
collect_vcir_local_outputs,
|
collect_vcir_local_outputs,
|
||||||
|
strict_cms_der: policy.strict.cms_der,
|
||||||
|
strict_name: policy.strict.name,
|
||||||
warnings,
|
warnings,
|
||||||
stats,
|
stats,
|
||||||
audit,
|
audit,
|
||||||
@ -1193,6 +1227,8 @@ pub(crate) fn reduce_parallel_roa_stage(
|
|||||||
stage.validation_time,
|
stage.validation_time,
|
||||||
timing,
|
timing,
|
||||||
stage.collect_vcir_local_outputs,
|
stage.collect_vcir_local_outputs,
|
||||||
|
stage.strict_cms_der,
|
||||||
|
stage.strict_name,
|
||||||
) {
|
) {
|
||||||
Ok((att, local_output)) => {
|
Ok((att, local_output)) => {
|
||||||
stats.aspa_ok += 1;
|
stats.aspa_ok += 1;
|
||||||
@ -1254,6 +1290,7 @@ fn process_publication_point_for_issuer_parallel_roa_inner<P: PublicationPointDa
|
|||||||
let stage = match prepare_publication_point_for_parallel_roa(
|
let stage = match prepare_publication_point_for_parallel_roa(
|
||||||
0,
|
0,
|
||||||
publication_point,
|
publication_point,
|
||||||
|
_policy,
|
||||||
issuer_ca_der,
|
issuer_ca_der,
|
||||||
issuer_ca_rsync_uri,
|
issuer_ca_rsync_uri,
|
||||||
issuer_effective_ip,
|
issuer_effective_ip,
|
||||||
@ -1411,6 +1448,8 @@ pub(crate) fn validate_roa_task_serial(
|
|||||||
validation_time: time::OffsetDateTime,
|
validation_time: time::OffsetDateTime,
|
||||||
timing: Option<&TimingHandle>,
|
timing: Option<&TimingHandle>,
|
||||||
collect_vcir_local_outputs: bool,
|
collect_vcir_local_outputs: bool,
|
||||||
|
strict_cms_der: bool,
|
||||||
|
strict_name: bool,
|
||||||
) -> RoaTaskResult {
|
) -> RoaTaskResult {
|
||||||
let sha256_hex = sha256_hex_from_32(&task.file.sha256);
|
let sha256_hex = sha256_hex_from_32(&task.file.sha256);
|
||||||
let outcome = process_roa_with_issuer(
|
let outcome = process_roa_with_issuer(
|
||||||
@ -1427,6 +1466,8 @@ pub(crate) fn validate_roa_task_serial(
|
|||||||
validation_time,
|
validation_time,
|
||||||
timing,
|
timing,
|
||||||
collect_vcir_local_outputs,
|
collect_vcir_local_outputs,
|
||||||
|
strict_cms_der,
|
||||||
|
strict_name,
|
||||||
)
|
)
|
||||||
.map(|(vrps, local_outputs)| RoaTaskOk {
|
.map(|(vrps, local_outputs)| RoaTaskOk {
|
||||||
vrps,
|
vrps,
|
||||||
@ -1459,11 +1500,17 @@ fn process_roa_with_issuer(
|
|||||||
validation_time: time::OffsetDateTime,
|
validation_time: time::OffsetDateTime,
|
||||||
timing: Option<&TimingHandle>,
|
timing: Option<&TimingHandle>,
|
||||||
collect_vcir_local_outputs: bool,
|
collect_vcir_local_outputs: bool,
|
||||||
|
strict_cms_der: bool,
|
||||||
|
strict_name: bool,
|
||||||
) -> Result<(Vec<Vrp>, Vec<VcirLocalOutput>), ObjectValidateError> {
|
) -> Result<(Vec<Vrp>, Vec<VcirLocalOutput>), ObjectValidateError> {
|
||||||
let _decode = timing
|
let _decode = timing
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|t| t.span_phase("objects_roa_decode_and_validate_total"));
|
.map(|t| t.span_phase("objects_roa_decode_and_validate_total"));
|
||||||
let roa = RoaObject::decode_der(file.bytes().map_err(ObjectValidateError::BytesLoad)?)?;
|
let roa = RoaObject::decode_der_with_strict_options(
|
||||||
|
file.bytes().map_err(ObjectValidateError::BytesLoad)?,
|
||||||
|
strict_cms_der,
|
||||||
|
strict_name,
|
||||||
|
)?;
|
||||||
drop(_decode);
|
drop(_decode);
|
||||||
|
|
||||||
let _ee_profile = timing
|
let _ee_profile = timing
|
||||||
@ -1575,11 +1622,17 @@ fn process_roa_with_issuer_parallel_cached(
|
|||||||
validation_time: time::OffsetDateTime,
|
validation_time: time::OffsetDateTime,
|
||||||
timing: Option<&TimingHandle>,
|
timing: Option<&TimingHandle>,
|
||||||
collect_vcir_local_outputs: bool,
|
collect_vcir_local_outputs: bool,
|
||||||
|
strict_cms_der: bool,
|
||||||
|
strict_name: bool,
|
||||||
) -> Result<(Vec<Vrp>, Vec<VcirLocalOutput>), ObjectValidateError> {
|
) -> Result<(Vec<Vrp>, Vec<VcirLocalOutput>), ObjectValidateError> {
|
||||||
let _decode = timing
|
let _decode = timing
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|t| t.span_phase("objects_roa_decode_and_validate_total"));
|
.map(|t| t.span_phase("objects_roa_decode_and_validate_total"));
|
||||||
let roa = RoaObject::decode_der(file.bytes().map_err(ObjectValidateError::BytesLoad)?)?;
|
let roa = RoaObject::decode_der_with_strict_options(
|
||||||
|
file.bytes().map_err(ObjectValidateError::BytesLoad)?,
|
||||||
|
strict_cms_der,
|
||||||
|
strict_name,
|
||||||
|
)?;
|
||||||
drop(_decode);
|
drop(_decode);
|
||||||
|
|
||||||
let _ee_profile = timing
|
let _ee_profile = timing
|
||||||
@ -1697,11 +1750,17 @@ fn process_aspa_with_issuer(
|
|||||||
validation_time: time::OffsetDateTime,
|
validation_time: time::OffsetDateTime,
|
||||||
timing: Option<&TimingHandle>,
|
timing: Option<&TimingHandle>,
|
||||||
collect_vcir_local_outputs: bool,
|
collect_vcir_local_outputs: bool,
|
||||||
|
strict_cms_der: bool,
|
||||||
|
strict_name: bool,
|
||||||
) -> Result<(AspaAttestation, Option<VcirLocalOutput>), ObjectValidateError> {
|
) -> Result<(AspaAttestation, Option<VcirLocalOutput>), ObjectValidateError> {
|
||||||
let _decode = timing
|
let _decode = timing
|
||||||
.as_ref()
|
.as_ref()
|
||||||
.map(|t| t.span_phase("objects_aspa_decode_and_validate_total"));
|
.map(|t| t.span_phase("objects_aspa_decode_and_validate_total"));
|
||||||
let aspa = AspaObject::decode_der(file.bytes().map_err(ObjectValidateError::BytesLoad)?)?;
|
let aspa = AspaObject::decode_der_with_strict_options(
|
||||||
|
file.bytes().map_err(ObjectValidateError::BytesLoad)?,
|
||||||
|
strict_cms_der,
|
||||||
|
strict_name,
|
||||||
|
)?;
|
||||||
drop(_decode);
|
drop(_decode);
|
||||||
|
|
||||||
let _ee_profile = timing
|
let _ee_profile = timing
|
||||||
|
|||||||
@ -24,7 +24,11 @@ use crate::replay::fetch_rsync::PayloadReplayRsyncFetcher;
|
|||||||
use crate::sync::rrdp::Fetcher;
|
use crate::sync::rrdp::Fetcher;
|
||||||
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_with_fetchers,
|
discover_root_ca_instance_from_tal_and_ta_der_with_strict_name,
|
||||||
|
discover_root_ca_instance_from_tal_url,
|
||||||
|
discover_root_ca_instance_from_tal_url_with_strict_name,
|
||||||
|
discover_root_ca_instance_from_tal_with_fetchers,
|
||||||
|
discover_root_ca_instance_from_tal_with_fetchers_strict_name,
|
||||||
};
|
};
|
||||||
use crate::validation::objects::ParallelRoaWorkerPool;
|
use crate::validation::objects::ParallelRoaWorkerPool;
|
||||||
use crate::validation::tree::{
|
use crate::validation::tree::{
|
||||||
@ -86,6 +90,7 @@ pub struct RunTreeFromTalOutput {
|
|||||||
pub struct RunTreeFromTalAuditOutput {
|
pub struct RunTreeFromTalAuditOutput {
|
||||||
pub discovery: DiscoveredRootCaInstance,
|
pub discovery: DiscoveredRootCaInstance,
|
||||||
pub discoveries: Vec<DiscoveredRootCaInstance>,
|
pub discoveries: Vec<DiscoveredRootCaInstance>,
|
||||||
|
pub successful_tal_inputs: Vec<TalInputSpec>,
|
||||||
pub tree: TreeRunOutput,
|
pub tree: TreeRunOutput,
|
||||||
pub publication_points: Vec<PublicationPointAudit>,
|
pub publication_points: Vec<PublicationPointAudit>,
|
||||||
pub downloads: Vec<crate::audit::AuditDownloadEvent>,
|
pub downloads: Vec<crate::audit::AuditDownloadEvent>,
|
||||||
@ -196,19 +201,48 @@ fn root_discovery_from_tal_input(
|
|||||||
tal_input: &TalInputSpec,
|
tal_input: &TalInputSpec,
|
||||||
http_fetcher: &dyn Fetcher,
|
http_fetcher: &dyn Fetcher,
|
||||||
rsync_fetcher: &dyn crate::fetch::rsync::RsyncFetcher,
|
rsync_fetcher: &dyn crate::fetch::rsync::RsyncFetcher,
|
||||||
|
strict_name: bool,
|
||||||
) -> Result<DiscoveredRootCaInstance, FromTalError> {
|
) -> Result<DiscoveredRootCaInstance, FromTalError> {
|
||||||
match &tal_input.source {
|
match &tal_input.source {
|
||||||
TalSource::Url(url) => discover_root_ca_instance_from_tal_url(http_fetcher, url),
|
TalSource::Url(url) => {
|
||||||
|
if strict_name {
|
||||||
|
discover_root_ca_instance_from_tal_url_with_strict_name(http_fetcher, url)
|
||||||
|
} else {
|
||||||
|
discover_root_ca_instance_from_tal_url(http_fetcher, url)
|
||||||
|
}
|
||||||
|
}
|
||||||
TalSource::DerBytes {
|
TalSource::DerBytes {
|
||||||
tal_bytes, ta_der, ..
|
tal_bytes, ta_der, ..
|
||||||
} => discover_root_ca_instance_from_tal_and_ta_der(tal_bytes, ta_der, None),
|
} => {
|
||||||
|
if strict_name {
|
||||||
|
discover_root_ca_instance_from_tal_and_ta_der_with_strict_name(
|
||||||
|
tal_bytes, ta_der, None,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
discover_root_ca_instance_from_tal_and_ta_der(tal_bytes, ta_der, None)
|
||||||
|
}
|
||||||
|
}
|
||||||
TalSource::FilePath(path) => {
|
TalSource::FilePath(path) => {
|
||||||
let tal_bytes = std::fs::read(path).map_err(|e| {
|
let tal_bytes = std::fs::read(path).map_err(|e| {
|
||||||
FromTalError::TalFetch(format!("read TAL file failed: {}: {e}", path.display()))
|
FromTalError::TalFetch(format!("read TAL file failed: {}: {e}", path.display()))
|
||||||
})?;
|
})?;
|
||||||
let tal = crate::data_model::tal::Tal::decode_bytes(&tal_bytes)
|
let tal = crate::data_model::tal::Tal::decode_bytes(&tal_bytes)
|
||||||
.map_err(FromTalError::from)?;
|
.map_err(FromTalError::from)?;
|
||||||
discover_root_ca_instance_from_tal_with_fetchers(http_fetcher, rsync_fetcher, tal, None)
|
if strict_name {
|
||||||
|
discover_root_ca_instance_from_tal_with_fetchers_strict_name(
|
||||||
|
http_fetcher,
|
||||||
|
rsync_fetcher,
|
||||||
|
tal,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
discover_root_ca_instance_from_tal_with_fetchers(
|
||||||
|
http_fetcher,
|
||||||
|
rsync_fetcher,
|
||||||
|
tal,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
TalSource::FilePathWithTa { tal_path, ta_path } => {
|
TalSource::FilePathWithTa { tal_path, ta_path } => {
|
||||||
let tal_bytes = std::fs::read(tal_path).map_err(|e| {
|
let tal_bytes = std::fs::read(tal_path).map_err(|e| {
|
||||||
@ -217,19 +251,76 @@ fn root_discovery_from_tal_input(
|
|||||||
let ta_der = std::fs::read(ta_path).map_err(|e| {
|
let ta_der = std::fs::read(ta_path).map_err(|e| {
|
||||||
FromTalError::TaFetch(format!("read TA file failed: {}: {e}", ta_path.display()))
|
FromTalError::TaFetch(format!("read TA file failed: {}: {e}", ta_path.display()))
|
||||||
})?;
|
})?;
|
||||||
|
if strict_name {
|
||||||
|
discover_root_ca_instance_from_tal_and_ta_der_with_strict_name(
|
||||||
|
&tal_bytes, &ta_der, None,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
discover_root_ca_instance_from_tal_and_ta_der(&tal_bytes, &ta_der, None)
|
discover_root_ca_instance_from_tal_and_ta_der(&tal_bytes, &ta_der, None)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn discover_root_ca_instance_from_tal_url_with_policy(
|
||||||
|
policy: &crate::policy::Policy,
|
||||||
|
http_fetcher: &dyn Fetcher,
|
||||||
|
tal_url: &str,
|
||||||
|
) -> Result<DiscoveredRootCaInstance, FromTalError> {
|
||||||
|
if policy.strict.name {
|
||||||
|
discover_root_ca_instance_from_tal_url_with_strict_name(http_fetcher, tal_url)
|
||||||
|
} else {
|
||||||
|
discover_root_ca_instance_from_tal_url(http_fetcher, tal_url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
fn discover_root_ca_instance_from_tal_and_ta_der_with_policy(
|
||||||
|
policy: &crate::policy::Policy,
|
||||||
|
tal_bytes: &[u8],
|
||||||
|
ta_der: &[u8],
|
||||||
|
resolved_ta_uri: Option<&Url>,
|
||||||
|
) -> Result<DiscoveredRootCaInstance, FromTalError> {
|
||||||
|
if policy.strict.name {
|
||||||
|
discover_root_ca_instance_from_tal_and_ta_der_with_strict_name(
|
||||||
|
tal_bytes,
|
||||||
|
ta_der,
|
||||||
|
resolved_ta_uri,
|
||||||
|
)
|
||||||
|
} else {
|
||||||
|
discover_root_ca_instance_from_tal_and_ta_der(tal_bytes, ta_der, resolved_ta_uri)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn discover_multiple_roots_from_tal_inputs(
|
fn discover_multiple_roots_from_tal_inputs(
|
||||||
tal_inputs: &[TalInputSpec],
|
tal_inputs: &[TalInputSpec],
|
||||||
http_fetcher: &dyn Fetcher,
|
http_fetcher: &dyn Fetcher,
|
||||||
rsync_fetcher: &dyn crate::fetch::rsync::RsyncFetcher,
|
rsync_fetcher: &dyn crate::fetch::rsync::RsyncFetcher,
|
||||||
|
strict_name: bool,
|
||||||
) -> Result<Vec<TalRootDiscovery>, RunTreeFromTalError> {
|
) -> Result<Vec<TalRootDiscovery>, RunTreeFromTalError> {
|
||||||
let mut roots = Vec::with_capacity(tal_inputs.len());
|
let mut roots = Vec::with_capacity(tal_inputs.len());
|
||||||
for tal_input in tal_inputs {
|
for tal_input in tal_inputs {
|
||||||
let discovery = root_discovery_from_tal_input(tal_input, http_fetcher, rsync_fetcher)?;
|
let discovery = match root_discovery_from_tal_input(
|
||||||
|
tal_input,
|
||||||
|
http_fetcher,
|
||||||
|
rsync_fetcher,
|
||||||
|
strict_name,
|
||||||
|
) {
|
||||||
|
Ok(discovery) => discovery,
|
||||||
|
Err(error)
|
||||||
|
if should_isolate_multi_tal_strict_name_failure(
|
||||||
|
tal_inputs.len(),
|
||||||
|
strict_name,
|
||||||
|
&error,
|
||||||
|
) =>
|
||||||
|
{
|
||||||
|
eprintln!(
|
||||||
|
"warning: skipping TAL '{}' because strict name validation failed during trust anchor discovery: {error}",
|
||||||
|
tal_input.tal_id
|
||||||
|
);
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
Err(error) => return Err(error.into()),
|
||||||
|
};
|
||||||
let root_handle = root_handle_from_trust_anchor(
|
let root_handle = root_handle_from_trust_anchor(
|
||||||
&discovery.trust_anchor,
|
&discovery.trust_anchor,
|
||||||
tal_input.tal_id.clone(),
|
tal_input.tal_id.clone(),
|
||||||
@ -242,9 +333,25 @@ fn discover_multiple_roots_from_tal_inputs(
|
|||||||
root_handle,
|
root_handle,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
if roots.is_empty() {
|
||||||
|
return Err(RunTreeFromTalError::Replay(
|
||||||
|
"multi-TAL root discovery returned no usable roots after strict name filtering"
|
||||||
|
.to_string(),
|
||||||
|
));
|
||||||
|
}
|
||||||
Ok(roots)
|
Ok(roots)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn should_isolate_multi_tal_strict_name_failure(
|
||||||
|
tal_input_count: usize,
|
||||||
|
strict_name: bool,
|
||||||
|
error: &FromTalError,
|
||||||
|
) -> bool {
|
||||||
|
strict_name
|
||||||
|
&& tal_input_count > 1
|
||||||
|
&& error.to_string().contains("Name strict validation failed")
|
||||||
|
}
|
||||||
|
|
||||||
#[derive(Debug, thiserror::Error)]
|
#[derive(Debug, thiserror::Error)]
|
||||||
pub enum RunTreeFromTalError {
|
pub enum RunTreeFromTalError {
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
@ -288,7 +395,8 @@ pub fn run_tree_from_tal_url_serial(
|
|||||||
validation_time: time::OffsetDateTime,
|
validation_time: time::OffsetDateTime,
|
||||||
config: &TreeRunConfig,
|
config: &TreeRunConfig,
|
||||||
) -> Result<RunTreeFromTalOutput, RunTreeFromTalError> {
|
) -> Result<RunTreeFromTalOutput, RunTreeFromTalError> {
|
||||||
let discovery = discover_root_ca_instance_from_tal_url(http_fetcher, tal_url)?;
|
let discovery =
|
||||||
|
discover_root_ca_instance_from_tal_url_with_policy(policy, http_fetcher, tal_url)?;
|
||||||
|
|
||||||
let runner = make_live_runner(
|
let runner = make_live_runner(
|
||||||
store,
|
store,
|
||||||
@ -325,7 +433,8 @@ pub fn run_tree_from_tal_url_serial_audit(
|
|||||||
validation_time: time::OffsetDateTime,
|
validation_time: time::OffsetDateTime,
|
||||||
config: &TreeRunConfig,
|
config: &TreeRunConfig,
|
||||||
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
|
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
|
||||||
let discovery = discover_root_ca_instance_from_tal_url(http_fetcher, tal_url)?;
|
let discovery =
|
||||||
|
discover_root_ca_instance_from_tal_url_with_policy(policy, http_fetcher, tal_url)?;
|
||||||
|
|
||||||
let download_log = DownloadLogHandle::new();
|
let download_log = DownloadLogHandle::new();
|
||||||
let runner = make_live_runner(
|
let runner = make_live_runner(
|
||||||
@ -359,6 +468,7 @@ pub fn run_tree_from_tal_url_serial_audit(
|
|||||||
Ok(RunTreeFromTalAuditOutput {
|
Ok(RunTreeFromTalAuditOutput {
|
||||||
discovery: discovery.clone(),
|
discovery: discovery.clone(),
|
||||||
discoveries: vec![discovery],
|
discoveries: vec![discovery],
|
||||||
|
successful_tal_inputs: Vec::new(),
|
||||||
tree,
|
tree,
|
||||||
publication_points,
|
publication_points,
|
||||||
downloads,
|
downloads,
|
||||||
@ -379,7 +489,8 @@ pub fn run_tree_from_tal_url_serial_audit_with_timing(
|
|||||||
timing: &TimingHandle,
|
timing: &TimingHandle,
|
||||||
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
|
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
|
||||||
let _tal = timing.span_phase("tal_bootstrap");
|
let _tal = timing.span_phase("tal_bootstrap");
|
||||||
let discovery = discover_root_ca_instance_from_tal_url(http_fetcher, tal_url)?;
|
let discovery =
|
||||||
|
discover_root_ca_instance_from_tal_url_with_policy(policy, http_fetcher, tal_url)?;
|
||||||
drop(_tal);
|
drop(_tal);
|
||||||
|
|
||||||
let download_log = DownloadLogHandle::new();
|
let download_log = DownloadLogHandle::new();
|
||||||
@ -415,6 +526,7 @@ pub fn run_tree_from_tal_url_serial_audit_with_timing(
|
|||||||
Ok(RunTreeFromTalAuditOutput {
|
Ok(RunTreeFromTalAuditOutput {
|
||||||
discovery: discovery.clone(),
|
discovery: discovery.clone(),
|
||||||
discoveries: vec![discovery],
|
discoveries: vec![discovery],
|
||||||
|
successful_tal_inputs: Vec::new(),
|
||||||
tree,
|
tree,
|
||||||
publication_points,
|
publication_points,
|
||||||
downloads,
|
downloads,
|
||||||
@ -489,6 +601,7 @@ where
|
|||||||
Ok(RunTreeFromTalAuditOutput {
|
Ok(RunTreeFromTalAuditOutput {
|
||||||
discovery: discovery.clone(),
|
discovery: discovery.clone(),
|
||||||
discoveries: vec![discovery],
|
discoveries: vec![discovery],
|
||||||
|
successful_tal_inputs: Vec::new(),
|
||||||
tree,
|
tree,
|
||||||
publication_points,
|
publication_points,
|
||||||
downloads,
|
downloads,
|
||||||
@ -523,7 +636,12 @@ where
|
|||||||
"multi-TAL run requires at least one TAL input".to_string(),
|
"multi-TAL run requires at least one TAL input".to_string(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let roots = discover_multiple_roots_from_tal_inputs(&tal_inputs, http_fetcher, rsync_fetcher)?;
|
let roots = discover_multiple_roots_from_tal_inputs(
|
||||||
|
&tal_inputs,
|
||||||
|
http_fetcher,
|
||||||
|
rsync_fetcher,
|
||||||
|
policy.strict.name,
|
||||||
|
)?;
|
||||||
let primary = roots.first().cloned().ok_or_else(|| {
|
let primary = roots.first().cloned().ok_or_else(|| {
|
||||||
RunTreeFromTalError::Replay("multi-TAL root discovery returned no roots".to_string())
|
RunTreeFromTalError::Replay("multi-TAL root discovery returned no roots".to_string())
|
||||||
})?;
|
})?;
|
||||||
@ -531,9 +649,13 @@ where
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|item| item.discovery.clone())
|
.map(|item| item.discovery.clone())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
let successful_tal_inputs = roots
|
||||||
|
.iter()
|
||||||
|
.map(|item| item.tal_input.clone())
|
||||||
|
.collect::<Vec<_>>();
|
||||||
let root_handles = roots
|
let root_handles = roots
|
||||||
.into_iter()
|
.iter()
|
||||||
.map(|item| item.root_handle)
|
.map(|item| item.root_handle.clone())
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
|
|
||||||
let download_log = DownloadLogHandle::new();
|
let download_log = DownloadLogHandle::new();
|
||||||
@ -545,7 +667,7 @@ where
|
|||||||
parallel_config,
|
parallel_config,
|
||||||
None,
|
None,
|
||||||
Some(download_log.clone()),
|
Some(download_log.clone()),
|
||||||
tal_inputs,
|
successful_tal_inputs.clone(),
|
||||||
)?;
|
)?;
|
||||||
let current_repo_index_for_output = current_repo_index.clone();
|
let current_repo_index_for_output = current_repo_index.clone();
|
||||||
let runner = make_live_runner(
|
let runner = make_live_runner(
|
||||||
@ -583,6 +705,7 @@ where
|
|||||||
Ok(RunTreeFromTalAuditOutput {
|
Ok(RunTreeFromTalAuditOutput {
|
||||||
discovery: primary.discovery.clone(),
|
discovery: primary.discovery.clone(),
|
||||||
discoveries,
|
discoveries,
|
||||||
|
successful_tal_inputs,
|
||||||
tree,
|
tree,
|
||||||
publication_points,
|
publication_points,
|
||||||
downloads,
|
downloads,
|
||||||
@ -610,7 +733,8 @@ where
|
|||||||
H: Fetcher + Clone + 'static,
|
H: Fetcher + Clone + 'static,
|
||||||
R: crate::fetch::rsync::RsyncFetcher + Clone + 'static,
|
R: crate::fetch::rsync::RsyncFetcher + Clone + 'static,
|
||||||
{
|
{
|
||||||
let discovery = discover_root_ca_instance_from_tal_url(http_fetcher, tal_url)?;
|
let discovery =
|
||||||
|
discover_root_ca_instance_from_tal_url_with_policy(policy, http_fetcher, tal_url)?;
|
||||||
run_single_root_parallel_audit_inner(
|
run_single_root_parallel_audit_inner(
|
||||||
store,
|
store,
|
||||||
policy,
|
policy,
|
||||||
@ -643,8 +767,12 @@ where
|
|||||||
H: Fetcher + Clone + 'static,
|
H: Fetcher + Clone + 'static,
|
||||||
R: crate::fetch::rsync::RsyncFetcher + Clone + 'static,
|
R: crate::fetch::rsync::RsyncFetcher + Clone + 'static,
|
||||||
{
|
{
|
||||||
let discovery =
|
let discovery = discover_root_ca_instance_from_tal_and_ta_der_with_policy(
|
||||||
discover_root_ca_instance_from_tal_and_ta_der(tal_bytes, ta_der, resolved_ta_uri)?;
|
policy,
|
||||||
|
tal_bytes,
|
||||||
|
ta_der,
|
||||||
|
resolved_ta_uri,
|
||||||
|
)?;
|
||||||
let derived_tal_id = derive_tal_id(&discovery);
|
let derived_tal_id = derive_tal_id(&discovery);
|
||||||
let tal_inputs = vec![TalInputSpec {
|
let tal_inputs = vec![TalInputSpec {
|
||||||
tal_id: derived_tal_id.clone(),
|
tal_id: derived_tal_id.clone(),
|
||||||
@ -718,7 +846,8 @@ where
|
|||||||
H: Fetcher + Clone + 'static,
|
H: Fetcher + Clone + 'static,
|
||||||
R: crate::fetch::rsync::RsyncFetcher + Clone + 'static,
|
R: crate::fetch::rsync::RsyncFetcher + Clone + 'static,
|
||||||
{
|
{
|
||||||
let discovery = discover_root_ca_instance_from_tal_url(http_fetcher, tal_url)?;
|
let discovery =
|
||||||
|
discover_root_ca_instance_from_tal_url_with_policy(policy, http_fetcher, tal_url)?;
|
||||||
run_single_root_parallel_audit_inner(
|
run_single_root_parallel_audit_inner(
|
||||||
store,
|
store,
|
||||||
policy,
|
policy,
|
||||||
@ -752,8 +881,12 @@ where
|
|||||||
H: Fetcher + Clone + 'static,
|
H: Fetcher + Clone + 'static,
|
||||||
R: crate::fetch::rsync::RsyncFetcher + Clone + 'static,
|
R: crate::fetch::rsync::RsyncFetcher + Clone + 'static,
|
||||||
{
|
{
|
||||||
let discovery =
|
let discovery = discover_root_ca_instance_from_tal_and_ta_der_with_policy(
|
||||||
discover_root_ca_instance_from_tal_and_ta_der(tal_bytes, ta_der, resolved_ta_uri)?;
|
policy,
|
||||||
|
tal_bytes,
|
||||||
|
ta_der,
|
||||||
|
resolved_ta_uri,
|
||||||
|
)?;
|
||||||
let derived_tal_id = derive_tal_id(&discovery);
|
let derived_tal_id = derive_tal_id(&discovery);
|
||||||
let tal_inputs = vec![TalInputSpec {
|
let tal_inputs = vec![TalInputSpec {
|
||||||
tal_id: derived_tal_id.clone(),
|
tal_id: derived_tal_id.clone(),
|
||||||
@ -823,8 +956,12 @@ pub fn run_tree_from_tal_and_ta_der_serial(
|
|||||||
validation_time: time::OffsetDateTime,
|
validation_time: time::OffsetDateTime,
|
||||||
config: &TreeRunConfig,
|
config: &TreeRunConfig,
|
||||||
) -> Result<RunTreeFromTalOutput, RunTreeFromTalError> {
|
) -> Result<RunTreeFromTalOutput, RunTreeFromTalError> {
|
||||||
let discovery =
|
let discovery = discover_root_ca_instance_from_tal_and_ta_der_with_policy(
|
||||||
discover_root_ca_instance_from_tal_and_ta_der(tal_bytes, ta_der, resolved_ta_uri)?;
|
policy,
|
||||||
|
tal_bytes,
|
||||||
|
ta_der,
|
||||||
|
resolved_ta_uri,
|
||||||
|
)?;
|
||||||
|
|
||||||
let runner = Rpkiv1PublicationPointRunner {
|
let runner = Rpkiv1PublicationPointRunner {
|
||||||
store,
|
store,
|
||||||
@ -916,6 +1053,7 @@ pub fn run_tree_from_tal_bytes_serial_audit(
|
|||||||
Ok(RunTreeFromTalAuditOutput {
|
Ok(RunTreeFromTalAuditOutput {
|
||||||
discovery: discovery.clone(),
|
discovery: discovery.clone(),
|
||||||
discoveries: vec![discovery],
|
discoveries: vec![discovery],
|
||||||
|
successful_tal_inputs: Vec::new(),
|
||||||
tree,
|
tree,
|
||||||
publication_points,
|
publication_points,
|
||||||
downloads,
|
downloads,
|
||||||
@ -987,6 +1125,7 @@ pub fn run_tree_from_tal_bytes_serial_audit_with_timing(
|
|||||||
Ok(RunTreeFromTalAuditOutput {
|
Ok(RunTreeFromTalAuditOutput {
|
||||||
discovery: discovery.clone(),
|
discovery: discovery.clone(),
|
||||||
discoveries: vec![discovery],
|
discoveries: vec![discovery],
|
||||||
|
successful_tal_inputs: Vec::new(),
|
||||||
tree,
|
tree,
|
||||||
publication_points,
|
publication_points,
|
||||||
downloads,
|
downloads,
|
||||||
@ -1007,8 +1146,12 @@ pub fn run_tree_from_tal_and_ta_der_serial_audit(
|
|||||||
validation_time: time::OffsetDateTime,
|
validation_time: time::OffsetDateTime,
|
||||||
config: &TreeRunConfig,
|
config: &TreeRunConfig,
|
||||||
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
|
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
|
||||||
let discovery =
|
let discovery = discover_root_ca_instance_from_tal_and_ta_der_with_policy(
|
||||||
discover_root_ca_instance_from_tal_and_ta_der(tal_bytes, ta_der, resolved_ta_uri)?;
|
policy,
|
||||||
|
tal_bytes,
|
||||||
|
ta_der,
|
||||||
|
resolved_ta_uri,
|
||||||
|
)?;
|
||||||
|
|
||||||
let download_log = DownloadLogHandle::new();
|
let download_log = DownloadLogHandle::new();
|
||||||
let runner = Rpkiv1PublicationPointRunner {
|
let runner = Rpkiv1PublicationPointRunner {
|
||||||
@ -1049,6 +1192,7 @@ pub fn run_tree_from_tal_and_ta_der_serial_audit(
|
|||||||
Ok(RunTreeFromTalAuditOutput {
|
Ok(RunTreeFromTalAuditOutput {
|
||||||
discovery: discovery.clone(),
|
discovery: discovery.clone(),
|
||||||
discoveries: vec![discovery],
|
discoveries: vec![discovery],
|
||||||
|
successful_tal_inputs: Vec::new(),
|
||||||
tree,
|
tree,
|
||||||
publication_points,
|
publication_points,
|
||||||
downloads,
|
downloads,
|
||||||
@ -1071,8 +1215,12 @@ pub fn run_tree_from_tal_and_ta_der_serial_audit_with_timing(
|
|||||||
timing: &TimingHandle,
|
timing: &TimingHandle,
|
||||||
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
|
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
|
||||||
let _tal = timing.span_phase("tal_bootstrap");
|
let _tal = timing.span_phase("tal_bootstrap");
|
||||||
let discovery =
|
let discovery = discover_root_ca_instance_from_tal_and_ta_der_with_policy(
|
||||||
discover_root_ca_instance_from_tal_and_ta_der(tal_bytes, ta_der, resolved_ta_uri)?;
|
policy,
|
||||||
|
tal_bytes,
|
||||||
|
ta_der,
|
||||||
|
resolved_ta_uri,
|
||||||
|
)?;
|
||||||
drop(_tal);
|
drop(_tal);
|
||||||
|
|
||||||
let download_log = DownloadLogHandle::new();
|
let download_log = DownloadLogHandle::new();
|
||||||
@ -1115,6 +1263,7 @@ pub fn run_tree_from_tal_and_ta_der_serial_audit_with_timing(
|
|||||||
Ok(RunTreeFromTalAuditOutput {
|
Ok(RunTreeFromTalAuditOutput {
|
||||||
discovery: discovery.clone(),
|
discovery: discovery.clone(),
|
||||||
discoveries: vec![discovery],
|
discoveries: vec![discovery],
|
||||||
|
successful_tal_inputs: Vec::new(),
|
||||||
tree,
|
tree,
|
||||||
publication_points,
|
publication_points,
|
||||||
downloads,
|
downloads,
|
||||||
@ -1135,8 +1284,12 @@ pub fn run_tree_from_tal_and_ta_der_payload_replay_serial(
|
|||||||
validation_time: time::OffsetDateTime,
|
validation_time: time::OffsetDateTime,
|
||||||
config: &TreeRunConfig,
|
config: &TreeRunConfig,
|
||||||
) -> Result<RunTreeFromTalOutput, RunTreeFromTalError> {
|
) -> Result<RunTreeFromTalOutput, RunTreeFromTalError> {
|
||||||
let discovery =
|
let discovery = discover_root_ca_instance_from_tal_and_ta_der_with_policy(
|
||||||
discover_root_ca_instance_from_tal_and_ta_der(tal_bytes, ta_der, resolved_ta_uri)?;
|
policy,
|
||||||
|
tal_bytes,
|
||||||
|
ta_der,
|
||||||
|
resolved_ta_uri,
|
||||||
|
)?;
|
||||||
let replay_index = Arc::new(
|
let replay_index = Arc::new(
|
||||||
ReplayArchiveIndex::load_allow_missing_rsync_modules(
|
ReplayArchiveIndex::load_allow_missing_rsync_modules(
|
||||||
payload_archive_root,
|
payload_archive_root,
|
||||||
@ -1192,8 +1345,12 @@ pub fn run_tree_from_tal_and_ta_der_payload_replay_serial_audit(
|
|||||||
validation_time: time::OffsetDateTime,
|
validation_time: time::OffsetDateTime,
|
||||||
config: &TreeRunConfig,
|
config: &TreeRunConfig,
|
||||||
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
|
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
|
||||||
let discovery =
|
let discovery = discover_root_ca_instance_from_tal_and_ta_der_with_policy(
|
||||||
discover_root_ca_instance_from_tal_and_ta_der(tal_bytes, ta_der, resolved_ta_uri)?;
|
policy,
|
||||||
|
tal_bytes,
|
||||||
|
ta_der,
|
||||||
|
resolved_ta_uri,
|
||||||
|
)?;
|
||||||
let replay_index = Arc::new(
|
let replay_index = Arc::new(
|
||||||
ReplayArchiveIndex::load_allow_missing_rsync_modules(
|
ReplayArchiveIndex::load_allow_missing_rsync_modules(
|
||||||
payload_archive_root,
|
payload_archive_root,
|
||||||
@ -1244,6 +1401,7 @@ pub fn run_tree_from_tal_and_ta_der_payload_replay_serial_audit(
|
|||||||
Ok(RunTreeFromTalAuditOutput {
|
Ok(RunTreeFromTalAuditOutput {
|
||||||
discovery: discovery.clone(),
|
discovery: discovery.clone(),
|
||||||
discoveries: vec![discovery],
|
discoveries: vec![discovery],
|
||||||
|
successful_tal_inputs: Vec::new(),
|
||||||
tree,
|
tree,
|
||||||
publication_points,
|
publication_points,
|
||||||
downloads,
|
downloads,
|
||||||
@ -1266,8 +1424,12 @@ pub fn run_tree_from_tal_and_ta_der_payload_replay_serial_audit_with_timing(
|
|||||||
timing: &TimingHandle,
|
timing: &TimingHandle,
|
||||||
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
|
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
|
||||||
let _tal = timing.span_phase("tal_bootstrap");
|
let _tal = timing.span_phase("tal_bootstrap");
|
||||||
let discovery =
|
let discovery = discover_root_ca_instance_from_tal_and_ta_der_with_policy(
|
||||||
discover_root_ca_instance_from_tal_and_ta_der(tal_bytes, ta_der, resolved_ta_uri)?;
|
policy,
|
||||||
|
tal_bytes,
|
||||||
|
ta_der,
|
||||||
|
resolved_ta_uri,
|
||||||
|
)?;
|
||||||
drop(_tal);
|
drop(_tal);
|
||||||
let replay_index = Arc::new(
|
let replay_index = Arc::new(
|
||||||
ReplayArchiveIndex::load_allow_missing_rsync_modules(
|
ReplayArchiveIndex::load_allow_missing_rsync_modules(
|
||||||
@ -1320,6 +1482,7 @@ pub fn run_tree_from_tal_and_ta_der_payload_replay_serial_audit_with_timing(
|
|||||||
Ok(RunTreeFromTalAuditOutput {
|
Ok(RunTreeFromTalAuditOutput {
|
||||||
discovery: discovery.clone(),
|
discovery: discovery.clone(),
|
||||||
discoveries: vec![discovery],
|
discoveries: vec![discovery],
|
||||||
|
successful_tal_inputs: Vec::new(),
|
||||||
tree,
|
tree,
|
||||||
publication_points,
|
publication_points,
|
||||||
downloads,
|
downloads,
|
||||||
@ -1536,6 +1699,7 @@ fn run_payload_delta_replay_audit_inner(
|
|||||||
Ok(RunTreeFromTalAuditOutput {
|
Ok(RunTreeFromTalAuditOutput {
|
||||||
discovery: discovery.clone(),
|
discovery: discovery.clone(),
|
||||||
discoveries: vec![discovery],
|
discoveries: vec![discovery],
|
||||||
|
successful_tal_inputs: Vec::new(),
|
||||||
tree,
|
tree,
|
||||||
publication_points,
|
publication_points,
|
||||||
downloads,
|
downloads,
|
||||||
@ -1559,8 +1723,12 @@ pub fn run_tree_from_tal_and_ta_der_payload_delta_replay_serial_audit(
|
|||||||
validation_time: time::OffsetDateTime,
|
validation_time: time::OffsetDateTime,
|
||||||
config: &TreeRunConfig,
|
config: &TreeRunConfig,
|
||||||
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
|
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
|
||||||
let discovery =
|
let discovery = discover_root_ca_instance_from_tal_and_ta_der_with_policy(
|
||||||
discover_root_ca_instance_from_tal_and_ta_der(tal_bytes, ta_der, resolved_ta_uri)?;
|
policy,
|
||||||
|
tal_bytes,
|
||||||
|
ta_der,
|
||||||
|
resolved_ta_uri,
|
||||||
|
)?;
|
||||||
run_payload_delta_replay_audit_inner(
|
run_payload_delta_replay_audit_inner(
|
||||||
store,
|
store,
|
||||||
policy,
|
policy,
|
||||||
@ -1592,8 +1760,12 @@ pub fn run_tree_from_tal_and_ta_der_payload_delta_replay_serial_audit_with_timin
|
|||||||
timing: &TimingHandle,
|
timing: &TimingHandle,
|
||||||
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
|
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
|
||||||
let _tal = timing.span_phase("tal_bootstrap");
|
let _tal = timing.span_phase("tal_bootstrap");
|
||||||
let discovery =
|
let discovery = discover_root_ca_instance_from_tal_and_ta_der_with_policy(
|
||||||
discover_root_ca_instance_from_tal_and_ta_der(tal_bytes, ta_der, resolved_ta_uri)?;
|
policy,
|
||||||
|
tal_bytes,
|
||||||
|
ta_der,
|
||||||
|
resolved_ta_uri,
|
||||||
|
)?;
|
||||||
drop(_tal);
|
drop(_tal);
|
||||||
run_payload_delta_replay_audit_inner(
|
run_payload_delta_replay_audit_inner(
|
||||||
store,
|
store,
|
||||||
@ -1681,6 +1853,7 @@ fn run_payload_delta_replay_step_audit_inner(
|
|||||||
Ok(RunTreeFromTalAuditOutput {
|
Ok(RunTreeFromTalAuditOutput {
|
||||||
discovery: discovery.clone(),
|
discovery: discovery.clone(),
|
||||||
discoveries: vec![discovery],
|
discoveries: vec![discovery],
|
||||||
|
successful_tal_inputs: Vec::new(),
|
||||||
tree,
|
tree,
|
||||||
publication_points,
|
publication_points,
|
||||||
downloads,
|
downloads,
|
||||||
@ -1702,8 +1875,12 @@ pub fn run_tree_from_tal_and_ta_der_payload_delta_replay_step_serial_audit(
|
|||||||
validation_time: time::OffsetDateTime,
|
validation_time: time::OffsetDateTime,
|
||||||
config: &TreeRunConfig,
|
config: &TreeRunConfig,
|
||||||
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
|
) -> Result<RunTreeFromTalAuditOutput, RunTreeFromTalError> {
|
||||||
let discovery =
|
let discovery = discover_root_ca_instance_from_tal_and_ta_der_with_policy(
|
||||||
discover_root_ca_instance_from_tal_and_ta_der(tal_bytes, ta_der, resolved_ta_uri)?;
|
policy,
|
||||||
|
tal_bytes,
|
||||||
|
ta_der,
|
||||||
|
resolved_ta_uri,
|
||||||
|
)?;
|
||||||
run_payload_delta_replay_step_audit_inner(
|
run_payload_delta_replay_step_audit_inner(
|
||||||
store,
|
store,
|
||||||
policy,
|
policy,
|
||||||
@ -1790,6 +1967,7 @@ mod multi_tal_tests {
|
|||||||
&tal_inputs,
|
&tal_inputs,
|
||||||
&RejectingHttpFetcher,
|
&RejectingHttpFetcher,
|
||||||
&RejectingRsyncFetcher,
|
&RejectingRsyncFetcher,
|
||||||
|
false,
|
||||||
)
|
)
|
||||||
.expect("discover roots");
|
.expect("discover roots");
|
||||||
|
|
||||||
@ -1803,6 +1981,54 @@ mod multi_tal_tests {
|
|||||||
roots[1].root_handle.manifest_rsync_uri
|
roots[1].root_handle.manifest_rsync_uri
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn discover_multiple_roots_isolates_strict_name_failure() {
|
||||||
|
let apnic_tal =
|
||||||
|
std::fs::read("tests/fixtures/tal/apnic-rfc7730-https.tal").expect("read apnic tal");
|
||||||
|
let apnic_ta = std::fs::read("tests/fixtures/ta/apnic-ta.cer").expect("read apnic ta");
|
||||||
|
let arin_tal = std::fs::read("tests/fixtures/tal/arin.tal").expect("read arin tal");
|
||||||
|
let arin_ta = std::fs::read("tests/fixtures/ta/arin-ta.cer").expect("read arin ta");
|
||||||
|
|
||||||
|
let tal_inputs = vec![
|
||||||
|
TalInputSpec::from_ta_der("https://example.test/apnic.tal", apnic_tal, apnic_ta),
|
||||||
|
TalInputSpec::from_ta_der("https://example.test/arin.tal", arin_tal, arin_ta),
|
||||||
|
];
|
||||||
|
|
||||||
|
let roots = discover_multiple_roots_from_tal_inputs(
|
||||||
|
&tal_inputs,
|
||||||
|
&RejectingHttpFetcher,
|
||||||
|
&RejectingRsyncFetcher,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.expect("strict discovery should keep usable roots");
|
||||||
|
|
||||||
|
assert_eq!(roots.len(), 1);
|
||||||
|
assert_eq!(roots[0].tal_input.tal_id, "arin");
|
||||||
|
assert_eq!(roots[0].root_handle.tal_id, "arin");
|
||||||
|
}
|
||||||
|
|
||||||
|
#[test]
|
||||||
|
fn discover_single_root_keeps_strict_name_failure_fatal() {
|
||||||
|
let apnic_tal =
|
||||||
|
std::fs::read("tests/fixtures/tal/apnic-rfc7730-https.tal").expect("read apnic tal");
|
||||||
|
let apnic_ta = std::fs::read("tests/fixtures/ta/apnic-ta.cer").expect("read apnic ta");
|
||||||
|
let tal_inputs = vec![TalInputSpec::from_ta_der(
|
||||||
|
"https://example.test/apnic.tal",
|
||||||
|
apnic_tal,
|
||||||
|
apnic_ta,
|
||||||
|
)];
|
||||||
|
|
||||||
|
let error = discover_multiple_roots_from_tal_inputs(
|
||||||
|
&tal_inputs,
|
||||||
|
&RejectingHttpFetcher,
|
||||||
|
&RejectingRsyncFetcher,
|
||||||
|
true,
|
||||||
|
)
|
||||||
|
.expect_err("single-TAL strict failure should remain fatal");
|
||||||
|
|
||||||
|
assert!(error.to_string().contains("Name strict validation failed"));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
|
|||||||
@ -858,6 +858,7 @@ fn stage_ready_publication_point(
|
|||||||
match prepare_publication_point_for_parallel_roa(
|
match prepare_publication_point_for_parallel_roa(
|
||||||
ready.node.id,
|
ready.node.id,
|
||||||
&fresh_stage.fresh_point,
|
&fresh_stage.fresh_point,
|
||||||
|
runner.policy,
|
||||||
&ready.node.handle.ca_certificate_der,
|
&ready.node.handle.ca_certificate_der,
|
||||||
ready.node.handle.ca_certificate_rsync_uri.as_deref(),
|
ready.node.handle.ca_certificate_rsync_uri.as_deref(),
|
||||||
ready.node.handle.effective_ip_resources.as_ref(),
|
ready.node.handle.effective_ip_resources.as_ref(),
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user