20260701 add validation-update-03 VRS mode
This commit is contained in:
parent
a501cfb709
commit
a5b194ce35
@ -63,6 +63,10 @@ PERIODIC_SNAPSHOT_RESET=0
|
||||
PERIODIC_SNAPSHOT_MAX_DELTAS=100
|
||||
DB_STATS_EXACT_EVERY=0
|
||||
|
||||
# Resource certificate validation mode passed to the rpki child process.
|
||||
# Options: validation-update-03 | rfc6487
|
||||
RESOURCE_VALIDATION_MODE=validation-update-03
|
||||
|
||||
# Validation and performance options aligned with current optimized soak defaults.
|
||||
ENABLE_CHILD_CERTIFICATE_VALIDATION_CACHE=1
|
||||
RPKI_ANALYZE=1
|
||||
|
||||
@ -33,6 +33,7 @@ Defaults:
|
||||
- `MAX_RUNS=-1`
|
||||
- `INTERVAL_SECS=600`
|
||||
- `TAL_INPUT_MODE=file-live-ta`
|
||||
- `RESOURCE_VALIDATION_MODE=validation-update-03`
|
||||
- `LIVE_TA_REFRESH_BEFORE_SNAPSHOT=1`
|
||||
- `PERIODIC_SNAPSHOT_RESET=0`
|
||||
- `PERIODIC_SNAPSHOT_MAX_DELTAS=100`
|
||||
@ -57,6 +58,22 @@ Container/image mapping:
|
||||
|
||||
The first snapshot refreshes live TA certificates before starting the RP process.
|
||||
|
||||
## Resource Validation Mode
|
||||
|
||||
New knob:
|
||||
|
||||
```bash
|
||||
RESOURCE_VALIDATION_MODE=validation-update-03
|
||||
```
|
||||
|
||||
Supported values:
|
||||
|
||||
- `validation-update-03`: default behavior, using the current validation-update draft semantics;
|
||||
- `rfc6487`: switch back to the original RFC 6487 resource containment behavior.
|
||||
|
||||
The installer / soak runner passes `--resource-validation-mode` to every `rpki` child start.
|
||||
When an upgraded deployment reuses an older `.env` that does not contain this key, the installer appends the new default `validation-update-03` automatically.
|
||||
|
||||
## Architecture Guardrails
|
||||
|
||||
The key scripts read:
|
||||
|
||||
@ -33,6 +33,7 @@ vim .env
|
||||
- `MAX_RUNS=-1`
|
||||
- `INTERVAL_SECS=600`
|
||||
- `TAL_INPUT_MODE=file-live-ta`
|
||||
- `RESOURCE_VALIDATION_MODE=validation-update-03`
|
||||
- `LIVE_TA_REFRESH_BEFORE_SNAPSHOT=1`
|
||||
- `PERIODIC_SNAPSHOT_RESET=0`
|
||||
- `PERIODIC_SNAPSHOT_MAX_DELTAS=100`
|
||||
@ -57,6 +58,22 @@ vim .env
|
||||
|
||||
第一轮 snapshot 会先拉取 live TA,避免 clean state 使用旧 fixture TA。
|
||||
|
||||
## 资源验证模式
|
||||
|
||||
新增配置:
|
||||
|
||||
```bash
|
||||
RESOURCE_VALIDATION_MODE=validation-update-03
|
||||
```
|
||||
|
||||
可选值:
|
||||
|
||||
- `validation-update-03`:默认,按当前 validation update draft 语义运行;
|
||||
- `rfc6487`:切换为 RFC 6487 原始资源包含判定语义。
|
||||
|
||||
installer / soak runner 每次启动 `rpki` 子进程时都会显式传入 `--resource-validation-mode`。
|
||||
如果升级时复用旧 `.env` 且缺少该变量,installer 会自动补入默认值 `validation-update-03`,不会破坏旧 `.env` 复用流程。
|
||||
|
||||
## 架构检查
|
||||
|
||||
关键脚本会读取:
|
||||
|
||||
@ -203,12 +203,14 @@ load_env() {
|
||||
GRAFANA_IMAGE="${GRAFANA_IMAGE:-grafana/grafana:11.3.1}"
|
||||
METRICS_INSTANCE="${METRICS_INSTANCE:-${PACKAGE_ARCH}-installer}"
|
||||
ALLOW_CROSS_ARCH="${ALLOW_CROSS_ARCH:-0}"
|
||||
RESOURCE_VALIDATION_MODE="${RESOURCE_VALIDATION_MODE:-validation-update-03}"
|
||||
FIRST_RUN_WAIT_TIMEOUT_SECS="${FIRST_RUN_WAIT_TIMEOUT_SECS:-7200}"
|
||||
|
||||
persist_env_default "$ENV_FILE" "METRICS_IMAGE" "$METRICS_IMAGE"
|
||||
persist_env_default "$ENV_FILE" "METRICS_PLATFORM" "$METRICS_PLATFORM"
|
||||
persist_env_default "$ENV_FILE" "RTR_REPORT_DIR" "$RTR_REPORT_DIR"
|
||||
persist_env_default "$ENV_FILE" "RTR_REPORT_CONTAINER_DIR" "$RTR_REPORT_CONTAINER_DIR"
|
||||
persist_env_default "$ENV_FILE" "RESOURCE_VALIDATION_MODE" "$RESOURCE_VALIDATION_MODE"
|
||||
}
|
||||
|
||||
compose_cmd() {
|
||||
|
||||
@ -81,6 +81,10 @@ RPKI_PROGRESS_PP_CONTROL_SLOW_MS=100
|
||||
# 是否在运行前尝试禁用 rpki-client timer 并杀掉竞争 RP 进程。
|
||||
DISABLE_COMPETING_RPS=1
|
||||
|
||||
# 资源证书验证模式;会显式传给 rpki 子进程。
|
||||
# 合法值:validation-update-03、rfc6487。
|
||||
RESOURCE_VALIDATION_MODE=validation-update-03
|
||||
|
||||
# 是否启用实验性 child CRT 验证缓存。独立于 ROA cache / PP cache;默认关闭。
|
||||
# 开启后会额外传入 --enable-child-certificate-validation-cache。
|
||||
ENABLE_CHILD_CERTIFICATE_VALIDATION_CACHE=0
|
||||
|
||||
@ -15,6 +15,7 @@ INTERVAL_SECS="${INTERVAL_SECS:-0}"
|
||||
STOP_AFTER_SECS="${STOP_AFTER_SECS:-0}"
|
||||
RIRS="${RIRS:-afrinic,apnic,arin,lacnic,ripe}"
|
||||
TAL_INPUT_MODE="${TAL_INPUT_MODE:-file-with-ta}"
|
||||
RESOURCE_VALIDATION_MODE="${RESOURCE_VALIDATION_MODE:-validation-update-03}"
|
||||
RUN_ROOT="${RUN_ROOT:-$PACKAGE_ROOT}"
|
||||
RETAIN_RUNS="${RETAIN_RUNS:-10}"
|
||||
CLEAN_TMP_AFTER_RUN="${CLEAN_TMP_AFTER_RUN:-0}"
|
||||
@ -125,6 +126,16 @@ validate_tal_input_mode() {
|
||||
esac
|
||||
}
|
||||
|
||||
validate_resource_validation_mode() {
|
||||
case "$RESOURCE_VALIDATION_MODE" in
|
||||
validation-update-03|rfc6487)
|
||||
;;
|
||||
*)
|
||||
die "RESOURCE_VALIDATION_MODE must be validation-update-03 or rfc6487: $RESOURCE_VALIDATION_MODE"
|
||||
;;
|
||||
esac
|
||||
}
|
||||
|
||||
normalize_token() {
|
||||
local token="$1"
|
||||
token="${token#"${token%%[![:space:]]*}"}"
|
||||
@ -1038,6 +1049,7 @@ build_child_args() {
|
||||
--db "$DB_DIR/work-db"
|
||||
--repo-bytes-db "$DB_DIR/repo-bytes.db"
|
||||
--rsync-scope "$RSYNC_SCOPE"
|
||||
--resource-validation-mode "$RESOURCE_VALIDATION_MODE"
|
||||
)
|
||||
if is_true "$ALLOW_RSYNC_MIRROR_REUSE"; then
|
||||
CHILD_ARGS+=(--rsync-mirror-root "$RSYNC_MIRROR_ROOT")
|
||||
@ -1326,6 +1338,7 @@ main() {
|
||||
validate_positive_int "RETAIN_RUNS" "$RETAIN_RUNS"
|
||||
validate_rsync_scope
|
||||
validate_tal_input_mode
|
||||
validate_resource_validation_mode
|
||||
validate_non_negative_int "PERIODIC_SNAPSHOT_MAX_DELTAS" "$PERIODIC_SNAPSHOT_MAX_DELTAS"
|
||||
if [[ -n "${DB_STATS_EXACT_EVERY:-}" && "$DB_STATS_EXACT_EVERY" != "0" ]]; then
|
||||
validate_positive_int "DB_STATS_EXACT_EVERY" "$DB_STATS_EXACT_EVERY"
|
||||
|
||||
23
src/cli.rs
23
src/cli.rs
@ -24,7 +24,7 @@ use crate::memory_telemetry::{
|
||||
};
|
||||
use crate::parallel::config::{ParallelPhase1Config, ParallelPhase2Config};
|
||||
use crate::parallel::types::TalInputSpec;
|
||||
use crate::policy::{Policy, StrictPolicy};
|
||||
use crate::policy::{Policy, ResourceValidationMode, StrictPolicy};
|
||||
use crate::storage::{RocksStore, VcirStorageSummary};
|
||||
use crate::validation::run_tree_from_tal::{
|
||||
RunTreeFromTalAuditOutput, run_tree_from_multiple_tals_parallel_phase2_audit,
|
||||
@ -130,6 +130,7 @@ pub struct CliArgs {
|
||||
pub repo_bytes_db: Option<PathBuf>,
|
||||
pub policy_path: Option<PathBuf>,
|
||||
pub strict_policy: Option<StrictPolicy>,
|
||||
pub resource_validation_mode: Option<ResourceValidationMode>,
|
||||
pub report_json_path: Option<PathBuf>,
|
||||
pub report_json_compact: bool,
|
||||
pub skip_report_build: bool,
|
||||
@ -189,6 +190,8 @@ Options:
|
||||
--repo-bytes-db <path> External repo object bytes DB path (optional)
|
||||
--policy <path> Policy TOML path (optional)
|
||||
--strict [policies] Enable strict policies (default all; comma list: name,cms-der,signed-attrs; none disables)
|
||||
--resource-validation-mode <validation-update-03|rfc6487>
|
||||
Resource certificate validation mode (default: validation-update-03)
|
||||
--report-json <path> Write full audit report as JSON (optional)
|
||||
--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
|
||||
@ -278,6 +281,7 @@ pub fn parse_args(argv: &[String]) -> Result<CliArgs, String> {
|
||||
let mut repo_bytes_db: Option<PathBuf> = None;
|
||||
let mut policy_path: Option<PathBuf> = None;
|
||||
let mut strict_policy: Option<StrictPolicy> = None;
|
||||
let mut resource_validation_mode: Option<ResourceValidationMode> = None;
|
||||
let mut report_json_path: Option<PathBuf> = None;
|
||||
let mut report_json_compact: bool = false;
|
||||
let mut skip_report_build: bool = false;
|
||||
@ -475,6 +479,19 @@ pub fn parse_args(argv: &[String]) -> Result<CliArgs, String> {
|
||||
let spec = arg.strip_prefix("--strict=").expect("prefix checked");
|
||||
strict_policy = Some(StrictPolicy::parse_cli_spec(Some(spec))?);
|
||||
}
|
||||
"--resource-validation-mode" => {
|
||||
i += 1;
|
||||
let v = argv
|
||||
.get(i)
|
||||
.ok_or("--resource-validation-mode requires a value")?;
|
||||
resource_validation_mode = Some(ResourceValidationMode::parse_cli_value(v)?);
|
||||
}
|
||||
_ if arg.starts_with("--resource-validation-mode=") => {
|
||||
let spec = arg
|
||||
.strip_prefix("--resource-validation-mode=")
|
||||
.expect("prefix checked");
|
||||
resource_validation_mode = Some(ResourceValidationMode::parse_cli_value(spec)?);
|
||||
}
|
||||
"--report-json" => {
|
||||
i += 1;
|
||||
let v = argv.get(i).ok_or("--report-json requires a value")?;
|
||||
@ -929,6 +946,7 @@ pub fn parse_args(argv: &[String]) -> Result<CliArgs, String> {
|
||||
repo_bytes_db,
|
||||
policy_path,
|
||||
strict_policy,
|
||||
resource_validation_mode,
|
||||
report_json_path,
|
||||
report_json_compact,
|
||||
skip_report_build,
|
||||
@ -1974,6 +1992,9 @@ pub fn run(argv: &[String]) -> Result<(), String> {
|
||||
if let Some(strict_policy) = args.strict_policy {
|
||||
policy.strict = strict_policy;
|
||||
}
|
||||
if let Some(resource_validation_mode) = args.resource_validation_mode {
|
||||
policy.resource_validation_mode = resource_validation_mode;
|
||||
}
|
||||
if args.disable_rrdp {
|
||||
policy.sync_preference = crate::policy::SyncPreference::RsyncOnly;
|
||||
}
|
||||
|
||||
@ -2,6 +2,7 @@ use super::*;
|
||||
use crate::memory_telemetry::{
|
||||
MemoryTelemetryCheckpoint, MemoryTelemetrySummary, ProcessMemorySnapshot,
|
||||
};
|
||||
use crate::policy::ResourceValidationMode;
|
||||
use crate::storage::{
|
||||
RocksDbMemorySnapshot, RocksDbMemoryTotals, VcirCcrProjectionSizeBreakdown,
|
||||
VcirChildResourceSizeBreakdown, VcirCoreFieldSizeBreakdown, VcirFieldSizeBreakdown,
|
||||
@ -19,6 +20,7 @@ fn parse_help_returns_usage() {
|
||||
assert!(err.contains("--parallel-phase2-object-workers"), "{err}");
|
||||
assert!(err.contains("--memory-trim-after-validation"), "{err}");
|
||||
assert!(err.contains("--enable-roa-validation-cache"), "{err}");
|
||||
assert!(err.contains("--resource-validation-mode"), "{err}");
|
||||
assert!(
|
||||
err.contains("--publication-point-cache-observe-only"),
|
||||
"{err}"
|
||||
@ -340,6 +342,39 @@ fn parse_accepts_strict_without_value_as_all() {
|
||||
assert_eq!(args.strict_policy, Some(StrictPolicy::all()));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_accepts_resource_validation_mode() {
|
||||
let argv = vec![
|
||||
"rpki".to_string(),
|
||||
"--db".to_string(),
|
||||
"db".to_string(),
|
||||
"--tal-url".to_string(),
|
||||
"https://example.test/x.tal".to_string(),
|
||||
"--resource-validation-mode".to_string(),
|
||||
"rfc6487".to_string(),
|
||||
];
|
||||
let args = parse_args(&argv).expect("parse args");
|
||||
assert_eq!(
|
||||
args.resource_validation_mode,
|
||||
Some(ResourceValidationMode::Rfc6487)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_rejects_unknown_resource_validation_mode() {
|
||||
let argv = vec![
|
||||
"rpki".to_string(),
|
||||
"--db".to_string(),
|
||||
"db".to_string(),
|
||||
"--tal-url".to_string(),
|
||||
"https://example.test/x.tal".to_string(),
|
||||
"--resource-validation-mode".to_string(),
|
||||
"bogus".to_string(),
|
||||
];
|
||||
let err = parse_args(&argv).expect_err("unknown mode should fail");
|
||||
assert!(err.contains("unknown resource validation mode"), "{err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn parse_rejects_unknown_strict_policy() {
|
||||
let argv = vec![
|
||||
@ -1407,6 +1442,10 @@ fn read_policy_accepts_valid_toml() {
|
||||
policy.signed_object_failure_policy,
|
||||
crate::policy::SignedObjectFailurePolicy::DropPublicationPoint
|
||||
);
|
||||
assert_eq!(
|
||||
policy.resource_validation_mode,
|
||||
ResourceValidationMode::ValidationUpdate03
|
||||
);
|
||||
assert_eq!(policy.strict, StrictPolicy::default());
|
||||
}
|
||||
|
||||
@ -1435,6 +1474,19 @@ fn read_policy_accepts_strict_table() {
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_policy_accepts_resource_validation_mode() {
|
||||
let dir = tempfile::tempdir().expect("tmpdir");
|
||||
let p = dir.path().join("policy.toml");
|
||||
std::fs::write(&p, "resource_validation_mode = \"rfc6487\"\n").expect("write policy");
|
||||
|
||||
let policy = read_policy(Some(&p)).expect("parse policy");
|
||||
assert_eq!(
|
||||
policy.resource_validation_mode,
|
||||
ResourceValidationMode::Rfc6487
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn read_policy_reports_missing_file() {
|
||||
let dir = tempfile::tempdir().expect("tmpdir");
|
||||
|
||||
@ -740,6 +740,29 @@ mod strict_name_tests {
|
||||
let err = validate_strict_rpki_name(&name, "subject").expect_err("strict name fails");
|
||||
assert!(err.to_string().contains("exactly once"), "{err}");
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn profile_rejects_rfc8360_v2_policy_oid() {
|
||||
let extensions = RcExtensionsParsed {
|
||||
basic_constraints_ca: vec![true],
|
||||
subject_key_identifier: Vec::new(),
|
||||
authority_key_identifier: Vec::new(),
|
||||
crl_distribution_points: Vec::new(),
|
||||
authority_info_access: Vec::new(),
|
||||
subject_info_access: Vec::new(),
|
||||
certificate_policies: vec![(vec!["1.3.6.1.5.5.7.14.3".to_string()], true)],
|
||||
ip_resources: Vec::new(),
|
||||
as_resources: Vec::new(),
|
||||
};
|
||||
|
||||
let err = extensions
|
||||
.validate_profile(true)
|
||||
.expect_err("v2 policy OID must remain invalid");
|
||||
assert!(
|
||||
matches!(&err, ResourceCertificateProfileError::InvalidCertificatePolicy(oid) if oid == "1.3.6.1.5.5.7.14.3"),
|
||||
"{err}"
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceCertificateParsed {
|
||||
|
||||
@ -39,6 +39,32 @@ impl Default for SignedObjectFailurePolicy {
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||
pub enum ResourceValidationMode {
|
||||
#[serde(rename = "validation-update-03")]
|
||||
ValidationUpdate03,
|
||||
#[serde(rename = "rfc6487")]
|
||||
Rfc6487,
|
||||
}
|
||||
|
||||
impl Default for ResourceValidationMode {
|
||||
fn default() -> Self {
|
||||
Self::ValidationUpdate03
|
||||
}
|
||||
}
|
||||
|
||||
impl ResourceValidationMode {
|
||||
pub fn parse_cli_value(raw: &str) -> Result<Self, String> {
|
||||
match raw.trim() {
|
||||
"validation-update-03" => Ok(Self::ValidationUpdate03),
|
||||
"rfc6487" => Ok(Self::Rfc6487),
|
||||
value => Err(format!(
|
||||
"unknown resource validation mode: {value}; supported: validation-update-03,rfc6487"
|
||||
)),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, Default, PartialEq, Eq, Serialize, Deserialize)]
|
||||
#[serde(default)]
|
||||
pub struct StrictPolicy {
|
||||
@ -99,6 +125,7 @@ pub struct Policy {
|
||||
pub sync_preference: SyncPreference,
|
||||
pub ca_failed_fetch_policy: CaFailedFetchPolicy,
|
||||
pub signed_object_failure_policy: SignedObjectFailurePolicy,
|
||||
pub resource_validation_mode: ResourceValidationMode,
|
||||
pub strict: StrictPolicy,
|
||||
}
|
||||
|
||||
@ -108,6 +135,7 @@ impl Default for Policy {
|
||||
sync_preference: SyncPreference::default(),
|
||||
ca_failed_fetch_policy: CaFailedFetchPolicy::default(),
|
||||
signed_object_failure_policy: SignedObjectFailurePolicy::default(),
|
||||
resource_validation_mode: ResourceValidationMode::default(),
|
||||
strict: StrictPolicy::default(),
|
||||
}
|
||||
}
|
||||
@ -150,6 +178,19 @@ mod tests {
|
||||
assert!(StrictPolicy::parse_cli_spec(Some("bogus")).is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn resource_validation_mode_parses_cli_values() {
|
||||
assert_eq!(
|
||||
ResourceValidationMode::parse_cli_value("validation-update-03").unwrap(),
|
||||
ResourceValidationMode::ValidationUpdate03
|
||||
);
|
||||
assert_eq!(
|
||||
ResourceValidationMode::parse_cli_value("rfc6487").unwrap(),
|
||||
ResourceValidationMode::Rfc6487
|
||||
);
|
||||
assert!(ResourceValidationMode::parse_cli_value("bogus").is_err());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn policy_toml_accepts_strict_table() {
|
||||
let policy = Policy::from_toml_str(
|
||||
@ -170,4 +211,18 @@ mod tests {
|
||||
}
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn policy_toml_accepts_resource_validation_mode() {
|
||||
let policy = Policy::from_toml_str(
|
||||
r#"
|
||||
resource_validation_mode = "rfc6487"
|
||||
"#,
|
||||
)
|
||||
.expect("parse policy");
|
||||
assert_eq!(
|
||||
policy.resource_validation_mode,
|
||||
ResourceValidationMode::Rfc6487
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@ -5,6 +5,7 @@ use crate::data_model::rc::{
|
||||
AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpResourceSet, ResourceCertKind,
|
||||
ResourceCertificate, ResourceCertificateDecodeError,
|
||||
};
|
||||
use crate::policy::ResourceValidationMode;
|
||||
use x509_parser::prelude::{FromDer, X509Certificate};
|
||||
|
||||
use crate::validation::x509_name::x509_names_equivalent;
|
||||
@ -18,6 +19,7 @@ pub struct ValidatedSubordinateCa {
|
||||
pub issuer_crl: RpkixCrl,
|
||||
pub effective_ip_resources: Option<IpResourceSet>,
|
||||
pub effective_as_resources: Option<AsResourceSet>,
|
||||
pub resource_warnings: ResourceValidationWarnings,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
@ -25,6 +27,41 @@ pub struct ValidatedSubordinateCaLite {
|
||||
pub child_ca: ResourceCertificate,
|
||||
pub effective_ip_resources: Option<IpResourceSet>,
|
||||
pub effective_as_resources: Option<AsResourceSet>,
|
||||
pub resource_warnings: ResourceValidationWarnings,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, PartialEq, Eq)]
|
||||
pub struct ResourceValidationWarnings {
|
||||
pub ip_reduced_by_vrs: bool,
|
||||
pub as_reduced_by_vrs: bool,
|
||||
pub ip_vrs_empty: bool,
|
||||
pub as_vrs_empty: bool,
|
||||
}
|
||||
|
||||
impl ResourceValidationWarnings {
|
||||
pub fn is_empty(&self) -> bool {
|
||||
!self.ip_reduced_by_vrs
|
||||
&& !self.as_reduced_by_vrs
|
||||
&& !self.ip_vrs_empty
|
||||
&& !self.as_vrs_empty
|
||||
}
|
||||
|
||||
pub fn summary(&self) -> String {
|
||||
let mut parts = Vec::new();
|
||||
if self.ip_reduced_by_vrs {
|
||||
parts.push("ip_reduced_by_vrs");
|
||||
}
|
||||
if self.as_reduced_by_vrs {
|
||||
parts.push("as_reduced_by_vrs");
|
||||
}
|
||||
if self.ip_vrs_empty {
|
||||
parts.push("ip_vrs_empty");
|
||||
}
|
||||
if self.as_vrs_empty {
|
||||
parts.push("as_vrs_empty");
|
||||
}
|
||||
parts.join(",")
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default)]
|
||||
@ -178,6 +215,30 @@ pub fn validate_subordinate_ca_cert(
|
||||
issuer_effective_ip: Option<&IpResourceSet>,
|
||||
issuer_effective_as: Option<&AsResourceSet>,
|
||||
validation_time: time::OffsetDateTime,
|
||||
) -> Result<ValidatedSubordinateCa, CaPathError> {
|
||||
validate_subordinate_ca_cert_with_resource_validation_mode(
|
||||
child_ca_der,
|
||||
issuer_ca_der,
|
||||
issuer_crl_der,
|
||||
issuer_ca_rsync_uri,
|
||||
issuer_crl_rsync_uri,
|
||||
issuer_effective_ip,
|
||||
issuer_effective_as,
|
||||
validation_time,
|
||||
ResourceValidationMode::Rfc6487,
|
||||
)
|
||||
}
|
||||
|
||||
pub fn validate_subordinate_ca_cert_with_resource_validation_mode(
|
||||
child_ca_der: &[u8],
|
||||
issuer_ca_der: &[u8],
|
||||
issuer_crl_der: &[u8],
|
||||
issuer_ca_rsync_uri: Option<&str>,
|
||||
issuer_crl_rsync_uri: &str,
|
||||
issuer_effective_ip: Option<&IpResourceSet>,
|
||||
issuer_effective_as: Option<&AsResourceSet>,
|
||||
validation_time: time::OffsetDateTime,
|
||||
resource_validation_mode: ResourceValidationMode,
|
||||
) -> Result<ValidatedSubordinateCa, CaPathError> {
|
||||
let child_ca = ResourceCertificate::decode_der(child_ca_der)?;
|
||||
if child_ca.kind != ResourceCertKind::Ca {
|
||||
@ -230,13 +291,20 @@ pub fn validate_subordinate_ca_cert(
|
||||
return Err(CaPathError::ChildRevoked);
|
||||
}
|
||||
|
||||
let effective_ip_resources = resolve_child_ip_resources(
|
||||
let ResourceResolution {
|
||||
effective_ip_resources,
|
||||
effective_as_resources,
|
||||
warnings: resource_warnings,
|
||||
} = resolve_child_resources(
|
||||
child_ca.tbs.extensions.ip_resources.as_ref(),
|
||||
issuer_effective_ip,
|
||||
)?;
|
||||
let effective_as_resources = resolve_child_as_resources(
|
||||
child_ca.tbs.extensions.as_resources.as_ref(),
|
||||
issuer_effective_as,
|
||||
&IssuerEffectiveResourcesIndex::from_effective_resources(
|
||||
issuer_effective_ip,
|
||||
issuer_effective_as,
|
||||
)?,
|
||||
resource_validation_mode,
|
||||
)?;
|
||||
if effective_ip_resources.is_none() && effective_as_resources.is_none() {
|
||||
return Err(CaPathError::ResourcesMissing);
|
||||
@ -248,6 +316,7 @@ pub fn validate_subordinate_ca_cert(
|
||||
issuer_crl,
|
||||
effective_ip_resources,
|
||||
effective_as_resources,
|
||||
resource_warnings,
|
||||
})
|
||||
}
|
||||
|
||||
@ -285,6 +354,7 @@ pub fn validate_subordinate_ca_cert_with_prevalidated_issuer(
|
||||
issuer_effective_as,
|
||||
&issuer_resources_index,
|
||||
validation_time,
|
||||
ResourceValidationMode::Rfc6487,
|
||||
)
|
||||
}
|
||||
|
||||
@ -301,6 +371,7 @@ pub fn validate_subordinate_ca_cert_with_prevalidated_issuer_and_resources(
|
||||
issuer_effective_as: Option<&AsResourceSet>,
|
||||
issuer_resources_index: &IssuerEffectiveResourcesIndex,
|
||||
validation_time: time::OffsetDateTime,
|
||||
resource_validation_mode: ResourceValidationMode,
|
||||
) -> Result<ValidatedSubordinateCaLite, CaPathError> {
|
||||
if child_ca.kind != ResourceCertKind::Ca {
|
||||
return Err(CaPathError::ChildNotCa);
|
||||
@ -347,17 +418,17 @@ pub fn validate_subordinate_ca_cert_with_prevalidated_issuer_and_resources(
|
||||
return Err(CaPathError::ChildRevoked);
|
||||
}
|
||||
|
||||
let effective_ip_resources = resolve_child_ip_resources_indexed(
|
||||
let ResourceResolution {
|
||||
effective_ip_resources,
|
||||
effective_as_resources,
|
||||
warnings: resource_warnings,
|
||||
} = resolve_child_resources(
|
||||
child_ca.tbs.extensions.ip_resources.as_ref(),
|
||||
issuer_effective_ip,
|
||||
issuer_resources_index.parent_ip_by_afi_items.as_ref(),
|
||||
&issuer_resources_index.parent_ip_merged_intervals,
|
||||
)?;
|
||||
let effective_as_resources = resolve_child_as_resources_indexed(
|
||||
child_ca.tbs.extensions.as_resources.as_ref(),
|
||||
issuer_effective_as,
|
||||
issuer_resources_index.parent_asnum_intervals.as_deref(),
|
||||
issuer_resources_index.parent_rdi_intervals.as_deref(),
|
||||
issuer_resources_index,
|
||||
resource_validation_mode,
|
||||
)?;
|
||||
if effective_ip_resources.is_none() && effective_as_resources.is_none() {
|
||||
return Err(CaPathError::ResourcesMissing);
|
||||
@ -367,6 +438,7 @@ pub fn validate_subordinate_ca_cert_with_prevalidated_issuer_and_resources(
|
||||
child_ca,
|
||||
effective_ip_resources,
|
||||
effective_as_resources,
|
||||
resource_warnings,
|
||||
})
|
||||
}
|
||||
|
||||
@ -506,6 +578,66 @@ fn is_serial_revoked_by_crl(cert: &ResourceCertificate, crl: &RpkixCrl) -> bool
|
||||
.any(|rc| rc.serial_number == serial)
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
struct ResourceResolution {
|
||||
effective_ip_resources: Option<IpResourceSet>,
|
||||
effective_as_resources: Option<AsResourceSet>,
|
||||
warnings: ResourceValidationWarnings,
|
||||
}
|
||||
|
||||
fn resolve_child_resources(
|
||||
child_ip: Option<&IpResourceSet>,
|
||||
issuer_effective_ip: Option<&IpResourceSet>,
|
||||
child_as: Option<&AsResourceSet>,
|
||||
issuer_effective_as: Option<&AsResourceSet>,
|
||||
issuer_resources_index: &IssuerEffectiveResourcesIndex,
|
||||
mode: ResourceValidationMode,
|
||||
) -> Result<ResourceResolution, CaPathError> {
|
||||
match mode {
|
||||
ResourceValidationMode::Rfc6487 => Ok(ResourceResolution {
|
||||
effective_ip_resources: resolve_child_ip_resources_indexed(
|
||||
child_ip,
|
||||
issuer_effective_ip,
|
||||
issuer_resources_index.parent_ip_by_afi_items.as_ref(),
|
||||
&issuer_resources_index.parent_ip_merged_intervals,
|
||||
)?,
|
||||
effective_as_resources: resolve_child_as_resources_indexed(
|
||||
child_as,
|
||||
issuer_effective_as,
|
||||
issuer_resources_index.parent_asnum_intervals.as_deref(),
|
||||
issuer_resources_index.parent_rdi_intervals.as_deref(),
|
||||
)?,
|
||||
warnings: ResourceValidationWarnings::default(),
|
||||
}),
|
||||
ResourceValidationMode::ValidationUpdate03 => {
|
||||
let (effective_ip_resources, ip_reduced_by_vrs, ip_vrs_empty) =
|
||||
resolve_child_ip_resources_vrs(
|
||||
child_ip,
|
||||
issuer_effective_ip,
|
||||
issuer_resources_index.parent_ip_by_afi_items.as_ref(),
|
||||
&issuer_resources_index.parent_ip_merged_intervals,
|
||||
)?;
|
||||
let (effective_as_resources, as_reduced_by_vrs, as_vrs_empty) =
|
||||
resolve_child_as_resources_vrs(
|
||||
child_as,
|
||||
issuer_effective_as,
|
||||
issuer_resources_index.parent_asnum_intervals.as_deref(),
|
||||
issuer_resources_index.parent_rdi_intervals.as_deref(),
|
||||
)?;
|
||||
Ok(ResourceResolution {
|
||||
effective_ip_resources,
|
||||
effective_as_resources,
|
||||
warnings: ResourceValidationWarnings {
|
||||
ip_reduced_by_vrs,
|
||||
as_reduced_by_vrs,
|
||||
ip_vrs_empty,
|
||||
as_vrs_empty,
|
||||
},
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn resolve_child_ip_resources(
|
||||
child_ip: Option<&IpResourceSet>,
|
||||
issuer_effective: Option<&IpResourceSet>,
|
||||
@ -581,6 +713,77 @@ fn resolve_child_ip_resources_indexed(
|
||||
}))
|
||||
}
|
||||
|
||||
fn resolve_child_ip_resources_vrs(
|
||||
child_ip: Option<&IpResourceSet>,
|
||||
issuer_effective: Option<&IpResourceSet>,
|
||||
parent_by_afi: Option<
|
||||
&BTreeMap<crate::data_model::rc::Afi, Vec<crate::data_model::rc::IpAddressOrRange>>,
|
||||
>,
|
||||
parent_intervals_by_afi: &HashMap<crate::data_model::rc::Afi, Vec<(Vec<u8>, Vec<u8>)>>,
|
||||
) -> Result<(Option<IpResourceSet>, bool, bool), CaPathError> {
|
||||
let Some(child_ip) = child_ip else {
|
||||
return Ok((None, false, false));
|
||||
};
|
||||
if child_ip.has_any_inherit() && issuer_effective.is_none() {
|
||||
return Err(CaPathError::InheritWithoutParentResources);
|
||||
}
|
||||
|
||||
let parent_by_afi = parent_by_afi.unwrap_or_else(|| {
|
||||
static EMPTY: std::sync::OnceLock<
|
||||
BTreeMap<crate::data_model::rc::Afi, Vec<crate::data_model::rc::IpAddressOrRange>>,
|
||||
> = std::sync::OnceLock::new();
|
||||
EMPTY.get_or_init(BTreeMap::new)
|
||||
});
|
||||
let mut out_families = Vec::new();
|
||||
let mut reduced = false;
|
||||
let mut saw_declared = false;
|
||||
|
||||
for fam in &child_ip.families {
|
||||
match &fam.choice {
|
||||
IpAddressChoice::Inherit => {
|
||||
let items = parent_by_afi
|
||||
.get(&fam.afi)
|
||||
.ok_or(CaPathError::InheritWithoutParentResources)?;
|
||||
out_families.push(crate::data_model::rc::IpAddressFamily {
|
||||
afi: fam.afi,
|
||||
choice: IpAddressChoice::AddressesOrRanges(items.clone()),
|
||||
});
|
||||
}
|
||||
IpAddressChoice::AddressesOrRanges(items) => {
|
||||
saw_declared = saw_declared || !items.is_empty();
|
||||
let parent_intervals = parent_intervals_by_afi
|
||||
.get(&fam.afi)
|
||||
.map(Vec::as_slice)
|
||||
.unwrap_or(&[]);
|
||||
let intersections =
|
||||
intersect_ip_items_with_parent_intervals(items, parent_intervals);
|
||||
let child_intervals = ip_items_to_merged_intervals(items);
|
||||
if intervals_changed(&child_intervals, &intersections) {
|
||||
reduced = true;
|
||||
}
|
||||
if !intersections.is_empty() {
|
||||
out_families.push(crate::data_model::rc::IpAddressFamily {
|
||||
afi: fam.afi,
|
||||
choice: IpAddressChoice::AddressesOrRanges(ip_intervals_to_ranges(
|
||||
fam.afi,
|
||||
&intersections,
|
||||
)),
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
let empty = saw_declared && out_families.is_empty();
|
||||
Ok((
|
||||
Some(IpResourceSet {
|
||||
families: out_families,
|
||||
}),
|
||||
reduced,
|
||||
empty,
|
||||
))
|
||||
}
|
||||
|
||||
fn resolve_child_as_resources(
|
||||
child_as: Option<&AsResourceSet>,
|
||||
issuer_effective: Option<&AsResourceSet>,
|
||||
@ -656,6 +859,80 @@ fn resolve_child_as_resources_indexed(
|
||||
Ok(Some(AsResourceSet { asnum, rdi }))
|
||||
}
|
||||
|
||||
fn resolve_child_as_resources_vrs(
|
||||
child_as: Option<&AsResourceSet>,
|
||||
issuer_effective: Option<&AsResourceSet>,
|
||||
parent_asnum_intervals: Option<&[(u32, u32)]>,
|
||||
parent_rdi_intervals: Option<&[(u32, u32)]>,
|
||||
) -> Result<(Option<AsResourceSet>, bool, bool), CaPathError> {
|
||||
let Some(child_as) = child_as else {
|
||||
return Ok((None, false, false));
|
||||
};
|
||||
if issuer_effective.is_none()
|
||||
&& (matches!(child_as.asnum, Some(AsIdentifierChoice::Inherit))
|
||||
|| matches!(child_as.rdi, Some(AsIdentifierChoice::Inherit)))
|
||||
{
|
||||
return Err(CaPathError::InheritWithoutParentResources);
|
||||
}
|
||||
|
||||
let (asnum, asnum_reduced, asnum_empty) = resolve_as_choice_vrs(
|
||||
child_as.asnum.as_ref(),
|
||||
issuer_effective.and_then(|p| p.asnum.as_ref()),
|
||||
parent_asnum_intervals,
|
||||
)?;
|
||||
let (rdi, rdi_reduced, rdi_empty) = resolve_as_choice_vrs(
|
||||
child_as.rdi.as_ref(),
|
||||
issuer_effective.and_then(|p| p.rdi.as_ref()),
|
||||
parent_rdi_intervals,
|
||||
)?;
|
||||
Ok((
|
||||
Some(AsResourceSet { asnum, rdi }),
|
||||
asnum_reduced || rdi_reduced,
|
||||
asnum_empty || rdi_empty,
|
||||
))
|
||||
}
|
||||
|
||||
fn resolve_as_choice_vrs(
|
||||
child: Option<&AsIdentifierChoice>,
|
||||
parent: Option<&AsIdentifierChoice>,
|
||||
parent_intervals_hint: Option<&[(u32, u32)]>,
|
||||
) -> Result<(Option<AsIdentifierChoice>, bool, bool), CaPathError> {
|
||||
let Some(child) = child else {
|
||||
return Ok((None, false, false));
|
||||
};
|
||||
match child {
|
||||
AsIdentifierChoice::Inherit => {
|
||||
let parent = parent
|
||||
.cloned()
|
||||
.ok_or(CaPathError::InheritWithoutParentResources)?;
|
||||
Ok((Some(parent), false, false))
|
||||
}
|
||||
AsIdentifierChoice::AsIdsOrRanges(_) => {
|
||||
let child_intervals = as_choice_to_merged_intervals(child);
|
||||
let parent_intervals;
|
||||
let parent_intervals = match parent_intervals_hint {
|
||||
Some(v) => v,
|
||||
None => {
|
||||
parent_intervals = parent
|
||||
.map(as_choice_to_merged_intervals)
|
||||
.unwrap_or_default();
|
||||
parent_intervals.as_slice()
|
||||
}
|
||||
};
|
||||
let intersections = intersect_as_intervals(&child_intervals, parent_intervals);
|
||||
let reduced = child_intervals != intersections;
|
||||
let empty = !child_intervals.is_empty() && intersections.is_empty();
|
||||
Ok((
|
||||
Some(AsIdentifierChoice::AsIdsOrRanges(as_intervals_to_items(
|
||||
&intersections,
|
||||
))),
|
||||
reduced,
|
||||
empty,
|
||||
))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn as_choice_subset(
|
||||
child: Option<&AsIdentifierChoice>,
|
||||
parent: Option<&AsIdentifierChoice>,
|
||||
@ -911,6 +1188,134 @@ fn ip_family_items_subset_with_parent_intervals(
|
||||
true
|
||||
}
|
||||
|
||||
fn ip_items_to_merged_intervals(
|
||||
items: &[crate::data_model::rc::IpAddressOrRange],
|
||||
) -> Vec<(Vec<u8>, Vec<u8>)> {
|
||||
let mut intervals = Vec::new();
|
||||
for item in items {
|
||||
match item {
|
||||
crate::data_model::rc::IpAddressOrRange::Prefix(p) => {
|
||||
intervals.push(prefix_to_range(p))
|
||||
}
|
||||
crate::data_model::rc::IpAddressOrRange::Range(r) => {
|
||||
intervals.push((r.min.clone(), r.max.clone()))
|
||||
}
|
||||
}
|
||||
}
|
||||
intervals.sort_by(|(a, _), (b, _)| a.cmp(b));
|
||||
merge_ip_intervals(&intervals)
|
||||
}
|
||||
|
||||
fn intersect_ip_items_with_parent_intervals(
|
||||
items: &[crate::data_model::rc::IpAddressOrRange],
|
||||
parent_intervals: &[(Vec<u8>, Vec<u8>)],
|
||||
) -> Vec<(Vec<u8>, Vec<u8>)> {
|
||||
let child_intervals = ip_items_to_merged_intervals(items);
|
||||
intersect_ip_intervals(&child_intervals, parent_intervals)
|
||||
}
|
||||
|
||||
fn intersect_ip_intervals(
|
||||
child_intervals: &[(Vec<u8>, Vec<u8>)],
|
||||
parent_intervals: &[(Vec<u8>, Vec<u8>)],
|
||||
) -> Vec<(Vec<u8>, Vec<u8>)> {
|
||||
let mut out = Vec::new();
|
||||
let mut parent_index = 0usize;
|
||||
for (child_min, child_max) in child_intervals {
|
||||
while parent_index < parent_intervals.len()
|
||||
&& parent_intervals[parent_index].1.as_slice() < child_min.as_slice()
|
||||
{
|
||||
parent_index += 1;
|
||||
}
|
||||
let mut scan = parent_index;
|
||||
while scan < parent_intervals.len()
|
||||
&& parent_intervals[scan].0.as_slice() <= child_max.as_slice()
|
||||
{
|
||||
let (parent_min, parent_max) = &parent_intervals[scan];
|
||||
let min = if bytes_leq(child_min, parent_min) {
|
||||
parent_min.clone()
|
||||
} else {
|
||||
child_min.clone()
|
||||
};
|
||||
let max = if bytes_leq(child_max, parent_max) {
|
||||
child_max.clone()
|
||||
} else {
|
||||
parent_max.clone()
|
||||
};
|
||||
if bytes_leq(&min, &max) {
|
||||
out.push((min, max));
|
||||
}
|
||||
scan += 1;
|
||||
}
|
||||
}
|
||||
merge_ip_intervals(&out)
|
||||
}
|
||||
|
||||
fn ip_intervals_to_ranges(
|
||||
afi: crate::data_model::rc::Afi,
|
||||
intervals: &[(Vec<u8>, Vec<u8>)],
|
||||
) -> Vec<crate::data_model::rc::IpAddressOrRange> {
|
||||
intervals
|
||||
.iter()
|
||||
.map(|(min, max)| {
|
||||
crate::data_model::rc::IpAddressOrRange::Range(crate::data_model::rc::IpAddressRange {
|
||||
min: normalize_ip_bytes(afi, min),
|
||||
max: normalize_ip_bytes(afi, max),
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn normalize_ip_bytes(afi: crate::data_model::rc::Afi, bytes: &[u8]) -> Vec<u8> {
|
||||
let target_len = afi.octets_len();
|
||||
if bytes.len() == target_len {
|
||||
return bytes.to_vec();
|
||||
}
|
||||
let mut out = vec![0u8; target_len];
|
||||
let copy_len = bytes.len().min(target_len);
|
||||
out[..copy_len].copy_from_slice(&bytes[..copy_len]);
|
||||
out
|
||||
}
|
||||
|
||||
fn intervals_changed<T: PartialEq>(left: &[T], right: &[T]) -> bool {
|
||||
left != right
|
||||
}
|
||||
|
||||
fn intersect_as_intervals(child: &[(u32, u32)], parent: &[(u32, u32)]) -> Vec<(u32, u32)> {
|
||||
let mut out = Vec::new();
|
||||
let mut parent_index = 0usize;
|
||||
for (child_min, child_max) in child {
|
||||
while parent_index < parent.len() && parent[parent_index].1 < *child_min {
|
||||
parent_index += 1;
|
||||
}
|
||||
let mut scan = parent_index;
|
||||
while scan < parent.len() && parent[scan].0 <= *child_max {
|
||||
let min = (*child_min).max(parent[scan].0);
|
||||
let max = (*child_max).min(parent[scan].1);
|
||||
if min <= max {
|
||||
out.push((min, max));
|
||||
}
|
||||
scan += 1;
|
||||
}
|
||||
}
|
||||
merge_as_intervals(&out)
|
||||
}
|
||||
|
||||
fn as_intervals_to_items(intervals: &[(u32, u32)]) -> Vec<crate::data_model::rc::AsIdOrRange> {
|
||||
intervals
|
||||
.iter()
|
||||
.map(|(min, max)| {
|
||||
if min == max {
|
||||
crate::data_model::rc::AsIdOrRange::Id(*min)
|
||||
} else {
|
||||
crate::data_model::rc::AsIdOrRange::Range {
|
||||
min: *min,
|
||||
max: *max,
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn merge_ip_intervals(v: &[(Vec<u8>, Vec<u8>)]) -> Vec<(Vec<u8>, Vec<u8>)> {
|
||||
let mut out: Vec<(Vec<u8>, Vec<u8>)> = Vec::new();
|
||||
for (min, max) in v {
|
||||
@ -971,8 +1376,8 @@ mod tests {
|
||||
use super::*;
|
||||
use crate::data_model::common::X509NameDer;
|
||||
use crate::data_model::rc::{
|
||||
Afi, AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressFamily, IpAddressOrRange,
|
||||
IpResourceSet,
|
||||
Afi, AsIdOrRange, AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressFamily,
|
||||
IpAddressOrRange, IpPrefix, IpResourceSet,
|
||||
};
|
||||
use crate::data_model::rc::{
|
||||
RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate,
|
||||
@ -1225,6 +1630,233 @@ mod tests {
|
||||
assert!(matches!(err, CaPathError::InheritWithoutParentResources));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validation_update_03_intersects_overclaiming_child_ip_resources() {
|
||||
let issuer = IpResourceSet {
|
||||
families: vec![IpAddressFamily {
|
||||
afi: Afi::Ipv4,
|
||||
choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
|
||||
IpPrefix {
|
||||
afi: Afi::Ipv4,
|
||||
prefix_len: 24,
|
||||
addr: vec![10, 0, 0, 0],
|
||||
},
|
||||
)]),
|
||||
}],
|
||||
};
|
||||
let child = IpResourceSet {
|
||||
families: vec![IpAddressFamily {
|
||||
afi: Afi::Ipv4,
|
||||
choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
|
||||
IpPrefix {
|
||||
afi: Afi::Ipv4,
|
||||
prefix_len: 23,
|
||||
addr: vec![10, 0, 0, 0],
|
||||
},
|
||||
)]),
|
||||
}],
|
||||
};
|
||||
let index = IssuerEffectiveResourcesIndex::from_effective_resources(Some(&issuer), None)
|
||||
.expect("index");
|
||||
let resolved = resolve_child_resources(
|
||||
Some(&child),
|
||||
Some(&issuer),
|
||||
None,
|
||||
None,
|
||||
&index,
|
||||
ResourceValidationMode::ValidationUpdate03,
|
||||
)
|
||||
.expect("vrs resolution");
|
||||
assert!(resolved.warnings.ip_reduced_by_vrs);
|
||||
assert!(!resolved.warnings.ip_vrs_empty);
|
||||
let effective = resolved.effective_ip_resources.expect("effective ip");
|
||||
assert!(effective.families[0].contains_prefix(&IpPrefix {
|
||||
afi: Afi::Ipv4,
|
||||
prefix_len: 24,
|
||||
addr: vec![10, 0, 0, 0],
|
||||
}));
|
||||
assert!(!effective.families[0].contains_prefix(&IpPrefix {
|
||||
afi: Afi::Ipv4,
|
||||
prefix_len: 24,
|
||||
addr: vec![10, 0, 1, 0],
|
||||
}));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validation_update_03_keeps_empty_vrs_instead_of_rejecting_child_ca() {
|
||||
let issuer = IpResourceSet {
|
||||
families: vec![IpAddressFamily {
|
||||
afi: Afi::Ipv4,
|
||||
choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
|
||||
IpPrefix {
|
||||
afi: Afi::Ipv4,
|
||||
prefix_len: 24,
|
||||
addr: vec![10, 0, 0, 0],
|
||||
},
|
||||
)]),
|
||||
}],
|
||||
};
|
||||
let child = IpResourceSet {
|
||||
families: vec![IpAddressFamily {
|
||||
afi: Afi::Ipv4,
|
||||
choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
|
||||
IpPrefix {
|
||||
afi: Afi::Ipv4,
|
||||
prefix_len: 24,
|
||||
addr: vec![192, 0, 2, 0],
|
||||
},
|
||||
)]),
|
||||
}],
|
||||
};
|
||||
let index = IssuerEffectiveResourcesIndex::from_effective_resources(Some(&issuer), None)
|
||||
.expect("index");
|
||||
let strict_err = resolve_child_resources(
|
||||
Some(&child),
|
||||
Some(&issuer),
|
||||
None,
|
||||
None,
|
||||
&index,
|
||||
ResourceValidationMode::Rfc6487,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(matches!(strict_err, CaPathError::ResourcesNotSubset));
|
||||
|
||||
let resolved = resolve_child_resources(
|
||||
Some(&child),
|
||||
Some(&issuer),
|
||||
None,
|
||||
None,
|
||||
&index,
|
||||
ResourceValidationMode::ValidationUpdate03,
|
||||
)
|
||||
.expect("vrs resolution");
|
||||
assert!(resolved.warnings.ip_reduced_by_vrs);
|
||||
assert!(resolved.warnings.ip_vrs_empty);
|
||||
assert!(
|
||||
resolved
|
||||
.effective_ip_resources
|
||||
.expect("effective ip")
|
||||
.families
|
||||
.is_empty()
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validation_update_03_intersects_overclaiming_child_as_resources() {
|
||||
let issuer = AsResourceSet {
|
||||
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![
|
||||
AsIdOrRange::Range {
|
||||
min: 64500,
|
||||
max: 64510,
|
||||
},
|
||||
])),
|
||||
rdi: None,
|
||||
};
|
||||
let child = AsResourceSet {
|
||||
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![
|
||||
AsIdOrRange::Range {
|
||||
min: 64505,
|
||||
max: 64520,
|
||||
},
|
||||
])),
|
||||
rdi: None,
|
||||
};
|
||||
let index = IssuerEffectiveResourcesIndex::from_effective_resources(None, Some(&issuer))
|
||||
.expect("index");
|
||||
let resolved = resolve_child_resources(
|
||||
None,
|
||||
None,
|
||||
Some(&child),
|
||||
Some(&issuer),
|
||||
&index,
|
||||
ResourceValidationMode::ValidationUpdate03,
|
||||
)
|
||||
.expect("vrs resolution");
|
||||
assert!(resolved.warnings.as_reduced_by_vrs);
|
||||
assert!(!resolved.warnings.as_vrs_empty);
|
||||
let effective = resolved.effective_as_resources.expect("effective as");
|
||||
assert_eq!(
|
||||
effective.asnum,
|
||||
Some(AsIdentifierChoice::AsIdsOrRanges(vec![
|
||||
AsIdOrRange::Range {
|
||||
min: 64505,
|
||||
max: 64510,
|
||||
},
|
||||
]))
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validation_update_03_records_all_resource_warning_summary_parts() {
|
||||
let issuer_ip = IpResourceSet {
|
||||
families: vec![IpAddressFamily {
|
||||
afi: Afi::Ipv4,
|
||||
choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
|
||||
IpPrefix {
|
||||
afi: Afi::Ipv4,
|
||||
prefix_len: 24,
|
||||
addr: vec![10, 0, 0, 0],
|
||||
},
|
||||
)]),
|
||||
}],
|
||||
};
|
||||
let child_ip = IpResourceSet {
|
||||
families: vec![IpAddressFamily {
|
||||
afi: Afi::Ipv4,
|
||||
choice: IpAddressChoice::AddressesOrRanges(vec![IpAddressOrRange::Prefix(
|
||||
IpPrefix {
|
||||
afi: Afi::Ipv4,
|
||||
prefix_len: 24,
|
||||
addr: vec![192, 0, 2, 0],
|
||||
},
|
||||
)]),
|
||||
}],
|
||||
};
|
||||
let issuer_as = AsResourceSet {
|
||||
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![
|
||||
AsIdOrRange::Range {
|
||||
min: 64500,
|
||||
max: 64510,
|
||||
},
|
||||
])),
|
||||
rdi: None,
|
||||
};
|
||||
let child_as = AsResourceSet {
|
||||
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![
|
||||
AsIdOrRange::Range {
|
||||
min: 64520,
|
||||
max: 64530,
|
||||
},
|
||||
])),
|
||||
rdi: None,
|
||||
};
|
||||
let index = IssuerEffectiveResourcesIndex::from_effective_resources(
|
||||
Some(&issuer_ip),
|
||||
Some(&issuer_as),
|
||||
)
|
||||
.expect("index");
|
||||
|
||||
let resolved = resolve_child_resources(
|
||||
Some(&child_ip),
|
||||
Some(&issuer_ip),
|
||||
Some(&child_as),
|
||||
Some(&issuer_as),
|
||||
&index,
|
||||
ResourceValidationMode::ValidationUpdate03,
|
||||
)
|
||||
.expect("vrs resolution");
|
||||
|
||||
assert!(resolved.warnings.ip_reduced_by_vrs);
|
||||
assert!(resolved.warnings.ip_vrs_empty);
|
||||
assert!(resolved.warnings.as_reduced_by_vrs);
|
||||
assert!(resolved.warnings.as_vrs_empty);
|
||||
assert_eq!(
|
||||
resolved.warnings.summary(),
|
||||
"ip_reduced_by_vrs,as_reduced_by_vrs,ip_vrs_empty,as_vrs_empty"
|
||||
);
|
||||
assert!(!resolved.warnings.is_empty());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn child_aki_mismatch_is_rejected() {
|
||||
let issuer = dummy_cert(
|
||||
|
||||
@ -13,7 +13,7 @@ use crate::parallel::config::ParallelPhase2Config;
|
||||
use crate::parallel::object_worker::{
|
||||
ObjectTaskExecutor, ObjectWorkerPool, ObjectWorkerSubmitError,
|
||||
};
|
||||
use crate::policy::{Policy, SignedObjectFailurePolicy};
|
||||
use crate::policy::{Policy, ResourceValidationMode, SignedObjectFailurePolicy};
|
||||
use crate::report::{RfcRef, Warning};
|
||||
use crate::storage::{
|
||||
PackFile, PackTime, RoaCacheObjectMeta, RoaCacheProjection, VcirLocalOutput,
|
||||
@ -1226,6 +1226,7 @@ pub fn process_publication_point_for_issuer_with_cache_options<P: PublicationPoi
|
||||
collect_vcir_local_outputs,
|
||||
policy.strict.cms_der,
|
||||
policy.strict.name,
|
||||
policy.resource_validation_mode,
|
||||
)
|
||||
}
|
||||
blocked @ (RoaCacheLookupResult::HashBlocked
|
||||
@ -1251,6 +1252,7 @@ pub fn process_publication_point_for_issuer_with_cache_options<P: PublicationPoi
|
||||
collect_vcir_local_outputs,
|
||||
policy.strict.cms_der,
|
||||
policy.strict.name,
|
||||
policy.resource_validation_mode,
|
||||
)
|
||||
}
|
||||
}
|
||||
@ -1273,6 +1275,7 @@ pub fn process_publication_point_for_issuer_with_cache_options<P: PublicationPoi
|
||||
collect_vcir_local_outputs,
|
||||
policy.strict.cms_der,
|
||||
policy.strict.name,
|
||||
policy.resource_validation_mode,
|
||||
)
|
||||
};
|
||||
match result.outcome {
|
||||
@ -1386,6 +1389,7 @@ pub fn process_publication_point_for_issuer_with_cache_options<P: PublicationPoi
|
||||
collect_vcir_local_outputs,
|
||||
policy.strict.cms_der,
|
||||
policy.strict.name,
|
||||
policy.resource_validation_mode,
|
||||
) {
|
||||
Ok((att, local_output)) => {
|
||||
stats.aspa_ok += 1;
|
||||
@ -1735,6 +1739,7 @@ pub(crate) struct RoaTaskShared {
|
||||
issuer_resources_index: Arc<IssuerResourcesIndex>,
|
||||
issuer_effective_ip: Option<Arc<crate::data_model::rc::IpResourceSet>>,
|
||||
issuer_effective_as: Option<Arc<crate::data_model::rc::AsResourceSet>>,
|
||||
resource_validation_mode: ResourceValidationMode,
|
||||
}
|
||||
|
||||
#[derive(Clone)]
|
||||
@ -1746,6 +1751,7 @@ pub(crate) struct OwnedRoaTask {
|
||||
collect_vcir_local_outputs: bool,
|
||||
strict_cms_der: bool,
|
||||
strict_name: bool,
|
||||
resource_validation_mode: ResourceValidationMode,
|
||||
pub(crate) submitted_at: Option<Instant>,
|
||||
}
|
||||
|
||||
@ -1854,6 +1860,7 @@ fn validate_owned_roa_task(worker_index: usize, task: OwnedRoaTask) -> RoaTaskRe
|
||||
task.collect_vcir_local_outputs,
|
||||
task.strict_cms_der,
|
||||
task.strict_name,
|
||||
task.resource_validation_mode,
|
||||
)
|
||||
.map(|(vrps, local_outputs, cache_object_meta)| RoaTaskOk {
|
||||
vrps,
|
||||
@ -1884,6 +1891,7 @@ pub(crate) struct ParallelObjectsStage {
|
||||
collect_vcir_local_outputs: bool,
|
||||
strict_cms_der: bool,
|
||||
strict_name: bool,
|
||||
resource_validation_mode: ResourceValidationMode,
|
||||
roa_task_indices: Vec<usize>,
|
||||
cached_roa_results: Vec<RoaTaskResult>,
|
||||
roa_cache_stats: RoaValidationCacheStats,
|
||||
@ -1921,6 +1929,7 @@ impl ParallelObjectsStage {
|
||||
collect_vcir_local_outputs: self.collect_vcir_local_outputs,
|
||||
strict_cms_der: self.strict_cms_der,
|
||||
strict_name: self.strict_name,
|
||||
resource_validation_mode: self.resource_validation_mode,
|
||||
submitted_at: None,
|
||||
});
|
||||
});
|
||||
@ -2257,11 +2266,13 @@ pub(crate) fn prepare_publication_point_for_parallel_roa_with_cache<P: Publicati
|
||||
)),
|
||||
issuer_effective_ip: issuer_effective_ip.cloned().map(Arc::new),
|
||||
issuer_effective_as: issuer_effective_as.cloned().map(Arc::new),
|
||||
resource_validation_mode: policy.resource_validation_mode,
|
||||
}),
|
||||
validation_time,
|
||||
collect_vcir_local_outputs,
|
||||
strict_cms_der: policy.strict.cms_der,
|
||||
strict_name: policy.strict.name,
|
||||
resource_validation_mode: policy.resource_validation_mode,
|
||||
roa_task_indices,
|
||||
cached_roa_results,
|
||||
roa_cache_stats,
|
||||
@ -2370,6 +2381,7 @@ pub(crate) fn reduce_parallel_roa_stage(
|
||||
collect_vcir_local_outputs,
|
||||
strict_cms_der,
|
||||
strict_name,
|
||||
shared.resource_validation_mode,
|
||||
) {
|
||||
Ok((att, local_output)) => {
|
||||
stats.aspa_ok += 1;
|
||||
@ -2604,6 +2616,7 @@ pub(crate) fn validate_roa_task_serial(
|
||||
collect_vcir_local_outputs: bool,
|
||||
strict_cms_der: bool,
|
||||
strict_name: bool,
|
||||
resource_validation_mode: ResourceValidationMode,
|
||||
) -> RoaTaskResult {
|
||||
let outcome = process_roa_with_issuer(
|
||||
task.file,
|
||||
@ -2621,6 +2634,7 @@ pub(crate) fn validate_roa_task_serial(
|
||||
collect_vcir_local_outputs,
|
||||
strict_cms_der,
|
||||
strict_name,
|
||||
resource_validation_mode,
|
||||
)
|
||||
.map(|(vrps, local_outputs, cache_object_meta)| RoaTaskOk {
|
||||
vrps,
|
||||
@ -2655,6 +2669,7 @@ fn process_roa_with_issuer(
|
||||
collect_vcir_local_outputs: bool,
|
||||
strict_cms_der: bool,
|
||||
strict_name: bool,
|
||||
resource_validation_mode: ResourceValidationMode,
|
||||
) -> Result<(Vec<Vrp>, Vec<VcirLocalOutput>, Option<RoaCacheObjectMeta>), ObjectValidateError> {
|
||||
let _decode = timing
|
||||
.as_ref()
|
||||
@ -2706,15 +2721,16 @@ fn process_roa_with_issuer(
|
||||
let _subset = timing
|
||||
.as_ref()
|
||||
.map(|t| t.span_phase("objects_roa_validate_ee_resources_subset_total"));
|
||||
validate_ee_resources_subset(
|
||||
let ee_vrs = validate_ee_resources_for_mode(
|
||||
&ee.resource_cert,
|
||||
issuer_effective_ip,
|
||||
issuer_effective_as,
|
||||
issuer_resources_index,
|
||||
resource_validation_mode,
|
||||
)?;
|
||||
drop(_subset);
|
||||
|
||||
let vrps = roa_to_vrps(&roa);
|
||||
let vrps = roa_to_vrps_with_vrs(&roa, ee_vrs.ip.as_ref())?;
|
||||
let cache_object_meta = RoaCacheObjectMeta {
|
||||
source_object_uri: file.rsync_uri.clone(),
|
||||
source_object_hash: file.sha256,
|
||||
@ -2777,6 +2793,7 @@ fn process_roa_with_issuer_parallel_cached(
|
||||
collect_vcir_local_outputs: bool,
|
||||
strict_cms_der: bool,
|
||||
strict_name: bool,
|
||||
resource_validation_mode: ResourceValidationMode,
|
||||
) -> Result<(Vec<Vrp>, Vec<VcirLocalOutput>, Option<RoaCacheObjectMeta>), ObjectValidateError> {
|
||||
let _decode = timing
|
||||
.as_ref()
|
||||
@ -2834,15 +2851,16 @@ fn process_roa_with_issuer_parallel_cached(
|
||||
let _subset = timing
|
||||
.as_ref()
|
||||
.map(|t| t.span_phase("objects_roa_validate_ee_resources_subset_total"));
|
||||
validate_ee_resources_subset(
|
||||
let ee_vrs = validate_ee_resources_for_mode(
|
||||
&ee.resource_cert,
|
||||
issuer_effective_ip,
|
||||
issuer_effective_as,
|
||||
issuer_resources_index,
|
||||
resource_validation_mode,
|
||||
)?;
|
||||
drop(_subset);
|
||||
|
||||
let vrps = roa_to_vrps(&roa);
|
||||
let vrps = roa_to_vrps_with_vrs(&roa, ee_vrs.ip.as_ref())?;
|
||||
let cache_object_meta = RoaCacheObjectMeta {
|
||||
source_object_uri: file.rsync_uri.clone(),
|
||||
source_object_hash: file.sha256,
|
||||
@ -2905,6 +2923,7 @@ fn process_aspa_with_issuer(
|
||||
collect_vcir_local_outputs: bool,
|
||||
strict_cms_der: bool,
|
||||
strict_name: bool,
|
||||
resource_validation_mode: ResourceValidationMode,
|
||||
) -> Result<(AspaAttestation, Option<VcirLocalOutput>), ObjectValidateError> {
|
||||
let _decode = timing
|
||||
.as_ref()
|
||||
@ -2956,14 +2975,17 @@ fn process_aspa_with_issuer(
|
||||
let _subset = timing
|
||||
.as_ref()
|
||||
.map(|t| t.span_phase("objects_aspa_validate_ee_resources_subset_total"));
|
||||
validate_ee_resources_subset(
|
||||
let ee_vrs = validate_ee_resources_for_mode(
|
||||
&ee.resource_cert,
|
||||
issuer_effective_ip,
|
||||
issuer_effective_as,
|
||||
issuer_resources_index,
|
||||
resource_validation_mode,
|
||||
)?;
|
||||
drop(_subset);
|
||||
|
||||
validate_aspa_customer_in_vrs(&aspa, ee_vrs.asn.as_ref())?;
|
||||
|
||||
let attestation = AspaAttestation {
|
||||
customer_as_id: aspa.aspa.customer_as_id,
|
||||
provider_as_ids: aspa.aspa.provider_as_ids.clone(),
|
||||
@ -3119,6 +3141,176 @@ fn validate_ee_resources_subset(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
struct EeVerifiedResources {
|
||||
ip: Option<crate::data_model::rc::IpResourceSet>,
|
||||
asn: Option<crate::data_model::rc::AsResourceSet>,
|
||||
}
|
||||
|
||||
fn validate_ee_resources_for_mode(
|
||||
ee: &ResourceCertificate,
|
||||
issuer_effective_ip: Option<&crate::data_model::rc::IpResourceSet>,
|
||||
issuer_effective_as: Option<&crate::data_model::rc::AsResourceSet>,
|
||||
issuer_resources_index: &IssuerResourcesIndex,
|
||||
mode: ResourceValidationMode,
|
||||
) -> Result<EeVerifiedResources, ObjectValidateError> {
|
||||
match mode {
|
||||
ResourceValidationMode::Rfc6487 => {
|
||||
validate_ee_resources_subset(
|
||||
ee,
|
||||
issuer_effective_ip,
|
||||
issuer_effective_as,
|
||||
issuer_resources_index,
|
||||
)?;
|
||||
Ok(EeVerifiedResources {
|
||||
ip: ee.tbs.extensions.ip_resources.clone(),
|
||||
asn: ee.tbs.extensions.as_resources.clone(),
|
||||
})
|
||||
}
|
||||
ResourceValidationMode::ValidationUpdate03 => {
|
||||
let ip = match ee.tbs.extensions.ip_resources.as_ref() {
|
||||
Some(child_ip) => Some(intersect_ee_ip_resources_vrs(
|
||||
child_ip,
|
||||
issuer_effective_ip,
|
||||
issuer_resources_index,
|
||||
)?),
|
||||
None => None,
|
||||
};
|
||||
let asn = match ee.tbs.extensions.as_resources.as_ref() {
|
||||
Some(child_as) => Some(intersect_ee_as_resources_vrs(
|
||||
child_as,
|
||||
issuer_effective_as,
|
||||
issuer_resources_index,
|
||||
)?),
|
||||
None => None,
|
||||
};
|
||||
Ok(EeVerifiedResources { ip, asn })
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn intersect_ee_ip_resources_vrs(
|
||||
child_ip: &crate::data_model::rc::IpResourceSet,
|
||||
issuer_effective_ip: Option<&crate::data_model::rc::IpResourceSet>,
|
||||
issuer_resources_index: &IssuerResourcesIndex,
|
||||
) -> Result<crate::data_model::rc::IpResourceSet, ObjectValidateError> {
|
||||
if child_ip.has_any_inherit() {
|
||||
return Err(ObjectValidateError::EeResourcesNotSubset);
|
||||
}
|
||||
let _ = issuer_effective_ip;
|
||||
let mut families = Vec::new();
|
||||
for fam in &child_ip.families {
|
||||
let parent_intervals = match fam.afi {
|
||||
crate::data_model::rc::Afi::Ipv4 => issuer_resources_index.ip_v4.as_deref(),
|
||||
crate::data_model::rc::Afi::Ipv6 => issuer_resources_index.ip_v6.as_deref(),
|
||||
}
|
||||
.unwrap_or(&[]);
|
||||
let items = match &fam.choice {
|
||||
IpAddressChoice::Inherit => return Err(ObjectValidateError::EeResourcesNotSubset),
|
||||
IpAddressChoice::AddressesOrRanges(items) => items,
|
||||
};
|
||||
let intersections = intersect_ip_items_with_parent_intervals(items, parent_intervals);
|
||||
if !intersections.is_empty() {
|
||||
families.push(crate::data_model::rc::IpAddressFamily {
|
||||
afi: fam.afi,
|
||||
choice: IpAddressChoice::AddressesOrRanges(ip_intervals_to_ranges(
|
||||
fam.afi,
|
||||
&intersections,
|
||||
)),
|
||||
});
|
||||
}
|
||||
}
|
||||
Ok(crate::data_model::rc::IpResourceSet { families })
|
||||
}
|
||||
|
||||
fn intersect_ee_as_resources_vrs(
|
||||
child_as: &crate::data_model::rc::AsResourceSet,
|
||||
issuer_effective_as: Option<&crate::data_model::rc::AsResourceSet>,
|
||||
issuer_resources_index: &IssuerResourcesIndex,
|
||||
) -> Result<crate::data_model::rc::AsResourceSet, ObjectValidateError> {
|
||||
let _ = issuer_effective_as;
|
||||
if matches!(child_as.asnum, Some(AsIdentifierChoice::Inherit))
|
||||
|| matches!(child_as.rdi, Some(AsIdentifierChoice::Inherit))
|
||||
{
|
||||
return Err(ObjectValidateError::EeResourcesNotSubset);
|
||||
}
|
||||
let asnum = child_as.asnum.as_ref().map(|choice| {
|
||||
let child_intervals = as_choice_to_merged_intervals(choice);
|
||||
AsIdentifierChoice::AsIdsOrRanges(as_intervals_to_items(&intersect_as_intervals(
|
||||
&child_intervals,
|
||||
issuer_resources_index.asnum.as_deref().unwrap_or(&[]),
|
||||
)))
|
||||
});
|
||||
let rdi = child_as.rdi.as_ref().map(|choice| {
|
||||
let child_intervals = as_choice_to_merged_intervals(choice);
|
||||
AsIdentifierChoice::AsIdsOrRanges(as_intervals_to_items(&intersect_as_intervals(
|
||||
&child_intervals,
|
||||
issuer_resources_index.rdi.as_deref().unwrap_or(&[]),
|
||||
)))
|
||||
});
|
||||
Ok(crate::data_model::rc::AsResourceSet { asnum, rdi })
|
||||
}
|
||||
|
||||
fn roa_to_vrps_with_vrs(
|
||||
roa: &RoaObject,
|
||||
ee_vrs_ip: Option<&crate::data_model::rc::IpResourceSet>,
|
||||
) -> Result<Vec<Vrp>, ObjectValidateError> {
|
||||
let vrps = roa_to_vrps(roa);
|
||||
let Some(ee_vrs_ip) = ee_vrs_ip else {
|
||||
return Err(ObjectValidateError::EeResourcesNotSubset);
|
||||
};
|
||||
for vrp in &vrps {
|
||||
let rc_prefix = roa_prefix_to_rc_prefix(&vrp.prefix);
|
||||
if !ee_vrs_ip.contains_prefix(&rc_prefix) {
|
||||
return Err(ObjectValidateError::EeResourcesNotSubset);
|
||||
}
|
||||
}
|
||||
Ok(vrps)
|
||||
}
|
||||
|
||||
fn validate_aspa_customer_in_vrs(
|
||||
aspa: &AspaObject,
|
||||
ee_vrs_as: Option<&crate::data_model::rc::AsResourceSet>,
|
||||
) -> Result<(), ObjectValidateError> {
|
||||
let Some(ee_vrs_as) = ee_vrs_as else {
|
||||
return Err(ObjectValidateError::EeResourcesNotSubset);
|
||||
};
|
||||
if !as_resource_set_contains_asn(ee_vrs_as, aspa.aspa.customer_as_id) {
|
||||
return Err(ObjectValidateError::EeResourcesNotSubset);
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn as_resource_set_contains_asn(
|
||||
resources: &crate::data_model::rc::AsResourceSet,
|
||||
asn: u32,
|
||||
) -> bool {
|
||||
let Some(choice) = resources.asnum.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
match choice {
|
||||
AsIdentifierChoice::Inherit => false,
|
||||
AsIdentifierChoice::AsIdsOrRanges(items) => items.iter().any(|item| match item {
|
||||
crate::data_model::rc::AsIdOrRange::Id(id) => *id == asn,
|
||||
crate::data_model::rc::AsIdOrRange::Range { min, max } => *min <= asn && asn <= *max,
|
||||
}),
|
||||
}
|
||||
}
|
||||
|
||||
fn roa_prefix_to_rc_prefix(prefix: &IpPrefix) -> RcIpPrefix {
|
||||
let afi = match prefix.afi {
|
||||
RoaAfi::Ipv4 => crate::data_model::rc::Afi::Ipv4,
|
||||
RoaAfi::Ipv6 => crate::data_model::rc::Afi::Ipv6,
|
||||
};
|
||||
let mut addr = prefix.addr.to_vec();
|
||||
addr.truncate(afi.octets_len());
|
||||
RcIpPrefix {
|
||||
afi,
|
||||
prefix_len: prefix.prefix_len,
|
||||
addr,
|
||||
}
|
||||
}
|
||||
|
||||
fn as_resources_is_subset(child: &AsResourceSet, parent: &AsResourceSet) -> bool {
|
||||
as_choice_subset(child.asnum.as_ref(), parent.asnum.as_ref())
|
||||
&& as_choice_subset(child.rdi.as_ref(), parent.rdi.as_ref())
|
||||
@ -3292,6 +3484,121 @@ fn ip_resources_is_subset_indexed(
|
||||
true
|
||||
}
|
||||
|
||||
fn ip_items_to_merged_intervals(
|
||||
items: &[crate::data_model::rc::IpAddressOrRange],
|
||||
) -> Vec<(Vec<u8>, Vec<u8>)> {
|
||||
let mut intervals = Vec::new();
|
||||
for item in items {
|
||||
match item {
|
||||
IpAddressOrRange::Prefix(p) => intervals.push(prefix_to_range(p)),
|
||||
IpAddressOrRange::Range(r) => intervals.push((r.min.clone(), r.max.clone())),
|
||||
}
|
||||
}
|
||||
intervals.sort_by(|(a, _), (b, _)| a.cmp(b));
|
||||
merge_ip_intervals_in_place(&mut intervals);
|
||||
intervals
|
||||
}
|
||||
|
||||
fn intersect_ip_items_with_parent_intervals(
|
||||
items: &[crate::data_model::rc::IpAddressOrRange],
|
||||
parent_intervals: &[(Vec<u8>, Vec<u8>)],
|
||||
) -> Vec<(Vec<u8>, Vec<u8>)> {
|
||||
let child_intervals = ip_items_to_merged_intervals(items);
|
||||
let mut out = Vec::new();
|
||||
let mut parent_index = 0usize;
|
||||
for (child_min, child_max) in &child_intervals {
|
||||
while parent_index < parent_intervals.len()
|
||||
&& parent_intervals[parent_index].1.as_slice() < child_min.as_slice()
|
||||
{
|
||||
parent_index += 1;
|
||||
}
|
||||
let mut scan = parent_index;
|
||||
while scan < parent_intervals.len()
|
||||
&& parent_intervals[scan].0.as_slice() <= child_max.as_slice()
|
||||
{
|
||||
let (parent_min, parent_max) = &parent_intervals[scan];
|
||||
let min = if bytes_leq(child_min, parent_min) {
|
||||
parent_min.clone()
|
||||
} else {
|
||||
child_min.clone()
|
||||
};
|
||||
let max = if bytes_leq(child_max, parent_max) {
|
||||
child_max.clone()
|
||||
} else {
|
||||
parent_max.clone()
|
||||
};
|
||||
if bytes_leq(&min, &max) {
|
||||
out.push((min, max));
|
||||
}
|
||||
scan += 1;
|
||||
}
|
||||
}
|
||||
merge_ip_intervals_in_place(&mut out);
|
||||
out
|
||||
}
|
||||
|
||||
fn ip_intervals_to_ranges(
|
||||
afi: crate::data_model::rc::Afi,
|
||||
intervals: &[(Vec<u8>, Vec<u8>)],
|
||||
) -> Vec<crate::data_model::rc::IpAddressOrRange> {
|
||||
intervals
|
||||
.iter()
|
||||
.map(|(min, max)| {
|
||||
IpAddressOrRange::Range(crate::data_model::rc::IpAddressRange {
|
||||
min: normalize_ip_bytes(afi, min),
|
||||
max: normalize_ip_bytes(afi, max),
|
||||
})
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn normalize_ip_bytes(afi: crate::data_model::rc::Afi, bytes: &[u8]) -> Vec<u8> {
|
||||
let target_len = afi.octets_len();
|
||||
if bytes.len() == target_len {
|
||||
return bytes.to_vec();
|
||||
}
|
||||
let mut out = vec![0u8; target_len];
|
||||
let copy_len = bytes.len().min(target_len);
|
||||
out[..copy_len].copy_from_slice(&bytes[..copy_len]);
|
||||
out
|
||||
}
|
||||
|
||||
fn intersect_as_intervals(child: &[(u32, u32)], parent: &[(u32, u32)]) -> Vec<(u32, u32)> {
|
||||
let mut out = Vec::new();
|
||||
let mut parent_index = 0usize;
|
||||
for (child_min, child_max) in child {
|
||||
while parent_index < parent.len() && parent[parent_index].1 < *child_min {
|
||||
parent_index += 1;
|
||||
}
|
||||
let mut scan = parent_index;
|
||||
while scan < parent.len() && parent[scan].0 <= *child_max {
|
||||
let min = (*child_min).max(parent[scan].0);
|
||||
let max = (*child_max).min(parent[scan].1);
|
||||
if min <= max {
|
||||
out.push((min, max));
|
||||
}
|
||||
scan += 1;
|
||||
}
|
||||
}
|
||||
merge_as_intervals(&out)
|
||||
}
|
||||
|
||||
fn as_intervals_to_items(intervals: &[(u32, u32)]) -> Vec<crate::data_model::rc::AsIdOrRange> {
|
||||
intervals
|
||||
.iter()
|
||||
.map(|(min, max)| {
|
||||
if min == max {
|
||||
crate::data_model::rc::AsIdOrRange::Id(*min)
|
||||
} else {
|
||||
crate::data_model::rc::AsIdOrRange::Range {
|
||||
min: *min,
|
||||
max: *max,
|
||||
}
|
||||
}
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq, Hash)]
|
||||
enum AfiKey {
|
||||
V4,
|
||||
@ -4724,6 +5031,120 @@ mod tests {
|
||||
assert!(matches!(err, ObjectValidateError::EeResourcesNotSubset));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validation_update_03_ee_resources_reduce_to_vrs_for_roa_checks() {
|
||||
let roa_path = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"))
|
||||
.join("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
|
||||
let roa_der = std::fs::read(roa_path).expect("read roa");
|
||||
let roa = RoaObject::decode_der(&roa_der).expect("decode roa");
|
||||
let ee = &roa.signed_object.signed_data.certificates[0].resource_cert;
|
||||
let ee_ip = ee
|
||||
.tbs
|
||||
.extensions
|
||||
.ip_resources
|
||||
.as_ref()
|
||||
.expect("fixture EE has IP resources");
|
||||
let first_family = ee_ip.families.first().expect("family");
|
||||
let first_item = match &first_family.choice {
|
||||
IpAddressChoice::AddressesOrRanges(items) => items.first().expect("item").clone(),
|
||||
IpAddressChoice::Inherit => panic!("fixture should not inherit"),
|
||||
};
|
||||
let issuer_ip = IpResourceSet {
|
||||
families: vec![IpAddressFamily {
|
||||
afi: first_family.afi,
|
||||
choice: IpAddressChoice::AddressesOrRanges(vec![first_item]),
|
||||
}],
|
||||
};
|
||||
let idx = build_issuer_resources_index(Some(&issuer_ip), None);
|
||||
|
||||
let strict_err = validate_ee_resources_for_mode(
|
||||
ee,
|
||||
Some(&issuer_ip),
|
||||
None,
|
||||
&idx,
|
||||
ResourceValidationMode::Rfc6487,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(matches!(
|
||||
strict_err,
|
||||
ObjectValidateError::EeResourcesNotSubset
|
||||
));
|
||||
|
||||
let vrs = validate_ee_resources_for_mode(
|
||||
ee,
|
||||
Some(&issuer_ip),
|
||||
None,
|
||||
&idx,
|
||||
ResourceValidationMode::ValidationUpdate03,
|
||||
)
|
||||
.expect("vrs resource resolution");
|
||||
assert_eq!(vrs.ip.expect("vrs ip").families.len(), 1);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validation_update_03_ee_as_vrs_filters_aspa_customer_resources() {
|
||||
let issuer_as = AsResourceSet {
|
||||
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![
|
||||
AsIdOrRange::Range {
|
||||
min: 64500,
|
||||
max: 64510,
|
||||
},
|
||||
])),
|
||||
rdi: None,
|
||||
};
|
||||
let child_as = AsResourceSet {
|
||||
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![
|
||||
AsIdOrRange::Range {
|
||||
min: 64505,
|
||||
max: 64520,
|
||||
},
|
||||
])),
|
||||
rdi: None,
|
||||
};
|
||||
let idx = build_issuer_resources_index(None, Some(&issuer_as));
|
||||
let vrs = intersect_ee_as_resources_vrs(&child_as, Some(&issuer_as), &idx)
|
||||
.expect("intersect AS resources");
|
||||
assert!(as_resource_set_contains_asn(&vrs, 64505));
|
||||
assert!(as_resource_set_contains_asn(&vrs, 64510));
|
||||
assert!(!as_resource_set_contains_asn(&vrs, 64511));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn validation_update_03_empty_ee_vrs_rejects_roa_and_aspa_outputs() {
|
||||
let roa_der =
|
||||
fixture_bytes("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/AS4538.roa");
|
||||
let roa = RoaObject::decode_der(&roa_der).expect("decode roa");
|
||||
let empty_ip_vrs = IpResourceSet {
|
||||
families: Vec::new(),
|
||||
};
|
||||
let roa_err = roa_to_vrps_with_vrs(&roa, Some(&empty_ip_vrs)).unwrap_err();
|
||||
assert!(matches!(roa_err, ObjectValidateError::EeResourcesNotSubset));
|
||||
let missing_ip_err = roa_to_vrps_with_vrs(&roa, None).unwrap_err();
|
||||
assert!(matches!(
|
||||
missing_ip_err,
|
||||
ObjectValidateError::EeResourcesNotSubset
|
||||
));
|
||||
|
||||
let aspa_der = fixture_bytes(
|
||||
"tests/fixtures/repository/chloe.sobornost.net/rpki/RIPE-nljobsnijders/5m80fwYws_3FiFD7JiQjAqZ1RYQ.asa",
|
||||
);
|
||||
let aspa = AspaObject::decode_der(&aspa_der).expect("decode aspa");
|
||||
let empty_as_vrs = AsResourceSet {
|
||||
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(Vec::new())),
|
||||
rdi: None,
|
||||
};
|
||||
let aspa_err = validate_aspa_customer_in_vrs(&aspa, Some(&empty_as_vrs)).unwrap_err();
|
||||
assert!(matches!(
|
||||
aspa_err,
|
||||
ObjectValidateError::EeResourcesNotSubset
|
||||
));
|
||||
let missing_as_err = validate_aspa_customer_in_vrs(&aspa, None).unwrap_err();
|
||||
assert!(matches!(
|
||||
missing_as_err,
|
||||
ObjectValidateError::EeResourcesNotSubset
|
||||
));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn extra_rfc_refs_for_crl_selection_distinguishes_crl_errors() {
|
||||
assert_eq!(
|
||||
@ -4976,11 +5397,13 @@ mod tests {
|
||||
issuer_resources_index: Arc::new(IssuerResourcesIndex::default()),
|
||||
issuer_effective_ip: None,
|
||||
issuer_effective_as: None,
|
||||
resource_validation_mode: ResourceValidationMode::default(),
|
||||
}),
|
||||
validation_time: OffsetDateTime::now_utc(),
|
||||
collect_vcir_local_outputs: false,
|
||||
strict_cms_der: false,
|
||||
strict_name: false,
|
||||
resource_validation_mode: ResourceValidationMode::default(),
|
||||
roa_task_indices: vec![0, 1],
|
||||
cached_roa_results: Vec::new(),
|
||||
roa_cache_stats: RoaValidationCacheStats::default(),
|
||||
|
||||
@ -21,7 +21,7 @@ use crate::data_model::router_cert::{
|
||||
use crate::fetch::rsync::RsyncFetcher;
|
||||
use crate::parallel::config::ParallelPhase2Config;
|
||||
use crate::parallel::repo_runtime::{RepoSyncRuntime, RepoSyncRuntimeOutcome};
|
||||
use crate::policy::Policy;
|
||||
use crate::policy::{Policy, ResourceValidationMode};
|
||||
use crate::replay::archive::ReplayArchiveIndex;
|
||||
use crate::replay::delta_archive::ReplayDeltaArchiveIndex;
|
||||
use crate::report::{RfcRef, Warning};
|
||||
@ -932,6 +932,7 @@ impl<'a> Rpkiv1PublicationPointRunner<'a> {
|
||||
&fresh_point,
|
||||
self.validation_time,
|
||||
self.timing.as_ref(),
|
||||
self.policy,
|
||||
if self.enable_child_certificate_validation_cache {
|
||||
Some(ChildCertificateValidationCacheContext {
|
||||
store: self.store,
|
||||
@ -2259,12 +2260,14 @@ fn discover_children_from_fresh_snapshot_with_audit<P: PublicationPointData>(
|
||||
return Err("lazy CA certificate requires store-backed child discovery".to_string());
|
||||
}
|
||||
};
|
||||
let default_policy = Policy::default();
|
||||
discover_children_from_fresh_snapshot_with_audit_cached_with_issuer_der(
|
||||
issuer,
|
||||
issuer_ca_der,
|
||||
publication_point,
|
||||
validation_time,
|
||||
timing,
|
||||
&default_policy,
|
||||
None,
|
||||
)
|
||||
}
|
||||
@ -2282,12 +2285,14 @@ fn discover_children_from_fresh_snapshot_with_audit_cached<P: PublicationPointDa
|
||||
return Err("lazy CA certificate requires store-backed child discovery".to_string());
|
||||
}
|
||||
};
|
||||
let default_policy = Policy::default();
|
||||
discover_children_from_fresh_snapshot_with_audit_cached_with_issuer_der(
|
||||
issuer,
|
||||
issuer_ca_der,
|
||||
publication_point,
|
||||
validation_time,
|
||||
timing,
|
||||
&default_policy,
|
||||
cache_context,
|
||||
)
|
||||
}
|
||||
@ -2300,6 +2305,7 @@ fn discover_children_from_fresh_snapshot_with_audit_cached_with_issuer_der<
|
||||
publication_point: &P,
|
||||
validation_time: time::OffsetDateTime,
|
||||
timing: Option<&TimingHandle>,
|
||||
policy: &Policy,
|
||||
cache_context: Option<ChildCertificateValidationCacheContext<'_>>,
|
||||
) -> Result<ChildDiscoveryOutput, String> {
|
||||
let locked_files = publication_point.files();
|
||||
@ -2906,6 +2912,7 @@ fn discover_children_from_fresh_snapshot_with_audit_cached_with_issuer_der<
|
||||
issuer.effective_as_resources.as_ref(),
|
||||
&issuer_resources_index,
|
||||
validation_time,
|
||||
policy.resource_validation_mode,
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(CaPathError::ChildNotCa) => {
|
||||
@ -2949,12 +2956,32 @@ fn discover_children_from_fresh_snapshot_with_audit_cached_with_issuer_der<
|
||||
|
||||
match router_result {
|
||||
Ok(router) => {
|
||||
let router_asns = match router_asns_for_resource_mode(
|
||||
&router.asns,
|
||||
issuer.effective_as_resources.as_ref(),
|
||||
policy.resource_validation_mode,
|
||||
) {
|
||||
Ok(v) => v,
|
||||
Err(detail) => {
|
||||
router_error = router_error.saturating_add(1);
|
||||
audits.push(ObjectAuditEntry {
|
||||
rsync_uri: f.rsync_uri.clone(),
|
||||
sha256_hex: sha256_hex_from_32(&f.sha256),
|
||||
kind: AuditObjectKind::RouterCertificate,
|
||||
result: AuditObjectResult::Error,
|
||||
detail: Some(format!(
|
||||
"router certificate AS resource validation failed: {detail}"
|
||||
)),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
};
|
||||
router_ok = router_ok.saturating_add(1);
|
||||
let source_object_hash = sha256_hex_from_32(&f.sha256);
|
||||
let item_effective_until = PackTime::from_utc_offset_datetime(
|
||||
router.resource_cert.tbs.validity_not_after,
|
||||
);
|
||||
for as_id in &router.asns {
|
||||
for as_id in &router_asns {
|
||||
router_keys.push(RouterKeyPayload {
|
||||
as_id: *as_id,
|
||||
ski: router.subject_key_identifier.clone(),
|
||||
@ -3006,8 +3033,7 @@ fn discover_children_from_fresh_snapshot_with_audit_cached_with_issuer_der<
|
||||
effective_not_before,
|
||||
effective_until,
|
||||
payload: ChildCertificateCachePayload::Router {
|
||||
router_keys: router
|
||||
.asns
|
||||
router_keys: router_asns
|
||||
.iter()
|
||||
.map(|as_id| ChildCertificateCacheRouterKeyProjection {
|
||||
as_id: *as_id,
|
||||
@ -3096,6 +3122,20 @@ fn discover_children_from_fresh_snapshot_with_audit_cached_with_issuer_der<
|
||||
validate_sub_ca_nanos = validate_sub_ca_nanos
|
||||
.saturating_add(t1.elapsed().as_nanos().min(u128::from(u64::MAX)) as u64);
|
||||
|
||||
if !validated.resource_warnings.is_empty() {
|
||||
audits.push(ObjectAuditEntry {
|
||||
rsync_uri: f.rsync_uri.clone(),
|
||||
sha256_hex: sha256_hex_from_32(&f.sha256),
|
||||
kind: AuditObjectKind::Certificate,
|
||||
result: AuditObjectResult::Ok,
|
||||
detail: Some(format!(
|
||||
"resource validation warning ({:?}): {}",
|
||||
policy.resource_validation_mode,
|
||||
validated.resource_warnings.summary()
|
||||
)),
|
||||
});
|
||||
}
|
||||
|
||||
let eff_ip_items = ip_item_count(validated.effective_ip_resources.as_ref());
|
||||
match bucketize(eff_ip_items) {
|
||||
0 => eff_ip_items_bucket_le_10 = eff_ip_items_bucket_le_10.saturating_add(1),
|
||||
@ -3482,6 +3522,65 @@ fn is_non_router_certificate(err: &BgpsecRouterCertificatePathError) -> bool {
|
||||
)
|
||||
}
|
||||
|
||||
fn router_asns_for_resource_mode(
|
||||
router_asns: &[u32],
|
||||
issuer_effective_as: Option<&crate::data_model::rc::AsResourceSet>,
|
||||
mode: ResourceValidationMode,
|
||||
) -> Result<Vec<u32>, String> {
|
||||
let Some(issuer_effective_as) = issuer_effective_as else {
|
||||
return Err("issuer has no effective AS resources".to_string());
|
||||
};
|
||||
|
||||
match mode {
|
||||
ResourceValidationMode::Rfc6487 => {
|
||||
let outside: Vec<u32> = router_asns
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|asn| !as_resource_set_contains_asn(issuer_effective_as, *asn))
|
||||
.collect();
|
||||
if outside.is_empty() {
|
||||
Ok(router_asns.to_vec())
|
||||
} else {
|
||||
Err(format!(
|
||||
"router AS resources are not a subset of issuer effective AS resources: {outside:?}"
|
||||
))
|
||||
}
|
||||
}
|
||||
ResourceValidationMode::ValidationUpdate03 => {
|
||||
let filtered: Vec<u32> = router_asns
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|asn| as_resource_set_contains_asn(issuer_effective_as, *asn))
|
||||
.collect();
|
||||
if filtered.is_empty() {
|
||||
Err("router AS resources have empty validated resource set".to_string())
|
||||
} else {
|
||||
Ok(filtered)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn as_resource_set_contains_asn(
|
||||
resources: &crate::data_model::rc::AsResourceSet,
|
||||
asn: u32,
|
||||
) -> bool {
|
||||
let Some(choice) = resources.asnum.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
match choice {
|
||||
crate::data_model::rc::AsIdentifierChoice::Inherit => false,
|
||||
crate::data_model::rc::AsIdentifierChoice::AsIdsOrRanges(items) => {
|
||||
items.iter().any(|item| match item {
|
||||
crate::data_model::rc::AsIdOrRange::Id(id) => *id == asn,
|
||||
crate::data_model::rc::AsIdOrRange::Range { min, max } => {
|
||||
*min <= asn && asn <= *max
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn select_issuer_crl_uri_for_child<'a>(
|
||||
child: &'a crate::data_model::rc::ResourceCertificate,
|
||||
crl_cache: &std::collections::HashMap<String, CachedIssuerCrl>,
|
||||
@ -3568,6 +3667,7 @@ fn validate_subordinate_ca_cert_with_cached_issuer(
|
||||
issuer_effective_as: Option<&crate::data_model::rc::AsResourceSet>,
|
||||
issuer_resources_index: &IssuerEffectiveResourcesIndex,
|
||||
validation_time: time::OffsetDateTime,
|
||||
resource_validation_mode: crate::policy::ResourceValidationMode,
|
||||
) -> Result<ValidatedSubordinateCaLite, CaPathError> {
|
||||
let verified_crl = ensure_issuer_crl_verified(issuer_crl_rsync_uri, crl_cache, issuer_ca_der)?;
|
||||
|
||||
@ -3584,6 +3684,7 @@ fn validate_subordinate_ca_cert_with_cached_issuer(
|
||||
issuer_effective_as,
|
||||
issuer_resources_index,
|
||||
validation_time,
|
||||
resource_validation_mode,
|
||||
)
|
||||
}
|
||||
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use super::*;
|
||||
use crate::data_model::rc::ResourceCertificate;
|
||||
use crate::data_model::rc::{AsIdOrRange, AsIdentifierChoice, AsResourceSet, ResourceCertificate};
|
||||
use crate::data_model::roa::RoaAfi;
|
||||
use crate::fetch::rsync::LocalDirRsyncFetcher;
|
||||
use crate::fetch::rsync::{RsyncFetchError, RsyncFetcher};
|
||||
@ -29,6 +29,48 @@ fn ipv4_addr(octets: [u8; 4]) -> [u8; 16] {
|
||||
addr
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn publication_point_cache_policy_fingerprint_includes_resource_validation_mode() {
|
||||
let mut strict_policy = Policy::default();
|
||||
strict_policy.resource_validation_mode = ResourceValidationMode::Rfc6487;
|
||||
let mut vrs_policy = Policy::default();
|
||||
vrs_policy.resource_validation_mode = ResourceValidationMode::ValidationUpdate03;
|
||||
|
||||
assert_ne!(
|
||||
publication_point_cache_policy_fingerprint(&strict_policy),
|
||||
publication_point_cache_policy_fingerprint(&vrs_policy)
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn router_asns_for_resource_mode_filters_vrs_and_rejects_strict_overclaim() {
|
||||
let issuer_as = AsResourceSet {
|
||||
asnum: Some(AsIdentifierChoice::AsIdsOrRanges(vec![
|
||||
AsIdOrRange::Range {
|
||||
min: 64500,
|
||||
max: 64510,
|
||||
},
|
||||
])),
|
||||
rdi: None,
|
||||
};
|
||||
|
||||
let strict = router_asns_for_resource_mode(
|
||||
&[64505, 64520],
|
||||
Some(&issuer_as),
|
||||
ResourceValidationMode::Rfc6487,
|
||||
)
|
||||
.unwrap_err();
|
||||
assert!(strict.contains("not a subset"), "{strict}");
|
||||
|
||||
let vrs = router_asns_for_resource_mode(
|
||||
&[64505, 64520],
|
||||
Some(&issuer_as),
|
||||
ResourceValidationMode::ValidationUpdate03,
|
||||
)
|
||||
.expect("vrs filters router asns");
|
||||
assert_eq!(vrs, vec![64505]);
|
||||
}
|
||||
|
||||
struct NeverHttpFetcher;
|
||||
impl Fetcher for NeverHttpFetcher {
|
||||
fn fetch(&self, _uri: &str) -> Result<Vec<u8>, String> {
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user