Compare commits
No commits in common. "b66b4255129a648fed746ad548f75dde1aceccb6" and "8613cfae6094c1a6d0dfe898136ab8ed05b16351" have entirely different histories.
b66b425512
...
8613cfae60
@ -29,7 +29,6 @@ pub const OID_AUTHORITY_INFO_ACCESS: &str = "1.3.6.1.5.5.7.1.1";
|
|||||||
pub const OID_AUTHORITY_INFO_ACCESS_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.1.1);
|
pub const OID_AUTHORITY_INFO_ACCESS_RAW: &[u8] = &asn1_rs::oid!(raw 1.3.6.1.5.5.7.1.1);
|
||||||
pub const OID_CERTIFICATE_POLICIES: &str = "2.5.29.32";
|
pub const OID_CERTIFICATE_POLICIES: &str = "2.5.29.32";
|
||||||
pub const OID_CERTIFICATE_POLICIES_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.32);
|
pub const OID_CERTIFICATE_POLICIES_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.32);
|
||||||
pub const OID_QT_CPS: &str = "1.3.6.1.5.5.7.2.1";
|
|
||||||
|
|
||||||
pub const OID_AUTHORITY_KEY_IDENTIFIER: &str = "2.5.29.35";
|
pub const OID_AUTHORITY_KEY_IDENTIFIER: &str = "2.5.29.35";
|
||||||
pub const OID_AUTHORITY_KEY_IDENTIFIER_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.35);
|
pub const OID_AUTHORITY_KEY_IDENTIFIER_RAW: &[u8] = &asn1_rs::oid!(raw 2.5.29.35);
|
||||||
|
|||||||
@ -13,14 +13,11 @@ use crate::data_model::common::{
|
|||||||
use crate::data_model::oid::{
|
use crate::data_model::oid::{
|
||||||
OID_AD_CA_ISSUERS_RAW, OID_AD_CA_REPOSITORY, OID_AD_CA_REPOSITORY_RAW, OID_AD_RPKI_MANIFEST,
|
OID_AD_CA_ISSUERS_RAW, OID_AD_CA_REPOSITORY, OID_AD_CA_REPOSITORY_RAW, OID_AD_RPKI_MANIFEST,
|
||||||
OID_AD_RPKI_MANIFEST_RAW, OID_AD_RPKI_NOTIFY, OID_AD_RPKI_NOTIFY_RAW, OID_AD_SIGNED_OBJECT,
|
OID_AD_RPKI_MANIFEST_RAW, OID_AD_RPKI_NOTIFY, OID_AD_RPKI_NOTIFY_RAW, OID_AD_SIGNED_OBJECT,
|
||||||
OID_AD_SIGNED_OBJECT_RAW, OID_AUTHORITY_INFO_ACCESS, OID_AUTHORITY_INFO_ACCESS_RAW,
|
OID_AD_SIGNED_OBJECT_RAW, OID_AUTHORITY_INFO_ACCESS_RAW, OID_AUTHORITY_KEY_IDENTIFIER_RAW,
|
||||||
OID_AUTHORITY_KEY_IDENTIFIER, OID_AUTHORITY_KEY_IDENTIFIER_RAW, OID_AUTONOMOUS_SYS_IDS,
|
OID_AUTONOMOUS_SYS_IDS_RAW, OID_BASIC_CONSTRAINTS_RAW, OID_CERTIFICATE_POLICIES_RAW,
|
||||||
OID_AUTONOMOUS_SYS_IDS_RAW, OID_BASIC_CONSTRAINTS, OID_BASIC_CONSTRAINTS_RAW,
|
OID_CP_IPADDR_ASNUMBER, OID_CP_IPADDR_ASNUMBER_RAW, OID_CRL_DISTRIBUTION_POINTS_RAW,
|
||||||
OID_CERTIFICATE_POLICIES, OID_CERTIFICATE_POLICIES_RAW, OID_CP_IPADDR_ASNUMBER,
|
OID_IP_ADDR_BLOCKS_RAW, OID_SHA256_WITH_RSA_ENCRYPTION, OID_SHA256_WITH_RSA_ENCRYPTION_RAW,
|
||||||
OID_CP_IPADDR_ASNUMBER_RAW, OID_CRL_DISTRIBUTION_POINTS, OID_CRL_DISTRIBUTION_POINTS_RAW,
|
OID_SUBJECT_INFO_ACCESS_RAW, OID_SUBJECT_KEY_IDENTIFIER_RAW,
|
||||||
OID_EXTENDED_KEY_USAGE, OID_IP_ADDR_BLOCKS, OID_IP_ADDR_BLOCKS_RAW, OID_KEY_USAGE, OID_QT_CPS,
|
|
||||||
OID_SHA256_WITH_RSA_ENCRYPTION, OID_SHA256_WITH_RSA_ENCRYPTION_RAW, OID_SUBJECT_INFO_ACCESS,
|
|
||||||
OID_SUBJECT_INFO_ACCESS_RAW, OID_SUBJECT_KEY_IDENTIFIER, OID_SUBJECT_KEY_IDENTIFIER_RAW,
|
|
||||||
};
|
};
|
||||||
|
|
||||||
/// Resource Certificate kind (semantic classification).
|
/// Resource Certificate kind (semantic classification).
|
||||||
@ -30,14 +27,6 @@ pub enum ResourceCertKind {
|
|||||||
Ee,
|
Ee,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
|
||||||
pub enum ResourceCertificateRole {
|
|
||||||
TrustAnchor,
|
|
||||||
Ca,
|
|
||||||
SignedObjectEe,
|
|
||||||
RouterEe,
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A parsed RPKI Resource Certificate (RFC 6487) data model.
|
/// A parsed RPKI Resource Certificate (RFC 6487) data model.
|
||||||
///
|
///
|
||||||
/// This module intentionally focuses on the semantics needed by Signed Object validation and
|
/// This module intentionally focuses on the semantics needed by Signed Object validation and
|
||||||
@ -70,7 +59,6 @@ pub struct RpkixTbsCertificate {
|
|||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct RcExtensions {
|
pub struct RcExtensions {
|
||||||
pub basic_constraints_ca: bool,
|
pub basic_constraints_ca: bool,
|
||||||
pub basic_constraints: Option<BasicConstraintsProfile>,
|
|
||||||
pub subject_key_identifier: Option<Vec<u8>>,
|
pub subject_key_identifier: Option<Vec<u8>>,
|
||||||
/// Authority Key Identifier (AKI) keyIdentifier value.
|
/// Authority Key Identifier (AKI) keyIdentifier value.
|
||||||
pub authority_key_identifier: Option<Vec<u8>>,
|
pub authority_key_identifier: Option<Vec<u8>>,
|
||||||
@ -80,26 +68,11 @@ pub struct RcExtensions {
|
|||||||
pub ca_issuers_uris: Option<Vec<String>>,
|
pub ca_issuers_uris: Option<Vec<String>>,
|
||||||
pub subject_info_access: Option<SubjectInfoAccess>,
|
pub subject_info_access: Option<SubjectInfoAccess>,
|
||||||
pub certificate_policies_oid: Option<String>,
|
pub certificate_policies_oid: Option<String>,
|
||||||
pub certificate_policies: Option<CertificatePoliciesProfile>,
|
|
||||||
pub extension_oids: Vec<String>,
|
|
||||||
|
|
||||||
pub ip_resources: Option<IpResourceSet>,
|
pub ip_resources: Option<IpResourceSet>,
|
||||||
pub as_resources: Option<AsResourceSet>,
|
pub as_resources: Option<AsResourceSet>,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct BasicConstraintsProfile {
|
|
||||||
pub ca: bool,
|
|
||||||
pub critical: bool,
|
|
||||||
pub path_len_constraint: Option<u32>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
|
||||||
pub struct CertificatePoliciesProfile {
|
|
||||||
pub policy_oid: String,
|
|
||||||
pub qualifier_oids: Vec<String>,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct ResourceCertificateParsed {
|
pub struct ResourceCertificateParsed {
|
||||||
pub raw_der: Vec<u8>,
|
pub raw_der: Vec<u8>,
|
||||||
@ -141,14 +114,13 @@ impl AlgorithmIdentifierValue {
|
|||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct RcExtensionsParsed {
|
pub struct RcExtensionsParsed {
|
||||||
pub basic_constraints: Vec<BasicConstraintsProfile>,
|
pub basic_constraints_ca: Vec<bool>,
|
||||||
pub subject_key_identifier: Vec<(Vec<u8>, bool)>,
|
pub subject_key_identifier: Vec<(Vec<u8>, bool)>,
|
||||||
pub authority_key_identifier: Vec<(AuthorityKeyIdentifierParsed, bool)>,
|
pub authority_key_identifier: Vec<(AuthorityKeyIdentifierParsed, bool)>,
|
||||||
pub crl_distribution_points: Vec<(CrlDistributionPointsParsed, bool)>,
|
pub crl_distribution_points: Vec<(CrlDistributionPointsParsed, bool)>,
|
||||||
pub authority_info_access: Vec<(AuthorityInfoAccessParsed, bool)>,
|
pub authority_info_access: Vec<(AuthorityInfoAccessParsed, bool)>,
|
||||||
pub subject_info_access: Vec<(SubjectInfoAccessParsed, bool)>,
|
pub subject_info_access: Vec<(SubjectInfoAccessParsed, bool)>,
|
||||||
pub certificate_policies: Vec<(Vec<CertificatePoliciesProfile>, bool)>,
|
pub certificate_policies: Vec<(Vec<String>, bool)>,
|
||||||
pub extension_oids: Vec<String>,
|
|
||||||
pub ip_resources: Vec<(IpResourceSet, bool)>,
|
pub ip_resources: Vec<(IpResourceSet, bool)>,
|
||||||
pub as_resources: Vec<(AsResourceSet, bool)>,
|
pub as_resources: Vec<(AsResourceSet, bool)>,
|
||||||
}
|
}
|
||||||
@ -434,45 +406,11 @@ pub enum ResourceCertificateProfileError {
|
|||||||
#[error("certificatePolicies criticality must be critical (RFC 6487 §4.8.9)")]
|
#[error("certificatePolicies criticality must be critical (RFC 6487 §4.8.9)")]
|
||||||
CertificatePoliciesCriticality,
|
CertificatePoliciesCriticality,
|
||||||
|
|
||||||
#[error("certificatePolicies must be present (RFC 6487 §4.8.9)")]
|
|
||||||
CertificatePoliciesMissing,
|
|
||||||
|
|
||||||
#[error(
|
#[error(
|
||||||
"certificatePolicies must contain RPKI policy OID {OID_CP_IPADDR_ASNUMBER}, got {0} (RFC 6487 §4.8.9)"
|
"certificatePolicies must contain RPKI policy OID {OID_CP_IPADDR_ASNUMBER}, got {0} (RFC 6487 §4.8.9)"
|
||||||
)]
|
)]
|
||||||
InvalidCertificatePolicy(String),
|
InvalidCertificatePolicy(String),
|
||||||
|
|
||||||
#[error("certificatePolicies may contain at most one CPS qualifier (RFC 6487 §4.8.9)")]
|
|
||||||
CertificatePoliciesTooManyQualifiers,
|
|
||||||
|
|
||||||
#[error(
|
|
||||||
"certificatePolicies qualifier must be id-qt-cps ({OID_QT_CPS}), got {0} (RFC 6487 §4.8.9)"
|
|
||||||
)]
|
|
||||||
CertificatePoliciesInvalidQualifier(String),
|
|
||||||
|
|
||||||
#[error("basicConstraints must be present in CA certificates (RFC 6487 §4.8.1)")]
|
|
||||||
BasicConstraintsMissing,
|
|
||||||
|
|
||||||
#[error("basicConstraints criticality must be critical in CA certificates (RFC 6487 §4.8.1)")]
|
|
||||||
BasicConstraintsCriticality,
|
|
||||||
|
|
||||||
#[error("basicConstraints cA must be TRUE in CA certificates (RFC 6487 §4.8.1)")]
|
|
||||||
BasicConstraintsCaFalse,
|
|
||||||
|
|
||||||
#[error(
|
|
||||||
"basicConstraints pathLenConstraint must be absent in CA certificates (RFC 6487 §4.8.1)"
|
|
||||||
)]
|
|
||||||
BasicConstraintsPathLenPresent,
|
|
||||||
|
|
||||||
#[error("basicConstraints must be absent in EE certificates (RFC 6487 §4.8.1)")]
|
|
||||||
BasicConstraintsEeMustOmit,
|
|
||||||
|
|
||||||
#[error("extension {oid} is not permitted for {role} resource certificates (RFC 6487 §4.8)")]
|
|
||||||
DisallowedExtension { role: &'static str, oid: String },
|
|
||||||
|
|
||||||
#[error("autonomousSysIds RDI field must be absent (RFC 6487 §4.8.11; RFC 3779 §3.2.3)")]
|
|
||||||
AsResourcesRdiPresent,
|
|
||||||
|
|
||||||
#[error(
|
#[error(
|
||||||
"SIA id-ad-signedObject accessLocation must be URI (RFC 6487 §4.8.8.2; RFC 5280 §4.2.2.2)"
|
"SIA id-ad-signedObject accessLocation must be URI (RFC 6487 §4.8.8.2; RFC 5280 §4.2.2.2)"
|
||||||
)]
|
)]
|
||||||
@ -629,87 +567,6 @@ impl ResourceCertificate {
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn validate_rfc6487_profile(
|
|
||||||
&self,
|
|
||||||
role: ResourceCertificateRole,
|
|
||||||
) -> Result<(), ResourceCertificateProfileError> {
|
|
||||||
let role_name = match role {
|
|
||||||
ResourceCertificateRole::TrustAnchor => "trust anchor CA",
|
|
||||||
ResourceCertificateRole::Ca => "CA",
|
|
||||||
ResourceCertificateRole::SignedObjectEe => "signed-object EE",
|
|
||||||
ResourceCertificateRole::RouterEe => "router EE",
|
|
||||||
};
|
|
||||||
let ca_role = matches!(
|
|
||||||
role,
|
|
||||||
ResourceCertificateRole::TrustAnchor | ResourceCertificateRole::Ca
|
|
||||||
);
|
|
||||||
|
|
||||||
if ca_role {
|
|
||||||
let constraints = self
|
|
||||||
.tbs
|
|
||||||
.extensions
|
|
||||||
.basic_constraints
|
|
||||||
.as_ref()
|
|
||||||
.ok_or(ResourceCertificateProfileError::BasicConstraintsMissing)?;
|
|
||||||
if !constraints.critical {
|
|
||||||
return Err(ResourceCertificateProfileError::BasicConstraintsCriticality);
|
|
||||||
}
|
|
||||||
if !constraints.ca {
|
|
||||||
return Err(ResourceCertificateProfileError::BasicConstraintsCaFalse);
|
|
||||||
}
|
|
||||||
if constraints.path_len_constraint.is_some() {
|
|
||||||
return Err(ResourceCertificateProfileError::BasicConstraintsPathLenPresent);
|
|
||||||
}
|
|
||||||
} else if self.tbs.extensions.basic_constraints.is_some() {
|
|
||||||
return Err(ResourceCertificateProfileError::BasicConstraintsEeMustOmit);
|
|
||||||
}
|
|
||||||
|
|
||||||
for oid in &self.tbs.extensions.extension_oids {
|
|
||||||
if !is_permitted_extension(oid, role) {
|
|
||||||
return Err(ResourceCertificateProfileError::DisallowedExtension {
|
|
||||||
role: role_name,
|
|
||||||
oid: oid.clone(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
let policies = self
|
|
||||||
.tbs
|
|
||||||
.extensions
|
|
||||||
.certificate_policies
|
|
||||||
.as_ref()
|
|
||||||
.ok_or(ResourceCertificateProfileError::CertificatePoliciesMissing)?;
|
|
||||||
if policies.policy_oid != OID_CP_IPADDR_ASNUMBER {
|
|
||||||
return Err(ResourceCertificateProfileError::InvalidCertificatePolicy(
|
|
||||||
policies.policy_oid.clone(),
|
|
||||||
));
|
|
||||||
}
|
|
||||||
if policies.qualifier_oids.len() > 1 {
|
|
||||||
return Err(ResourceCertificateProfileError::CertificatePoliciesTooManyQualifiers);
|
|
||||||
}
|
|
||||||
if let Some(qualifier_oid) = policies.qualifier_oids.first() {
|
|
||||||
if qualifier_oid != OID_QT_CPS {
|
|
||||||
return Err(
|
|
||||||
ResourceCertificateProfileError::CertificatePoliciesInvalidQualifier(
|
|
||||||
qualifier_oid.clone(),
|
|
||||||
),
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if self
|
|
||||||
.tbs
|
|
||||||
.extensions
|
|
||||||
.as_resources
|
|
||||||
.as_ref()
|
|
||||||
.is_some_and(|resources| resources.rdi.is_some())
|
|
||||||
{
|
|
||||||
return Err(ResourceCertificateProfileError::AsResourcesRdiPresent);
|
|
||||||
}
|
|
||||||
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn validate_strict_name_profile(&self) -> Result<(), ResourceCertificateProfileError> {
|
pub fn validate_strict_name_profile(&self) -> Result<(), ResourceCertificateProfileError> {
|
||||||
validate_strict_rpki_name(&self.tbs.issuer_name, "issuer")?;
|
validate_strict_rpki_name(&self.tbs.issuer_name, "issuer")?;
|
||||||
validate_strict_rpki_name(&self.tbs.subject_name, "subject")?;
|
validate_strict_rpki_name(&self.tbs.subject_name, "subject")?;
|
||||||
@ -733,22 +590,6 @@ impl ResourceCertificate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn is_permitted_extension(oid: &str, role: ResourceCertificateRole) -> bool {
|
|
||||||
matches!(
|
|
||||||
oid,
|
|
||||||
OID_BASIC_CONSTRAINTS
|
|
||||||
| OID_KEY_USAGE
|
|
||||||
| OID_SUBJECT_KEY_IDENTIFIER
|
|
||||||
| OID_AUTHORITY_KEY_IDENTIFIER
|
|
||||||
| OID_CRL_DISTRIBUTION_POINTS
|
|
||||||
| OID_AUTHORITY_INFO_ACCESS
|
|
||||||
| OID_SUBJECT_INFO_ACCESS
|
|
||||||
| OID_CERTIFICATE_POLICIES
|
|
||||||
| OID_IP_ADDR_BLOCKS
|
|
||||||
| OID_AUTONOMOUS_SYS_IDS
|
|
||||||
) || (role == ResourceCertificateRole::RouterEe && oid == OID_EXTENDED_KEY_USAGE)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn validate_strict_rpki_name(
|
fn validate_strict_rpki_name(
|
||||||
name: &X509NameDer,
|
name: &X509NameDer,
|
||||||
role: &'static str,
|
role: &'static str,
|
||||||
@ -903,24 +744,13 @@ mod strict_name_tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn profile_rejects_rfc8360_v2_policy_oid() {
|
fn profile_rejects_rfc8360_v2_policy_oid() {
|
||||||
let extensions = RcExtensionsParsed {
|
let extensions = RcExtensionsParsed {
|
||||||
basic_constraints: vec![BasicConstraintsProfile {
|
basic_constraints_ca: vec![true],
|
||||||
ca: true,
|
|
||||||
critical: true,
|
|
||||||
path_len_constraint: None,
|
|
||||||
}],
|
|
||||||
subject_key_identifier: Vec::new(),
|
subject_key_identifier: Vec::new(),
|
||||||
authority_key_identifier: Vec::new(),
|
authority_key_identifier: Vec::new(),
|
||||||
crl_distribution_points: Vec::new(),
|
crl_distribution_points: Vec::new(),
|
||||||
authority_info_access: Vec::new(),
|
authority_info_access: Vec::new(),
|
||||||
subject_info_access: Vec::new(),
|
subject_info_access: Vec::new(),
|
||||||
certificate_policies: vec![(
|
certificate_policies: vec![(vec!["1.3.6.1.5.5.7.14.3".to_string()], true)],
|
||||||
vec![CertificatePoliciesProfile {
|
|
||||||
policy_oid: "1.3.6.1.5.5.7.14.3".to_string(),
|
|
||||||
qualifier_oids: Vec::new(),
|
|
||||||
}],
|
|
||||||
true,
|
|
||||||
)],
|
|
||||||
extension_oids: Vec::new(),
|
|
||||||
ip_resources: Vec::new(),
|
ip_resources: Vec::new(),
|
||||||
as_resources: Vec::new(),
|
as_resources: Vec::new(),
|
||||||
};
|
};
|
||||||
@ -991,25 +821,23 @@ impl RcExtensionsParsed {
|
|||||||
// NOTE(perf): `self` is consumed. Prefer moving decoded fields out rather than cloning,
|
// NOTE(perf): `self` is consumed. Prefer moving decoded fields out rather than cloning,
|
||||||
// especially for large resource sets and URI lists.
|
// especially for large resource sets and URI lists.
|
||||||
let RcExtensionsParsed {
|
let RcExtensionsParsed {
|
||||||
basic_constraints,
|
basic_constraints_ca,
|
||||||
subject_key_identifier,
|
subject_key_identifier,
|
||||||
authority_key_identifier,
|
authority_key_identifier,
|
||||||
crl_distribution_points,
|
crl_distribution_points,
|
||||||
authority_info_access,
|
authority_info_access,
|
||||||
subject_info_access,
|
subject_info_access,
|
||||||
certificate_policies,
|
certificate_policies,
|
||||||
extension_oids,
|
|
||||||
ip_resources,
|
ip_resources,
|
||||||
as_resources,
|
as_resources,
|
||||||
} = self;
|
} = self;
|
||||||
|
|
||||||
if basic_constraints.len() > 1 {
|
if basic_constraints_ca.len() > 1 {
|
||||||
return Err(ResourceCertificateProfileError::DuplicateExtension(
|
return Err(ResourceCertificateProfileError::DuplicateExtension(
|
||||||
"basicConstraints",
|
"basicConstraints",
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let basic_constraints = basic_constraints.into_iter().next();
|
let basic_constraints_ca = basic_constraints_ca.first().copied().unwrap_or(false);
|
||||||
let basic_constraints_ca = basic_constraints.as_ref().is_some_and(|bc| bc.ca);
|
|
||||||
|
|
||||||
let subject_key_identifier = match subject_key_identifier.len() {
|
let subject_key_identifier = match subject_key_identifier.len() {
|
||||||
0 => None,
|
0 => None,
|
||||||
@ -1204,25 +1032,25 @@ impl RcExtensionsParsed {
|
|||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
let certificate_policies = match certificate_policies.len() {
|
let certificate_policies_oid = match certificate_policies.len() {
|
||||||
0 => None,
|
0 => None,
|
||||||
1 => {
|
1 => {
|
||||||
let (policies, critical) = certificate_policies.into_iter().next().expect("len==1");
|
let (oids, critical) = certificate_policies.into_iter().next().expect("len==1");
|
||||||
if !critical {
|
if !critical {
|
||||||
return Err(ResourceCertificateProfileError::CertificatePoliciesCriticality);
|
return Err(ResourceCertificateProfileError::CertificatePoliciesCriticality);
|
||||||
}
|
}
|
||||||
if policies.len() != 1 {
|
if oids.len() != 1 {
|
||||||
return Err(ResourceCertificateProfileError::InvalidCertificatePolicy(
|
return Err(ResourceCertificateProfileError::InvalidCertificatePolicy(
|
||||||
"expected exactly one policy".into(),
|
"expected exactly one policy".into(),
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
let policy = policies.into_iter().next().expect("len==1");
|
let policy_oid = oids.into_iter().next().expect("len==1");
|
||||||
if policy.policy_oid != OID_CP_IPADDR_ASNUMBER {
|
if policy_oid != OID_CP_IPADDR_ASNUMBER {
|
||||||
return Err(ResourceCertificateProfileError::InvalidCertificatePolicy(
|
return Err(ResourceCertificateProfileError::InvalidCertificatePolicy(
|
||||||
policy.policy_oid,
|
policy_oid,
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
Some(policy)
|
Some(OID_CP_IPADDR_ASNUMBER.to_string())
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
return Err(ResourceCertificateProfileError::DuplicateExtension(
|
return Err(ResourceCertificateProfileError::DuplicateExtension(
|
||||||
@ -1265,17 +1093,12 @@ impl RcExtensionsParsed {
|
|||||||
|
|
||||||
Ok(RcExtensions {
|
Ok(RcExtensions {
|
||||||
basic_constraints_ca,
|
basic_constraints_ca,
|
||||||
basic_constraints,
|
|
||||||
subject_key_identifier,
|
subject_key_identifier,
|
||||||
authority_key_identifier,
|
authority_key_identifier,
|
||||||
crl_distribution_points_uris,
|
crl_distribution_points_uris,
|
||||||
ca_issuers_uris,
|
ca_issuers_uris,
|
||||||
subject_info_access,
|
subject_info_access,
|
||||||
certificate_policies_oid: certificate_policies
|
certificate_policies_oid,
|
||||||
.as_ref()
|
|
||||||
.map(|_| OID_CP_IPADDR_ASNUMBER.to_string()),
|
|
||||||
certificate_policies,
|
|
||||||
extension_oids,
|
|
||||||
ip_resources,
|
ip_resources,
|
||||||
as_resources,
|
as_resources,
|
||||||
})
|
})
|
||||||
@ -1304,32 +1127,26 @@ fn algorithm_identifier_value(
|
|||||||
fn parse_extensions_parse(
|
fn parse_extensions_parse(
|
||||||
exts: &[X509Extension<'_>],
|
exts: &[X509Extension<'_>],
|
||||||
) -> Result<RcExtensionsParsed, ResourceCertificateParseError> {
|
) -> Result<RcExtensionsParsed, ResourceCertificateParseError> {
|
||||||
let mut basic_constraints: Vec<BasicConstraintsProfile> = Vec::new();
|
let mut basic_constraints_ca: Vec<bool> = Vec::new();
|
||||||
let mut ski: Vec<(Vec<u8>, bool)> = Vec::new();
|
let mut ski: Vec<(Vec<u8>, bool)> = Vec::new();
|
||||||
let mut aki: Vec<(AuthorityKeyIdentifierParsed, bool)> = Vec::new();
|
let mut aki: Vec<(AuthorityKeyIdentifierParsed, bool)> = Vec::new();
|
||||||
let mut crldp: Vec<(CrlDistributionPointsParsed, bool)> = Vec::new();
|
let mut crldp: Vec<(CrlDistributionPointsParsed, bool)> = Vec::new();
|
||||||
let mut aia: Vec<(AuthorityInfoAccessParsed, bool)> = Vec::new();
|
let mut aia: Vec<(AuthorityInfoAccessParsed, bool)> = Vec::new();
|
||||||
let mut sia: Vec<(SubjectInfoAccessParsed, bool)> = Vec::new();
|
let mut sia: Vec<(SubjectInfoAccessParsed, bool)> = Vec::new();
|
||||||
let mut cert_policies: Vec<(Vec<CertificatePoliciesProfile>, bool)> = Vec::new();
|
let mut cert_policies: Vec<(Vec<String>, bool)> = Vec::new();
|
||||||
let mut extension_oids: Vec<String> = Vec::with_capacity(exts.len());
|
|
||||||
|
|
||||||
let mut ip_resources: Vec<(IpResourceSet, bool)> = Vec::new();
|
let mut ip_resources: Vec<(IpResourceSet, bool)> = Vec::new();
|
||||||
let mut as_resources: Vec<(AsResourceSet, bool)> = Vec::new();
|
let mut as_resources: Vec<(AsResourceSet, bool)> = Vec::new();
|
||||||
|
|
||||||
for ext in exts {
|
for ext in exts {
|
||||||
let oid = ext.oid.as_bytes();
|
let oid = ext.oid.as_bytes();
|
||||||
extension_oids.push(ext.oid.to_id_string());
|
|
||||||
if oid == OID_BASIC_CONSTRAINTS_RAW {
|
if oid == OID_BASIC_CONSTRAINTS_RAW {
|
||||||
let ParsedExtension::BasicConstraints(bc) = ext.parsed_extension() else {
|
let ParsedExtension::BasicConstraints(bc) = ext.parsed_extension() else {
|
||||||
return Err(ResourceCertificateParseError::Parse(
|
return Err(ResourceCertificateParseError::Parse(
|
||||||
"basicConstraints parse failed".into(),
|
"basicConstraints parse failed".into(),
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
basic_constraints.push(BasicConstraintsProfile {
|
basic_constraints_ca.push(bc.ca);
|
||||||
ca: bc.ca,
|
|
||||||
critical: ext.critical,
|
|
||||||
path_len_constraint: bc.path_len_constraint,
|
|
||||||
});
|
|
||||||
} else if oid == OID_SUBJECT_KEY_IDENTIFIER_RAW {
|
} else if oid == OID_SUBJECT_KEY_IDENTIFIER_RAW {
|
||||||
let ParsedExtension::SubjectKeyIdentifier(s) = ext.parsed_extension() else {
|
let ParsedExtension::SubjectKeyIdentifier(s) = ext.parsed_extension() else {
|
||||||
return Err(ResourceCertificateParseError::Parse(
|
return Err(ResourceCertificateParseError::Parse(
|
||||||
@ -1378,30 +1195,16 @@ fn parse_extensions_parse(
|
|||||||
"certificatePolicies parse failed".into(),
|
"certificatePolicies parse failed".into(),
|
||||||
));
|
));
|
||||||
};
|
};
|
||||||
let mut policies: Vec<CertificatePoliciesProfile> = Vec::with_capacity(cp.len());
|
let mut oids: Vec<String> = Vec::with_capacity(cp.len());
|
||||||
for p in cp.iter() {
|
for p in cp.iter() {
|
||||||
let b = p.policy_id.as_bytes();
|
let b = p.policy_id.as_bytes();
|
||||||
let policy_oid = if b == OID_CP_IPADDR_ASNUMBER_RAW {
|
if b == OID_CP_IPADDR_ASNUMBER_RAW {
|
||||||
OID_CP_IPADDR_ASNUMBER.to_string()
|
oids.push(OID_CP_IPADDR_ASNUMBER.to_string());
|
||||||
} else {
|
} else {
|
||||||
p.policy_id.to_id_string()
|
oids.push(p.policy_id.to_id_string());
|
||||||
};
|
}
|
||||||
let qualifier_oids = p
|
|
||||||
.policy_qualifiers
|
|
||||||
.as_ref()
|
|
||||||
.map(|qualifiers| {
|
|
||||||
qualifiers
|
|
||||||
.iter()
|
|
||||||
.map(|qualifier| qualifier.policy_qualifier_id.to_id_string())
|
|
||||||
.collect()
|
|
||||||
})
|
|
||||||
.unwrap_or_default();
|
|
||||||
policies.push(CertificatePoliciesProfile {
|
|
||||||
policy_oid,
|
|
||||||
qualifier_oids,
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
cert_policies.push((policies, ext.critical));
|
cert_policies.push((oids, ext.critical));
|
||||||
} else if oid == OID_IP_ADDR_BLOCKS_RAW {
|
} else if oid == OID_IP_ADDR_BLOCKS_RAW {
|
||||||
let parsed = IpResourceSet::decode_extn_value(ext.value)
|
let parsed = IpResourceSet::decode_extn_value(ext.value)
|
||||||
.map_err(|_e| ResourceCertificateParseError::InvalidIpResourcesEncoding)?;
|
.map_err(|_e| ResourceCertificateParseError::InvalidIpResourcesEncoding)?;
|
||||||
@ -1414,14 +1217,13 @@ fn parse_extensions_parse(
|
|||||||
}
|
}
|
||||||
|
|
||||||
Ok(RcExtensionsParsed {
|
Ok(RcExtensionsParsed {
|
||||||
basic_constraints,
|
basic_constraints_ca,
|
||||||
subject_key_identifier: ski,
|
subject_key_identifier: ski,
|
||||||
authority_key_identifier: aki,
|
authority_key_identifier: aki,
|
||||||
crl_distribution_points: crldp,
|
crl_distribution_points: crldp,
|
||||||
authority_info_access: aia,
|
authority_info_access: aia,
|
||||||
subject_info_access: sia,
|
subject_info_access: sia,
|
||||||
certificate_policies: cert_policies,
|
certificate_policies: cert_policies,
|
||||||
extension_oids,
|
|
||||||
ip_resources,
|
ip_resources,
|
||||||
as_resources,
|
as_resources,
|
||||||
})
|
})
|
||||||
|
|||||||
@ -4,7 +4,6 @@ use crate::data_model::oid::{
|
|||||||
use crate::data_model::rc::{
|
use crate::data_model::rc::{
|
||||||
AsIdOrRange, AsIdentifierChoice, ResourceCertKind, ResourceCertificate,
|
AsIdOrRange, AsIdentifierChoice, ResourceCertKind, ResourceCertificate,
|
||||||
ResourceCertificateParseError, ResourceCertificateParsed, ResourceCertificateProfileError,
|
ResourceCertificateParseError, ResourceCertificateParsed, ResourceCertificateProfileError,
|
||||||
ResourceCertificateRole,
|
|
||||||
};
|
};
|
||||||
use crate::validation::cert_path::{CertPathError, validate_ee_cert_path_with_predecoded_ee};
|
use crate::validation::cert_path::{CertPathError, validate_ee_cert_path_with_predecoded_ee};
|
||||||
use x509_parser::extensions::ParsedExtension;
|
use x509_parser::extensions::ParsedExtension;
|
||||||
@ -203,7 +202,6 @@ impl BgpsecRouterCertificateParsed {
|
|||||||
self,
|
self,
|
||||||
) -> Result<BgpsecRouterCertificate, BgpsecRouterCertificateProfileError> {
|
) -> Result<BgpsecRouterCertificate, BgpsecRouterCertificateProfileError> {
|
||||||
let rc = self.rc_parsed.validate_profile()?;
|
let rc = self.rc_parsed.validate_profile()?;
|
||||||
rc.validate_rfc6487_profile(ResourceCertificateRole::RouterEe)?;
|
|
||||||
if rc.kind != ResourceCertKind::Ee {
|
if rc.kind != ResourceCertKind::Ee {
|
||||||
return Err(BgpsecRouterCertificateProfileError::NotEe);
|
return Err(BgpsecRouterCertificateProfileError::NotEe);
|
||||||
}
|
}
|
||||||
|
|||||||
@ -8,7 +8,7 @@ use crate::data_model::oid::{
|
|||||||
OID_SHA256_WITH_RSA_ENCRYPTION, OID_SHA256_WITH_RSA_ENCRYPTION_RAW, OID_SIGNED_DATA,
|
OID_SHA256_WITH_RSA_ENCRYPTION, OID_SHA256_WITH_RSA_ENCRYPTION_RAW, OID_SIGNED_DATA,
|
||||||
OID_SIGNED_DATA_RAW, OID_SUBJECT_INFO_ACCESS,
|
OID_SIGNED_DATA_RAW, OID_SUBJECT_INFO_ACCESS,
|
||||||
};
|
};
|
||||||
use crate::data_model::rc::{ResourceCertificate, ResourceCertificateRole, SubjectInfoAccess};
|
use crate::data_model::rc::{ResourceCertificate, SubjectInfoAccess};
|
||||||
use asn1_rs::{Any, Class, FromBer, FromDer as Asn1FromDer, Header, Tag};
|
use asn1_rs::{Any, Class, FromBer, FromDer as Asn1FromDer, Header, Tag};
|
||||||
use ring::digest;
|
use ring::digest;
|
||||||
use x509_parser::extensions::ParsedExtension;
|
use x509_parser::extensions::ParsedExtension;
|
||||||
@ -822,8 +822,6 @@ fn validate_ee_certificate(
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
rc.validate_rfc6487_profile(ResourceCertificateRole::SignedObjectEe)
|
|
||||||
.map_err(|e| SignedObjectValidateError::EeCertificateParse(e.to_string()))?;
|
|
||||||
if strict_name {
|
if strict_name {
|
||||||
rc.validate_strict_name_profile()
|
rc.validate_strict_name_profile()
|
||||||
.map_err(|e| SignedObjectValidateError::EeCertificateParse(e.to_string()))?;
|
.map_err(|e| SignedObjectValidateError::EeCertificateParse(e.to_string()))?;
|
||||||
|
|||||||
@ -5,7 +5,6 @@ use crate::data_model::oid::OID_CP_IPADDR_ASNUMBER;
|
|||||||
use crate::data_model::rc::{
|
use crate::data_model::rc::{
|
||||||
AsIdentifierChoice, IpAddressChoice, ResourceCertKind, ResourceCertificate,
|
AsIdentifierChoice, IpAddressChoice, ResourceCertKind, ResourceCertificate,
|
||||||
ResourceCertificateParseError, ResourceCertificateParsed, ResourceCertificateProfileError,
|
ResourceCertificateParseError, ResourceCertificateParsed, ResourceCertificateProfileError,
|
||||||
ResourceCertificateRole,
|
|
||||||
};
|
};
|
||||||
use crate::data_model::tal::Tal;
|
use crate::data_model::tal::Tal;
|
||||||
|
|
||||||
@ -219,7 +218,6 @@ impl TaCertificateParsed {
|
|||||||
if rc_ca.kind != ResourceCertKind::Ca {
|
if rc_ca.kind != ResourceCertKind::Ca {
|
||||||
return Err(TaCertificateProfileError::NotCa);
|
return Err(TaCertificateProfileError::NotCa);
|
||||||
}
|
}
|
||||||
rc_ca.validate_rfc6487_profile(ResourceCertificateRole::TrustAnchor)?;
|
|
||||||
|
|
||||||
if rc_ca.tbs.issuer_name != rc_ca.tbs.subject_name {
|
if rc_ca.tbs.issuer_name != rc_ca.tbs.subject_name {
|
||||||
return Err(TaCertificateProfileError::NotSelfSignedIssuerSubject);
|
return Err(TaCertificateProfileError::NotSelfSignedIssuerSubject);
|
||||||
@ -246,9 +244,6 @@ pub enum TrustAnchorError {
|
|||||||
#[error("TA certificate error: {0} (RFC 8630 §2.3)")]
|
#[error("TA certificate error: {0} (RFC 8630 §2.3)")]
|
||||||
TaCertificate(#[from] TaCertificateDecodeError),
|
TaCertificate(#[from] TaCertificateDecodeError),
|
||||||
|
|
||||||
#[error("TA certificate self-signature error: {0} (RFC 8630 §2.3)")]
|
|
||||||
TaSelfSignature(#[from] TaCertificateVerifyError),
|
|
||||||
|
|
||||||
#[error("{0}")]
|
#[error("{0}")]
|
||||||
Bind(#[from] TrustAnchorBindError),
|
Bind(#[from] TrustAnchorBindError),
|
||||||
}
|
}
|
||||||
@ -274,7 +269,6 @@ impl TrustAnchor {
|
|||||||
resolved_uri: Option<&Url>,
|
resolved_uri: Option<&Url>,
|
||||||
) -> Result<Self, TrustAnchorError> {
|
) -> Result<Self, TrustAnchorError> {
|
||||||
let ta_certificate = TaCertificate::decode_der(ta_der)?;
|
let ta_certificate = TaCertificate::decode_der(ta_der)?;
|
||||||
ta_certificate.verify_self_signature()?;
|
|
||||||
Ok(Self::bind(tal, ta_certificate, resolved_uri)?)
|
Ok(Self::bind(tal, ta_certificate, resolved_uri)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -284,7 +278,6 @@ impl TrustAnchor {
|
|||||||
resolved_uri: Option<&Url>,
|
resolved_uri: Option<&Url>,
|
||||||
) -> Result<Self, TrustAnchorError> {
|
) -> Result<Self, TrustAnchorError> {
|
||||||
let ta_certificate = TaCertificate::decode_der_with_strict_name(ta_der)?;
|
let ta_certificate = TaCertificate::decode_der_with_strict_name(ta_der)?;
|
||||||
ta_certificate.verify_self_signature()?;
|
|
||||||
Ok(Self::bind(tal, ta_certificate, resolved_uri)?)
|
Ok(Self::bind(tal, ta_certificate, resolved_uri)?)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
218
src/storage.rs
218
src/storage.rs
@ -19,7 +19,7 @@ pub use config::{
|
|||||||
ALL_COLUMN_FAMILY_NAMES, CF_CHILD_CERTIFICATE_CACHE_PROJECTION, CF_MANIFEST_REPLAY_META,
|
ALL_COLUMN_FAMILY_NAMES, CF_CHILD_CERTIFICATE_CACHE_PROJECTION, CF_MANIFEST_REPLAY_META,
|
||||||
CF_PUBLICATION_POINT_CACHE_PROJECTION, CF_RAW_BY_HASH, CF_REPOSITORY_VIEW,
|
CF_PUBLICATION_POINT_CACHE_PROJECTION, CF_RAW_BY_HASH, CF_REPOSITORY_VIEW,
|
||||||
CF_ROA_CACHE_PROJECTION, CF_RRDP_SOURCE, CF_RRDP_SOURCE_MEMBER, CF_RRDP_URI_OWNER,
|
CF_ROA_CACHE_PROJECTION, CF_RRDP_SOURCE, CF_RRDP_SOURCE_MEMBER, CF_RRDP_URI_OWNER,
|
||||||
CF_TRANSPORT_PREFETCH, CF_VCIR, CF_VCIR_FAILED_FETCH_REUSE_IDENTITY, column_family_descriptors,
|
CF_TRANSPORT_PREFETCH, CF_VCIR, column_family_descriptors,
|
||||||
};
|
};
|
||||||
use keys::*;
|
use keys::*;
|
||||||
use pack::compute_sha256_32;
|
use pack::compute_sha256_32;
|
||||||
@ -585,56 +585,6 @@ impl VcirInstanceGate {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
|
||||||
pub struct VcirFailedFetchReuseIdentity {
|
|
||||||
#[serde(rename = "c")]
|
|
||||||
#[serde(with = "serde_bytes_32")]
|
|
||||||
pub current_ca_sha256: [u8; 32],
|
|
||||||
#[serde(rename = "t")]
|
|
||||||
#[serde(with = "serde_bytes_32")]
|
|
||||||
pub ta_context_digest: [u8; 32],
|
|
||||||
#[serde(rename = "p")]
|
|
||||||
#[serde(with = "serde_bytes_32")]
|
|
||||||
pub parent_context_digest: [u8; 32],
|
|
||||||
#[serde(rename = "f")]
|
|
||||||
#[serde(with = "serde_bytes_32")]
|
|
||||||
pub policy_fingerprint: [u8; 32],
|
|
||||||
#[serde(rename = "nb")]
|
|
||||||
pub effective_not_before: PackTime,
|
|
||||||
#[serde(rename = "nu")]
|
|
||||||
pub effective_until: PackTime,
|
|
||||||
}
|
|
||||||
|
|
||||||
impl VcirFailedFetchReuseIdentity {
|
|
||||||
pub fn validate_internal(&self) -> StorageResult<()> {
|
|
||||||
let effective_not_before = parse_time(
|
|
||||||
"vcir_failed_fetch_reuse_identity.effective_not_before",
|
|
||||||
&self.effective_not_before,
|
|
||||||
)?;
|
|
||||||
let effective_until = parse_time(
|
|
||||||
"vcir_failed_fetch_reuse_identity.effective_until",
|
|
||||||
&self.effective_until,
|
|
||||||
)?;
|
|
||||||
if effective_not_before >= effective_until {
|
|
||||||
return Err(StorageError::InvalidData {
|
|
||||||
entity: "vcir_failed_fetch_reuse_identity.effective_window",
|
|
||||||
detail: "effective_not_before must be before effective_until".to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn contains_validation_time(&self, validation_time: time::OffsetDateTime) -> bool {
|
|
||||||
let Ok(effective_not_before) = self.effective_not_before.parse() else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
let Ok(effective_until) = self.effective_until.parse() else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
validation_time >= effective_not_before && validation_time < effective_until
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
#[derive(Clone, Debug, PartialEq, Eq, Serialize, Deserialize)]
|
||||||
pub struct VcirChildEntry {
|
pub struct VcirChildEntry {
|
||||||
pub child_manifest_rsync_uri: String,
|
pub child_manifest_rsync_uri: String,
|
||||||
@ -1112,7 +1062,6 @@ pub struct RoaCacheObjectMeta {
|
|||||||
pub source_object_hash: [u8; 32],
|
pub source_object_hash: [u8; 32],
|
||||||
pub ee_serial: Vec<u8>,
|
pub ee_serial: Vec<u8>,
|
||||||
pub crl_uri: String,
|
pub crl_uri: String,
|
||||||
pub earliest_safe_reuse_time: PackTime,
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
@ -1178,8 +1127,6 @@ pub struct RoaCacheObjectProjection {
|
|||||||
pub ee_serial: Option<Vec<u8>>,
|
pub ee_serial: Option<Vec<u8>>,
|
||||||
#[serde(rename = "c", default, skip_serializing_if = "Option::is_none")]
|
#[serde(rename = "c", default, skip_serializing_if = "Option::is_none")]
|
||||||
pub crl_uri: Option<String>,
|
pub crl_uri: Option<String>,
|
||||||
#[serde(rename = "n", default, skip_serializing_if = "Option::is_none")]
|
|
||||||
pub earliest_safe_reuse_time_unix: Option<i64>,
|
|
||||||
#[serde(rename = "x")]
|
#[serde(rename = "x")]
|
||||||
pub outputs_effective_until_unix: i64,
|
pub outputs_effective_until_unix: i64,
|
||||||
#[serde(rename = "o")]
|
#[serde(rename = "o")]
|
||||||
@ -1203,15 +1150,6 @@ impl RoaCacheObjectProjection {
|
|||||||
if let Some(crl_uri) = &self.crl_uri {
|
if let Some(crl_uri) = &self.crl_uri {
|
||||||
validate_non_empty("roa_cache_projection.entries[].crl_uri", crl_uri)?;
|
validate_non_empty("roa_cache_projection.entries[].crl_uri", crl_uri)?;
|
||||||
}
|
}
|
||||||
if let Some(earliest_safe_reuse_time_unix) = self.earliest_safe_reuse_time_unix
|
|
||||||
&& earliest_safe_reuse_time_unix >= self.outputs_effective_until_unix
|
|
||||||
{
|
|
||||||
return Err(StorageError::InvalidData {
|
|
||||||
entity: "roa_cache_projection.entries[].effective_window",
|
|
||||||
detail: "earliest_safe_reuse_time_unix must be before outputs_effective_until_unix"
|
|
||||||
.to_string(),
|
|
||||||
});
|
|
||||||
}
|
|
||||||
if self.outputs.is_empty() {
|
if self.outputs.is_empty() {
|
||||||
return Err(StorageError::InvalidData {
|
return Err(StorageError::InvalidData {
|
||||||
entity: "roa_cache_projection.entries[]",
|
entity: "roa_cache_projection.entries[]",
|
||||||
@ -1929,9 +1867,6 @@ impl RoaCacheProjection {
|
|||||||
vcir: &ValidatedCaInstanceResult,
|
vcir: &ValidatedCaInstanceResult,
|
||||||
context: Option<&RoaCacheProjectionContext>,
|
context: Option<&RoaCacheProjectionContext>,
|
||||||
) -> StorageResult<Option<Self>> {
|
) -> StorageResult<Option<Self>> {
|
||||||
let Some(context) = context else {
|
|
||||||
return Ok(None);
|
|
||||||
};
|
|
||||||
let mut issuer_ca_sha256_hex = None;
|
let mut issuer_ca_sha256_hex = None;
|
||||||
let mut crl_sha256_by_uri = Vec::new();
|
let mut crl_sha256_by_uri = Vec::new();
|
||||||
for artifact in &vcir.related_artifacts {
|
for artifact in &vcir.related_artifacts {
|
||||||
@ -1958,18 +1893,13 @@ impl RoaCacheProjection {
|
|||||||
}
|
}
|
||||||
crl_sha256_by_uri.sort_by(|left, right| left.uri.cmp(&right.uri));
|
crl_sha256_by_uri.sort_by(|left, right| left.uri.cmp(&right.uri));
|
||||||
|
|
||||||
let meta_by_uri = context
|
let meta_by_uri = context.map(|context| {
|
||||||
.object_meta
|
context
|
||||||
.iter()
|
.object_meta
|
||||||
.map(|meta| (meta.source_object_uri.as_str(), meta))
|
.iter()
|
||||||
.collect::<HashMap<_, _>>();
|
.map(|meta| (meta.source_object_uri.as_str(), meta))
|
||||||
let vcir_validation_time =
|
.collect::<HashMap<_, _>>()
|
||||||
vcir.last_successful_validation_time
|
});
|
||||||
.parse()
|
|
||||||
.map_err(|detail| StorageError::InvalidData {
|
|
||||||
entity: "roa_cache_projection.entries[].earliest_safe_reuse_time_unix",
|
|
||||||
detail,
|
|
||||||
})?;
|
|
||||||
let mut entries: Vec<RoaCacheObjectProjection> = Vec::new();
|
let mut entries: Vec<RoaCacheObjectProjection> = Vec::new();
|
||||||
let mut entry_index_by_uri: HashMap<String, usize> = HashMap::new();
|
let mut entry_index_by_uri: HashMap<String, usize> = HashMap::new();
|
||||||
for output in &vcir.local_outputs {
|
for output in &vcir.local_outputs {
|
||||||
@ -1985,26 +1915,23 @@ impl RoaCacheProjection {
|
|||||||
entity: "roa_cache_projection.entries[].outputs_effective_until_unix",
|
entity: "roa_cache_projection.entries[].outputs_effective_until_unix",
|
||||||
detail,
|
detail,
|
||||||
})?;
|
})?;
|
||||||
let Some(meta) = meta_by_uri.get(output.source_object_uri.as_str()).copied() else {
|
let meta = meta_by_uri
|
||||||
|
.as_ref()
|
||||||
|
.and_then(|meta| meta.get(output.source_object_uri.as_str()).copied());
|
||||||
|
if context.is_some() && meta.is_none() {
|
||||||
continue;
|
continue;
|
||||||
};
|
|
||||||
if meta.source_object_hash != output.source_object_hash {
|
|
||||||
return Err(StorageError::InvalidData {
|
|
||||||
entity: "roa_cache_projection.entries[]",
|
|
||||||
detail: format!(
|
|
||||||
"metadata source object hash mismatch for {}",
|
|
||||||
output.source_object_uri
|
|
||||||
),
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
let earliest_safe_reuse_time_unix = meta
|
if let Some(meta) = meta {
|
||||||
.earliest_safe_reuse_time
|
if meta.source_object_hash != output.source_object_hash {
|
||||||
.parse()
|
return Err(StorageError::InvalidData {
|
||||||
.map(|time| time.max(vcir_validation_time).unix_timestamp())
|
entity: "roa_cache_projection.entries[]",
|
||||||
.map_err(|detail| StorageError::InvalidData {
|
detail: format!(
|
||||||
entity: "roa_cache_projection.entries[].earliest_safe_reuse_time_unix",
|
"metadata source object hash mismatch for {}",
|
||||||
detail,
|
output.source_object_uri
|
||||||
})?;
|
),
|
||||||
|
});
|
||||||
|
}
|
||||||
|
}
|
||||||
if let Some(entry_index) = entry_index_by_uri.get(output.source_object_uri.as_str()) {
|
if let Some(entry_index) = entry_index_by_uri.get(output.source_object_uri.as_str()) {
|
||||||
let entry = &mut entries[*entry_index];
|
let entry = &mut entries[*entry_index];
|
||||||
if entry.source_object_hash != output.source_object_hash {
|
if entry.source_object_hash != output.source_object_hash {
|
||||||
@ -2025,9 +1952,8 @@ impl RoaCacheProjection {
|
|||||||
entries.push(RoaCacheObjectProjection {
|
entries.push(RoaCacheObjectProjection {
|
||||||
source_object_uri: output.source_object_uri.clone(),
|
source_object_uri: output.source_object_uri.clone(),
|
||||||
source_object_hash: output.source_object_hash,
|
source_object_hash: output.source_object_hash,
|
||||||
ee_serial: Some(meta.ee_serial.clone()),
|
ee_serial: meta.map(|meta| meta.ee_serial.clone()),
|
||||||
crl_uri: Some(meta.crl_uri.clone()),
|
crl_uri: meta.map(|meta| meta.crl_uri.clone()),
|
||||||
earliest_safe_reuse_time_unix: Some(earliest_safe_reuse_time_unix),
|
|
||||||
outputs_effective_until_unix: projected_output_effective_until,
|
outputs_effective_until_unix: projected_output_effective_until,
|
||||||
outputs: vec![projected_output],
|
outputs: vec![projected_output],
|
||||||
});
|
});
|
||||||
@ -2042,8 +1968,8 @@ impl RoaCacheProjection {
|
|||||||
manifest_rsync_uri: vcir.manifest_rsync_uri.clone(),
|
manifest_rsync_uri: vcir.manifest_rsync_uri.clone(),
|
||||||
instance_effective_until: vcir.instance_gate.instance_effective_until.clone(),
|
instance_effective_until: vcir.instance_gate.instance_effective_until.clone(),
|
||||||
issuer_ca_sha256_hex,
|
issuer_ca_sha256_hex,
|
||||||
parent_context_digest: Some(context.parent_context_digest),
|
parent_context_digest: context.map(|context| context.parent_context_digest),
|
||||||
policy_fingerprint: Some(context.policy_fingerprint),
|
policy_fingerprint: context.map(|context| context.policy_fingerprint),
|
||||||
crl_sha256_by_uri,
|
crl_sha256_by_uri,
|
||||||
entries,
|
entries,
|
||||||
};
|
};
|
||||||
@ -2848,24 +2774,6 @@ fn write_roa_cache_projection_to_batch(
|
|||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_vcir_failed_fetch_reuse_identity_to_batch(
|
|
||||||
failed_fetch_reuse_identity_cf: &ColumnFamily,
|
|
||||||
batch: &mut WriteBatch,
|
|
||||||
manifest_rsync_uri: &str,
|
|
||||||
failed_fetch_reuse_identity: Option<&VcirFailedFetchReuseIdentity>,
|
|
||||||
) -> StorageResult<()> {
|
|
||||||
let key = vcir_failed_fetch_reuse_identity_key(manifest_rsync_uri);
|
|
||||||
match failed_fetch_reuse_identity {
|
|
||||||
Some(identity) => {
|
|
||||||
identity.validate_internal()?;
|
|
||||||
let value = encode_cbor(identity, "vcir_failed_fetch_reuse_identity")?;
|
|
||||||
batch.put_cf(failed_fetch_reuse_identity_cf, key.as_bytes(), value);
|
|
||||||
}
|
|
||||||
None => batch.delete_cf(failed_fetch_reuse_identity_cf, key.as_bytes()),
|
|
||||||
}
|
|
||||||
Ok(())
|
|
||||||
}
|
|
||||||
|
|
||||||
fn write_publication_point_cache_projection_to_batch(
|
fn write_publication_point_cache_projection_to_batch(
|
||||||
projection_cf: &ColumnFamily,
|
projection_cf: &ColumnFamily,
|
||||||
batch: &mut WriteBatch,
|
batch: &mut WriteBatch,
|
||||||
@ -3359,19 +3267,6 @@ impl RocksStore {
|
|||||||
self.put_vcir_with_publication_point_cache_projection(vcir, None)
|
self.put_vcir_with_publication_point_cache_projection(vcir, None)
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn put_vcir_with_failed_fetch_reuse_identity(
|
|
||||||
&self,
|
|
||||||
vcir: &ValidatedCaInstanceResult,
|
|
||||||
failed_fetch_reuse_identity: &VcirFailedFetchReuseIdentity,
|
|
||||||
) -> StorageResult<()> {
|
|
||||||
self.put_vcir_with_projection_action(
|
|
||||||
vcir,
|
|
||||||
None,
|
|
||||||
PublicationPointCacheProjectionWriteAction::Keep,
|
|
||||||
Some(failed_fetch_reuse_identity),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn put_vcir_with_publication_point_cache_projection(
|
pub fn put_vcir_with_publication_point_cache_projection(
|
||||||
&self,
|
&self,
|
||||||
vcir: &ValidatedCaInstanceResult,
|
vcir: &ValidatedCaInstanceResult,
|
||||||
@ -3393,7 +3288,6 @@ impl RocksStore {
|
|||||||
vcir,
|
vcir,
|
||||||
roa_cache_context,
|
roa_cache_context,
|
||||||
publication_point_projection_action,
|
publication_point_projection_action,
|
||||||
None,
|
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -3402,11 +3296,9 @@ impl RocksStore {
|
|||||||
vcir: &ValidatedCaInstanceResult,
|
vcir: &ValidatedCaInstanceResult,
|
||||||
roa_cache_context: Option<&RoaCacheProjectionContext>,
|
roa_cache_context: Option<&RoaCacheProjectionContext>,
|
||||||
publication_point_projection_action: PublicationPointCacheProjectionWriteAction<'_>,
|
publication_point_projection_action: PublicationPointCacheProjectionWriteAction<'_>,
|
||||||
failed_fetch_reuse_identity: Option<&VcirFailedFetchReuseIdentity>,
|
|
||||||
) -> StorageResult<()> {
|
) -> StorageResult<()> {
|
||||||
vcir.validate_internal()?;
|
vcir.validate_internal()?;
|
||||||
let vcir_cf = self.cf(CF_VCIR)?;
|
let vcir_cf = self.cf(CF_VCIR)?;
|
||||||
let failed_fetch_reuse_identity_cf = self.cf(CF_VCIR_FAILED_FETCH_REUSE_IDENTITY)?;
|
|
||||||
let replay_cf = self.cf(CF_MANIFEST_REPLAY_META)?;
|
let replay_cf = self.cf(CF_MANIFEST_REPLAY_META)?;
|
||||||
let projection_cf = self.cf(CF_ROA_CACHE_PROJECTION)?;
|
let projection_cf = self.cf(CF_ROA_CACHE_PROJECTION)?;
|
||||||
let pp_projection_cf = self.cf(CF_PUBLICATION_POINT_CACHE_PROJECTION)?;
|
let pp_projection_cf = self.cf(CF_PUBLICATION_POINT_CACHE_PROJECTION)?;
|
||||||
@ -3416,12 +3308,6 @@ impl RocksStore {
|
|||||||
let key = vcir_key(&vcir.manifest_rsync_uri);
|
let key = vcir_key(&vcir.manifest_rsync_uri);
|
||||||
let value = encode_cbor(vcir, "vcir")?;
|
let value = encode_cbor(vcir, "vcir")?;
|
||||||
batch.put_cf(vcir_cf, key.as_bytes(), value);
|
batch.put_cf(vcir_cf, key.as_bytes(), value);
|
||||||
write_vcir_failed_fetch_reuse_identity_to_batch(
|
|
||||||
failed_fetch_reuse_identity_cf,
|
|
||||||
&mut batch,
|
|
||||||
&vcir.manifest_rsync_uri,
|
|
||||||
failed_fetch_reuse_identity,
|
|
||||||
)?;
|
|
||||||
let replay_key = manifest_replay_meta_key(&replay_meta.manifest_rsync_uri);
|
let replay_key = manifest_replay_meta_key(&replay_meta.manifest_rsync_uri);
|
||||||
let replay_value = encode_cbor(&replay_meta, "manifest_replay_meta")?;
|
let replay_value = encode_cbor(&replay_meta, "manifest_replay_meta")?;
|
||||||
batch.put_cf(replay_cf, replay_key.as_bytes(), replay_value);
|
batch.put_cf(replay_cf, replay_key.as_bytes(), replay_value);
|
||||||
@ -3485,21 +3371,6 @@ impl RocksStore {
|
|||||||
vcir: &ValidatedCaInstanceResult,
|
vcir: &ValidatedCaInstanceResult,
|
||||||
roa_cache_context: Option<&RoaCacheProjectionContext>,
|
roa_cache_context: Option<&RoaCacheProjectionContext>,
|
||||||
publication_point_projection_action: PublicationPointCacheProjectionWriteAction<'_>,
|
publication_point_projection_action: PublicationPointCacheProjectionWriteAction<'_>,
|
||||||
) -> StorageResult<VcirReplaceTimingBreakdown> {
|
|
||||||
self.replace_vcir_manifest_replay_meta_and_projection_action_with_failed_fetch_reuse_identity(
|
|
||||||
vcir,
|
|
||||||
roa_cache_context,
|
|
||||||
publication_point_projection_action,
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(crate) fn replace_vcir_manifest_replay_meta_and_projection_action_with_failed_fetch_reuse_identity(
|
|
||||||
&self,
|
|
||||||
vcir: &ValidatedCaInstanceResult,
|
|
||||||
roa_cache_context: Option<&RoaCacheProjectionContext>,
|
|
||||||
publication_point_projection_action: PublicationPointCacheProjectionWriteAction<'_>,
|
|
||||||
failed_fetch_reuse_identity: Option<&VcirFailedFetchReuseIdentity>,
|
|
||||||
) -> StorageResult<VcirReplaceTimingBreakdown> {
|
) -> StorageResult<VcirReplaceTimingBreakdown> {
|
||||||
let mut timing = VcirReplaceTimingBreakdown {
|
let mut timing = VcirReplaceTimingBreakdown {
|
||||||
rss_before_kb: process_vm_rss_kb(),
|
rss_before_kb: process_vm_rss_kb(),
|
||||||
@ -3514,7 +3385,6 @@ impl RocksStore {
|
|||||||
|
|
||||||
let batch_build_started = std::time::Instant::now();
|
let batch_build_started = std::time::Instant::now();
|
||||||
let vcir_cf = self.cf(CF_VCIR)?;
|
let vcir_cf = self.cf(CF_VCIR)?;
|
||||||
let failed_fetch_reuse_identity_cf = self.cf(CF_VCIR_FAILED_FETCH_REUSE_IDENTITY)?;
|
|
||||||
let replay_cf = self.cf(CF_MANIFEST_REPLAY_META)?;
|
let replay_cf = self.cf(CF_MANIFEST_REPLAY_META)?;
|
||||||
let projection_cf = self.cf(CF_ROA_CACHE_PROJECTION)?;
|
let projection_cf = self.cf(CF_ROA_CACHE_PROJECTION)?;
|
||||||
let pp_projection_cf = self.cf(CF_PUBLICATION_POINT_CACHE_PROJECTION)?;
|
let pp_projection_cf = self.cf(CF_PUBLICATION_POINT_CACHE_PROJECTION)?;
|
||||||
@ -3527,12 +3397,6 @@ impl RocksStore {
|
|||||||
timing.vcir_value_bytes = vcir_value.len() as u64;
|
timing.vcir_value_bytes = vcir_value.len() as u64;
|
||||||
batch.put_cf(vcir_cf, vcir_key.as_bytes(), vcir_value);
|
batch.put_cf(vcir_cf, vcir_key.as_bytes(), vcir_value);
|
||||||
timing.rss_after_vcir_encode_kb = process_vm_rss_kb();
|
timing.rss_after_vcir_encode_kb = process_vm_rss_kb();
|
||||||
write_vcir_failed_fetch_reuse_identity_to_batch(
|
|
||||||
failed_fetch_reuse_identity_cf,
|
|
||||||
&mut batch,
|
|
||||||
&vcir.manifest_rsync_uri,
|
|
||||||
failed_fetch_reuse_identity,
|
|
||||||
)?;
|
|
||||||
|
|
||||||
let replay_meta_encode_started = std::time::Instant::now();
|
let replay_meta_encode_started = std::time::Instant::now();
|
||||||
let replay_meta = ManifestReplayMeta::from_vcir(vcir);
|
let replay_meta = ManifestReplayMeta::from_vcir(vcir);
|
||||||
@ -3601,27 +3465,6 @@ impl RocksStore {
|
|||||||
Ok(Some(vcir))
|
Ok(Some(vcir))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn get_vcir_failed_fetch_reuse_identity(
|
|
||||||
&self,
|
|
||||||
manifest_rsync_uri: &str,
|
|
||||||
) -> StorageResult<Option<VcirFailedFetchReuseIdentity>> {
|
|
||||||
let cf = self.cf(CF_VCIR_FAILED_FETCH_REUSE_IDENTITY)?;
|
|
||||||
let key = vcir_failed_fetch_reuse_identity_key(manifest_rsync_uri);
|
|
||||||
let Some(bytes) = self
|
|
||||||
.db
|
|
||||||
.get_cf(cf, key.as_bytes())
|
|
||||||
.map_err(|e| StorageError::RocksDb(e.to_string()))?
|
|
||||||
else {
|
|
||||||
return Ok(None);
|
|
||||||
};
|
|
||||||
let identity = decode_cbor::<VcirFailedFetchReuseIdentity>(
|
|
||||||
&bytes,
|
|
||||||
"vcir_failed_fetch_reuse_identity",
|
|
||||||
)?;
|
|
||||||
identity.validate_internal()?;
|
|
||||||
Ok(Some(identity))
|
|
||||||
}
|
|
||||||
|
|
||||||
pub fn get_manifest_replay_meta(
|
pub fn get_manifest_replay_meta(
|
||||||
&self,
|
&self,
|
||||||
manifest_rsync_uri: &str,
|
manifest_rsync_uri: &str,
|
||||||
@ -4465,18 +4308,11 @@ impl RocksStore {
|
|||||||
|
|
||||||
pub fn delete_vcir(&self, manifest_rsync_uri: &str) -> StorageResult<()> {
|
pub fn delete_vcir(&self, manifest_rsync_uri: &str) -> StorageResult<()> {
|
||||||
let vcir_cf = self.cf(CF_VCIR)?;
|
let vcir_cf = self.cf(CF_VCIR)?;
|
||||||
let failed_fetch_reuse_identity_cf = self.cf(CF_VCIR_FAILED_FETCH_REUSE_IDENTITY)?;
|
|
||||||
let replay_cf = self.cf(CF_MANIFEST_REPLAY_META)?;
|
let replay_cf = self.cf(CF_MANIFEST_REPLAY_META)?;
|
||||||
let projection_cf = self.cf(CF_ROA_CACHE_PROJECTION)?;
|
let projection_cf = self.cf(CF_ROA_CACHE_PROJECTION)?;
|
||||||
let mut batch = WriteBatch::default();
|
let mut batch = WriteBatch::default();
|
||||||
let key = vcir_key(manifest_rsync_uri);
|
let key = vcir_key(manifest_rsync_uri);
|
||||||
batch.delete_cf(vcir_cf, key.as_bytes());
|
batch.delete_cf(vcir_cf, key.as_bytes());
|
||||||
let failed_fetch_reuse_identity_key =
|
|
||||||
vcir_failed_fetch_reuse_identity_key(manifest_rsync_uri);
|
|
||||||
batch.delete_cf(
|
|
||||||
failed_fetch_reuse_identity_cf,
|
|
||||||
failed_fetch_reuse_identity_key.as_bytes(),
|
|
||||||
);
|
|
||||||
let replay_key = manifest_replay_meta_key(manifest_rsync_uri);
|
let replay_key = manifest_replay_meta_key(manifest_rsync_uri);
|
||||||
batch.delete_cf(replay_cf, replay_key.as_bytes());
|
batch.delete_cf(replay_cf, replay_key.as_bytes());
|
||||||
let projection_key = roa_cache_projection_key(manifest_rsync_uri);
|
let projection_key = roa_cache_projection_key(manifest_rsync_uri);
|
||||||
|
|||||||
@ -4,7 +4,6 @@ pub const CF_REPOSITORY_VIEW: &str = "repository_view";
|
|||||||
pub const CF_RAW_BY_HASH: &str = "raw_by_hash";
|
pub const CF_RAW_BY_HASH: &str = "raw_by_hash";
|
||||||
pub const CF_RAW_BLOB: &str = "raw_blob";
|
pub const CF_RAW_BLOB: &str = "raw_blob";
|
||||||
pub const CF_VCIR: &str = "vcir";
|
pub const CF_VCIR: &str = "vcir";
|
||||||
pub const CF_VCIR_FAILED_FETCH_REUSE_IDENTITY: &str = "vcir_failed_fetch_reuse_identity";
|
|
||||||
pub const CF_MANIFEST_REPLAY_META: &str = "manifest_replay_meta";
|
pub const CF_MANIFEST_REPLAY_META: &str = "manifest_replay_meta";
|
||||||
pub const CF_ROA_CACHE_PROJECTION: &str = "roa_cache_projection";
|
pub const CF_ROA_CACHE_PROJECTION: &str = "roa_cache_projection";
|
||||||
pub const CF_PUBLICATION_POINT_CACHE_PROJECTION: &str = "publication_point_cache_projection";
|
pub const CF_PUBLICATION_POINT_CACHE_PROJECTION: &str = "publication_point_cache_projection";
|
||||||
@ -19,7 +18,6 @@ pub const ALL_COLUMN_FAMILY_NAMES: &[&str] = &[
|
|||||||
CF_RAW_BY_HASH,
|
CF_RAW_BY_HASH,
|
||||||
CF_RAW_BLOB,
|
CF_RAW_BLOB,
|
||||||
CF_VCIR,
|
CF_VCIR,
|
||||||
CF_VCIR_FAILED_FETCH_REUSE_IDENTITY,
|
|
||||||
CF_MANIFEST_REPLAY_META,
|
CF_MANIFEST_REPLAY_META,
|
||||||
CF_ROA_CACHE_PROJECTION,
|
CF_ROA_CACHE_PROJECTION,
|
||||||
CF_PUBLICATION_POINT_CACHE_PROJECTION,
|
CF_PUBLICATION_POINT_CACHE_PROJECTION,
|
||||||
@ -34,8 +32,6 @@ pub(super) const REPOSITORY_VIEW_KEY_PREFIX: &str = "repo_view:";
|
|||||||
pub(super) const RAW_BY_HASH_KEY_PREFIX: &str = "rawbyhash:";
|
pub(super) const RAW_BY_HASH_KEY_PREFIX: &str = "rawbyhash:";
|
||||||
pub(super) const RAW_BLOB_KEY_PREFIX: &str = "rawblob:";
|
pub(super) const RAW_BLOB_KEY_PREFIX: &str = "rawblob:";
|
||||||
pub(super) const VCIR_KEY_PREFIX: &str = "vcir:";
|
pub(super) const VCIR_KEY_PREFIX: &str = "vcir:";
|
||||||
pub(super) const VCIR_FAILED_FETCH_REUSE_IDENTITY_KEY_PREFIX: &str =
|
|
||||||
"vcir_failed_fetch_reuse_identity:";
|
|
||||||
pub(super) const MANIFEST_REPLAY_META_KEY_PREFIX: &str = "manifest_replay_meta:";
|
pub(super) const MANIFEST_REPLAY_META_KEY_PREFIX: &str = "manifest_replay_meta:";
|
||||||
pub(super) const ROA_CACHE_PROJECTION_KEY_PREFIX: &str = "roa_cache_projection:";
|
pub(super) const ROA_CACHE_PROJECTION_KEY_PREFIX: &str = "roa_cache_projection:";
|
||||||
pub(super) const PUBLICATION_POINT_CACHE_PROJECTION_KEY_PREFIX: &str =
|
pub(super) const PUBLICATION_POINT_CACHE_PROJECTION_KEY_PREFIX: &str =
|
||||||
|
|||||||
@ -26,10 +26,6 @@ pub(super) fn vcir_key(manifest_rsync_uri: &str) -> String {
|
|||||||
format!("{VCIR_KEY_PREFIX}{manifest_rsync_uri}")
|
format!("{VCIR_KEY_PREFIX}{manifest_rsync_uri}")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub(super) fn vcir_failed_fetch_reuse_identity_key(manifest_rsync_uri: &str) -> String {
|
|
||||||
format!("{VCIR_FAILED_FETCH_REUSE_IDENTITY_KEY_PREFIX}{manifest_rsync_uri}")
|
|
||||||
}
|
|
||||||
|
|
||||||
pub(super) fn manifest_replay_meta_key(manifest_rsync_uri: &str) -> String {
|
pub(super) fn manifest_replay_meta_key(manifest_rsync_uri: &str) -> String {
|
||||||
format!("{MANIFEST_REPLAY_META_KEY_PREFIX}{manifest_rsync_uri}")
|
format!("{MANIFEST_REPLAY_META_KEY_PREFIX}{manifest_rsync_uri}")
|
||||||
}
|
}
|
||||||
|
|||||||
@ -284,28 +284,6 @@ fn sample_vcir(manifest_rsync_uri: &str) -> ValidatedCaInstanceResult {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn roa_cache_projection_context(vcir: &ValidatedCaInstanceResult) -> RoaCacheProjectionContext {
|
|
||||||
let roa_output = vcir
|
|
||||||
.local_outputs
|
|
||||||
.iter()
|
|
||||||
.find(|output| {
|
|
||||||
output.output_type == VcirOutputType::Vrp
|
|
||||||
&& output.source_object_type == VcirSourceObjectType::Roa
|
|
||||||
})
|
|
||||||
.expect("sample VCIR has a ROA output");
|
|
||||||
RoaCacheProjectionContext {
|
|
||||||
parent_context_digest: [0x31; 32],
|
|
||||||
policy_fingerprint: [0x32; 32],
|
|
||||||
object_meta: vec![RoaCacheObjectMeta {
|
|
||||||
source_object_uri: roa_output.source_object_uri.clone(),
|
|
||||||
source_object_hash: roa_output.source_object_hash,
|
|
||||||
ee_serial: vec![0x01],
|
|
||||||
crl_uri: vcir.current_crl_rsync_uri.clone(),
|
|
||||||
earliest_safe_reuse_time: vcir.last_successful_validation_time.clone(),
|
|
||||||
}],
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn vcir_ccr_manifest_projection_validate_accepts_valid_projection() {
|
fn vcir_ccr_manifest_projection_validate_accepts_valid_projection() {
|
||||||
let projection = sample_ccr_manifest_projection(
|
let projection = sample_ccr_manifest_projection(
|
||||||
@ -354,8 +332,7 @@ fn vcir_ccr_manifest_projection_validate_rejects_invalid_fields() {
|
|||||||
#[test]
|
#[test]
|
||||||
fn roa_cache_projection_from_vcir_keeps_only_roa_vrp_outputs() {
|
fn roa_cache_projection_from_vcir_keeps_only_roa_vrp_outputs() {
|
||||||
let vcir = sample_vcir("rsync://example.test/repo/current.mft");
|
let vcir = sample_vcir("rsync://example.test/repo/current.mft");
|
||||||
let context = roa_cache_projection_context(&vcir);
|
let projection = RoaCacheProjection::from_vcir(&vcir)
|
||||||
let projection = RoaCacheProjection::from_vcir_with_context(&vcir, Some(&context))
|
|
||||||
.expect("projection build")
|
.expect("projection build")
|
||||||
.expect("projection exists");
|
.expect("projection exists");
|
||||||
|
|
||||||
@ -389,8 +366,7 @@ fn roa_cache_projection_groups_multiple_outputs_by_roa_uri() {
|
|||||||
vcir.local_outputs.push(second);
|
vcir.local_outputs.push(second);
|
||||||
vcir.summary.local_vrp_count = 2;
|
vcir.summary.local_vrp_count = 2;
|
||||||
|
|
||||||
let context = roa_cache_projection_context(&vcir);
|
let projection = RoaCacheProjection::from_vcir(&vcir)
|
||||||
let projection = RoaCacheProjection::from_vcir_with_context(&vcir, Some(&context))
|
|
||||||
.expect("projection build")
|
.expect("projection build")
|
||||||
.expect("projection exists");
|
.expect("projection exists");
|
||||||
|
|
||||||
@ -772,8 +748,7 @@ fn roa_cache_projection_rejects_duplicate_uri_with_different_hash() {
|
|||||||
vcir.local_outputs.push(duplicate);
|
vcir.local_outputs.push(duplicate);
|
||||||
vcir.summary.local_vrp_count = 2;
|
vcir.summary.local_vrp_count = 2;
|
||||||
|
|
||||||
let context = roa_cache_projection_context(&vcir);
|
let err = RoaCacheProjection::from_vcir(&vcir)
|
||||||
let err = RoaCacheProjection::from_vcir_with_context(&vcir, Some(&context))
|
|
||||||
.expect_err("same ROA URI with different hash must fail");
|
.expect_err("same ROA URI with different hash must fail");
|
||||||
assert!(err.to_string().contains("source object hash mismatch"));
|
assert!(err.to_string().contains("source object hash mismatch"));
|
||||||
}
|
}
|
||||||
@ -1231,10 +1206,7 @@ fn vcir_roundtrip_and_validation_failures_are_reported() {
|
|||||||
let store = RocksStore::open(td.path()).expect("open rocksdb");
|
let store = RocksStore::open(td.path()).expect("open rocksdb");
|
||||||
|
|
||||||
let vcir = sample_vcir("rsync://example.test/repo/current.mft");
|
let vcir = sample_vcir("rsync://example.test/repo/current.mft");
|
||||||
let roa_context = roa_cache_projection_context(&vcir);
|
store.put_vcir(&vcir).expect("put vcir");
|
||||||
store
|
|
||||||
.put_vcir_with_projections(&vcir, Some(&roa_context), None)
|
|
||||||
.expect("put vcir");
|
|
||||||
let got = store
|
let got = store
|
||||||
.get_vcir(&vcir.manifest_rsync_uri)
|
.get_vcir(&vcir.manifest_rsync_uri)
|
||||||
.expect("get vcir")
|
.expect("get vcir")
|
||||||
@ -1474,13 +1446,8 @@ fn replace_vcir_and_manifest_replay_meta_replaces_current_entry() {
|
|||||||
previous.summary.local_vrp_count = 1;
|
previous.summary.local_vrp_count = 1;
|
||||||
previous.summary.local_aspa_count = 0;
|
previous.summary.local_aspa_count = 0;
|
||||||
previous.summary.local_router_key_count = 0;
|
previous.summary.local_router_key_count = 0;
|
||||||
let previous_roa_context = roa_cache_projection_context(&previous);
|
|
||||||
let previous_timing = store
|
let previous_timing = store
|
||||||
.replace_vcir_manifest_replay_meta_and_projections(
|
.replace_vcir_and_manifest_replay_meta(&previous)
|
||||||
&previous,
|
|
||||||
Some(&previous_roa_context),
|
|
||||||
None,
|
|
||||||
)
|
|
||||||
.expect("store previous vcir");
|
.expect("store previous vcir");
|
||||||
assert!(previous_timing.vcir_value_bytes > 0);
|
assert!(previous_timing.vcir_value_bytes > 0);
|
||||||
assert!(previous_timing.replay_meta_value_bytes > 0);
|
assert!(previous_timing.replay_meta_value_bytes > 0);
|
||||||
|
|||||||
@ -3,8 +3,7 @@ use crate::data_model::crl::{CrlDecodeError, CrlVerifyError, RpkixCrl};
|
|||||||
use crate::data_model::oid::OID_KEY_USAGE_RAW;
|
use crate::data_model::oid::OID_KEY_USAGE_RAW;
|
||||||
use crate::data_model::rc::{
|
use crate::data_model::rc::{
|
||||||
AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpResourceSet, ResourceCertKind,
|
AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpResourceSet, ResourceCertKind,
|
||||||
ResourceCertificate, ResourceCertificateDecodeError, ResourceCertificateProfileError,
|
ResourceCertificate, ResourceCertificateDecodeError,
|
||||||
ResourceCertificateRole,
|
|
||||||
};
|
};
|
||||||
use crate::policy::ResourceValidationMode;
|
use crate::policy::ResourceValidationMode;
|
||||||
use x509_parser::prelude::{FromDer, X509Certificate};
|
use x509_parser::prelude::{FromDer, X509Certificate};
|
||||||
@ -109,12 +108,6 @@ pub enum CaPathError {
|
|||||||
#[error("issuer CA certificate decode failed: {0} (RFC 6487 §4; RFC 5280 §4.1)")]
|
#[error("issuer CA certificate decode failed: {0} (RFC 6487 §4; RFC 5280 §4.1)")]
|
||||||
IssuerDecode(ResourceCertificateDecodeError),
|
IssuerDecode(ResourceCertificateDecodeError),
|
||||||
|
|
||||||
#[error("child CA certificate profile validation failed: {0} (RFC 6487 §4.8)")]
|
|
||||||
ChildProfile(ResourceCertificateProfileError),
|
|
||||||
|
|
||||||
#[error("issuer CA certificate profile validation failed: {0} (RFC 6487 §4.8)")]
|
|
||||||
IssuerProfile(ResourceCertificateProfileError),
|
|
||||||
|
|
||||||
#[error("issuer CRL decode failed: {0} (RFC 6487 §5; RFC 9829 §3.1; RFC 5280 §5.1)")]
|
#[error("issuer CRL decode failed: {0} (RFC 6487 §5; RFC 9829 §3.1; RFC 5280 §5.1)")]
|
||||||
CrlDecode(#[from] CrlDecodeError),
|
CrlDecode(#[from] CrlDecodeError),
|
||||||
|
|
||||||
@ -251,18 +244,12 @@ pub fn validate_subordinate_ca_cert_with_resource_validation_mode(
|
|||||||
if child_ca.kind != ResourceCertKind::Ca {
|
if child_ca.kind != ResourceCertKind::Ca {
|
||||||
return Err(CaPathError::ChildNotCa);
|
return Err(CaPathError::ChildNotCa);
|
||||||
}
|
}
|
||||||
child_ca
|
|
||||||
.validate_rfc6487_profile(ResourceCertificateRole::Ca)
|
|
||||||
.map_err(CaPathError::ChildProfile)?;
|
|
||||||
|
|
||||||
let issuer_ca =
|
let issuer_ca =
|
||||||
ResourceCertificate::decode_der(issuer_ca_der).map_err(CaPathError::IssuerDecode)?;
|
ResourceCertificate::decode_der(issuer_ca_der).map_err(CaPathError::IssuerDecode)?;
|
||||||
if issuer_ca.kind != ResourceCertKind::Ca {
|
if issuer_ca.kind != ResourceCertKind::Ca {
|
||||||
return Err(CaPathError::IssuerNotCa);
|
return Err(CaPathError::IssuerNotCa);
|
||||||
}
|
}
|
||||||
issuer_ca
|
|
||||||
.validate_rfc6487_profile(ResourceCertificateRole::Ca)
|
|
||||||
.map_err(CaPathError::IssuerProfile)?;
|
|
||||||
let issuer_spki = parse_subject_pki_from_der(&issuer_ca.tbs.subject_public_key_info)?;
|
let issuer_spki = parse_subject_pki_from_der(&issuer_ca.tbs.subject_public_key_info)?;
|
||||||
|
|
||||||
if !x509_names_equivalent(&child_ca.tbs.issuer_name, &issuer_ca.tbs.subject_name) {
|
if !x509_names_equivalent(&child_ca.tbs.issuer_name, &issuer_ca.tbs.subject_name) {
|
||||||
@ -392,12 +379,6 @@ pub fn validate_subordinate_ca_cert_with_prevalidated_issuer_and_resources(
|
|||||||
if issuer_ca.kind != ResourceCertKind::Ca {
|
if issuer_ca.kind != ResourceCertKind::Ca {
|
||||||
return Err(CaPathError::IssuerNotCa);
|
return Err(CaPathError::IssuerNotCa);
|
||||||
}
|
}
|
||||||
child_ca
|
|
||||||
.validate_rfc6487_profile(ResourceCertificateRole::Ca)
|
|
||||||
.map_err(CaPathError::ChildProfile)?;
|
|
||||||
issuer_ca
|
|
||||||
.validate_rfc6487_profile(ResourceCertificateRole::Ca)
|
|
||||||
.map_err(CaPathError::IssuerProfile)?;
|
|
||||||
|
|
||||||
if !x509_names_equivalent(&child_ca.tbs.issuer_name, &issuer_ca.tbs.subject_name) {
|
if !x509_names_equivalent(&child_ca.tbs.issuer_name, &issuer_ca.tbs.subject_name) {
|
||||||
return Err(CaPathError::IssuerSubjectMismatch {
|
return Err(CaPathError::IssuerSubjectMismatch {
|
||||||
@ -1394,14 +1375,12 @@ fn bytes_leq(a: &[u8], b: &[u8]) -> bool {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::data_model::common::X509NameDer;
|
use crate::data_model::common::X509NameDer;
|
||||||
use crate::data_model::oid::OID_CP_IPADDR_ASNUMBER;
|
|
||||||
use crate::data_model::rc::{
|
use crate::data_model::rc::{
|
||||||
Afi, AsIdOrRange, AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressFamily,
|
Afi, AsIdOrRange, AsIdentifierChoice, AsResourceSet, IpAddressChoice, IpAddressFamily,
|
||||||
IpAddressOrRange, IpPrefix, IpResourceSet,
|
IpAddressOrRange, IpPrefix, IpResourceSet,
|
||||||
};
|
};
|
||||||
use crate::data_model::rc::{
|
use crate::data_model::rc::{
|
||||||
BasicConstraintsProfile, CertificatePoliciesProfile, RcExtensions, ResourceCertKind,
|
RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate,
|
||||||
ResourceCertificate, RpkixTbsCertificate,
|
|
||||||
};
|
};
|
||||||
use der_parser::num_bigint::BigUint;
|
use der_parser::num_bigint::BigUint;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
@ -1431,27 +1410,12 @@ mod tests {
|
|||||||
subject_public_key_info: Vec::new(),
|
subject_public_key_info: Vec::new(),
|
||||||
extensions: RcExtensions {
|
extensions: RcExtensions {
|
||||||
basic_constraints_ca: kind == ResourceCertKind::Ca,
|
basic_constraints_ca: kind == ResourceCertKind::Ca,
|
||||||
basic_constraints: (kind == ResourceCertKind::Ca).then_some(
|
|
||||||
BasicConstraintsProfile {
|
|
||||||
ca: true,
|
|
||||||
critical: true,
|
|
||||||
path_len_constraint: None,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
subject_key_identifier: ski,
|
subject_key_identifier: ski,
|
||||||
authority_key_identifier: aki,
|
authority_key_identifier: aki,
|
||||||
crl_distribution_points_uris: crldp,
|
crl_distribution_points_uris: crldp,
|
||||||
ca_issuers_uris: aia,
|
ca_issuers_uris: aia,
|
||||||
subject_info_access: None,
|
subject_info_access: None,
|
||||||
certificate_policies_oid: (kind == ResourceCertKind::Ca)
|
certificate_policies_oid: None,
|
||||||
.then_some(OID_CP_IPADDR_ASNUMBER.to_string()),
|
|
||||||
certificate_policies: (kind == ResourceCertKind::Ca).then_some(
|
|
||||||
CertificatePoliciesProfile {
|
|
||||||
policy_oid: OID_CP_IPADDR_ASNUMBER.to_string(),
|
|
||||||
qualifier_oids: Vec::new(),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
extension_oids: Vec::new(),
|
|
||||||
ip_resources: None,
|
ip_resources: None,
|
||||||
as_resources: None,
|
as_resources: None,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -2,7 +2,6 @@ use crate::data_model::common::BigUnsigned;
|
|||||||
use crate::data_model::crl::{CrlDecodeError, CrlVerifyError, RpkixCrl};
|
use crate::data_model::crl::{CrlDecodeError, CrlVerifyError, RpkixCrl};
|
||||||
use crate::data_model::rc::{
|
use crate::data_model::rc::{
|
||||||
ResourceCertKind, ResourceCertificate, ResourceCertificateDecodeError,
|
ResourceCertKind, ResourceCertificate, ResourceCertificateDecodeError,
|
||||||
ResourceCertificateProfileError, ResourceCertificateRole,
|
|
||||||
};
|
};
|
||||||
use crate::data_model::signed_object::{EeKeyUsageSummary, ResourceEeCertificate};
|
use crate::data_model::signed_object::{EeKeyUsageSummary, ResourceEeCertificate};
|
||||||
use ring::signature;
|
use ring::signature;
|
||||||
@ -35,9 +34,6 @@ pub enum CertPathError {
|
|||||||
)]
|
)]
|
||||||
IssuerNotCa,
|
IssuerNotCa,
|
||||||
|
|
||||||
#[error("issuer CA certificate profile validation failed: {0} (RFC 6487 §4.8)")]
|
|
||||||
IssuerProfile(ResourceCertificateProfileError),
|
|
||||||
|
|
||||||
#[error("EE certificate must be an EE resource certificate (RFC 6487 §4)")]
|
#[error("EE certificate must be an EE resource certificate (RFC 6487 §4)")]
|
||||||
EeNotEe,
|
EeNotEe,
|
||||||
|
|
||||||
@ -132,9 +128,6 @@ pub fn validate_ee_cert_path(
|
|||||||
if issuer_ca.kind != ResourceCertKind::Ca {
|
if issuer_ca.kind != ResourceCertKind::Ca {
|
||||||
return Err(CertPathError::IssuerNotCa);
|
return Err(CertPathError::IssuerNotCa);
|
||||||
}
|
}
|
||||||
issuer_ca
|
|
||||||
.validate_rfc6487_profile(ResourceCertificateRole::Ca)
|
|
||||||
.map_err(CertPathError::IssuerProfile)?;
|
|
||||||
let issuer_spki = parse_subject_pki_from_der(&issuer_ca.tbs.subject_public_key_info)?;
|
let issuer_spki = parse_subject_pki_from_der(&issuer_ca.tbs.subject_public_key_info)?;
|
||||||
|
|
||||||
if !x509_names_equivalent(&ee.tbs.issuer_name, &issuer_ca.tbs.subject_name) {
|
if !x509_names_equivalent(&ee.tbs.issuer_name, &issuer_ca.tbs.subject_name) {
|
||||||
@ -318,9 +311,6 @@ fn validate_ee_cert_path_bindings_and_status(
|
|||||||
if issuer_ca.kind != ResourceCertKind::Ca {
|
if issuer_ca.kind != ResourceCertKind::Ca {
|
||||||
return Err(CertPathError::IssuerNotCa);
|
return Err(CertPathError::IssuerNotCa);
|
||||||
}
|
}
|
||||||
issuer_ca
|
|
||||||
.validate_rfc6487_profile(ResourceCertificateRole::Ca)
|
|
||||||
.map_err(CertPathError::IssuerProfile)?;
|
|
||||||
|
|
||||||
if !x509_names_equivalent(&ee.tbs.issuer_name, &issuer_ca.tbs.subject_name) {
|
if !x509_names_equivalent(&ee.tbs.issuer_name, &issuer_ca.tbs.subject_name) {
|
||||||
return Err(CertPathError::IssuerSubjectMismatch {
|
return Err(CertPathError::IssuerSubjectMismatch {
|
||||||
@ -525,10 +515,8 @@ fn is_serial_revoked_by_crl(ee: &ResourceCertificate, crl: &RpkixCrl) -> bool {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::data_model::common::X509NameDer;
|
use crate::data_model::common::X509NameDer;
|
||||||
use crate::data_model::oid::OID_CP_IPADDR_ASNUMBER;
|
|
||||||
use crate::data_model::rc::{
|
use crate::data_model::rc::{
|
||||||
BasicConstraintsProfile, CertificatePoliciesProfile, RcExtensions, ResourceCertKind,
|
RcExtensions, ResourceCertKind, ResourceCertificate, RpkixTbsCertificate,
|
||||||
ResourceCertificate, RpkixTbsCertificate,
|
|
||||||
};
|
};
|
||||||
use der_parser::num_bigint::BigUint;
|
use der_parser::num_bigint::BigUint;
|
||||||
|
|
||||||
@ -557,27 +545,12 @@ mod tests {
|
|||||||
subject_public_key_info: Vec::new(),
|
subject_public_key_info: Vec::new(),
|
||||||
extensions: RcExtensions {
|
extensions: RcExtensions {
|
||||||
basic_constraints_ca: kind == ResourceCertKind::Ca,
|
basic_constraints_ca: kind == ResourceCertKind::Ca,
|
||||||
basic_constraints: (kind == ResourceCertKind::Ca).then_some(
|
|
||||||
BasicConstraintsProfile {
|
|
||||||
ca: true,
|
|
||||||
critical: true,
|
|
||||||
path_len_constraint: None,
|
|
||||||
},
|
|
||||||
),
|
|
||||||
subject_key_identifier: ski,
|
subject_key_identifier: ski,
|
||||||
authority_key_identifier: aki,
|
authority_key_identifier: aki,
|
||||||
crl_distribution_points_uris: crldp,
|
crl_distribution_points_uris: crldp,
|
||||||
ca_issuers_uris: aia,
|
ca_issuers_uris: aia,
|
||||||
subject_info_access: None,
|
subject_info_access: None,
|
||||||
certificate_policies_oid: (kind == ResourceCertKind::Ca)
|
certificate_policies_oid: None,
|
||||||
.then_some(OID_CP_IPADDR_ASNUMBER.to_string()),
|
|
||||||
certificate_policies: (kind == ResourceCertKind::Ca).then_some(
|
|
||||||
CertificatePoliciesProfile {
|
|
||||||
policy_oid: OID_CP_IPADDR_ASNUMBER.to_string(),
|
|
||||||
qualifier_oids: Vec::new(),
|
|
||||||
},
|
|
||||||
),
|
|
||||||
extension_oids: Vec::new(),
|
|
||||||
ip_resources: None,
|
ip_resources: None,
|
||||||
as_resources: None,
|
as_resources: None,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -506,7 +506,6 @@ pub struct CachedRoaValidationResult {
|
|||||||
source_object_hash: [u8; 32],
|
source_object_hash: [u8; 32],
|
||||||
ee_serial: Option<Vec<u8>>,
|
ee_serial: Option<Vec<u8>>,
|
||||||
crl_uri: Option<String>,
|
crl_uri: Option<String>,
|
||||||
earliest_safe_reuse_time_unix: i64,
|
|
||||||
outputs_effective_until_unix: i64,
|
outputs_effective_until_unix: i64,
|
||||||
outputs: Vec<VcirLocalOutput>,
|
outputs: Vec<VcirLocalOutput>,
|
||||||
}
|
}
|
||||||
@ -544,12 +543,6 @@ impl RoaValidationCacheView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
for entry in &projection.entries {
|
for entry in &projection.entries {
|
||||||
let Some(earliest_safe_reuse_time_unix) = entry.earliest_safe_reuse_time_unix else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
if time::OffsetDateTime::from_unix_timestamp(earliest_safe_reuse_time_unix).is_err() {
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
let outputs = entry
|
let outputs = entry
|
||||||
.outputs
|
.outputs
|
||||||
.iter()
|
.iter()
|
||||||
@ -570,7 +563,6 @@ impl RoaValidationCacheView {
|
|||||||
source_object_hash: entry.source_object_hash,
|
source_object_hash: entry.source_object_hash,
|
||||||
ee_serial: entry.ee_serial.clone(),
|
ee_serial: entry.ee_serial.clone(),
|
||||||
crl_uri: entry.crl_uri.clone(),
|
crl_uri: entry.crl_uri.clone(),
|
||||||
earliest_safe_reuse_time_unix,
|
|
||||||
outputs_effective_until_unix: entry.outputs_effective_until_unix,
|
outputs_effective_until_unix: entry.outputs_effective_until_unix,
|
||||||
outputs,
|
outputs,
|
||||||
},
|
},
|
||||||
@ -682,9 +674,7 @@ impl RoaValidationCacheView {
|
|||||||
if cached.outputs.is_empty() {
|
if cached.outputs.is_empty() {
|
||||||
return_entry_gate!(RoaCacheLookupResult::Miss);
|
return_entry_gate!(RoaCacheLookupResult::Miss);
|
||||||
}
|
}
|
||||||
if validation_time.unix_timestamp() < cached.earliest_safe_reuse_time_unix
|
if cached.outputs_effective_until_unix <= validation_time.unix_timestamp() {
|
||||||
|| cached.outputs_effective_until_unix <= validation_time.unix_timestamp()
|
|
||||||
{
|
|
||||||
return_entry_gate!(RoaCacheLookupResult::ExpiredBlocked);
|
return_entry_gate!(RoaCacheLookupResult::ExpiredBlocked);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -770,10 +760,6 @@ impl RoaValidationCacheView {
|
|||||||
source_object_hash: file.sha256,
|
source_object_hash: file.sha256,
|
||||||
ee_serial: ee_serial.clone(),
|
ee_serial: ee_serial.clone(),
|
||||||
crl_uri: crl_uri.to_string(),
|
crl_uri: crl_uri.to_string(),
|
||||||
earliest_safe_reuse_time: PackTime::from_utc_offset_datetime(
|
|
||||||
time::OffsetDateTime::from_unix_timestamp(cached.earliest_safe_reuse_time_unix)
|
|
||||||
.expect("cached ROA safe reuse time must be valid"),
|
|
||||||
),
|
|
||||||
}),
|
}),
|
||||||
};
|
};
|
||||||
metrics.materialize_nanos = metrics
|
metrics.materialize_nanos = metrics
|
||||||
@ -835,14 +821,6 @@ fn crl_valid_at_time(
|
|||||||
validation_time >= this_update && validation_time < next_update
|
validation_time >= this_update && validation_time < next_update
|
||||||
}
|
}
|
||||||
|
|
||||||
fn roa_cache_earliest_safe_reuse_time(
|
|
||||||
ee_not_before: time::OffsetDateTime,
|
|
||||||
crl_this_update: time::OffsetDateTime,
|
|
||||||
validation_time: time::OffsetDateTime,
|
|
||||||
) -> PackTime {
|
|
||||||
PackTime::from_utc_offset_datetime(ee_not_before.max(crl_this_update).max(validation_time))
|
|
||||||
}
|
|
||||||
|
|
||||||
fn record_roa_cache_block(
|
fn record_roa_cache_block(
|
||||||
stats: &mut RoaValidationCacheStats,
|
stats: &mut RoaValidationCacheStats,
|
||||||
lookup_result: &RoaCacheLookupResult,
|
lookup_result: &RoaCacheLookupResult,
|
||||||
@ -2758,11 +2736,6 @@ fn process_roa_with_issuer(
|
|||||||
source_object_hash: file.sha256,
|
source_object_hash: file.sha256,
|
||||||
ee_serial: BigUnsigned::from_biguint(&ee.resource_cert.tbs.serial_number).bytes_be,
|
ee_serial: BigUnsigned::from_biguint(&ee.resource_cert.tbs.serial_number).bytes_be,
|
||||||
crl_uri: issuer_crl_rsync_uri.to_string(),
|
crl_uri: issuer_crl_rsync_uri.to_string(),
|
||||||
earliest_safe_reuse_time: roa_cache_earliest_safe_reuse_time(
|
|
||||||
ee.resource_cert.tbs.validity_not_before,
|
|
||||||
verified_crl.crl.this_update.utc,
|
|
||||||
validation_time,
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
if !collect_vcir_local_outputs {
|
if !collect_vcir_local_outputs {
|
||||||
return Ok((vrps, Vec::new(), Some(cache_object_meta)));
|
return Ok((vrps, Vec::new(), Some(cache_object_meta)));
|
||||||
@ -2893,11 +2866,6 @@ fn process_roa_with_issuer_parallel_cached(
|
|||||||
source_object_hash: file.sha256,
|
source_object_hash: file.sha256,
|
||||||
ee_serial: BigUnsigned::from_biguint(&ee.resource_cert.tbs.serial_number).bytes_be,
|
ee_serial: BigUnsigned::from_biguint(&ee.resource_cert.tbs.serial_number).bytes_be,
|
||||||
crl_uri: issuer_crl_rsync_uri.clone(),
|
crl_uri: issuer_crl_rsync_uri.clone(),
|
||||||
earliest_safe_reuse_time: roa_cache_earliest_safe_reuse_time(
|
|
||||||
ee.resource_cert.tbs.validity_not_before,
|
|
||||||
verified_crl.crl.this_update.utc,
|
|
||||||
validation_time,
|
|
||||||
),
|
|
||||||
};
|
};
|
||||||
if !collect_vcir_local_outputs {
|
if !collect_vcir_local_outputs {
|
||||||
return Ok((vrps, Vec::new(), Some(cache_object_meta)));
|
return Ok((vrps, Vec::new(), Some(cache_object_meta)));
|
||||||
@ -3940,7 +3908,6 @@ mod tests {
|
|||||||
source_object_hash: roa_hash,
|
source_object_hash: roa_hash,
|
||||||
ee_serial: vec![0x01],
|
ee_serial: vec![0x01],
|
||||||
crl_uri: TEST_CRL_URI.to_string(),
|
crl_uri: TEST_CRL_URI.to_string(),
|
||||||
earliest_safe_reuse_time: vcir.last_successful_validation_time.clone(),
|
|
||||||
}],
|
}],
|
||||||
}),
|
}),
|
||||||
)
|
)
|
||||||
@ -4332,7 +4299,7 @@ mod tests {
|
|||||||
issuer_der,
|
issuer_der,
|
||||||
crl_hash,
|
crl_hash,
|
||||||
roa_hash,
|
roa_hash,
|
||||||
fixed_time("2026-06-04T01:00:00Z"),
|
fixed_time("2026-06-04T00:00:00Z"),
|
||||||
fixed_time("2026-06-08T00:00:00Z"),
|
fixed_time("2026-06-08T00:00:00Z"),
|
||||||
);
|
);
|
||||||
let projection = sample_roa_cache_projection(&vcir, roa_hash);
|
let projection = sample_roa_cache_projection(&vcir, roa_hash);
|
||||||
@ -4346,58 +4313,6 @@ mod tests {
|
|||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn roa_validation_cache_view_blocks_before_safe_reuse_time() {
|
|
||||||
let validation_time = fixed_time("2026-06-05T00:00:00Z");
|
|
||||||
let issuer_der = b"issuer-ca";
|
|
||||||
let crl_bytes = b"current-crl".to_vec();
|
|
||||||
let crl_hash = sha256_32(&crl_bytes);
|
|
||||||
let roa_hash = [0x11; 32];
|
|
||||||
let vcir = sample_roa_cache_vcir(
|
|
||||||
issuer_der,
|
|
||||||
crl_hash,
|
|
||||||
roa_hash,
|
|
||||||
fixed_time("2026-06-07T00:00:00Z"),
|
|
||||||
fixed_time("2026-06-08T00:00:00Z"),
|
|
||||||
);
|
|
||||||
let mut projection = sample_roa_cache_projection(&vcir, roa_hash);
|
|
||||||
projection.entries[0].earliest_safe_reuse_time_unix =
|
|
||||||
Some(fixed_time("2026-06-06T00:00:00Z").unix_timestamp());
|
|
||||||
let view = RoaValidationCacheView::from_projection(&projection, validation_time);
|
|
||||||
let file = PackFile::from_bytes_with_sha256(TEST_ROA_URI, vec![0x01], roa_hash);
|
|
||||||
let mut crl_cache = sample_crl_cache(crl_bytes);
|
|
||||||
|
|
||||||
assert!(matches!(
|
|
||||||
view.lookup(&file, &mut crl_cache, issuer_der, validation_time),
|
|
||||||
RoaCacheLookupResult::ExpiredBlocked
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn roa_validation_cache_view_treats_legacy_entry_without_lower_bound_as_miss() {
|
|
||||||
let validation_time = fixed_time("2026-06-05T00:00:00Z");
|
|
||||||
let issuer_der = b"issuer-ca";
|
|
||||||
let crl_hash = sha256_32(b"current-crl");
|
|
||||||
let roa_hash = [0x11; 32];
|
|
||||||
let vcir = sample_roa_cache_vcir(
|
|
||||||
issuer_der,
|
|
||||||
crl_hash,
|
|
||||||
roa_hash,
|
|
||||||
fixed_time("2026-06-07T00:00:00Z"),
|
|
||||||
fixed_time("2026-06-08T00:00:00Z"),
|
|
||||||
);
|
|
||||||
let mut projection = sample_roa_cache_projection(&vcir, roa_hash);
|
|
||||||
projection.entries[0].earliest_safe_reuse_time_unix = None;
|
|
||||||
let view = RoaValidationCacheView::from_projection(&projection, validation_time);
|
|
||||||
let file = PackFile::from_bytes_with_sha256(TEST_ROA_URI, vec![0x01], roa_hash);
|
|
||||||
let mut crl_cache = sample_crl_cache(b"current-crl".to_vec());
|
|
||||||
|
|
||||||
assert!(matches!(
|
|
||||||
view.lookup(&file, &mut crl_cache, issuer_der, validation_time),
|
|
||||||
RoaCacheLookupResult::Miss
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn roa_validation_cache_stats_records_vcir_miss_to_timing() {
|
fn roa_validation_cache_stats_records_vcir_miss_to_timing() {
|
||||||
let stats = RoaValidationCacheStats::for_input(RoaValidationCacheInput::enabled(None), 3);
|
let stats = RoaValidationCacheStats::for_input(RoaValidationCacheInput::enabled(None), 3);
|
||||||
@ -4679,7 +4594,6 @@ mod tests {
|
|||||||
source_object_hash: roa_hash,
|
source_object_hash: roa_hash,
|
||||||
ee_serial: Some(vec![0x01]),
|
ee_serial: Some(vec![0x01]),
|
||||||
crl_uri: None,
|
crl_uri: None,
|
||||||
earliest_safe_reuse_time_unix: validation_time.unix_timestamp(),
|
|
||||||
outputs_effective_until_unix,
|
outputs_effective_until_unix,
|
||||||
outputs: vec![output.clone()],
|
outputs: vec![output.clone()],
|
||||||
},
|
},
|
||||||
@ -4696,7 +4610,6 @@ mod tests {
|
|||||||
source_object_hash: roa_hash,
|
source_object_hash: roa_hash,
|
||||||
ee_serial: None,
|
ee_serial: None,
|
||||||
crl_uri: Some(TEST_CRL_URI.to_string()),
|
crl_uri: Some(TEST_CRL_URI.to_string()),
|
||||||
earliest_safe_reuse_time_unix: validation_time.unix_timestamp(),
|
|
||||||
outputs_effective_until_unix,
|
outputs_effective_until_unix,
|
||||||
outputs: vec![output.clone()],
|
outputs: vec![output.clone()],
|
||||||
},
|
},
|
||||||
@ -4713,7 +4626,6 @@ mod tests {
|
|||||||
source_object_hash: roa_hash,
|
source_object_hash: roa_hash,
|
||||||
ee_serial: Some(vec![0x01]),
|
ee_serial: Some(vec![0x01]),
|
||||||
crl_uri: Some(TEST_CRL_URI.to_string()),
|
crl_uri: Some(TEST_CRL_URI.to_string()),
|
||||||
earliest_safe_reuse_time_unix: validation_time.unix_timestamp(),
|
|
||||||
outputs_effective_until_unix,
|
outputs_effective_until_unix,
|
||||||
outputs: vec![output.clone()],
|
outputs: vec![output.clone()],
|
||||||
},
|
},
|
||||||
@ -4740,7 +4652,6 @@ mod tests {
|
|||||||
source_object_hash: [0xff; 32],
|
source_object_hash: [0xff; 32],
|
||||||
ee_serial: Some(vec![0x01]),
|
ee_serial: Some(vec![0x01]),
|
||||||
crl_uri: Some(TEST_CRL_URI.to_string()),
|
crl_uri: Some(TEST_CRL_URI.to_string()),
|
||||||
earliest_safe_reuse_time_unix: validation_time.unix_timestamp(),
|
|
||||||
outputs_effective_until_unix,
|
outputs_effective_until_unix,
|
||||||
outputs: vec![output.clone()],
|
outputs: vec![output.clone()],
|
||||||
},
|
},
|
||||||
@ -4757,7 +4668,6 @@ mod tests {
|
|||||||
source_object_hash: roa_hash,
|
source_object_hash: roa_hash,
|
||||||
ee_serial: Some(vec![0x01]),
|
ee_serial: Some(vec![0x01]),
|
||||||
crl_uri: Some(TEST_CRL_URI.to_string()),
|
crl_uri: Some(TEST_CRL_URI.to_string()),
|
||||||
earliest_safe_reuse_time_unix: validation_time.unix_timestamp(),
|
|
||||||
outputs_effective_until_unix,
|
outputs_effective_until_unix,
|
||||||
outputs: Vec::new(),
|
outputs: Vec::new(),
|
||||||
},
|
},
|
||||||
@ -4778,7 +4688,6 @@ mod tests {
|
|||||||
source_object_hash: roa_hash,
|
source_object_hash: roa_hash,
|
||||||
ee_serial: Some(vec![0x01]),
|
ee_serial: Some(vec![0x01]),
|
||||||
crl_uri: Some(TEST_CRL_URI.to_string()),
|
crl_uri: Some(TEST_CRL_URI.to_string()),
|
||||||
earliest_safe_reuse_time_unix: validation_time.unix_timestamp(),
|
|
||||||
outputs_effective_until_unix,
|
outputs_effective_until_unix,
|
||||||
outputs: vec![output],
|
outputs: vec![output],
|
||||||
},
|
},
|
||||||
|
|||||||
@ -33,9 +33,8 @@ use crate::storage::{
|
|||||||
PublicationPointCacheProjectionWriteAction, RawByHashEntry, RoaCacheProjectionContext,
|
PublicationPointCacheProjectionWriteAction, RawByHashEntry, RoaCacheProjectionContext,
|
||||||
RocksStore, ValidatedCaInstanceResult, VcirArtifactKind, VcirArtifactRole,
|
RocksStore, ValidatedCaInstanceResult, VcirArtifactKind, VcirArtifactRole,
|
||||||
VcirArtifactValidationStatus, VcirAuditSummary, VcirCcrManifestProjection, VcirChildEntry,
|
VcirArtifactValidationStatus, VcirAuditSummary, VcirCcrManifestProjection, VcirChildEntry,
|
||||||
VcirFailedFetchReuseIdentity, VcirInstanceGate, VcirLocalOutput, VcirLocalOutputPayload,
|
VcirInstanceGate, VcirLocalOutput, VcirLocalOutputPayload, VcirOutputType, VcirRelatedArtifact,
|
||||||
VcirOutputType, VcirRelatedArtifact, VcirReplaceTimingBreakdown, VcirSourceObjectType,
|
VcirReplaceTimingBreakdown, VcirSourceObjectType, VcirSummary,
|
||||||
VcirSummary,
|
|
||||||
};
|
};
|
||||||
use crate::sync::repo::{
|
use crate::sync::repo::{
|
||||||
sync_publication_point, sync_publication_point_replay, sync_publication_point_replay_delta,
|
sync_publication_point, sync_publication_point_replay, sync_publication_point_replay_delta,
|
||||||
@ -1752,7 +1751,6 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
|
|||||||
self.store,
|
self.store,
|
||||||
ca,
|
ca,
|
||||||
&fresh_err,
|
&fresh_err,
|
||||||
self.policy,
|
|
||||||
self.validation_time,
|
self.validation_time,
|
||||||
)
|
)
|
||||||
.map_err(|e| format!("failed fetch VCIR projection failed: {e}"))?;
|
.map_err(|e| format!("failed fetch VCIR projection failed: {e}"))?;
|
||||||
@ -2316,18 +2314,8 @@ fn discover_children_from_fresh_snapshot_with_audit_cached_with_issuer_der<
|
|||||||
let issuer_ca_decode_error: Option<String>;
|
let issuer_ca_decode_error: Option<String>;
|
||||||
let issuer_ca = match crate::data_model::rc::ResourceCertificate::decode_der(issuer_ca_der) {
|
let issuer_ca = match crate::data_model::rc::ResourceCertificate::decode_der(issuer_ca_der) {
|
||||||
Ok(v) => {
|
Ok(v) => {
|
||||||
match v.validate_rfc6487_profile(crate::data_model::rc::ResourceCertificateRole::Ca) {
|
issuer_ca_decode_error = None;
|
||||||
Ok(()) => {
|
Some(v)
|
||||||
issuer_ca_decode_error = None;
|
|
||||||
Some(v)
|
|
||||||
}
|
|
||||||
Err(e) => {
|
|
||||||
issuer_ca_decode_error = Some(format!(
|
|
||||||
"issuer CA profile validation failed: {e} (RFC 6487 §4.8)"
|
|
||||||
));
|
|
||||||
None
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
Err(e) => {
|
Err(e) => {
|
||||||
issuer_ca_decode_error = Some(format!(
|
issuer_ca_decode_error = Some(format!(
|
||||||
@ -4268,7 +4256,6 @@ fn project_current_instance_vcir_on_failed_fetch(
|
|||||||
store: &RocksStore,
|
store: &RocksStore,
|
||||||
ca: &CaInstanceHandle,
|
ca: &CaInstanceHandle,
|
||||||
fresh_err: &ManifestFreshError,
|
fresh_err: &ManifestFreshError,
|
||||||
policy: &Policy,
|
|
||||||
validation_time: time::OffsetDateTime,
|
validation_time: time::OffsetDateTime,
|
||||||
) -> Result<VcirReuseProjection, String> {
|
) -> Result<VcirReuseProjection, String> {
|
||||||
let mut warnings = Vec::new();
|
let mut warnings = Vec::new();
|
||||||
@ -4277,57 +4264,80 @@ fn project_current_instance_vcir_on_failed_fetch(
|
|||||||
.get_vcir(&ca.manifest_rsync_uri)
|
.get_vcir(&ca.manifest_rsync_uri)
|
||||||
.map_err(|e| format!("load VCIR failed: {e}"))?
|
.map_err(|e| format!("load VCIR failed: {e}"))?
|
||||||
else {
|
else {
|
||||||
return Ok(failed_fetch_no_cache_projection(
|
warnings.push(
|
||||||
ca,
|
Warning::new(format!("manifest failed fetch: {fresh_err}"))
|
||||||
fresh_err,
|
.with_rfc_refs(&[RfcRef("RFC 9286 §6.6")])
|
||||||
None,
|
.with_context(&ca.manifest_rsync_uri),
|
||||||
"no latest validated result for current CA instance; no cached output reused",
|
);
|
||||||
));
|
warnings.push(
|
||||||
|
Warning::new(
|
||||||
|
"no latest validated result for current CA instance; no cached output reused",
|
||||||
|
)
|
||||||
|
.with_rfc_refs(&[RfcRef("RFC 9286 §6.6")])
|
||||||
|
.with_context(&ca.manifest_rsync_uri),
|
||||||
|
);
|
||||||
|
return Ok(VcirReuseProjection {
|
||||||
|
source: PublicationPointSource::FailedFetchNoCache,
|
||||||
|
vcir: None,
|
||||||
|
ccr_manifest_projection: None,
|
||||||
|
snapshot: None,
|
||||||
|
objects: empty_objects_output(),
|
||||||
|
child_audits: Vec::new(),
|
||||||
|
discovered_children: Vec::new(),
|
||||||
|
warnings,
|
||||||
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
if !vcir.audit_summary.failed_fetch_eligible {
|
if !vcir.audit_summary.failed_fetch_eligible {
|
||||||
return Ok(failed_fetch_no_cache_projection(
|
warnings.push(
|
||||||
ca,
|
Warning::new(format!("manifest failed fetch: {fresh_err}"))
|
||||||
fresh_err,
|
.with_rfc_refs(&[RfcRef("RFC 9286 §6.6")])
|
||||||
Some(vcir),
|
.with_context(&ca.manifest_rsync_uri),
|
||||||
"latest VCIR is not marked failed-fetch eligible; no cached output reused",
|
);
|
||||||
));
|
warnings.push(
|
||||||
|
Warning::new(
|
||||||
|
"latest VCIR is not marked failed-fetch eligible; no cached output reused",
|
||||||
|
)
|
||||||
|
.with_rfc_refs(&[RfcRef("RFC 9286 §6.6")])
|
||||||
|
.with_context(&ca.manifest_rsync_uri),
|
||||||
|
);
|
||||||
|
return Ok(VcirReuseProjection {
|
||||||
|
source: PublicationPointSource::FailedFetchNoCache,
|
||||||
|
vcir: Some(vcir),
|
||||||
|
ccr_manifest_projection: None,
|
||||||
|
snapshot: None,
|
||||||
|
objects: empty_objects_output(),
|
||||||
|
child_audits: Vec::new(),
|
||||||
|
discovered_children: Vec::new(),
|
||||||
|
warnings,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
let reuse_identity = match store.get_vcir_failed_fetch_reuse_identity(&ca.manifest_rsync_uri) {
|
let instance_effective_until =
|
||||||
Ok(Some(identity)) => identity,
|
parse_snapshot_time_value(&vcir.instance_gate.instance_effective_until)?;
|
||||||
Ok(None) => {
|
if validation_time > instance_effective_until {
|
||||||
return Ok(failed_fetch_no_cache_projection(
|
warnings.push(
|
||||||
ca,
|
Warning::new(format!("manifest failed fetch: {fresh_err}"))
|
||||||
fresh_err,
|
.with_rfc_refs(&[RfcRef("RFC 9286 §6.6")])
|
||||||
Some(vcir),
|
.with_context(&ca.manifest_rsync_uri),
|
||||||
"latest VCIR reuse identity is missing; no cached output or child reused",
|
);
|
||||||
));
|
warnings.push(
|
||||||
}
|
Warning::new(
|
||||||
Err(error) => {
|
"latest VCIR instance_gate expired; current instance contributes no cached output",
|
||||||
return Ok(failed_fetch_no_cache_projection(
|
)
|
||||||
ca,
|
.with_rfc_refs(&[RfcRef("RFC 9286 §6.6")])
|
||||||
fresh_err,
|
.with_context(&ca.manifest_rsync_uri),
|
||||||
Some(vcir),
|
);
|
||||||
&format!(
|
return Ok(VcirReuseProjection {
|
||||||
"latest VCIR reuse identity is invalid ({error}); no cached output or child reused"
|
source: PublicationPointSource::FailedFetchNoCache,
|
||||||
),
|
vcir: Some(vcir),
|
||||||
));
|
ccr_manifest_projection: None,
|
||||||
}
|
snapshot: None,
|
||||||
};
|
objects: empty_objects_output(),
|
||||||
if !failed_fetch_reuse_identity_matches_current(
|
child_audits: Vec::new(),
|
||||||
&reuse_identity,
|
discovered_children: Vec::new(),
|
||||||
&vcir,
|
warnings,
|
||||||
ca,
|
});
|
||||||
policy,
|
|
||||||
validation_time,
|
|
||||||
) {
|
|
||||||
return Ok(failed_fetch_no_cache_projection(
|
|
||||||
ca,
|
|
||||||
fresh_err,
|
|
||||||
Some(vcir),
|
|
||||||
"latest VCIR reuse identity does not match the current CA context or time window; no cached output or child reused",
|
|
||||||
));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
let ccr_manifest_projection = reuse_ccr_manifest_projection_from_vcir(ca, &vcir)?;
|
let ccr_manifest_projection = reuse_ccr_manifest_projection_from_vcir(ca, &vcir)?;
|
||||||
@ -4357,73 +4367,6 @@ fn project_current_instance_vcir_on_failed_fetch(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn failed_fetch_no_cache_projection(
|
|
||||||
ca: &CaInstanceHandle,
|
|
||||||
fresh_err: &ManifestFreshError,
|
|
||||||
vcir: Option<ValidatedCaInstanceResult>,
|
|
||||||
reason: &str,
|
|
||||||
) -> VcirReuseProjection {
|
|
||||||
let warnings = vec![
|
|
||||||
Warning::new(format!("manifest failed fetch: {fresh_err}"))
|
|
||||||
.with_rfc_refs(&[RfcRef("RFC 9286 §6.6")])
|
|
||||||
.with_context(&ca.manifest_rsync_uri),
|
|
||||||
Warning::new(reason)
|
|
||||||
.with_rfc_refs(&[RfcRef("RFC 9286 §6.6")])
|
|
||||||
.with_context(&ca.manifest_rsync_uri),
|
|
||||||
];
|
|
||||||
VcirReuseProjection {
|
|
||||||
source: PublicationPointSource::FailedFetchNoCache,
|
|
||||||
vcir,
|
|
||||||
ccr_manifest_projection: None,
|
|
||||||
snapshot: None,
|
|
||||||
objects: empty_objects_output(),
|
|
||||||
child_audits: Vec::new(),
|
|
||||||
discovered_children: Vec::new(),
|
|
||||||
warnings,
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fn failed_fetch_reuse_identity_for_fresh_result(
|
|
||||||
ca: &CaInstanceHandle,
|
|
||||||
policy: &Policy,
|
|
||||||
validation_time: time::OffsetDateTime,
|
|
||||||
effective_until: PackTime,
|
|
||||||
) -> Result<VcirFailedFetchReuseIdentity, String> {
|
|
||||||
let current_ca_sha256 = ca.ca_certificate_sha256_32().ok_or_else(|| {
|
|
||||||
"current CA certificate hash unavailable for VCIR reuse identity".to_string()
|
|
||||||
})?;
|
|
||||||
let identity = VcirFailedFetchReuseIdentity {
|
|
||||||
current_ca_sha256,
|
|
||||||
ta_context_digest: ta_context_digest_for_ca(ca),
|
|
||||||
parent_context_digest: parent_context_digest_for_ca(ca),
|
|
||||||
policy_fingerprint: publication_point_cache_policy_fingerprint(policy),
|
|
||||||
effective_not_before: PackTime::from_utc_offset_datetime(validation_time),
|
|
||||||
effective_until,
|
|
||||||
};
|
|
||||||
identity
|
|
||||||
.validate_internal()
|
|
||||||
.map_err(|error| error.to_string())?;
|
|
||||||
Ok(identity)
|
|
||||||
}
|
|
||||||
|
|
||||||
fn failed_fetch_reuse_identity_matches_current(
|
|
||||||
cached: &VcirFailedFetchReuseIdentity,
|
|
||||||
vcir: &ValidatedCaInstanceResult,
|
|
||||||
ca: &CaInstanceHandle,
|
|
||||||
policy: &Policy,
|
|
||||||
validation_time: time::OffsetDateTime,
|
|
||||||
) -> bool {
|
|
||||||
let Some(current_ca_sha256) = ca.ca_certificate_sha256_32() else {
|
|
||||||
return false;
|
|
||||||
};
|
|
||||||
cached.current_ca_sha256 == current_ca_sha256
|
|
||||||
&& cached.ta_context_digest == ta_context_digest_for_ca(ca)
|
|
||||||
&& cached.parent_context_digest == parent_context_digest_for_ca(ca)
|
|
||||||
&& cached.policy_fingerprint == publication_point_cache_policy_fingerprint(policy)
|
|
||||||
&& cached.effective_until == vcir.instance_gate.instance_effective_until
|
|
||||||
&& cached.contains_validation_time(validation_time)
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
fn reconstruct_snapshot_from_vcir(
|
fn reconstruct_snapshot_from_vcir(
|
||||||
store: &RocksStore,
|
store: &RocksStore,
|
||||||
@ -5337,14 +5280,8 @@ fn persist_vcir_for_fresh_result_with_timing(
|
|||||||
.expect("publication point projection must exist when guard is not active"),
|
.expect("publication point projection must exist when guard is not active"),
|
||||||
)
|
)
|
||||||
};
|
};
|
||||||
let failed_fetch_reuse_identity = failed_fetch_reuse_identity_for_fresh_result(
|
|
||||||
ca,
|
|
||||||
policy,
|
|
||||||
validation_time,
|
|
||||||
vcir.instance_gate.instance_effective_until.clone(),
|
|
||||||
)?;
|
|
||||||
let replace_timing = store
|
let replace_timing = store
|
||||||
.replace_vcir_manifest_replay_meta_and_projection_action_with_failed_fetch_reuse_identity(
|
.replace_vcir_manifest_replay_meta_and_projection_action(
|
||||||
&vcir,
|
&vcir,
|
||||||
Some(&RoaCacheProjectionContext {
|
Some(&RoaCacheProjectionContext {
|
||||||
parent_context_digest: parent_context_digest_for_ca(ca),
|
parent_context_digest: parent_context_digest_for_ca(ca),
|
||||||
@ -5352,7 +5289,6 @@ fn persist_vcir_for_fresh_result_with_timing(
|
|||||||
object_meta: objects.roa_cache_object_meta.clone(),
|
object_meta: objects.roa_cache_object_meta.clone(),
|
||||||
}),
|
}),
|
||||||
publication_point_cache_projection_action,
|
publication_point_cache_projection_action,
|
||||||
Some(&failed_fetch_reuse_identity),
|
|
||||||
)
|
)
|
||||||
.map_err(|e| format!("store VCIR and manifest replay meta failed: {e}"))?;
|
.map_err(|e| format!("store VCIR and manifest replay meta failed: {e}"))?;
|
||||||
timing.replace_vcir_ms = replace_vcir_started.elapsed().as_millis() as u64;
|
timing.replace_vcir_ms = replace_vcir_started.elapsed().as_millis() as u64;
|
||||||
|
|||||||
@ -753,44 +753,6 @@ fn sample_vcir_for_projection(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn put_vcir_for_failed_fetch_reuse(
|
|
||||||
store: &RocksStore,
|
|
||||||
ca: &CaInstanceHandle,
|
|
||||||
policy: &Policy,
|
|
||||||
vcir: &ValidatedCaInstanceResult,
|
|
||||||
) {
|
|
||||||
let validation_time = vcir
|
|
||||||
.last_successful_validation_time
|
|
||||||
.parse()
|
|
||||||
.expect("parse VCIR validation time");
|
|
||||||
let identity = failed_fetch_reuse_identity_for_fresh_result(
|
|
||||||
ca,
|
|
||||||
policy,
|
|
||||||
validation_time,
|
|
||||||
vcir.instance_gate.instance_effective_until.clone(),
|
|
||||||
)
|
|
||||||
.expect("build VCIR failed-fetch reuse identity");
|
|
||||||
store
|
|
||||||
.put_vcir_with_failed_fetch_reuse_identity(vcir, &identity)
|
|
||||||
.expect("put reusable VCIR");
|
|
||||||
}
|
|
||||||
|
|
||||||
fn sample_ca_for_failed_fetch_reuse(vcir: &ValidatedCaInstanceResult) -> CaInstanceHandle {
|
|
||||||
CaInstanceHandle {
|
|
||||||
depth: 0,
|
|
||||||
tal_id: "test-tal".to_string(),
|
|
||||||
parent_manifest_rsync_uri: None,
|
|
||||||
ca_certificate: CaCertificateRef::inline_der(Vec::new()),
|
|
||||||
ca_certificate_rsync_uri: None,
|
|
||||||
effective_ip_resources: None,
|
|
||||||
effective_as_resources: None,
|
|
||||||
rsync_base_uri: "rsync://example.test/repo/issuer/".to_string(),
|
|
||||||
manifest_rsync_uri: vcir.manifest_rsync_uri.clone(),
|
|
||||||
publication_point_rsync_uri: "rsync://example.test/repo/issuer/".to_string(),
|
|
||||||
rrdp_notification_uri: Some("https://example.test/notify.xml".to_string()),
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn never_http_fetcher_returns_error() {
|
fn never_http_fetcher_returns_error() {
|
||||||
let f = NeverHttpFetcher;
|
let f = NeverHttpFetcher;
|
||||||
@ -3865,6 +3827,7 @@ fn project_current_instance_vcir_reuses_local_outputs_and_restores_children() {
|
|||||||
let repo_bytes_db = store_dir.path().join("repo-bytes.db");
|
let repo_bytes_db = store_dir.path().join("repo-bytes.db");
|
||||||
let store = RocksStore::open_with_external_repo_bytes(&main_db, &repo_bytes_db)
|
let store = RocksStore::open_with_external_repo_bytes(&main_db, &repo_bytes_db)
|
||||||
.expect("open rocksdb with external repo bytes");
|
.expect("open rocksdb with external repo bytes");
|
||||||
|
store.put_vcir(&vcir).expect("put vcir");
|
||||||
store
|
store
|
||||||
.put_blob_bytes_batch(&[(child_cert_hash.clone(), g.child_ca_der.clone())])
|
.put_blob_bytes_batch(&[(child_cert_hash.clone(), g.child_ca_der.clone())])
|
||||||
.expect("put child cert repo bytes");
|
.expect("put child cert repo bytes");
|
||||||
@ -3889,8 +3852,6 @@ fn project_current_instance_vcir_reuses_local_outputs_and_restores_children() {
|
|||||||
publication_point_rsync_uri: "rsync://example.test/repo/issuer/".to_string(),
|
publication_point_rsync_uri: "rsync://example.test/repo/issuer/".to_string(),
|
||||||
rrdp_notification_uri: Some("https://example.test/notify.xml".to_string()),
|
rrdp_notification_uri: Some("https://example.test/notify.xml".to_string()),
|
||||||
};
|
};
|
||||||
let policy = Policy::default();
|
|
||||||
put_vcir_for_failed_fetch_reuse(&store, &ca, &policy, &vcir);
|
|
||||||
|
|
||||||
let projection = project_current_instance_vcir_on_failed_fetch(
|
let projection = project_current_instance_vcir_on_failed_fetch(
|
||||||
&store,
|
&store,
|
||||||
@ -3898,7 +3859,6 @@ fn project_current_instance_vcir_reuses_local_outputs_and_restores_children() {
|
|||||||
&ManifestFreshError::RepoSyncFailed {
|
&ManifestFreshError::RepoSyncFailed {
|
||||||
detail: "synthetic".to_string(),
|
detail: "synthetic".to_string(),
|
||||||
},
|
},
|
||||||
&policy,
|
|
||||||
now,
|
now,
|
||||||
)
|
)
|
||||||
.expect("project vcir");
|
.expect("project vcir");
|
||||||
@ -3953,79 +3913,6 @@ fn project_current_instance_vcir_reuses_local_outputs_and_restores_children() {
|
|||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn project_current_instance_vcir_does_not_reuse_without_identity() {
|
|
||||||
let now = time::OffsetDateTime::now_utc();
|
|
||||||
let child_cert_hash = sha256_hex(b"child-cert");
|
|
||||||
let vcir = sample_vcir_for_projection(now, &child_cert_hash);
|
|
||||||
let store_dir = tempfile::tempdir().expect("store dir");
|
|
||||||
let store = RocksStore::open(store_dir.path()).expect("open rocksdb");
|
|
||||||
store.put_vcir(&vcir).expect("put legacy-style VCIR");
|
|
||||||
let ca = sample_ca_for_failed_fetch_reuse(&vcir);
|
|
||||||
|
|
||||||
let projection = project_current_instance_vcir_on_failed_fetch(
|
|
||||||
&store,
|
|
||||||
&ca,
|
|
||||||
&ManifestFreshError::RepoSyncFailed {
|
|
||||||
detail: "synthetic".to_string(),
|
|
||||||
},
|
|
||||||
&Policy::default(),
|
|
||||||
now,
|
|
||||||
)
|
|
||||||
.expect("project VCIR");
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
projection.source,
|
|
||||||
PublicationPointSource::FailedFetchNoCache
|
|
||||||
);
|
|
||||||
assert!(projection.objects.vrps.is_empty());
|
|
||||||
assert!(projection.discovered_children.is_empty());
|
|
||||||
assert!(
|
|
||||||
projection
|
|
||||||
.warnings
|
|
||||||
.iter()
|
|
||||||
.any(|warning| warning.message.contains("reuse identity is missing"))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn project_current_instance_vcir_does_not_reuse_when_ta_context_changes() {
|
|
||||||
let now = time::OffsetDateTime::now_utc();
|
|
||||||
let child_cert_hash = sha256_hex(b"child-cert");
|
|
||||||
let vcir = sample_vcir_for_projection(now, &child_cert_hash);
|
|
||||||
let store_dir = tempfile::tempdir().expect("store dir");
|
|
||||||
let store = RocksStore::open(store_dir.path()).expect("open rocksdb");
|
|
||||||
let policy = Policy::default();
|
|
||||||
let original_ca = sample_ca_for_failed_fetch_reuse(&vcir);
|
|
||||||
put_vcir_for_failed_fetch_reuse(&store, &original_ca, &policy, &vcir);
|
|
||||||
let mut changed_ca = original_ca;
|
|
||||||
changed_ca.tal_id = "different-tal".to_string();
|
|
||||||
|
|
||||||
let projection = project_current_instance_vcir_on_failed_fetch(
|
|
||||||
&store,
|
|
||||||
&changed_ca,
|
|
||||||
&ManifestFreshError::RepoSyncFailed {
|
|
||||||
detail: "synthetic".to_string(),
|
|
||||||
},
|
|
||||||
&policy,
|
|
||||||
now,
|
|
||||||
)
|
|
||||||
.expect("project VCIR");
|
|
||||||
|
|
||||||
assert_eq!(
|
|
||||||
projection.source,
|
|
||||||
PublicationPointSource::FailedFetchNoCache
|
|
||||||
);
|
|
||||||
assert!(projection.objects.vrps.is_empty());
|
|
||||||
assert!(projection.discovered_children.is_empty());
|
|
||||||
assert!(
|
|
||||||
projection
|
|
||||||
.warnings
|
|
||||||
.iter()
|
|
||||||
.any(|warning| warning.message.contains("identity does not match"))
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn project_current_instance_vcir_returns_no_output_when_instance_gate_expired() {
|
fn project_current_instance_vcir_returns_no_output_when_instance_gate_expired() {
|
||||||
let now = time::OffsetDateTime::now_utc();
|
let now = time::OffsetDateTime::now_utc();
|
||||||
@ -4063,7 +3950,6 @@ fn project_current_instance_vcir_returns_no_output_when_instance_gate_expired()
|
|||||||
&ManifestFreshError::RepoSyncFailed {
|
&ManifestFreshError::RepoSyncFailed {
|
||||||
detail: "synthetic".to_string(),
|
detail: "synthetic".to_string(),
|
||||||
},
|
},
|
||||||
&Policy::default(),
|
|
||||||
now,
|
now,
|
||||||
)
|
)
|
||||||
.expect("project vcir");
|
.expect("project vcir");
|
||||||
@ -4089,6 +3975,7 @@ fn project_current_instance_vcir_keeps_real_fresh_validation_warning() {
|
|||||||
let repo_bytes_db = store_dir.path().join("repo-bytes.db");
|
let repo_bytes_db = store_dir.path().join("repo-bytes.db");
|
||||||
let store = RocksStore::open_with_external_repo_bytes(&main_db, &repo_bytes_db)
|
let store = RocksStore::open_with_external_repo_bytes(&main_db, &repo_bytes_db)
|
||||||
.expect("open rocksdb with external repo bytes");
|
.expect("open rocksdb with external repo bytes");
|
||||||
|
store.put_vcir(&vcir).expect("put vcir");
|
||||||
store
|
store
|
||||||
.put_blob_bytes_batch(&[(child_cert_hash, b"child-cert".to_vec())])
|
.put_blob_bytes_batch(&[(child_cert_hash, b"child-cert".to_vec())])
|
||||||
.expect("put child cert repo bytes");
|
.expect("put child cert repo bytes");
|
||||||
@ -4106,8 +3993,6 @@ fn project_current_instance_vcir_keeps_real_fresh_validation_warning() {
|
|||||||
publication_point_rsync_uri: "rsync://example.test/repo/issuer/".to_string(),
|
publication_point_rsync_uri: "rsync://example.test/repo/issuer/".to_string(),
|
||||||
rrdp_notification_uri: None,
|
rrdp_notification_uri: None,
|
||||||
};
|
};
|
||||||
let policy = Policy::default();
|
|
||||||
put_vcir_for_failed_fetch_reuse(&store, &ca, &policy, &vcir);
|
|
||||||
|
|
||||||
let projection = project_current_instance_vcir_on_failed_fetch(
|
let projection = project_current_instance_vcir_on_failed_fetch(
|
||||||
&store,
|
&store,
|
||||||
@ -4115,7 +4000,6 @@ fn project_current_instance_vcir_keeps_real_fresh_validation_warning() {
|
|||||||
&ManifestFreshError::HashMismatch {
|
&ManifestFreshError::HashMismatch {
|
||||||
rsync_uri: "rsync://example.test/repo/issuer/a.roa".to_string(),
|
rsync_uri: "rsync://example.test/repo/issuer/a.roa".to_string(),
|
||||||
},
|
},
|
||||||
&policy,
|
|
||||||
now,
|
now,
|
||||||
)
|
)
|
||||||
.expect("project vcir");
|
.expect("project vcir");
|
||||||
@ -4164,7 +4048,6 @@ fn project_current_instance_vcir_returns_no_output_when_latest_result_missing()
|
|||||||
&ManifestFreshError::RepoSyncFailed {
|
&ManifestFreshError::RepoSyncFailed {
|
||||||
detail: "synthetic".to_string(),
|
detail: "synthetic".to_string(),
|
||||||
},
|
},
|
||||||
&Policy::default(),
|
|
||||||
now,
|
now,
|
||||||
)
|
)
|
||||||
.expect("project without cached vcir");
|
.expect("project without cached vcir");
|
||||||
@ -4216,7 +4099,6 @@ fn project_current_instance_vcir_returns_no_output_when_latest_result_is_ineligi
|
|||||||
&ManifestFreshError::RepoSyncFailed {
|
&ManifestFreshError::RepoSyncFailed {
|
||||||
detail: "synthetic".to_string(),
|
detail: "synthetic".to_string(),
|
||||||
},
|
},
|
||||||
&Policy::default(),
|
|
||||||
now,
|
now,
|
||||||
)
|
)
|
||||||
.expect("project ineligible vcir");
|
.expect("project ineligible vcir");
|
||||||
@ -4246,6 +4128,7 @@ fn project_current_instance_vcir_rejects_mismatched_ccr_projection_uri() {
|
|||||||
|
|
||||||
let store_dir = tempfile::tempdir().expect("store dir");
|
let store_dir = tempfile::tempdir().expect("store dir");
|
||||||
let store = RocksStore::open(store_dir.path()).expect("open rocksdb");
|
let store = RocksStore::open(store_dir.path()).expect("open rocksdb");
|
||||||
|
store.put_vcir(&vcir).expect("put vcir");
|
||||||
|
|
||||||
let ca = CaInstanceHandle {
|
let ca = CaInstanceHandle {
|
||||||
depth: 0,
|
depth: 0,
|
||||||
@ -4260,8 +4143,6 @@ fn project_current_instance_vcir_rejects_mismatched_ccr_projection_uri() {
|
|||||||
publication_point_rsync_uri: "rsync://example.test/repo/issuer/".to_string(),
|
publication_point_rsync_uri: "rsync://example.test/repo/issuer/".to_string(),
|
||||||
rrdp_notification_uri: None,
|
rrdp_notification_uri: None,
|
||||||
};
|
};
|
||||||
let policy = Policy::default();
|
|
||||||
put_vcir_for_failed_fetch_reuse(&store, &ca, &policy, &vcir);
|
|
||||||
|
|
||||||
let err = project_current_instance_vcir_on_failed_fetch(
|
let err = project_current_instance_vcir_on_failed_fetch(
|
||||||
&store,
|
&store,
|
||||||
@ -4269,7 +4150,6 @@ fn project_current_instance_vcir_rejects_mismatched_ccr_projection_uri() {
|
|||||||
&ManifestFreshError::RepoSyncFailed {
|
&ManifestFreshError::RepoSyncFailed {
|
||||||
detail: "synthetic".to_string(),
|
detail: "synthetic".to_string(),
|
||||||
},
|
},
|
||||||
&policy,
|
|
||||||
now,
|
now,
|
||||||
)
|
)
|
||||||
.unwrap_err();
|
.unwrap_err();
|
||||||
|
|||||||
@ -383,7 +383,7 @@ fn metrics_for(obj_type: ObjType, der: &[u8]) -> Result<Metrics, String> {
|
|||||||
ObjType::Cer => {
|
ObjType::Cer => {
|
||||||
let parsed = ResourceCertificate::parse_der(der).map_err(|e| e.to_string())?;
|
let parsed = ResourceCertificate::parse_der(der).map_err(|e| e.to_string())?;
|
||||||
let spki_len = parsed.subject_public_key_info.len() as u32;
|
let spki_len = parsed.subject_public_key_info.len() as u32;
|
||||||
let ext_count = (parsed.extensions.basic_constraints.len()
|
let ext_count = (parsed.extensions.basic_constraints_ca.len()
|
||||||
+ parsed.extensions.subject_key_identifier.len()
|
+ parsed.extensions.subject_key_identifier.len()
|
||||||
+ parsed.extensions.authority_key_identifier.len()
|
+ parsed.extensions.authority_key_identifier.len()
|
||||||
+ parsed.extensions.crl_distribution_points.len()
|
+ parsed.extensions.crl_distribution_points.len()
|
||||||
|
|||||||
@ -25,7 +25,6 @@ fn dummy_ee(
|
|||||||
subject_public_key_info: vec![],
|
subject_public_key_info: vec![],
|
||||||
extensions: RcExtensions {
|
extensions: RcExtensions {
|
||||||
basic_constraints_ca: false,
|
basic_constraints_ca: false,
|
||||||
basic_constraints: None,
|
|
||||||
subject_key_identifier: Some(vec![0x01]),
|
subject_key_identifier: Some(vec![0x01]),
|
||||||
authority_key_identifier: None,
|
authority_key_identifier: None,
|
||||||
crl_distribution_points_uris: None,
|
crl_distribution_points_uris: None,
|
||||||
@ -36,8 +35,6 @@ fn dummy_ee(
|
|||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
certificate_policies_oid: None,
|
certificate_policies_oid: None,
|
||||||
certificate_policies: None,
|
|
||||||
extension_oids: Vec::new(),
|
|
||||||
ip_resources,
|
ip_resources,
|
||||||
as_resources,
|
as_resources,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -81,7 +81,6 @@ CN = Test Issuer CA
|
|||||||
[ v3_issuer_ca ]
|
[ v3_issuer_ca ]
|
||||||
basicConstraints = critical,CA:true
|
basicConstraints = critical,CA:true
|
||||||
keyUsage = critical, keyCertSign, cRLSign
|
keyUsage = critical, keyCertSign, cRLSign
|
||||||
certificatePolicies = critical,1.3.6.1.5.5.7.14.2
|
|
||||||
subjectKeyIdentifier = hash
|
subjectKeyIdentifier = hash
|
||||||
authorityKeyIdentifier = keyid:always
|
authorityKeyIdentifier = keyid:always
|
||||||
|
|
||||||
|
|||||||
@ -88,18 +88,6 @@ fn offline_discovery_from_apnic_tal_and_ta_der_fixture_works() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn offline_discovery_rejects_tampered_ta_self_signature() {
|
|
||||||
let tal_bytes = apnic_tal_bytes();
|
|
||||||
let mut ta_der = apnic_ta_der();
|
|
||||||
let last = ta_der.last_mut().expect("TA fixture must not be empty");
|
|
||||||
*last ^= 0x01;
|
|
||||||
|
|
||||||
let err = discover_root_ca_instance_from_tal_and_ta_der(&tal_bytes, &ta_der, None)
|
|
||||||
.expect_err("tampered TA signature must not form a root CA instance");
|
|
||||||
assert!(err.to_string().contains("self-signature"), "{err}");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn discover_root_from_tal_url_works_with_mock_fetcher() {
|
fn discover_root_from_tal_url_works_with_mock_fetcher() {
|
||||||
let tal_bytes = apnic_tal_bytes();
|
let tal_bytes = apnic_tal_bytes();
|
||||||
|
|||||||
@ -1,131 +0,0 @@
|
|||||||
use der_parser::num_bigint::BigUint;
|
|
||||||
use rpki::data_model::common::X509NameDer;
|
|
||||||
use rpki::data_model::oid::{
|
|
||||||
OID_AUTONOMOUS_SYS_IDS, OID_BASIC_CONSTRAINTS, OID_CERTIFICATE_POLICIES, OID_CP_IPADDR_ASNUMBER,
|
|
||||||
};
|
|
||||||
use rpki::data_model::rc::{
|
|
||||||
AsIdOrRange, AsIdentifierChoice, AsResourceSet, BasicConstraintsProfile,
|
|
||||||
CertificatePoliciesProfile, RcExtensions, ResourceCertKind, ResourceCertificate,
|
|
||||||
ResourceCertificateProfileError, ResourceCertificateRole, RpkixTbsCertificate,
|
|
||||||
};
|
|
||||||
use time::OffsetDateTime;
|
|
||||||
|
|
||||||
fn valid_ca_certificate() -> ResourceCertificate {
|
|
||||||
let validation_time = OffsetDateTime::from_unix_timestamp(0).expect("valid epoch");
|
|
||||||
ResourceCertificate {
|
|
||||||
raw_der: Vec::new(),
|
|
||||||
kind: ResourceCertKind::Ca,
|
|
||||||
tbs: RpkixTbsCertificate {
|
|
||||||
version: 2,
|
|
||||||
serial_number: BigUint::from(1u32),
|
|
||||||
signature_algorithm: "1.2.840.113549.1.1.11".to_string(),
|
|
||||||
issuer_name: X509NameDer(b"CN=issuer".to_vec()),
|
|
||||||
subject_name: X509NameDer(b"CN=subject".to_vec()),
|
|
||||||
validity_not_before: validation_time,
|
|
||||||
validity_not_after: validation_time,
|
|
||||||
subject_public_key_info: Vec::new(),
|
|
||||||
extensions: RcExtensions {
|
|
||||||
basic_constraints_ca: true,
|
|
||||||
basic_constraints: Some(BasicConstraintsProfile {
|
|
||||||
ca: true,
|
|
||||||
critical: true,
|
|
||||||
path_len_constraint: None,
|
|
||||||
}),
|
|
||||||
subject_key_identifier: None,
|
|
||||||
authority_key_identifier: None,
|
|
||||||
crl_distribution_points_uris: None,
|
|
||||||
ca_issuers_uris: None,
|
|
||||||
subject_info_access: None,
|
|
||||||
certificate_policies_oid: Some(OID_CP_IPADDR_ASNUMBER.to_string()),
|
|
||||||
certificate_policies: Some(CertificatePoliciesProfile {
|
|
||||||
policy_oid: OID_CP_IPADDR_ASNUMBER.to_string(),
|
|
||||||
qualifier_oids: Vec::new(),
|
|
||||||
}),
|
|
||||||
extension_oids: vec![
|
|
||||||
OID_BASIC_CONSTRAINTS.to_string(),
|
|
||||||
OID_CERTIFICATE_POLICIES.to_string(),
|
|
||||||
],
|
|
||||||
ip_resources: None,
|
|
||||||
as_resources: None,
|
|
||||||
},
|
|
||||||
},
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn ca_profile_accepts_required_basic_constraints_and_policy() {
|
|
||||||
valid_ca_certificate()
|
|
||||||
.validate_rfc6487_profile(ResourceCertificateRole::Ca)
|
|
||||||
.expect("valid CA profile");
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn ca_profile_rejects_noncritical_basic_constraints() {
|
|
||||||
let mut certificate = valid_ca_certificate();
|
|
||||||
certificate
|
|
||||||
.tbs
|
|
||||||
.extensions
|
|
||||||
.basic_constraints
|
|
||||||
.as_mut()
|
|
||||||
.expect("constraints")
|
|
||||||
.critical = false;
|
|
||||||
|
|
||||||
assert!(matches!(
|
|
||||||
certificate.validate_rfc6487_profile(ResourceCertificateRole::Ca),
|
|
||||||
Err(ResourceCertificateProfileError::BasicConstraintsCriticality)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn ca_profile_rejects_unknown_extension() {
|
|
||||||
let mut certificate = valid_ca_certificate();
|
|
||||||
certificate
|
|
||||||
.tbs
|
|
||||||
.extensions
|
|
||||||
.extension_oids
|
|
||||||
.push("1.2.3.4".to_string());
|
|
||||||
|
|
||||||
assert!(matches!(
|
|
||||||
certificate.validate_rfc6487_profile(ResourceCertificateRole::Ca),
|
|
||||||
Err(ResourceCertificateProfileError::DisallowedExtension { .. })
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn ca_profile_rejects_non_cps_policy_qualifier() {
|
|
||||||
let mut certificate = valid_ca_certificate();
|
|
||||||
certificate
|
|
||||||
.tbs
|
|
||||||
.extensions
|
|
||||||
.certificate_policies
|
|
||||||
.as_mut()
|
|
||||||
.expect("certificate policies")
|
|
||||||
.qualifier_oids
|
|
||||||
.push("1.2.3.4".to_string());
|
|
||||||
|
|
||||||
assert!(matches!(
|
|
||||||
certificate.validate_rfc6487_profile(ResourceCertificateRole::Ca),
|
|
||||||
Err(ResourceCertificateProfileError::CertificatePoliciesInvalidQualifier(_))
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn ca_profile_rejects_autonomous_system_rdi() {
|
|
||||||
let mut certificate = valid_ca_certificate();
|
|
||||||
certificate.tbs.extensions.as_resources = Some(AsResourceSet {
|
|
||||||
asnum: None,
|
|
||||||
rdi: Some(AsIdentifierChoice::AsIdsOrRanges(vec![AsIdOrRange::Id(
|
|
||||||
64512,
|
|
||||||
)])),
|
|
||||||
});
|
|
||||||
certificate
|
|
||||||
.tbs
|
|
||||||
.extensions
|
|
||||||
.extension_oids
|
|
||||||
.push(OID_AUTONOMOUS_SYS_IDS.to_string());
|
|
||||||
|
|
||||||
assert!(matches!(
|
|
||||||
certificate.validate_rfc6487_profile(ResourceCertificateRole::Ca),
|
|
||||||
Err(ResourceCertificateProfileError::AsResourcesRdiPresent)
|
|
||||||
));
|
|
||||||
}
|
|
||||||
@ -28,7 +28,6 @@ fn dummy_ee(
|
|||||||
subject_public_key_info: vec![],
|
subject_public_key_info: vec![],
|
||||||
extensions: RcExtensions {
|
extensions: RcExtensions {
|
||||||
basic_constraints_ca: false,
|
basic_constraints_ca: false,
|
||||||
basic_constraints: None,
|
|
||||||
subject_key_identifier: Some(vec![0x01]),
|
subject_key_identifier: Some(vec![0x01]),
|
||||||
authority_key_identifier: None,
|
authority_key_identifier: None,
|
||||||
crl_distribution_points_uris: None,
|
crl_distribution_points_uris: None,
|
||||||
@ -39,8 +38,6 @@ fn dummy_ee(
|
|||||||
},
|
},
|
||||||
)),
|
)),
|
||||||
certificate_policies_oid: None,
|
certificate_policies_oid: None,
|
||||||
certificate_policies: None,
|
|
||||||
extension_oids: Vec::new(),
|
|
||||||
ip_resources,
|
ip_resources,
|
||||||
as_resources,
|
as_resources,
|
||||||
},
|
},
|
||||||
|
|||||||
@ -794,22 +794,13 @@ fn ee_certificate_missing_signed_object_sia_is_rejected() {
|
|||||||
));
|
));
|
||||||
|
|
||||||
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
|
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
|
||||||
let rejected_for_sia = matches!(
|
assert!(matches!(
|
||||||
&err,
|
err,
|
||||||
SignedObjectDecodeError::Validate(SignedObjectValidateError::EeCertificateMissingSia)
|
SignedObjectDecodeError::Validate(SignedObjectValidateError::EeCertificateMissingSia)
|
||||||
| SignedObjectDecodeError::Validate(
|
| SignedObjectDecodeError::Validate(
|
||||||
SignedObjectValidateError::EeCertificateMissingSignedObjectSia
|
SignedObjectValidateError::EeCertificateMissingSignedObjectSia
|
||||||
)
|
)
|
||||||
);
|
));
|
||||||
let rejected_for_ee_profile = matches!(
|
|
||||||
&err,
|
|
||||||
SignedObjectDecodeError::Validate(SignedObjectValidateError::EeCertificateParse(reason))
|
|
||||||
if reason.contains("basicConstraints must be absent")
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
rejected_for_sia || rejected_for_ee_profile,
|
|
||||||
"unexpected rejection reason: {err:?}"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -843,21 +834,12 @@ fn ee_certificate_sia_without_signed_object_access_method_is_rejected() {
|
|||||||
vec![si],
|
vec![si],
|
||||||
));
|
));
|
||||||
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
|
let err = RpkiSignedObject::decode_der(&so).unwrap_err();
|
||||||
let rejected_for_sia = matches!(
|
assert!(matches!(
|
||||||
&err,
|
err,
|
||||||
SignedObjectDecodeError::Validate(
|
SignedObjectDecodeError::Validate(
|
||||||
SignedObjectValidateError::EeCertificateMissingSignedObjectSia
|
SignedObjectValidateError::EeCertificateMissingSignedObjectSia
|
||||||
)
|
)
|
||||||
);
|
));
|
||||||
let rejected_for_ee_profile = matches!(
|
|
||||||
&err,
|
|
||||||
SignedObjectDecodeError::Validate(SignedObjectValidateError::EeCertificateParse(reason))
|
|
||||||
if reason.contains("basicConstraints must be absent")
|
|
||||||
);
|
|
||||||
assert!(
|
|
||||||
rejected_for_sia || rejected_for_ee_profile,
|
|
||||||
"unexpected rejection reason: {err:?}"
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
|
|||||||
@ -68,15 +68,12 @@ fn ta_certificate_rejects_non_self_signed_ca() {
|
|||||||
fn ta_constraints_require_policies_and_ski() {
|
fn ta_constraints_require_policies_and_ski() {
|
||||||
let rc = dummy_rc_ca(RcExtensions {
|
let rc = dummy_rc_ca(RcExtensions {
|
||||||
basic_constraints_ca: true,
|
basic_constraints_ca: true,
|
||||||
basic_constraints: None,
|
|
||||||
subject_key_identifier: None,
|
subject_key_identifier: None,
|
||||||
authority_key_identifier: None,
|
authority_key_identifier: None,
|
||||||
crl_distribution_points_uris: None,
|
crl_distribution_points_uris: None,
|
||||||
ca_issuers_uris: None,
|
ca_issuers_uris: None,
|
||||||
subject_info_access: None,
|
subject_info_access: None,
|
||||||
certificate_policies_oid: None,
|
certificate_policies_oid: None,
|
||||||
certificate_policies: None,
|
|
||||||
extension_oids: Vec::new(),
|
|
||||||
ip_resources: Some(rpki::data_model::rc::IpResourceSet { families: vec![] }),
|
ip_resources: Some(rpki::data_model::rc::IpResourceSet { families: vec![] }),
|
||||||
as_resources: None,
|
as_resources: None,
|
||||||
});
|
});
|
||||||
@ -100,15 +97,12 @@ fn ta_constraints_require_non_empty_resources_and_no_inherit() {
|
|||||||
// Missing both IP and AS resources.
|
// Missing both IP and AS resources.
|
||||||
let rc = dummy_rc_ca(RcExtensions {
|
let rc = dummy_rc_ca(RcExtensions {
|
||||||
basic_constraints_ca: true,
|
basic_constraints_ca: true,
|
||||||
basic_constraints: None,
|
|
||||||
subject_key_identifier: Some(vec![1]),
|
subject_key_identifier: Some(vec![1]),
|
||||||
authority_key_identifier: None,
|
authority_key_identifier: None,
|
||||||
crl_distribution_points_uris: None,
|
crl_distribution_points_uris: None,
|
||||||
ca_issuers_uris: None,
|
ca_issuers_uris: None,
|
||||||
subject_info_access: None,
|
subject_info_access: None,
|
||||||
certificate_policies_oid: Some(OID_CP_IPADDR_ASNUMBER.to_string()),
|
certificate_policies_oid: Some(OID_CP_IPADDR_ASNUMBER.to_string()),
|
||||||
certificate_policies: None,
|
|
||||||
extension_oids: Vec::new(),
|
|
||||||
ip_resources: None,
|
ip_resources: None,
|
||||||
as_resources: None,
|
as_resources: None,
|
||||||
});
|
});
|
||||||
|
|||||||
@ -1,6 +1,4 @@
|
|||||||
use rpki::data_model::ta::{
|
use rpki::data_model::ta::{TrustAnchor, TrustAnchorBindError, TrustAnchorError};
|
||||||
TaCertificateVerifyError, TrustAnchor, TrustAnchorBindError, TrustAnchorError,
|
|
||||||
};
|
|
||||||
use rpki::data_model::tal::Tal;
|
use rpki::data_model::tal::Tal;
|
||||||
use url::Url;
|
use url::Url;
|
||||||
|
|
||||||
@ -78,21 +76,3 @@ fn bind_rejects_resolved_uri_not_listed_in_tal() {
|
|||||||
))
|
))
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
|
||||||
fn bind_rejects_tampered_ta_self_signature() {
|
|
||||||
let tal_raw =
|
|
||||||
std::fs::read("tests/fixtures/tal/apnic-rfc7730-https.tal").expect("read TAL fixture");
|
|
||||||
let tal = Tal::decode_bytes(&tal_raw).expect("decode TAL fixture");
|
|
||||||
let mut ta_der = std::fs::read("tests/fixtures/ta/apnic-ta.cer").expect("read TA fixture");
|
|
||||||
|
|
||||||
let last = ta_der.last_mut().expect("TA fixture must not be empty");
|
|
||||||
*last ^= 0x01;
|
|
||||||
|
|
||||||
assert!(matches!(
|
|
||||||
TrustAnchor::bind_der(tal, &ta_der, None),
|
|
||||||
Err(TrustAnchorError::TaSelfSignature(
|
|
||||||
TaCertificateVerifyError::InvalidSelfSignature(_)
|
|
||||||
))
|
|
||||||
));
|
|
||||||
}
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user