Harden first-release validation and dependency hygiene
This commit is contained in:
@@ -24,10 +24,9 @@ fn verify_child_signature(
|
||||
child: &X509Certificate<'_>,
|
||||
issuer_spki: &SubjectPublicKeyInfo<'_>,
|
||||
) -> Result<(), CaPathError> {
|
||||
|
||||
child
|
||||
.verify_signature(Some(issuer_spki))
|
||||
.map_err(|e| CaPathError::ChildSignatureInvalid(e.to_string()))
|
||||
child
|
||||
.verify_signature(Some(issuer_spki))
|
||||
.map_err(|e| CaPathError::ChildSignatureInvalid(e.to_string()))
|
||||
}
|
||||
|
||||
fn validate_child_aki_matches_issuer_ski(
|
||||
|
||||
@@ -3,10 +3,7 @@
|
||||
fn ip_resources_by_afi_items(
|
||||
set: &IpResourceSet,
|
||||
) -> Result<
|
||||
std::collections::BTreeMap<
|
||||
crate::model::rc::Afi,
|
||||
Vec<crate::model::rc::IpAddressOrRange>,
|
||||
>,
|
||||
std::collections::BTreeMap<crate::model::rc::Afi, Vec<crate::model::rc::IpAddressOrRange>>,
|
||||
CaPathError,
|
||||
> {
|
||||
let mut m: std::collections::BTreeMap<
|
||||
@@ -96,9 +93,7 @@ fn ip_items_to_merged_intervals(
|
||||
let mut intervals = Vec::new();
|
||||
for item in items {
|
||||
match item {
|
||||
crate::model::rc::IpAddressOrRange::Prefix(p) => {
|
||||
intervals.push(prefix_to_range(p))
|
||||
}
|
||||
crate::model::rc::IpAddressOrRange::Prefix(p) => intervals.push(prefix_to_range(p)),
|
||||
crate::model::rc::IpAddressOrRange::Range(r) => {
|
||||
intervals.push((r.min.clone(), r.max.clone()))
|
||||
}
|
||||
|
||||
@@ -65,8 +65,8 @@ fn validate_manifest_embedded_ee_cert_path(
|
||||
if !rem.is_empty() {
|
||||
return Err(CertPathError::IssuerSpkiTrailingBytes(rem.len()).into());
|
||||
}
|
||||
let issuer_crl = crate::model::crl::RpkixCrl::decode_der(crl_bytes)
|
||||
.map_err(CertPathError::from)?;
|
||||
let issuer_crl =
|
||||
crate::model::crl::RpkixCrl::decode_der(crl_bytes).map_err(CertPathError::from)?;
|
||||
let revoked_serials = issuer_crl
|
||||
.revoked_certs
|
||||
.iter()
|
||||
|
||||
@@ -502,9 +502,7 @@ pub(crate) fn try_build_fresh_publication_point_with_timing(
|
||||
// - If manifestNumber is higher, require thisUpdate to be more recent than the previously
|
||||
// validated thisUpdate.
|
||||
let anti_rollback_started = std::time::Instant::now();
|
||||
if let Some(old_meta) = store
|
||||
.get_manifest_anti_rollback_meta(manifest_rsync_uri)?
|
||||
{
|
||||
if let Some(old_meta) = store.get_manifest_anti_rollback_meta(manifest_rsync_uri)? {
|
||||
timing.anti_rollback_meta_hit = true;
|
||||
if old_meta.manifest_rsync_uri == manifest_rsync_uri {
|
||||
let new_num = manifest.manifest.manifest_number.bytes_be.as_slice();
|
||||
|
||||
@@ -16,17 +16,47 @@ fn process_roa_with_issuer(
|
||||
ta_constraints: Option<&crate::ta_constraints::TaConstraints>,
|
||||
) -> Result<Vec<Vrp>, ObjectValidateError> {
|
||||
let roa = {
|
||||
let _span = timing.as_ref().map(|t| t.span_phase("objects_roa_decode_and_validate_total"));
|
||||
RoaObject::decode_der_with_strict_options(file.bytes().map_err(ObjectValidateError::BytesLoad)?, strict_cms_der, strict_name)?
|
||||
let _span = timing
|
||||
.as_ref()
|
||||
.map(|t| t.span_phase("objects_roa_decode_and_validate_total"));
|
||||
RoaObject::decode_der_with_strict_options(
|
||||
file.bytes().map_err(ObjectValidateError::BytesLoad)?,
|
||||
strict_cms_der,
|
||||
strict_name,
|
||||
)?
|
||||
};
|
||||
roa.validate_embedded_ee_cert()?;
|
||||
roa.signed_object.verify()?;
|
||||
let ee = &roa.signed_object.signed_data.certificates[0];
|
||||
let crl_uri = choose_crl_uri_for_certificate(ee.resource_cert.tbs.extensions.crl_distribution_points_uris.as_ref(), crl_states)?;
|
||||
let crl_uri = choose_crl_uri_for_certificate(
|
||||
ee.resource_cert
|
||||
.tbs
|
||||
.extensions
|
||||
.crl_distribution_points_uris
|
||||
.as_ref(),
|
||||
crl_states,
|
||||
)?;
|
||||
let verified_crl = ensure_issuer_crl_verified(crl_uri, crl_states, issuer_ca_der)?;
|
||||
validate_signed_object_ee_cert_path_fast(ee, issuer_ca, issuer_spki, &verified_crl.crl, &verified_crl.revoked_serials, issuer_ca_rsync_uri, Some(crl_uri), validation_time)?;
|
||||
let ee_vrs = validate_ee_resources_for_mode(&ee.resource_cert, issuer_effective_ip, issuer_effective_as, issuer_resources_index, resource_validation_mode)?;
|
||||
if let Some(constraints) = ta_constraints { constraints.validate_ee_certificate(&ee.resource_cert)?; }
|
||||
validate_signed_object_ee_cert_path_fast(
|
||||
ee,
|
||||
issuer_ca,
|
||||
issuer_spki,
|
||||
&verified_crl.crl,
|
||||
&verified_crl.revoked_serials,
|
||||
issuer_ca_rsync_uri,
|
||||
Some(crl_uri),
|
||||
validation_time,
|
||||
)?;
|
||||
let ee_vrs = validate_ee_resources_for_mode(
|
||||
&ee.resource_cert,
|
||||
issuer_effective_ip,
|
||||
issuer_effective_as,
|
||||
issuer_resources_index,
|
||||
resource_validation_mode,
|
||||
)?;
|
||||
if let Some(constraints) = ta_constraints {
|
||||
constraints.validate_ee_certificate(&ee.resource_cert)?;
|
||||
}
|
||||
roa_to_vrps_with_vrs(&roa, ee_vrs.ip.as_ref())
|
||||
}
|
||||
|
||||
@@ -48,21 +78,52 @@ fn process_roa_with_issuer_parallel(
|
||||
ta_constraints: Option<&crate::ta_constraints::TaConstraints>,
|
||||
) -> Result<Vec<Vrp>, ObjectValidateError> {
|
||||
let roa = {
|
||||
let _span = timing.as_ref().map(|t| t.span_phase("objects_roa_decode_and_validate_total"));
|
||||
RoaObject::decode_der_with_strict_options(file.bytes().map_err(ObjectValidateError::BytesLoad)?, strict_cms_der, strict_name)?
|
||||
let _span = timing
|
||||
.as_ref()
|
||||
.map(|t| t.span_phase("objects_roa_decode_and_validate_total"));
|
||||
RoaObject::decode_der_with_strict_options(
|
||||
file.bytes().map_err(ObjectValidateError::BytesLoad)?,
|
||||
strict_cms_der,
|
||||
strict_name,
|
||||
)?
|
||||
};
|
||||
roa.validate_embedded_ee_cert()?;
|
||||
roa.signed_object.verify()?;
|
||||
let ee = &roa.signed_object.signed_data.certificates[0];
|
||||
let (crl_uri, verified_crl) = {
|
||||
let mut states = crl_states.lock().expect("parallel issuer CRL state lock");
|
||||
let uri = choose_crl_uri_for_certificate(ee.resource_cert.tbs.extensions.crl_distribution_points_uris.as_ref(), &states)?.to_string();
|
||||
let uri = choose_crl_uri_for_certificate(
|
||||
ee.resource_cert
|
||||
.tbs
|
||||
.extensions
|
||||
.crl_distribution_points_uris
|
||||
.as_ref(),
|
||||
&states,
|
||||
)?
|
||||
.to_string();
|
||||
let value = ensure_issuer_crl_verified(&uri, &mut states, issuer_ca_der)?;
|
||||
(uri, value)
|
||||
};
|
||||
validate_signed_object_ee_cert_path_fast(ee, issuer_ca, issuer_spki, &verified_crl.crl, &verified_crl.revoked_serials, issuer_ca_rsync_uri, Some(crl_uri.as_str()), validation_time)?;
|
||||
let ee_vrs = validate_ee_resources_for_mode(&ee.resource_cert, issuer_effective_ip, issuer_effective_as, issuer_resources_index, resource_validation_mode)?;
|
||||
if let Some(constraints) = ta_constraints { constraints.validate_ee_certificate(&ee.resource_cert)?; }
|
||||
validate_signed_object_ee_cert_path_fast(
|
||||
ee,
|
||||
issuer_ca,
|
||||
issuer_spki,
|
||||
&verified_crl.crl,
|
||||
&verified_crl.revoked_serials,
|
||||
issuer_ca_rsync_uri,
|
||||
Some(crl_uri.as_str()),
|
||||
validation_time,
|
||||
)?;
|
||||
let ee_vrs = validate_ee_resources_for_mode(
|
||||
&ee.resource_cert,
|
||||
issuer_effective_ip,
|
||||
issuer_effective_as,
|
||||
issuer_resources_index,
|
||||
resource_validation_mode,
|
||||
)?;
|
||||
if let Some(constraints) = ta_constraints {
|
||||
constraints.validate_ee_certificate(&ee.resource_cert)?;
|
||||
}
|
||||
roa_to_vrps_with_vrs(&roa, ee_vrs.ip.as_ref())
|
||||
}
|
||||
|
||||
@@ -84,17 +145,50 @@ fn process_aspa_with_issuer(
|
||||
ta_constraints: Option<&crate::ta_constraints::TaConstraints>,
|
||||
) -> Result<AspaAttestation, ObjectValidateError> {
|
||||
let aspa = {
|
||||
let _span = timing.as_ref().map(|t| t.span_phase("objects_aspa_decode_and_validate_total"));
|
||||
AspaObject::decode_der_with_strict_options(file.bytes().map_err(ObjectValidateError::BytesLoad)?, strict_cms_der, strict_name)?
|
||||
let _span = timing
|
||||
.as_ref()
|
||||
.map(|t| t.span_phase("objects_aspa_decode_and_validate_total"));
|
||||
AspaObject::decode_der_with_strict_options(
|
||||
file.bytes().map_err(ObjectValidateError::BytesLoad)?,
|
||||
strict_cms_der,
|
||||
strict_name,
|
||||
)?
|
||||
};
|
||||
aspa.validate_embedded_ee_cert()?;
|
||||
aspa.signed_object.verify()?;
|
||||
let ee = &aspa.signed_object.signed_data.certificates[0];
|
||||
let crl_uri = choose_crl_uri_for_certificate(ee.resource_cert.tbs.extensions.crl_distribution_points_uris.as_ref(), crl_states)?;
|
||||
let crl_uri = choose_crl_uri_for_certificate(
|
||||
ee.resource_cert
|
||||
.tbs
|
||||
.extensions
|
||||
.crl_distribution_points_uris
|
||||
.as_ref(),
|
||||
crl_states,
|
||||
)?;
|
||||
let verified_crl = ensure_issuer_crl_verified(crl_uri, crl_states, issuer_ca_der)?;
|
||||
validate_signed_object_ee_cert_path_fast(ee, issuer_ca, issuer_spki, &verified_crl.crl, &verified_crl.revoked_serials, issuer_ca_rsync_uri, Some(crl_uri), validation_time)?;
|
||||
let ee_vrs = validate_ee_resources_for_mode(&ee.resource_cert, issuer_effective_ip, issuer_effective_as, issuer_resources_index, resource_validation_mode)?;
|
||||
if let Some(constraints) = ta_constraints { constraints.validate_ee_certificate(&ee.resource_cert)?; }
|
||||
validate_signed_object_ee_cert_path_fast(
|
||||
ee,
|
||||
issuer_ca,
|
||||
issuer_spki,
|
||||
&verified_crl.crl,
|
||||
&verified_crl.revoked_serials,
|
||||
issuer_ca_rsync_uri,
|
||||
Some(crl_uri),
|
||||
validation_time,
|
||||
)?;
|
||||
let ee_vrs = validate_ee_resources_for_mode(
|
||||
&ee.resource_cert,
|
||||
issuer_effective_ip,
|
||||
issuer_effective_as,
|
||||
issuer_resources_index,
|
||||
resource_validation_mode,
|
||||
)?;
|
||||
if let Some(constraints) = ta_constraints {
|
||||
constraints.validate_ee_certificate(&ee.resource_cert)?;
|
||||
}
|
||||
validate_aspa_customer_in_vrs(&aspa, ee_vrs.asn.as_ref())?;
|
||||
Ok(AspaAttestation { customer_as_id: aspa.aspa.customer_as_id, provider_as_ids: aspa.aspa.provider_as_ids.clone() })
|
||||
Ok(AspaAttestation {
|
||||
customer_as_id: aspa.aspa.customer_as_id,
|
||||
provider_as_ids: aspa.aspa.provider_as_ids.clone(),
|
||||
})
|
||||
}
|
||||
|
||||
@@ -2,21 +2,33 @@
|
||||
|
||||
const RFC_NONE: &[RfcRef] = &[];
|
||||
const RFC_CRLDP: &[RfcRef] = &[RfcRef("RFC 6487 §4.8.6")];
|
||||
const RFC_CRLDP_AND_LOCKED_PACK: &[RfcRef] = &[RfcRef("RFC 6487 §4.8.6"), RfcRef("RFC 9286 §4.2.1")];
|
||||
const RFC_CRLDP_AND_LOCKED_PACK: &[RfcRef] =
|
||||
&[RfcRef("RFC 6487 §4.8.6"), RfcRef("RFC 9286 §4.2.1")];
|
||||
|
||||
fn ber_compatible_cms_warning(der: &[u8], rsync_uri: &str, object_kind: &str) -> Option<Warning> {
|
||||
let strict_error = RpkiSignedObject::strict_cms_der_error(der)?;
|
||||
Some(Warning::new(format!("accepted BER-compatible CMS encoding for {object_kind}: {rsync_uri}: {strict_error}"))
|
||||
Some(
|
||||
Warning::new(format!(
|
||||
"accepted BER-compatible CMS encoding for {object_kind}: {rsync_uri}: {strict_error}"
|
||||
))
|
||||
.with_category(WarningCategory::BerCompatibleCmsEncoding)
|
||||
.with_rfc_refs(&[RfcRef("X.690 §10"), RfcRef("RFC 6488 §2")])
|
||||
.with_context(rsync_uri))
|
||||
.with_context(rsync_uri),
|
||||
)
|
||||
}
|
||||
fn ber_compatible_cms_warning_for_file(file: &PackFile, object_kind: &str) -> Option<Warning> {
|
||||
ber_compatible_cms_warning(file.bytes().ok()?, &file.rsync_uri, object_kind)
|
||||
}
|
||||
|
||||
fn decode_resource_certificate_with_policy(der: &[u8], policy: &Policy) -> Result<ResourceCertificate, crate::model::rc::ResourceCertificateDecodeError> {
|
||||
if policy.strict.name { ResourceCertificate::decode_der_with_strict_name(der) } else { ResourceCertificate::decode_der(der) }
|
||||
fn decode_resource_certificate_with_policy(
|
||||
der: &[u8],
|
||||
policy: &Policy,
|
||||
) -> Result<ResourceCertificate, crate::model::rc::ResourceCertificateDecodeError> {
|
||||
if policy.strict.name {
|
||||
ResourceCertificate::decode_der_with_strict_name(der)
|
||||
} else {
|
||||
ResourceCertificate::decode_der(der)
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug)]
|
||||
@@ -39,13 +51,24 @@ pub(crate) struct IssuerResourcesIndex {
|
||||
}
|
||||
|
||||
fn extra_rfc_refs_for_crl_selection(error: &ObjectValidateError) -> &'static [RfcRef] {
|
||||
match error { ObjectValidateError::MissingCrlDpUris => RFC_CRLDP, ObjectValidateError::CrlNotFound(_) => RFC_CRLDP_AND_LOCKED_PACK, _ => RFC_NONE }
|
||||
match error {
|
||||
ObjectValidateError::MissingCrlDpUris => RFC_CRLDP,
|
||||
ObjectValidateError::CrlNotFound(_) => RFC_CRLDP_AND_LOCKED_PACK,
|
||||
_ => RFC_NONE,
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct Vrp { pub asn: u32, pub prefix: IpPrefix, pub max_length: u16 }
|
||||
pub struct Vrp {
|
||||
pub asn: u32,
|
||||
pub prefix: IpPrefix,
|
||||
pub max_length: u16,
|
||||
}
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct AspaAttestation { pub customer_as_id: u32, pub provider_as_ids: Vec<u32> }
|
||||
pub struct AspaAttestation {
|
||||
pub customer_as_id: u32,
|
||||
pub provider_as_ids: Vec<u32>,
|
||||
}
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
pub struct RouterKeyPayload {
|
||||
pub as_id: u32,
|
||||
@@ -76,7 +99,9 @@ pub struct ObjectsStats {
|
||||
}
|
||||
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct RoaTaskOk { pub(crate) vrps: Vec<Vrp> }
|
||||
pub(crate) struct RoaTaskOk {
|
||||
pub(crate) vrps: Vec<Vrp>,
|
||||
}
|
||||
#[derive(Debug)]
|
||||
pub(crate) struct RoaTaskResult {
|
||||
pub(crate) publication_point_id: u64,
|
||||
|
||||
@@ -88,11 +88,9 @@ pub(crate) fn prepare_publication_point_for_parallel_roa_and_ta_constraints<
|
||||
);
|
||||
return ParallelObjectsPrepare::Complete(empty(stats, warnings));
|
||||
}
|
||||
if let Some(warning) = ber_compatible_cms_warning(
|
||||
publication_point.manifest_bytes(),
|
||||
manifest_uri,
|
||||
"manifest",
|
||||
) {
|
||||
if let Some(warning) =
|
||||
ber_compatible_cms_warning(publication_point.manifest_bytes(), manifest_uri, "manifest")
|
||||
{
|
||||
warnings.push(warning);
|
||||
}
|
||||
let issuer_ca = match decode_resource_certificate_with_policy(issuer_ca_der, policy) {
|
||||
@@ -138,22 +136,17 @@ pub(crate) fn prepare_publication_point_for_parallel_roa_and_ta_constraints<
|
||||
.iter()
|
||||
.filter(|file| file.rsync_uri.ends_with(".crl"))
|
||||
.filter_map(|file| {
|
||||
file.bytes_cloned().ok().map(|bytes| {
|
||||
(
|
||||
file.rsync_uri.clone(),
|
||||
IssuerCrlState::Pending { bytes },
|
||||
)
|
||||
})
|
||||
file.bytes_cloned()
|
||||
.ok()
|
||||
.map(|bytes| (file.rsync_uri.clone(), IssuerCrlState::Pending { bytes }))
|
||||
})
|
||||
.collect::<HashMap<_, _>>();
|
||||
if crl_states.is_empty() && (stats.roa_total > 0 || stats.aspa_total > 0) {
|
||||
stats.publication_point_dropped = true;
|
||||
warnings.push(
|
||||
Warning::new(
|
||||
"dropping publication point: no CRL files in validated publication point",
|
||||
)
|
||||
.with_rfc_refs(&[RfcRef("RFC 6487 §4.8.6"), RfcRef("RFC 9286 §7")])
|
||||
.with_context(manifest_uri),
|
||||
Warning::new("dropping publication point: no CRL files in validated publication point")
|
||||
.with_rfc_refs(&[RfcRef("RFC 6487 §4.8.6"), RfcRef("RFC 9286 §7")])
|
||||
.with_context(manifest_uri),
|
||||
);
|
||||
return ParallelObjectsPrepare::Complete(empty(stats, warnings));
|
||||
}
|
||||
@@ -217,9 +210,7 @@ pub(crate) fn reduce_parallel_roa_stage(
|
||||
for (index, file) in shared.locked_files.iter().enumerate() {
|
||||
if file.rsync_uri.ends_with(".roa") {
|
||||
let result = match result_iter.peek() {
|
||||
Some(result) if result.index == index => {
|
||||
result_iter.next().expect("peeked result")
|
||||
}
|
||||
Some(result) if result.index == index => result_iter.next().expect("peeked result"),
|
||||
Some(result) => {
|
||||
return Err(format!(
|
||||
"unexpected ROA task result index {} while reducing {}",
|
||||
@@ -254,12 +245,9 @@ pub(crate) fn reduce_parallel_roa_stage(
|
||||
let mut refs = vec![RfcRef("RFC 6488 §3"), RfcRef("RFC 9582 §4-§5")];
|
||||
refs.extend_from_slice(extra_rfc_refs_for_crl_selection(&error));
|
||||
warnings.push(
|
||||
Warning::new(format!(
|
||||
"dropping invalid ROA: {}: {error}",
|
||||
file.rsync_uri
|
||||
))
|
||||
.with_rfc_refs(&refs)
|
||||
.with_context(&file.rsync_uri),
|
||||
Warning::new(format!("dropping invalid ROA: {}: {error}", file.rsync_uri))
|
||||
.with_rfc_refs(&refs)
|
||||
.with_context(&file.rsync_uri),
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -37,8 +37,8 @@ fn ensure_issuer_crl_verified(
|
||||
IssuerCrlState::Verified(v) => Ok(Arc::clone(v)),
|
||||
IssuerCrlState::Pending { bytes } => {
|
||||
let der = std::mem::take(bytes);
|
||||
let crl = crate::model::crl::RpkixCrl::decode_der(&der)
|
||||
.map_err(CertPathError::CrlDecode)?;
|
||||
let crl =
|
||||
crate::model::crl::RpkixCrl::decode_der(&der).map_err(CertPathError::CrlDecode)?;
|
||||
crl.verify_signature_with_issuer_certificate_der(issuer_ca_der)
|
||||
.map_err(CertPathError::CrlVerify)?;
|
||||
|
||||
@@ -227,10 +227,7 @@ fn validate_aspa_customer_in_vrs(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
fn as_resource_set_contains_asn(
|
||||
resources: &crate::model::rc::AsResourceSet,
|
||||
asn: u32,
|
||||
) -> bool {
|
||||
fn as_resource_set_contains_asn(resources: &crate::model::rc::AsResourceSet, asn: u32) -> bool {
|
||||
let Some(choice) = resources.asnum.as_ref() else {
|
||||
return false;
|
||||
};
|
||||
@@ -605,13 +602,15 @@ fn build_issuer_resources_index(
|
||||
|
||||
if let Some(asr) = issuer_effective_as {
|
||||
if let Some(choice) = asr.asnum.as_ref()
|
||||
&& !matches!(choice, AsIdentifierChoice::Inherit) {
|
||||
idx.asnum = Some(as_choice_to_merged_intervals(choice));
|
||||
}
|
||||
&& !matches!(choice, AsIdentifierChoice::Inherit)
|
||||
{
|
||||
idx.asnum = Some(as_choice_to_merged_intervals(choice));
|
||||
}
|
||||
if let Some(choice) = asr.rdi.as_ref()
|
||||
&& !matches!(choice, AsIdentifierChoice::Inherit) {
|
||||
idx.rdi = Some(as_choice_to_merged_intervals(choice));
|
||||
}
|
||||
&& !matches!(choice, AsIdentifierChoice::Inherit)
|
||||
{
|
||||
idx.rdi = Some(as_choice_to_merged_intervals(choice));
|
||||
}
|
||||
}
|
||||
|
||||
idx
|
||||
|
||||
@@ -9,8 +9,15 @@ pub fn process_publication_point_for_issuer<P: PublicationPointData>(
|
||||
timing: Option<&TimingHandle>,
|
||||
) -> ObjectsOutput {
|
||||
process_publication_point_for_issuer_with_ta_constraints(
|
||||
publication_point, policy, issuer_ca_der, issuer_ca_rsync_uri,
|
||||
issuer_effective_ip, issuer_effective_as, validation_time, timing, None,
|
||||
publication_point,
|
||||
policy,
|
||||
issuer_ca_der,
|
||||
issuer_ca_rsync_uri,
|
||||
issuer_effective_ip,
|
||||
issuer_effective_as,
|
||||
validation_time,
|
||||
timing,
|
||||
None,
|
||||
)
|
||||
}
|
||||
|
||||
@@ -28,30 +35,58 @@ pub fn process_publication_point_for_issuer_with_ta_constraints<P: PublicationPo
|
||||
let manifest_uri = publication_point.manifest_rsync_uri();
|
||||
let files = publication_point.files();
|
||||
let mut stats = ObjectsStats {
|
||||
roa_total: files.iter().filter(|f| f.rsync_uri.ends_with(".roa")).count(),
|
||||
aspa_total: files.iter().filter(|f| f.rsync_uri.ends_with(".asa")).count(),
|
||||
roa_total: files
|
||||
.iter()
|
||||
.filter(|f| f.rsync_uri.ends_with(".roa"))
|
||||
.count(),
|
||||
aspa_total: files
|
||||
.iter()
|
||||
.filter(|f| f.rsync_uri.ends_with(".asa"))
|
||||
.count(),
|
||||
..ObjectsStats::default()
|
||||
};
|
||||
let mut warnings = Vec::new();
|
||||
let mut audit = Vec::new();
|
||||
let empty = |stats: ObjectsStats, warnings: Vec<Warning>, audit: Vec<ObjectAuditEntry>| ObjectsOutput {
|
||||
vrps: Vec::new(), aspas: Vec::new(), router_keys: Vec::new(), warnings, stats, audit,
|
||||
};
|
||||
let empty =
|
||||
|stats: ObjectsStats, warnings: Vec<Warning>, audit: Vec<ObjectAuditEntry>| ObjectsOutput {
|
||||
vrps: Vec::new(),
|
||||
aspas: Vec::new(),
|
||||
router_keys: Vec::new(),
|
||||
warnings,
|
||||
stats,
|
||||
audit,
|
||||
};
|
||||
if let Err(error) = ManifestObject::decode_der_with_strict_options(
|
||||
publication_point.manifest_bytes(), policy.strict.cms_der, policy.strict.name,
|
||||
publication_point.manifest_bytes(),
|
||||
policy.strict.cms_der,
|
||||
policy.strict.name,
|
||||
) {
|
||||
stats.publication_point_dropped = true;
|
||||
warnings.push(Warning::new(format!("dropping publication point: manifest decode failed: {error}"))
|
||||
.with_rfc_refs(&[RfcRef("RFC 9286 §4"), RfcRef("RFC 9286 §6.6")]).with_context(manifest_uri));
|
||||
warnings.push(
|
||||
Warning::new(format!(
|
||||
"dropping publication point: manifest decode failed: {error}"
|
||||
))
|
||||
.with_rfc_refs(&[RfcRef("RFC 9286 §4"), RfcRef("RFC 9286 §6.6")])
|
||||
.with_context(manifest_uri),
|
||||
);
|
||||
return empty(stats, warnings, audit);
|
||||
}
|
||||
if let Some(warning) = ber_compatible_cms_warning(publication_point.manifest_bytes(), manifest_uri, "manifest") { warnings.push(warning); }
|
||||
if let Some(warning) =
|
||||
ber_compatible_cms_warning(publication_point.manifest_bytes(), manifest_uri, "manifest")
|
||||
{
|
||||
warnings.push(warning);
|
||||
}
|
||||
let issuer_ca = match decode_resource_certificate_with_policy(issuer_ca_der, policy) {
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
stats.publication_point_dropped = true;
|
||||
warnings.push(Warning::new(format!("dropping publication point: issuer CA decode failed: {error}"))
|
||||
.with_rfc_refs(&[RfcRef("RFC 6487 §7.2"), RfcRef("RFC 5280 §6.1")]).with_context(manifest_uri));
|
||||
warnings.push(
|
||||
Warning::new(format!(
|
||||
"dropping publication point: issuer CA decode failed: {error}"
|
||||
))
|
||||
.with_rfc_refs(&[RfcRef("RFC 6487 §7.2"), RfcRef("RFC 5280 §6.1")])
|
||||
.with_context(manifest_uri),
|
||||
);
|
||||
return empty(stats, warnings, audit);
|
||||
}
|
||||
};
|
||||
@@ -59,59 +94,205 @@ pub fn process_publication_point_for_issuer_with_ta_constraints<P: PublicationPo
|
||||
Ok(([], spki)) => spki,
|
||||
Ok((remaining, _)) => {
|
||||
stats.publication_point_dropped = true;
|
||||
warnings.push(Warning::new(format!("dropping publication point: issuer SPKI has {} trailing bytes", remaining.len())).with_context(manifest_uri));
|
||||
warnings.push(
|
||||
Warning::new(format!(
|
||||
"dropping publication point: issuer SPKI has {} trailing bytes",
|
||||
remaining.len()
|
||||
))
|
||||
.with_context(manifest_uri),
|
||||
);
|
||||
return empty(stats, warnings, audit);
|
||||
}
|
||||
Err(error) => {
|
||||
stats.publication_point_dropped = true;
|
||||
warnings.push(Warning::new(format!("dropping publication point: issuer SPKI parse failed: {error}")).with_context(manifest_uri));
|
||||
warnings.push(
|
||||
Warning::new(format!(
|
||||
"dropping publication point: issuer SPKI parse failed: {error}"
|
||||
))
|
||||
.with_context(manifest_uri),
|
||||
);
|
||||
return empty(stats, warnings, audit);
|
||||
}
|
||||
};
|
||||
let mut crl_states: std::collections::HashMap<String, IssuerCrlState> = match files.iter().filter(|f| f.rsync_uri.ends_with(".crl")).map(|file| {
|
||||
Ok((file.rsync_uri.clone(), IssuerCrlState::Pending { bytes: file.bytes_cloned().map_err(|e| format!("CRL bytes load failed: {e}"))? }))
|
||||
}).collect::<Result<_, String>>() { Ok(value) => value, Err(error) => { stats.publication_point_dropped = true; warnings.push(Warning::new(error).with_context(manifest_uri)); return empty(stats, warnings, audit); } };
|
||||
let issuer_resources_index = build_issuer_resources_index(issuer_effective_ip, issuer_effective_as);
|
||||
let mut crl_states: std::collections::HashMap<String, IssuerCrlState> = match files
|
||||
.iter()
|
||||
.filter(|f| f.rsync_uri.ends_with(".crl"))
|
||||
.map(|file| {
|
||||
Ok((
|
||||
file.rsync_uri.clone(),
|
||||
IssuerCrlState::Pending {
|
||||
bytes: file
|
||||
.bytes_cloned()
|
||||
.map_err(|e| format!("CRL bytes load failed: {e}"))?,
|
||||
},
|
||||
))
|
||||
})
|
||||
.collect::<Result<_, String>>()
|
||||
{
|
||||
Ok(value) => value,
|
||||
Err(error) => {
|
||||
stats.publication_point_dropped = true;
|
||||
warnings.push(Warning::new(error).with_context(manifest_uri));
|
||||
return empty(stats, warnings, audit);
|
||||
}
|
||||
};
|
||||
let issuer_resources_index =
|
||||
build_issuer_resources_index(issuer_effective_ip, issuer_effective_as);
|
||||
if crl_states.is_empty() && (stats.roa_total > 0 || stats.aspa_total > 0) {
|
||||
stats.publication_point_dropped = true;
|
||||
warnings.push(Warning::new("dropping publication point: no CRL files in validated publication point").with_rfc_refs(&[RfcRef("RFC 6487 §4.8.6"), RfcRef("RFC 9286 §7")]).with_context(manifest_uri));
|
||||
warnings.push(
|
||||
Warning::new("dropping publication point: no CRL files in validated publication point")
|
||||
.with_rfc_refs(&[RfcRef("RFC 6487 §4.8.6"), RfcRef("RFC 9286 §7")])
|
||||
.with_context(manifest_uri),
|
||||
);
|
||||
return empty(stats, warnings, audit);
|
||||
}
|
||||
let mut vrps = Vec::new();
|
||||
let mut aspas = Vec::new();
|
||||
for (index, file) in files.iter().enumerate() {
|
||||
if file.rsync_uri.ends_with(".roa") {
|
||||
let result = process_roa_with_issuer(file, issuer_ca_der, &issuer_ca, &issuer_spki, issuer_ca_rsync_uri, &mut crl_states, &issuer_resources_index, issuer_effective_ip, issuer_effective_as, validation_time, timing, policy.strict.cms_der, policy.strict.name, policy.resource_validation_mode, ta_constraints);
|
||||
let result = process_roa_with_issuer(
|
||||
file,
|
||||
issuer_ca_der,
|
||||
&issuer_ca,
|
||||
&issuer_spki,
|
||||
issuer_ca_rsync_uri,
|
||||
&mut crl_states,
|
||||
&issuer_resources_index,
|
||||
issuer_effective_ip,
|
||||
issuer_effective_as,
|
||||
validation_time,
|
||||
timing,
|
||||
policy.strict.cms_der,
|
||||
policy.strict.name,
|
||||
policy.resource_validation_mode,
|
||||
ta_constraints,
|
||||
);
|
||||
match result {
|
||||
Ok(mut values) => {
|
||||
stats.roa_ok += 1;
|
||||
vrps.append(&mut values);
|
||||
audit.push(ObjectAuditEntry { rsync_uri: file.rsync_uri.clone(), sha256_hex: sha256_hex_from_32(&file.sha256), kind: AuditObjectKind::Roa, result: AuditObjectResult::Ok, detail: None });
|
||||
if let Some(warning) = ber_compatible_cms_warning_for_file(file, "ROA") { warnings.push(warning); }
|
||||
audit.push(ObjectAuditEntry {
|
||||
rsync_uri: file.rsync_uri.clone(),
|
||||
sha256_hex: sha256_hex_from_32(&file.sha256),
|
||||
kind: AuditObjectKind::Roa,
|
||||
result: AuditObjectResult::Ok,
|
||||
detail: None,
|
||||
});
|
||||
if let Some(warning) = ber_compatible_cms_warning_for_file(file, "ROA") {
|
||||
warnings.push(warning);
|
||||
}
|
||||
}
|
||||
Err(error) => match policy.signed_object_failure_policy {
|
||||
SignedObjectFailurePolicy::DropObject => {
|
||||
audit.push(ObjectAuditEntry { rsync_uri: file.rsync_uri.clone(), sha256_hex: sha256_hex_from_32(&file.sha256), kind: AuditObjectKind::Roa, result: AuditObjectResult::Error, detail: Some(error.to_string()) });
|
||||
let mut refs = vec![RfcRef("RFC 6488 §3"), RfcRef("RFC 9582 §4-§5")]; refs.extend_from_slice(extra_rfc_refs_for_crl_selection(&error));
|
||||
warnings.push(Warning::new(format!("dropping invalid ROA: {}: {error}", file.rsync_uri)).with_rfc_refs(&refs).with_context(&file.rsync_uri));
|
||||
audit.push(ObjectAuditEntry {
|
||||
rsync_uri: file.rsync_uri.clone(),
|
||||
sha256_hex: sha256_hex_from_32(&file.sha256),
|
||||
kind: AuditObjectKind::Roa,
|
||||
result: AuditObjectResult::Error,
|
||||
detail: Some(error.to_string()),
|
||||
});
|
||||
let mut refs = vec![RfcRef("RFC 6488 §3"), RfcRef("RFC 9582 §4-§5")];
|
||||
refs.extend_from_slice(extra_rfc_refs_for_crl_selection(&error));
|
||||
warnings.push(
|
||||
Warning::new(format!(
|
||||
"dropping invalid ROA: {}: {error}",
|
||||
file.rsync_uri
|
||||
))
|
||||
.with_rfc_refs(&refs)
|
||||
.with_context(&file.rsync_uri),
|
||||
);
|
||||
}
|
||||
SignedObjectFailurePolicy::DropPublicationPoint => {
|
||||
stats.publication_point_dropped = true;
|
||||
warnings.push(Warning::new(format!("dropping publication point due to invalid ROA: {}: {error}", file.rsync_uri)).with_context(manifest_uri));
|
||||
for later in files.iter().skip(index + 1) { if later.rsync_uri.ends_with(".roa") || later.rsync_uri.ends_with(".asa") { audit.push(ObjectAuditEntry { rsync_uri: later.rsync_uri.clone(), sha256_hex: sha256_hex_from_32(&later.sha256), kind: if later.rsync_uri.ends_with(".roa") { AuditObjectKind::Roa } else { AuditObjectKind::Aspa }, result: AuditObjectResult::Skipped, detail: Some("skipped due to signed_object_failure_policy=drop_publication_point".to_string()) }); } }
|
||||
warnings.push(
|
||||
Warning::new(format!(
|
||||
"dropping publication point due to invalid ROA: {}: {error}",
|
||||
file.rsync_uri
|
||||
))
|
||||
.with_context(manifest_uri),
|
||||
);
|
||||
for later in files.iter().skip(index + 1) {
|
||||
if later.rsync_uri.ends_with(".roa")
|
||||
|| later.rsync_uri.ends_with(".asa")
|
||||
{
|
||||
audit.push(ObjectAuditEntry { rsync_uri: later.rsync_uri.clone(), sha256_hex: sha256_hex_from_32(&later.sha256), kind: if later.rsync_uri.ends_with(".roa") { AuditObjectKind::Roa } else { AuditObjectKind::Aspa }, result: AuditObjectResult::Skipped, detail: Some("skipped due to signed_object_failure_policy=drop_publication_point".to_string()) });
|
||||
}
|
||||
}
|
||||
return empty(stats, warnings, audit);
|
||||
}
|
||||
},
|
||||
}
|
||||
} else if file.rsync_uri.ends_with(".asa") {
|
||||
match process_aspa_with_issuer(file, issuer_ca_der, &issuer_ca, &issuer_spki, issuer_ca_rsync_uri, &mut crl_states, &issuer_resources_index, issuer_effective_ip, issuer_effective_as, validation_time, timing, policy.strict.cms_der, policy.strict.name, policy.resource_validation_mode, ta_constraints) {
|
||||
Ok(attestation) => { stats.aspa_ok += 1; aspas.push(attestation); audit.push(ObjectAuditEntry { rsync_uri: file.rsync_uri.clone(), sha256_hex: sha256_hex_from_32(&file.sha256), kind: AuditObjectKind::Aspa, result: AuditObjectResult::Ok, detail: None }); if let Some(warning) = ber_compatible_cms_warning_for_file(file, "ASPA") { warnings.push(warning); } }
|
||||
Err(error) => match policy.signed_object_failure_policy {
|
||||
SignedObjectFailurePolicy::DropObject => { audit.push(ObjectAuditEntry { rsync_uri: file.rsync_uri.clone(), sha256_hex: sha256_hex_from_32(&file.sha256), kind: AuditObjectKind::Aspa, result: AuditObjectResult::Error, detail: Some(error.to_string()) }); warnings.push(Warning::new(format!("dropping invalid ASPA: {}: {error}", file.rsync_uri)).with_context(&file.rsync_uri)); }
|
||||
SignedObjectFailurePolicy::DropPublicationPoint => { stats.publication_point_dropped = true; warnings.push(Warning::new(format!("dropping publication point due to invalid ASPA: {}: {error}", file.rsync_uri)).with_context(manifest_uri)); return empty(stats, warnings, audit); }
|
||||
match process_aspa_with_issuer(
|
||||
file,
|
||||
issuer_ca_der,
|
||||
&issuer_ca,
|
||||
&issuer_spki,
|
||||
issuer_ca_rsync_uri,
|
||||
&mut crl_states,
|
||||
&issuer_resources_index,
|
||||
issuer_effective_ip,
|
||||
issuer_effective_as,
|
||||
validation_time,
|
||||
timing,
|
||||
policy.strict.cms_der,
|
||||
policy.strict.name,
|
||||
policy.resource_validation_mode,
|
||||
ta_constraints,
|
||||
) {
|
||||
Ok(attestation) => {
|
||||
stats.aspa_ok += 1;
|
||||
aspas.push(attestation);
|
||||
audit.push(ObjectAuditEntry {
|
||||
rsync_uri: file.rsync_uri.clone(),
|
||||
sha256_hex: sha256_hex_from_32(&file.sha256),
|
||||
kind: AuditObjectKind::Aspa,
|
||||
result: AuditObjectResult::Ok,
|
||||
detail: None,
|
||||
});
|
||||
if let Some(warning) = ber_compatible_cms_warning_for_file(file, "ASPA") {
|
||||
warnings.push(warning);
|
||||
}
|
||||
}
|
||||
Err(error) => match policy.signed_object_failure_policy {
|
||||
SignedObjectFailurePolicy::DropObject => {
|
||||
audit.push(ObjectAuditEntry {
|
||||
rsync_uri: file.rsync_uri.clone(),
|
||||
sha256_hex: sha256_hex_from_32(&file.sha256),
|
||||
kind: AuditObjectKind::Aspa,
|
||||
result: AuditObjectResult::Error,
|
||||
detail: Some(error.to_string()),
|
||||
});
|
||||
warnings.push(
|
||||
Warning::new(format!(
|
||||
"dropping invalid ASPA: {}: {error}",
|
||||
file.rsync_uri
|
||||
))
|
||||
.with_context(&file.rsync_uri),
|
||||
);
|
||||
}
|
||||
SignedObjectFailurePolicy::DropPublicationPoint => {
|
||||
stats.publication_point_dropped = true;
|
||||
warnings.push(
|
||||
Warning::new(format!(
|
||||
"dropping publication point due to invalid ASPA: {}: {error}",
|
||||
file.rsync_uri
|
||||
))
|
||||
.with_context(manifest_uri),
|
||||
);
|
||||
return empty(stats, warnings, audit);
|
||||
}
|
||||
},
|
||||
}
|
||||
}
|
||||
}
|
||||
ObjectsOutput { vrps, aspas, router_keys: Vec::new(), warnings, stats, audit }
|
||||
ObjectsOutput {
|
||||
vrps,
|
||||
aspas,
|
||||
router_keys: Vec::new(),
|
||||
warnings,
|
||||
stats,
|
||||
audit,
|
||||
}
|
||||
}
|
||||
|
||||
@@ -187,8 +187,8 @@ fn root_discovery_from_tal_input(
|
||||
let tal_bytes = std::fs::read(path).map_err(|e| {
|
||||
FromTalError::TalFetch(format!("read TAL file failed: {}: {e}", path.display()))
|
||||
})?;
|
||||
let tal = crate::model::tal::Tal::decode_bytes(&tal_bytes)
|
||||
.map_err(FromTalError::from)?;
|
||||
let tal =
|
||||
crate::model::tal::Tal::decode_bytes(&tal_bytes).map_err(FromTalError::from)?;
|
||||
if strict_name {
|
||||
discover_root_ca_instance_from_tal_with_fetchers_strict_name(
|
||||
http_fetcher,
|
||||
|
||||
@@ -243,9 +243,10 @@ fn compute_ready_publication_point_stage(
|
||||
.ta_constraints
|
||||
.shared_for_tal(&ready.node.handle.tal_id);
|
||||
if ta_constraints.is_some()
|
||||
&& let Some(timing) = runner.timing.as_ref() {
|
||||
timing.record_count("ta_constraints_parallel_publication_points", 1);
|
||||
}
|
||||
&& let Some(timing) = runner.timing.as_ref()
|
||||
{
|
||||
timing.record_count("ta_constraints_parallel_publication_points", 1);
|
||||
}
|
||||
match prepare_publication_point_for_parallel_roa_and_ta_constraints(
|
||||
ready.node.id,
|
||||
&fresh_stage.fresh_point,
|
||||
|
||||
@@ -102,7 +102,6 @@ enum StageOutcome {
|
||||
Fresh(Box<StagedOutcome>),
|
||||
}
|
||||
|
||||
|
||||
struct FreshErrorOutcome {
|
||||
ready: ReadyCaInstance,
|
||||
publication_point_started: Instant,
|
||||
|
||||
@@ -86,7 +86,10 @@ fn build_publication_point_audit_from_snapshot(
|
||||
}));
|
||||
}
|
||||
|
||||
let mut warnings = runner_warnings.iter().map(AuditWarning::from).collect::<Vec<_>>();
|
||||
let mut warnings = runner_warnings
|
||||
.iter()
|
||||
.map(AuditWarning::from)
|
||||
.collect::<Vec<_>>();
|
||||
warnings.extend(objects.warnings.iter().map(AuditWarning::from));
|
||||
PublicationPointAudit {
|
||||
node_id: None,
|
||||
@@ -119,7 +122,10 @@ fn build_publication_point_audit_from_failed_fetch(
|
||||
runner_warnings: &[Warning],
|
||||
fresh_error: &ManifestFreshError,
|
||||
) -> PublicationPointAudit {
|
||||
let mut warnings = runner_warnings.iter().map(AuditWarning::from).collect::<Vec<_>>();
|
||||
let mut warnings = runner_warnings
|
||||
.iter()
|
||||
.map(AuditWarning::from)
|
||||
.collect::<Vec<_>>();
|
||||
warnings.push(AuditWarning::from(
|
||||
&Warning::new(fresh_error.to_string()).with_context(&ca.manifest_rsync_uri),
|
||||
));
|
||||
|
||||
@@ -12,15 +12,25 @@ fn discover_children_from_fresh_snapshot_with_audit_with_issuer_der<P: Publicati
|
||||
) -> Result<ChildDiscoveryOutput, String> {
|
||||
let locked_files = publication_point.files();
|
||||
let issuer_ca = match crate::model::rc::ResourceCertificate::decode_der(issuer_ca_der) {
|
||||
Ok(ca) => match ca.validate_rfc6487_profile(crate::model::rc::ResourceCertificateRole::Ca) {
|
||||
Ok(()) => Some(ca),
|
||||
Err(error) => {
|
||||
crate::logging::emit(crate::logging::Level::Debug, "issuer_ca_profile_error", || serde_json::json!({"error": error.to_string()}));
|
||||
None
|
||||
Ok(ca) => {
|
||||
match ca.validate_rfc6487_profile(crate::model::rc::ResourceCertificateRole::Ca) {
|
||||
Ok(()) => Some(ca),
|
||||
Err(error) => {
|
||||
crate::logging::emit(
|
||||
crate::logging::Level::Debug,
|
||||
"issuer_ca_profile_error",
|
||||
|| serde_json::json!({"error": error.to_string()}),
|
||||
);
|
||||
None
|
||||
}
|
||||
}
|
||||
},
|
||||
}
|
||||
Err(error) => {
|
||||
crate::logging::emit(crate::logging::Level::Debug, "issuer_ca_decode_error", || serde_json::json!({"error": error.to_string()}));
|
||||
crate::logging::emit(
|
||||
crate::logging::Level::Debug,
|
||||
"issuer_ca_decode_error",
|
||||
|| serde_json::json!({"error": error.to_string()}),
|
||||
);
|
||||
None
|
||||
}
|
||||
};
|
||||
@@ -36,10 +46,7 @@ fn discover_children_from_fresh_snapshot_with_audit_with_issuer_der<P: Publicati
|
||||
let bytes = file
|
||||
.bytes_cloned()
|
||||
.map_err(|error| format!("snapshot CRL bytes load failed: {error}"))?;
|
||||
Ok((
|
||||
file.rsync_uri.clone(),
|
||||
IssuerCrlState::Pending { bytes },
|
||||
))
|
||||
Ok((file.rsync_uri.clone(), IssuerCrlState::Pending { bytes }))
|
||||
})
|
||||
.collect::<Result<_, String>>()?;
|
||||
|
||||
@@ -63,7 +70,10 @@ fn discover_children_from_fresh_snapshot_with_audit_with_issuer_der<P: Publicati
|
||||
let mut child_load_nanos = 0u64;
|
||||
let mut child_load_count = 0u64;
|
||||
|
||||
for file in locked_files.iter().filter(|file| file.rsync_uri.ends_with(".cer")) {
|
||||
for file in locked_files
|
||||
.iter()
|
||||
.filter(|file| file.rsync_uri.ends_with(".cer"))
|
||||
{
|
||||
let child_der = load_child_certificate_der_for_discovery(
|
||||
file,
|
||||
&mut child_load_nanos,
|
||||
@@ -104,7 +114,8 @@ fn discover_children_from_fresh_snapshot_with_audit_with_issuer_der<P: Publicati
|
||||
continue;
|
||||
}
|
||||
};
|
||||
let (Some(issuer_ca), Some(issuer_spki)) = (issuer_ca.as_ref(), issuer_spki.as_ref()) else {
|
||||
let (Some(issuer_ca), Some(issuer_spki)) = (issuer_ca.as_ref(), issuer_spki.as_ref())
|
||||
else {
|
||||
ca_error = ca_error.saturating_add(1);
|
||||
audits.push(ObjectAuditEntry {
|
||||
rsync_uri: file.rsync_uri.clone(),
|
||||
@@ -139,16 +150,18 @@ fn discover_children_from_fresh_snapshot_with_audit_with_issuer_der<P: Publicati
|
||||
&mut crl_states,
|
||||
issuer_ca_der,
|
||||
) {
|
||||
Ok(verified_crl) => BgpsecRouterCertificate::validate_path_with_prevalidated_issuer(
|
||||
child_der,
|
||||
issuer_ca,
|
||||
issuer_spki,
|
||||
&verified_crl.crl,
|
||||
&verified_crl.revoked_serials,
|
||||
issuer.ca_certificate_rsync_uri.as_deref(),
|
||||
Some(issuer_crl_uri.as_str()),
|
||||
validation_time,
|
||||
),
|
||||
Ok(verified_crl) => {
|
||||
BgpsecRouterCertificate::validate_path_with_prevalidated_issuer(
|
||||
child_der,
|
||||
issuer_ca,
|
||||
issuer_spki,
|
||||
&verified_crl.crl,
|
||||
&verified_crl.revoked_serials,
|
||||
issuer.ca_certificate_rsync_uri.as_deref(),
|
||||
Some(issuer_crl_uri.as_str()),
|
||||
validation_time,
|
||||
)
|
||||
}
|
||||
Err(error) => {
|
||||
router_error = router_error.saturating_add(1);
|
||||
audits.push(ObjectAuditEntry {
|
||||
@@ -156,7 +169,9 @@ fn discover_children_from_fresh_snapshot_with_audit_with_issuer_der<P: Publicati
|
||||
sha256_hex: sha256_hex_from_32(&file.sha256),
|
||||
kind: AuditObjectKind::RouterCertificate,
|
||||
result: AuditObjectResult::Error,
|
||||
detail: Some(format!("router certificate issuer CRL validation failed: {error}")),
|
||||
detail: Some(format!(
|
||||
"router certificate issuer CRL validation failed: {error}"
|
||||
)),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
@@ -164,17 +179,21 @@ fn discover_children_from_fresh_snapshot_with_audit_with_issuer_der<P: Publicati
|
||||
match router_result {
|
||||
Ok(router) => {
|
||||
if let Some(constraints) = policy.ta_constraints.for_tal(&issuer.tal_id)
|
||||
&& let Err(error) = constraints.validate_ee_certificate(&router.resource_cert) {
|
||||
router_error = router_error.saturating_add(1);
|
||||
audits.push(ObjectAuditEntry {
|
||||
rsync_uri: file.rsync_uri.clone(),
|
||||
sha256_hex: sha256_hex_from_32(&file.sha256),
|
||||
kind: AuditObjectKind::RouterCertificate,
|
||||
result: AuditObjectResult::Error,
|
||||
detail: Some(format!("router certificate violates TA constraints: {error}")),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
&& let Err(error) =
|
||||
constraints.validate_ee_certificate(&router.resource_cert)
|
||||
{
|
||||
router_error = router_error.saturating_add(1);
|
||||
audits.push(ObjectAuditEntry {
|
||||
rsync_uri: file.rsync_uri.clone(),
|
||||
sha256_hex: sha256_hex_from_32(&file.sha256),
|
||||
kind: AuditObjectKind::RouterCertificate,
|
||||
result: AuditObjectResult::Error,
|
||||
detail: Some(format!(
|
||||
"router certificate violates TA constraints: {error}"
|
||||
)),
|
||||
});
|
||||
continue;
|
||||
}
|
||||
let asns = match router_asns_for_resource_mode(
|
||||
&router.asns,
|
||||
issuer.effective_as_resources.as_ref(),
|
||||
@@ -194,7 +213,9 @@ fn discover_children_from_fresh_snapshot_with_audit_with_issuer_der<P: Publicati
|
||||
}
|
||||
};
|
||||
let source_hash = sha256_hex_from_32(&file.sha256);
|
||||
let until = PackTime::from_utc_offset_datetime(router.resource_cert.tbs.validity_not_after);
|
||||
let until = PackTime::from_utc_offset_datetime(
|
||||
router.resource_cert.tbs.validity_not_after,
|
||||
);
|
||||
for as_id in asns {
|
||||
router_keys.push(RouterKeyPayload {
|
||||
as_id,
|
||||
@@ -212,7 +233,9 @@ fn discover_children_from_fresh_snapshot_with_audit_with_issuer_der<P: Publicati
|
||||
sha256_hex: source_hash,
|
||||
kind: AuditObjectKind::RouterCertificate,
|
||||
result: AuditObjectResult::Ok,
|
||||
detail: Some("validated BGPsec router certificate (RFC 8209)".to_string()),
|
||||
detail: Some(
|
||||
"validated BGPsec router certificate (RFC 8209)".to_string(),
|
||||
),
|
||||
});
|
||||
}
|
||||
Err(error) if is_non_router_certificate(&error) => {
|
||||
@@ -221,7 +244,10 @@ fn discover_children_from_fresh_snapshot_with_audit_with_issuer_der<P: Publicati
|
||||
sha256_hex: sha256_hex_from_32(&file.sha256),
|
||||
kind: AuditObjectKind::Certificate,
|
||||
result: AuditObjectResult::Skipped,
|
||||
detail: Some("certificate is neither a CA nor a BGPsec router certificate".to_string()),
|
||||
detail: Some(
|
||||
"certificate is neither a CA nor a BGPsec router certificate"
|
||||
.to_string(),
|
||||
),
|
||||
});
|
||||
}
|
||||
Err(error) => {
|
||||
@@ -271,13 +297,14 @@ fn discover_children_from_fresh_snapshot_with_audit_with_issuer_der<P: Publicati
|
||||
continue;
|
||||
}
|
||||
};
|
||||
uri_nanos = uri_nanos.saturating_add(
|
||||
uri_started
|
||||
.elapsed()
|
||||
.as_nanos()
|
||||
.min(u128::from(u64::MAX)) as u64,
|
||||
);
|
||||
let child_ski = validated.child_ca.tbs.extensions.subject_key_identifier.clone();
|
||||
uri_nanos = uri_nanos
|
||||
.saturating_add(uri_started.elapsed().as_nanos().min(u128::from(u64::MAX)) as u64);
|
||||
let child_ski = validated
|
||||
.child_ca
|
||||
.tbs
|
||||
.extensions
|
||||
.subject_key_identifier
|
||||
.clone();
|
||||
let child_hash = sha256_hex_from_32(&file.sha256);
|
||||
children.push(DiscoveredChildCaInstance {
|
||||
handle: CaInstanceHandle {
|
||||
@@ -298,9 +325,11 @@ fn discover_children_from_fresh_snapshot_with_audit_with_issuer_der<P: Publicati
|
||||
child_ca_certificate_rsync_uri: file.rsync_uri.clone(),
|
||||
child_ca_certificate_sha256_hex: child_hash.clone(),
|
||||
},
|
||||
child_entry_projection: child_ski.as_ref().map(|ski| DiscoveredChildEntryProjection {
|
||||
child_ski: hex::encode(ski),
|
||||
}),
|
||||
child_entry_projection: child_ski
|
||||
.as_ref()
|
||||
.map(|ski| DiscoveredChildEntryProjection {
|
||||
child_ski: hex::encode(ski),
|
||||
}),
|
||||
});
|
||||
ca_ok = ca_ok.saturating_add(1);
|
||||
audits.push(ObjectAuditEntry {
|
||||
@@ -313,7 +342,13 @@ fn discover_children_from_fresh_snapshot_with_audit_with_issuer_der<P: Publicati
|
||||
}
|
||||
|
||||
if let Some(timing) = timing {
|
||||
timing.record_count("child_cer_seen", locked_files.iter().filter(|file| file.rsync_uri.ends_with(".cer")).count() as u64);
|
||||
timing.record_count(
|
||||
"child_cer_seen",
|
||||
locked_files
|
||||
.iter()
|
||||
.filter(|file| file.rsync_uri.ends_with(".cer"))
|
||||
.count() as u64,
|
||||
);
|
||||
timing.record_count("child_ca_ok", ca_ok);
|
||||
timing.record_count("child_ca_error", ca_error);
|
||||
timing.record_count("child_router_ok", router_ok);
|
||||
@@ -325,5 +360,9 @@ fn discover_children_from_fresh_snapshot_with_audit_with_issuer_der<P: Publicati
|
||||
timing.record_phase_nanos("child_certificate_der_load_total", child_load_nanos);
|
||||
timing.record_count("child_certificate_der_load_count", child_load_count);
|
||||
}
|
||||
Ok(ChildDiscoveryOutput { children, audits, router_keys })
|
||||
Ok(ChildDiscoveryOutput {
|
||||
children,
|
||||
audits,
|
||||
router_keys,
|
||||
})
|
||||
}
|
||||
|
||||
@@ -22,7 +22,6 @@ enum IssuerCrlState {
|
||||
Verified(VerifiedIssuerCrl),
|
||||
}
|
||||
|
||||
|
||||
fn load_child_certificate_der_for_discovery<'a>(
|
||||
file: &'a PackFile,
|
||||
elapsed_nanos: &mut u64,
|
||||
@@ -32,12 +31,8 @@ fn load_child_certificate_der_for_discovery<'a>(
|
||||
let bytes = file
|
||||
.bytes()
|
||||
.map_err(|e| format!("child certificate bytes load failed: {e}"))?;
|
||||
*elapsed_nanos = elapsed_nanos.saturating_add(
|
||||
started
|
||||
.elapsed()
|
||||
.as_nanos()
|
||||
.min(u128::from(u64::MAX)) as u64,
|
||||
);
|
||||
*elapsed_nanos =
|
||||
elapsed_nanos.saturating_add(started.elapsed().as_nanos().min(u128::from(u64::MAX)) as u64);
|
||||
*count = count.saturating_add(1);
|
||||
Ok(bytes)
|
||||
}
|
||||
@@ -51,10 +46,7 @@ fn ca_certificate_der_for_validation<'a>(
|
||||
let was_lazy = ca.ca_certificate_sha256_hex().is_some();
|
||||
let der = ca.ca_certificate_der(store)?;
|
||||
if was_lazy {
|
||||
let elapsed = started
|
||||
.elapsed()
|
||||
.as_nanos()
|
||||
.min(u128::from(u64::MAX)) as u64;
|
||||
let elapsed = started.elapsed().as_nanos().min(u128::from(u64::MAX)) as u64;
|
||||
if let Some(timing) = timing {
|
||||
timing.record_count("ca_certificate_lazy_load_count", 1);
|
||||
timing.record_count("ca_certificate_lazy_load_bytes", der.len() as u64);
|
||||
@@ -86,7 +78,11 @@ fn select_issuer_crl_uri_for_child<'a>(
|
||||
}
|
||||
Err(format!(
|
||||
"CRL referenced by child certificate CRLDistributionPoints not found in publication point snapshot: {} (RFC 6487 §4.8.6; RFC 9286 §4.2.1)",
|
||||
crldp_uris.iter().map(|uri| uri.as_str()).collect::<Vec<_>>().join(", ")
|
||||
crldp_uris
|
||||
.iter()
|
||||
.map(|uri| uri.as_str())
|
||||
.collect::<Vec<_>>()
|
||||
.join(", ")
|
||||
))
|
||||
}
|
||||
|
||||
@@ -104,7 +100,8 @@ fn ensure_issuer_crl_verified<'a>(
|
||||
let der = std::mem::take(bytes);
|
||||
let crl = crate::model::crl::RpkixCrl::decode_der(&der)?;
|
||||
crl.verify_signature_with_issuer_certificate_der(issuer_ca_der)?;
|
||||
let mut revoked_serials = std::collections::HashSet::with_capacity(crl.revoked_certs.len());
|
||||
let mut revoked_serials =
|
||||
std::collections::HashSet::with_capacity(crl.revoked_certs.len());
|
||||
for revoked in &crl.revoked_certs {
|
||||
revoked_serials.insert(revoked.serial_number.bytes_be.clone());
|
||||
}
|
||||
@@ -131,22 +128,40 @@ fn router_asns_for_resource_mode(
|
||||
let contains = |asn: u32| {
|
||||
resources.asnum.as_ref().is_some_and(|choice| match choice {
|
||||
crate::model::rc::AsIdentifierChoice::Inherit => false,
|
||||
crate::model::rc::AsIdentifierChoice::AsIdsOrRanges(items) => items.iter().any(|item| match item {
|
||||
crate::model::rc::AsIdOrRange::Id(id) => *id == asn,
|
||||
crate::model::rc::AsIdOrRange::Range { min, max } => *min <= asn && asn <= *max,
|
||||
}),
|
||||
crate::model::rc::AsIdentifierChoice::AsIdsOrRanges(items) => {
|
||||
items.iter().any(|item| match item {
|
||||
crate::model::rc::AsIdOrRange::Id(id) => *id == asn,
|
||||
crate::model::rc::AsIdOrRange::Range { min, max } => *min <= asn && asn <= *max,
|
||||
})
|
||||
}
|
||||
})
|
||||
};
|
||||
match mode {
|
||||
ResourceValidationMode::Rfc6487 => {
|
||||
let outside = router_asns.iter().copied().filter(|asn| !contains(*asn)).collect::<Vec<_>>();
|
||||
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:?}"))
|
||||
let outside = router_asns
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|asn| !contains(*asn))
|
||||
.collect::<Vec<_>>();
|
||||
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 = router_asns.iter().copied().filter(|asn| contains(*asn)).collect::<Vec<_>>();
|
||||
if filtered.is_empty() { Err("router AS resources have empty validated resource set".to_string()) } else { Ok(filtered) }
|
||||
let filtered = router_asns
|
||||
.iter()
|
||||
.copied()
|
||||
.filter(|asn| contains(*asn))
|
||||
.collect::<Vec<_>>();
|
||||
if filtered.is_empty() {
|
||||
Err("router AS resources have empty validated resource set".to_string())
|
||||
} else {
|
||||
Ok(filtered)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -232,16 +232,26 @@ impl<'a> Rpkiv1PublicationPointRunner<'a> {
|
||||
let mut ccr_append_ms = 0;
|
||||
if self.ccr_accumulator.is_some() {
|
||||
let ccr_projection_build_started = std::time::Instant::now();
|
||||
let subordinate_skis = discovered_children.iter().map(|child| {
|
||||
if let Some(projection) = &child.child_entry_projection {
|
||||
return hex::decode(&projection.child_ski).map_err(|error| error.to_string());
|
||||
}
|
||||
let der = child.handle.ca_certificate_der(self.store)?;
|
||||
let certificate = ResourceCertificate::decode_der(der.as_ref()).map_err(|error| error.to_string())?;
|
||||
certificate.tbs.extensions.subject_key_identifier.clone()
|
||||
.ok_or_else(|| "child certificate missing SubjectKeyIdentifier".to_string())
|
||||
}).collect::<Result<Vec<_>, String>>()?;
|
||||
let ccr_manifest_projection = crate::ccr::projection::from_snapshot(ca, &pack, subordinate_skis)?;
|
||||
let subordinate_skis = discovered_children
|
||||
.iter()
|
||||
.map(|child| {
|
||||
if let Some(projection) = &child.child_entry_projection {
|
||||
return hex::decode(&projection.child_ski)
|
||||
.map_err(|error| error.to_string());
|
||||
}
|
||||
let der = child.handle.ca_certificate_der(self.store)?;
|
||||
let certificate = ResourceCertificate::decode_der(der.as_ref())
|
||||
.map_err(|error| error.to_string())?;
|
||||
certificate
|
||||
.tbs
|
||||
.extensions
|
||||
.subject_key_identifier
|
||||
.clone()
|
||||
.ok_or_else(|| "child certificate missing SubjectKeyIdentifier".to_string())
|
||||
})
|
||||
.collect::<Result<Vec<_>, String>>()?;
|
||||
let ccr_manifest_projection =
|
||||
crate::ccr::projection::from_snapshot(ca, &pack, subordinate_skis)?;
|
||||
ccr_projection_build_ms = ccr_projection_build_started.elapsed().as_millis() as u64;
|
||||
let ccr_append_started = std::time::Instant::now();
|
||||
self.append_ccr_manifest_projection(&ccr_manifest_projection)?;
|
||||
@@ -296,15 +306,18 @@ impl<'a> Rpkiv1PublicationPointRunner<'a> {
|
||||
|
||||
// Commit protocol freshness only after successful publication-point
|
||||
// validation/finalization, independently of optional CCR output.
|
||||
self.store.put_manifest_anti_rollback_meta(
|
||||
&crate::repository::storage::ManifestAntiRollbackMeta {
|
||||
manifest_rsync_uri: pack.manifest_rsync_uri.clone(),
|
||||
manifest_number_be: pack.manifest_number_be.clone(),
|
||||
manifest_this_update: pack.this_update.clone(),
|
||||
manifest_sha256: <sha2::Sha256 as sha2::Digest>::digest(&pack.manifest_bytes).to_vec(),
|
||||
updated_at_validation_time: pack.verified_at.clone(),
|
||||
},
|
||||
).map_err(|error| format!("persist manifest freshness failed: {error}"))?;
|
||||
self.store
|
||||
.put_manifest_anti_rollback_meta(
|
||||
&crate::repository::storage::ManifestAntiRollbackMeta {
|
||||
manifest_rsync_uri: pack.manifest_rsync_uri.clone(),
|
||||
manifest_number_be: pack.manifest_number_be.clone(),
|
||||
manifest_this_update: pack.this_update.clone(),
|
||||
manifest_sha256: <sha2::Sha256 as sha2::Digest>::digest(&pack.manifest_bytes)
|
||||
.to_vec(),
|
||||
updated_at_validation_time: pack.verified_at.clone(),
|
||||
},
|
||||
)
|
||||
.map_err(|error| format!("persist manifest freshness failed: {error}"))?;
|
||||
|
||||
Ok(FreshPublicationPointFinalizeOutput {
|
||||
result: PublicationPointRunResult {
|
||||
|
||||
@@ -99,9 +99,12 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
|
||||
Err(stage_error) => {
|
||||
let mut warnings = Vec::new();
|
||||
warnings.push(
|
||||
Warning::new(format!("publication point processing failed: {}", stage_error.error))
|
||||
.with_rfc_refs(&[RfcRef("RFC 9286 §6.6")])
|
||||
.with_context(&ca.manifest_rsync_uri),
|
||||
Warning::new(format!(
|
||||
"publication point processing failed: {}",
|
||||
stage_error.error
|
||||
))
|
||||
.with_rfc_refs(&[RfcRef("RFC 9286 §6.6")])
|
||||
.with_context(&ca.manifest_rsync_uri),
|
||||
);
|
||||
let audit = build_publication_point_audit_from_failed_fetch(
|
||||
ca,
|
||||
@@ -186,8 +189,14 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
|
||||
)
|
||||
};
|
||||
let object_ms = object_started.elapsed().as_millis() as u64;
|
||||
self.record_publication_point_step_ms(&ca.manifest_rsync_uri, "objects_processing", object_ms);
|
||||
objects.router_keys.extend(stage.discovered_router_keys.clone());
|
||||
self.record_publication_point_step_ms(
|
||||
&ca.manifest_rsync_uri,
|
||||
"objects_processing",
|
||||
object_ms,
|
||||
);
|
||||
objects
|
||||
.router_keys
|
||||
.extend(stage.discovered_router_keys.clone());
|
||||
|
||||
let finalized = self.finalize_fresh_publication_point_from_reducer(
|
||||
ca,
|
||||
|
||||
@@ -19,5 +19,4 @@ impl<'a> Rpkiv1PublicationPointRunner<'a> {
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user