20260609 CIR区分fresh和cached验证输入
This commit is contained in:
parent
74012c686d
commit
4047214ddf
@ -87,6 +87,10 @@ pub struct PublicationPointAudit {
|
||||
|
||||
pub warnings: Vec<AuditWarning>,
|
||||
pub objects: Vec<ObjectAuditEntry>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub cir_fresh_objects: Vec<ObjectAuditEntry>,
|
||||
#[serde(default, skip_serializing_if = "Vec::is_empty")]
|
||||
pub cir_cached_objects: Vec<ObjectAuditEntry>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq, Serialize)]
|
||||
|
||||
@ -127,7 +127,7 @@ fn main() -> Result<(), String> {
|
||||
.map_err(|e| format!("open repo bytes db failed: {e}"))?;
|
||||
|
||||
let mut object_hash_by_uri = BTreeMap::new();
|
||||
for object in &cir.objects {
|
||||
for object in cir.validated_objects() {
|
||||
object_hash_by_uri.insert(object.rsync_uri.clone(), hex::encode(&object.sha256));
|
||||
}
|
||||
|
||||
|
||||
@ -56,9 +56,24 @@ fn real_main() -> Result<(), String> {
|
||||
.map_err(|e| format!("read cir failed: {}: {e}", cir_path.display()))?;
|
||||
let cir = rpki::cir::decode_cir(&bytes).map_err(|e| format!("decode cir failed: {e}"))?;
|
||||
|
||||
println!("object_count={}", cir.objects.len());
|
||||
println!("version={}", cir.version);
|
||||
println!("object_count={}", cir.validated_object_count());
|
||||
println!("fresh_object_count={}", cir.fresh_validated_objects.len());
|
||||
println!("cached_object_count={}", cir.cached_validated_objects.len());
|
||||
println!(
|
||||
"object_list_sha256={}",
|
||||
hex::encode(&cir.object_list_sha256)
|
||||
);
|
||||
println!(
|
||||
"fresh_object_list_sha256={}",
|
||||
hex::encode(&cir.fresh_object_list_sha256)
|
||||
);
|
||||
println!(
|
||||
"cached_object_list_sha256={}",
|
||||
hex::encode(&cir.cached_object_list_sha256)
|
||||
);
|
||||
println!("trust_anchor_count={}", cir.trust_anchors.len());
|
||||
for (index, item) in cir.objects.iter().take(args.limit).enumerate() {
|
||||
for (index, item) in cir.validated_objects().take(args.limit).enumerate() {
|
||||
println!(
|
||||
"{:04} object={} sha256={}",
|
||||
index + 1,
|
||||
@ -70,8 +85,18 @@ fn real_main() -> Result<(), String> {
|
||||
"reject_list_sha256={}",
|
||||
hex::encode(&cir.reject_list_sha256)
|
||||
);
|
||||
println!("reject_count={}", cir.rejected_objects.len());
|
||||
for (index, item) in cir.rejected_objects.iter().take(args.limit).enumerate() {
|
||||
println!("reject_count={}", cir.rejected_object_count());
|
||||
println!("fresh_reject_count={}", cir.fresh_rejected_objects.len());
|
||||
println!("cached_reject_count={}", cir.cached_rejected_objects.len());
|
||||
println!(
|
||||
"fresh_reject_list_sha256={}",
|
||||
hex::encode(&cir.fresh_reject_list_sha256)
|
||||
);
|
||||
println!(
|
||||
"cached_reject_list_sha256={}",
|
||||
hex::encode(&cir.cached_reject_list_sha256)
|
||||
);
|
||||
for (index, item) in cir.rejected_objects().take(args.limit).enumerate() {
|
||||
println!(
|
||||
"{:04} uri={} reason={}",
|
||||
index + 1,
|
||||
|
||||
@ -108,13 +108,11 @@ fn run(args: Args) -> Result<(), String> {
|
||||
let peer = decode_cir(&read_file(peer_cir_path)?)
|
||||
.map_err(|e| format!("decode rpki-client CIR failed: {e}"))?;
|
||||
let peer_objects = peer
|
||||
.objects
|
||||
.iter()
|
||||
.validated_objects()
|
||||
.map(|item| item.rsync_uri.as_str())
|
||||
.collect::<BTreeSet<_>>();
|
||||
let only_in_ours = ours
|
||||
.objects
|
||||
.iter()
|
||||
.validated_objects()
|
||||
.filter(|item| !peer_objects.contains(item.rsync_uri.as_str()))
|
||||
.map(|item| ProbeObject {
|
||||
uri: item.rsync_uri.clone(),
|
||||
@ -442,10 +440,7 @@ fn uri_extension(uri: &str) -> String {
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rpki::cir::{
|
||||
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirObject,
|
||||
CirRejectedObject, CirTrustAnchor, compute_reject_list_sha256, encode_cir,
|
||||
};
|
||||
use rpki::cir::{CanonicalInputRepresentation, CirObject, CirTrustAnchor, encode_cir};
|
||||
|
||||
#[test]
|
||||
fn parse_args_accepts_required_flags() {
|
||||
@ -663,15 +658,14 @@ mod tests {
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
objects.sort_by(|left, right| left.rsync_uri.cmp(&right.rsync_uri));
|
||||
CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: time::OffsetDateTime::UNIX_EPOCH,
|
||||
CanonicalInputRepresentation::new_v4(
|
||||
time::OffsetDateTime::UNIX_EPOCH,
|
||||
objects,
|
||||
trust_anchors: vec![sample_trust_anchor()],
|
||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||
rejected_objects: Vec::<CirRejectedObject>::new(),
|
||||
}
|
||||
Vec::new(),
|
||||
vec![sample_trust_anchor()],
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
)
|
||||
}
|
||||
|
||||
fn sample_trust_anchor() -> CirTrustAnchor {
|
||||
|
||||
@ -95,23 +95,19 @@ fn run(args: Args) -> Result<(), String> {
|
||||
.map_err(|e| format!("decode rpki-client CIR failed: {e}"))?;
|
||||
|
||||
let ours_objects = ours
|
||||
.objects
|
||||
.iter()
|
||||
.validated_objects()
|
||||
.map(|item| (item.rsync_uri.clone(), hex::encode(&item.sha256)))
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
let peer_objects = peer
|
||||
.objects
|
||||
.iter()
|
||||
.validated_objects()
|
||||
.map(|item| (item.rsync_uri.clone(), hex::encode(&item.sha256)))
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
let ours_rejects = ours
|
||||
.rejected_objects
|
||||
.iter()
|
||||
.rejected_objects()
|
||||
.map(|item| item.object_uri.clone())
|
||||
.collect::<BTreeSet<_>>();
|
||||
let peer_rejects = peer
|
||||
.rejected_objects
|
||||
.iter()
|
||||
.rejected_objects()
|
||||
.map(|item| item.object_uri.clone())
|
||||
.collect::<BTreeSet<_>>();
|
||||
let ours_trust_anchors = ours
|
||||
@ -153,18 +149,18 @@ fn run(args: Args) -> Result<(), String> {
|
||||
"trust_anchors": trust_anchor_summary.to_json(),
|
||||
"rejectListSha256Match": reject_hash_match,
|
||||
"ours": {
|
||||
"objectCount": ours.objects.len(),
|
||||
"objectCount": ours.validated_object_count(),
|
||||
"talCount": ours.trust_anchors.len(),
|
||||
"trustAnchorCount": ours.trust_anchors.len(),
|
||||
"rejectCount": ours.rejected_objects.len(),
|
||||
"rejectCount": ours.rejected_object_count(),
|
||||
"rejectListSha256": hex::encode(&ours.reject_list_sha256),
|
||||
"validationTime": ours.validation_time.to_string(),
|
||||
},
|
||||
"rpkiClient": {
|
||||
"objectCount": peer.objects.len(),
|
||||
"objectCount": peer.validated_object_count(),
|
||||
"talCount": peer.trust_anchors.len(),
|
||||
"trustAnchorCount": peer.trust_anchors.len(),
|
||||
"rejectCount": peer.rejected_objects.len(),
|
||||
"rejectCount": peer.rejected_object_count(),
|
||||
"rejectListSha256": hex::encode(&peer.reject_list_sha256),
|
||||
"validationTime": peer.validation_time.to_string(),
|
||||
}
|
||||
@ -428,8 +424,8 @@ fn uri_extension(uri: &str) -> String {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use rpki::cir::{
|
||||
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirObject,
|
||||
CirRejectedObject, CirTrustAnchor, compute_reject_list_sha256, encode_cir, sha256,
|
||||
CanonicalInputRepresentation, CirObject, CirRejectedObject, CirTrustAnchor, encode_cir,
|
||||
sha256,
|
||||
};
|
||||
|
||||
#[test]
|
||||
@ -533,7 +529,7 @@ mod tests {
|
||||
assert_eq!(summary["rejects"]["match"], true);
|
||||
assert_eq!(summary["trustAnchors"]["match"], true);
|
||||
assert_eq!(summary["rejectListSha256Match"], true);
|
||||
assert_eq!(summary["ours"]["objectCount"], 2);
|
||||
assert_eq!(summary["ours"]["objectCount"], 3);
|
||||
assert!(
|
||||
std::fs::read_to_string(out_md)
|
||||
.expect("read markdown")
|
||||
@ -721,6 +717,24 @@ mod tests {
|
||||
trust_anchors: &[&str],
|
||||
rejected_objects: &[(&str, Option<&str>)],
|
||||
) -> CanonicalInputRepresentation {
|
||||
let mut cir_objects = objects
|
||||
.iter()
|
||||
.map(|(rsync_uri, fill)| CirObject {
|
||||
rsync_uri: (*rsync_uri).to_string(),
|
||||
sha256: vec![*fill; 32],
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
for (object_uri, _) in rejected_objects {
|
||||
if cir_objects
|
||||
.iter()
|
||||
.all(|object| object.rsync_uri != *object_uri)
|
||||
{
|
||||
cir_objects.push(CirObject {
|
||||
rsync_uri: (*object_uri).to_string(),
|
||||
sha256: vec![0xee; 32],
|
||||
});
|
||||
}
|
||||
}
|
||||
let rejected_objects = rejected_objects
|
||||
.iter()
|
||||
.map(|(object_uri, reason)| CirRejectedObject {
|
||||
@ -728,18 +742,11 @@ mod tests {
|
||||
reason: reason.map(str::to_string),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: sample_time(),
|
||||
objects: objects
|
||||
.iter()
|
||||
.map(|(rsync_uri, fill)| CirObject {
|
||||
rsync_uri: (*rsync_uri).to_string(),
|
||||
sha256: vec![*fill; 32],
|
||||
})
|
||||
.collect(),
|
||||
trust_anchors: trust_anchors
|
||||
CanonicalInputRepresentation::new_v4(
|
||||
sample_time(),
|
||||
cir_objects,
|
||||
Vec::new(),
|
||||
trust_anchors
|
||||
.iter()
|
||||
.map(|tal_uri| {
|
||||
let name = tal_uri
|
||||
@ -758,11 +765,9 @@ mod tests {
|
||||
}
|
||||
})
|
||||
.collect(),
|
||||
reject_list_sha256: compute_reject_list_sha256(
|
||||
rejected_objects.iter().map(|item| item.object_uri.as_str()),
|
||||
),
|
||||
rejected_objects,
|
||||
}
|
||||
Vec::new(),
|
||||
)
|
||||
}
|
||||
|
||||
fn write_cir(path: &Path, cir: &CanonicalInputRepresentation) {
|
||||
|
||||
@ -1,9 +1,6 @@
|
||||
use std::path::PathBuf;
|
||||
|
||||
use rpki::cir::{
|
||||
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirTrustAnchor,
|
||||
compute_reject_list_sha256, encode_cir, sha256,
|
||||
};
|
||||
use rpki::cir::{CanonicalInputRepresentation, CirTrustAnchor, encode_cir, sha256};
|
||||
|
||||
const USAGE: &str = "Usage: cir_ta_only_fixture --tal-path <path> --ta-path <path> --tal-uri <url> --validation-time <rfc3339> --cir-out <path> --repo-bytes-db <path>";
|
||||
|
||||
@ -102,21 +99,20 @@ fn main() -> Result<(), String> {
|
||||
|
||||
let ta_certificate_sha256 = sha256(&ta_bytes);
|
||||
|
||||
let cir = CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
let cir = CanonicalInputRepresentation::new_v4(
|
||||
validation_time,
|
||||
objects: Vec::new(),
|
||||
trust_anchors: vec![CirTrustAnchor {
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
vec![CirTrustAnchor {
|
||||
ta_rsync_uri,
|
||||
tal_uri,
|
||||
tal_bytes,
|
||||
ta_certificate_der: ta_bytes,
|
||||
ta_certificate_sha256,
|
||||
}],
|
||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||
rejected_objects: Vec::new(),
|
||||
};
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
);
|
||||
let der = encode_cir(&cir).map_err(|e| format!("encode cir failed: {e}"))?;
|
||||
if let Some(parent) = cir_out.parent() {
|
||||
std::fs::create_dir_all(parent).map_err(|e| format!("create cir parent failed: {e}"))?;
|
||||
|
||||
@ -273,23 +273,19 @@ fn build_cir_summary(args: &Args) -> Result<Value, String> {
|
||||
let left = decode_cir(&read_file(&args.left_cir)?).map_err(|e| e.to_string())?;
|
||||
let right = decode_cir(&read_file(&args.right_cir)?).map_err(|e| e.to_string())?;
|
||||
let left_objects = left
|
||||
.objects
|
||||
.iter()
|
||||
.validated_objects()
|
||||
.map(|item| (item.rsync_uri.clone(), hex::encode(&item.sha256)))
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
let right_objects = right
|
||||
.objects
|
||||
.iter()
|
||||
.validated_objects()
|
||||
.map(|item| (item.rsync_uri.clone(), hex::encode(&item.sha256)))
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
let left_rejects = left
|
||||
.rejected_objects
|
||||
.iter()
|
||||
.rejected_objects()
|
||||
.map(|item| item.object_uri.clone())
|
||||
.collect::<BTreeSet<_>>();
|
||||
let right_rejects = right
|
||||
.rejected_objects
|
||||
.iter()
|
||||
.rejected_objects()
|
||||
.map(|item| item.object_uri.clone())
|
||||
.collect::<BTreeSet<_>>();
|
||||
let left_trust_anchors = left
|
||||
@ -338,15 +334,15 @@ fn build_cir_summary(args: &Args) -> Result<Value, String> {
|
||||
"rejectListSha256Match": reject_hash_match,
|
||||
"validationTimeMatch": validation_time_match,
|
||||
"left": {
|
||||
"objectCount": left.objects.len(),
|
||||
"rejectCount": left.rejected_objects.len(),
|
||||
"objectCount": left.validated_object_count(),
|
||||
"rejectCount": left.rejected_object_count(),
|
||||
"trustAnchorCount": left.trust_anchors.len(),
|
||||
"rejectListSha256": hex::encode(&left.reject_list_sha256),
|
||||
"validationTime": format_time(left.validation_time)?,
|
||||
},
|
||||
"right": {
|
||||
"objectCount": right.objects.len(),
|
||||
"rejectCount": right.rejected_objects.len(),
|
||||
"objectCount": right.validated_object_count(),
|
||||
"rejectCount": right.rejected_object_count(),
|
||||
"trustAnchorCount": right.trust_anchors.len(),
|
||||
"rejectListSha256": hex::encode(&right.reject_list_sha256),
|
||||
"validationTime": format_time(right.validation_time)?,
|
||||
@ -977,8 +973,8 @@ mod tests {
|
||||
build_aspa_payload_state, build_roa_payload_state, encode_content_info,
|
||||
};
|
||||
use rpki::cir::{
|
||||
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirObject,
|
||||
CirRejectedObject, CirTrustAnchor, compute_reject_list_sha256, encode_cir, sha256,
|
||||
CanonicalInputRepresentation, CirObject, CirRejectedObject, CirTrustAnchor, encode_cir,
|
||||
sha256,
|
||||
};
|
||||
use rpki::data_model::roa::{IpPrefix, RoaAfi};
|
||||
use rpki::validation::objects::{AspaAttestation, Vrp};
|
||||
@ -1193,6 +1189,7 @@ mod tests {
|
||||
codes,
|
||||
vec![
|
||||
"P1_TRUST_ANCHOR_DIFFERENCE",
|
||||
"P2_OBJECT_URI_SET_DIFFERENCE",
|
||||
"P3_OBJECT_CONTENT_HASH_DIFFERENCE",
|
||||
"P4_REJECT_DECISION_DIFFERENCE",
|
||||
"P5_VALIDATION_TIME_DIFFERENCE",
|
||||
@ -1392,20 +1389,30 @@ mod tests {
|
||||
}]
|
||||
})
|
||||
.unwrap_or_default();
|
||||
CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time,
|
||||
objects: vec![CirObject {
|
||||
rsync_uri: "rsync://example.net/repo/a.roa".to_string(),
|
||||
sha256: vec![object_hash_fill; 32],
|
||||
}],
|
||||
trust_anchors: vec![trust_anchor],
|
||||
reject_list_sha256: compute_reject_list_sha256(
|
||||
rejected_objects.iter().map(|item| item.object_uri.as_str()),
|
||||
),
|
||||
rejected_objects,
|
||||
let mut objects = vec![CirObject {
|
||||
rsync_uri: "rsync://example.net/repo/a.roa".to_string(),
|
||||
sha256: vec![object_hash_fill; 32],
|
||||
}];
|
||||
for rejected in &rejected_objects {
|
||||
if !objects
|
||||
.iter()
|
||||
.any(|object| object.rsync_uri == rejected.object_uri)
|
||||
{
|
||||
objects.push(CirObject {
|
||||
rsync_uri: rejected.object_uri.clone(),
|
||||
sha256: vec![object_hash_fill; 32],
|
||||
});
|
||||
}
|
||||
}
|
||||
objects.sort_by(|a, b| a.rsync_uri.cmp(&b.rsync_uri));
|
||||
CanonicalInputRepresentation::new_v4(
|
||||
validation_time,
|
||||
objects,
|
||||
Vec::new(),
|
||||
vec![trust_anchor],
|
||||
rejected_objects,
|
||||
Vec::new(),
|
||||
)
|
||||
}
|
||||
|
||||
fn sample_trust_anchor(name: &str) -> CirTrustAnchor {
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use crate::cir::model::{
|
||||
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirRejectedObject,
|
||||
CIR_VERSION_V4, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirRejectedObject,
|
||||
CirTrustAnchor,
|
||||
};
|
||||
use crate::data_model::common::DerReader;
|
||||
@ -32,9 +32,9 @@ pub fn decode_cir(der: &[u8]) -> Result<CanonicalInputRepresentation, CirDecodeE
|
||||
}
|
||||
|
||||
let version = seq.take_uint_u64().map_err(CirDecodeError::Parse)? as u32;
|
||||
if version != CIR_VERSION_V3 {
|
||||
if version != CIR_VERSION_V4 {
|
||||
return Err(CirDecodeError::UnexpectedVersion {
|
||||
expected: CIR_VERSION_V3,
|
||||
expected: CIR_VERSION_V4,
|
||||
actual: version,
|
||||
});
|
||||
}
|
||||
@ -42,14 +42,24 @@ pub fn decode_cir(der: &[u8]) -> Result<CanonicalInputRepresentation, CirDecodeE
|
||||
let validation_time =
|
||||
parse_generalized_time(seq.take_tag(0x18).map_err(CirDecodeError::Parse)?)?;
|
||||
|
||||
let objects_der = seq.take_tag(0x30).map_err(CirDecodeError::Parse)?;
|
||||
let mut objects_reader = DerReader::new(objects_der);
|
||||
let mut objects = Vec::new();
|
||||
while !objects_reader.is_empty() {
|
||||
let (_tag, full, _value) = objects_reader
|
||||
let fresh_objects_der = seq.take_tag(0x30).map_err(CirDecodeError::Parse)?;
|
||||
let mut fresh_objects_reader = DerReader::new(fresh_objects_der);
|
||||
let mut fresh_validated_objects = Vec::new();
|
||||
while !fresh_objects_reader.is_empty() {
|
||||
let (_tag, full, _value) = fresh_objects_reader
|
||||
.take_any_full()
|
||||
.map_err(CirDecodeError::Parse)?;
|
||||
objects.push(decode_object(full)?);
|
||||
fresh_validated_objects.push(decode_object(full)?);
|
||||
}
|
||||
|
||||
let cached_objects_der = seq.take_tag(0x30).map_err(CirDecodeError::Parse)?;
|
||||
let mut cached_objects_reader = DerReader::new(cached_objects_der);
|
||||
let mut cached_validated_objects = Vec::new();
|
||||
while !cached_objects_reader.is_empty() {
|
||||
let (_tag, full, _value) = cached_objects_reader
|
||||
.take_any_full()
|
||||
.map_err(CirDecodeError::Parse)?;
|
||||
cached_validated_objects.push(decode_object(full)?);
|
||||
}
|
||||
|
||||
let trust_anchors_der = seq.take_tag(0x30).map_err(CirDecodeError::Parse)?;
|
||||
@ -62,19 +72,49 @@ pub fn decode_cir(der: &[u8]) -> Result<CanonicalInputRepresentation, CirDecodeE
|
||||
trust_anchors.push(decode_trust_anchor(full)?);
|
||||
}
|
||||
|
||||
let object_list_sha256 = seq
|
||||
.take_octet_string()
|
||||
.map_err(CirDecodeError::Parse)?
|
||||
.to_vec();
|
||||
let fresh_object_list_sha256 = seq
|
||||
.take_octet_string()
|
||||
.map_err(CirDecodeError::Parse)?
|
||||
.to_vec();
|
||||
let cached_object_list_sha256 = seq
|
||||
.take_octet_string()
|
||||
.map_err(CirDecodeError::Parse)?
|
||||
.to_vec();
|
||||
let reject_list_sha256 = seq
|
||||
.take_octet_string()
|
||||
.map_err(CirDecodeError::Parse)?
|
||||
.to_vec();
|
||||
let fresh_reject_list_sha256 = seq
|
||||
.take_octet_string()
|
||||
.map_err(CirDecodeError::Parse)?
|
||||
.to_vec();
|
||||
let cached_reject_list_sha256 = seq
|
||||
.take_octet_string()
|
||||
.map_err(CirDecodeError::Parse)?
|
||||
.to_vec();
|
||||
|
||||
let rejected_der = seq.take_tag(0x30).map_err(CirDecodeError::Parse)?;
|
||||
let mut rejected_reader = DerReader::new(rejected_der);
|
||||
let mut rejected_objects = Vec::new();
|
||||
while !rejected_reader.is_empty() {
|
||||
let (_tag, full, _value) = rejected_reader
|
||||
let fresh_rejected_der = seq.take_tag(0x30).map_err(CirDecodeError::Parse)?;
|
||||
let mut fresh_rejected_reader = DerReader::new(fresh_rejected_der);
|
||||
let mut fresh_rejected_objects = Vec::new();
|
||||
while !fresh_rejected_reader.is_empty() {
|
||||
let (_tag, full, _value) = fresh_rejected_reader
|
||||
.take_any_full()
|
||||
.map_err(CirDecodeError::Parse)?;
|
||||
rejected_objects.push(decode_rejected_object(full)?);
|
||||
fresh_rejected_objects.push(decode_rejected_object(full)?);
|
||||
}
|
||||
|
||||
let cached_rejected_der = seq.take_tag(0x30).map_err(CirDecodeError::Parse)?;
|
||||
let mut cached_rejected_reader = DerReader::new(cached_rejected_der);
|
||||
let mut cached_rejected_objects = Vec::new();
|
||||
while !cached_rejected_reader.is_empty() {
|
||||
let (_tag, full, _value) = cached_rejected_reader
|
||||
.take_any_full()
|
||||
.map_err(CirDecodeError::Parse)?;
|
||||
cached_rejected_objects.push(decode_rejected_object(full)?);
|
||||
}
|
||||
|
||||
if !seq.is_empty() {
|
||||
@ -85,10 +125,17 @@ pub fn decode_cir(der: &[u8]) -> Result<CanonicalInputRepresentation, CirDecodeE
|
||||
version,
|
||||
hash_alg,
|
||||
validation_time,
|
||||
objects,
|
||||
fresh_validated_objects,
|
||||
cached_validated_objects,
|
||||
trust_anchors,
|
||||
object_list_sha256,
|
||||
fresh_object_list_sha256,
|
||||
cached_object_list_sha256,
|
||||
reject_list_sha256,
|
||||
rejected_objects,
|
||||
fresh_reject_list_sha256,
|
||||
cached_reject_list_sha256,
|
||||
fresh_rejected_objects,
|
||||
cached_rejected_objects,
|
||||
};
|
||||
cir.validate().map_err(CirDecodeError::Validate)?;
|
||||
Ok(cir)
|
||||
|
||||
@ -1,5 +1,5 @@
|
||||
use crate::cir::model::{
|
||||
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirRejectedObject,
|
||||
CIR_VERSION_V4, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirRejectedObject,
|
||||
CirTrustAnchor,
|
||||
};
|
||||
use crate::data_model::oid::OID_SHA256_RAW;
|
||||
@ -13,13 +13,19 @@ pub enum CirEncodeError {
|
||||
pub fn encode_cir(cir: &CanonicalInputRepresentation) -> Result<Vec<u8>, CirEncodeError> {
|
||||
cir.validate().map_err(CirEncodeError::Validate)?;
|
||||
Ok(encode_sequence(&[
|
||||
encode_integer_u32(cir.version),
|
||||
encode_integer_u32(CIR_VERSION_V4),
|
||||
encode_oid(match cir.hash_alg {
|
||||
CirHashAlgorithm::Sha256 => OID_SHA256_RAW,
|
||||
}),
|
||||
encode_generalized_time(cir.validation_time),
|
||||
encode_sequence(
|
||||
&cir.objects
|
||||
&cir.fresh_validated_objects
|
||||
.iter()
|
||||
.map(encode_object)
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
),
|
||||
encode_sequence(
|
||||
&cir.cached_validated_objects
|
||||
.iter()
|
||||
.map(encode_object)
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
@ -30,9 +36,20 @@ pub fn encode_cir(cir: &CanonicalInputRepresentation) -> Result<Vec<u8>, CirEnco
|
||||
.map(encode_trust_anchor)
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
),
|
||||
encode_octet_string(&cir.object_list_sha256),
|
||||
encode_octet_string(&cir.fresh_object_list_sha256),
|
||||
encode_octet_string(&cir.cached_object_list_sha256),
|
||||
encode_octet_string(&cir.reject_list_sha256),
|
||||
encode_octet_string(&cir.fresh_reject_list_sha256),
|
||||
encode_octet_string(&cir.cached_reject_list_sha256),
|
||||
encode_sequence(
|
||||
&cir.rejected_objects
|
||||
&cir.fresh_rejected_objects
|
||||
.iter()
|
||||
.map(encode_rejected_object)
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
),
|
||||
encode_sequence(
|
||||
&cir.cached_rejected_objects
|
||||
.iter()
|
||||
.map(encode_rejected_object)
|
||||
.collect::<Result<Vec<_>, _>>()?,
|
||||
@ -160,8 +177,3 @@ fn encode_len_into(len: usize, out: &mut Vec<u8>) {
|
||||
out.push(0x80 | (len_bytes.len() as u8));
|
||||
out.extend_from_slice(len_bytes);
|
||||
}
|
||||
|
||||
#[allow(dead_code)]
|
||||
const _: () = {
|
||||
let _ = CIR_VERSION_V3;
|
||||
};
|
||||
|
||||
@ -2,11 +2,10 @@ use std::collections::BTreeMap;
|
||||
use std::collections::BTreeSet;
|
||||
use std::path::Path;
|
||||
|
||||
use crate::audit::{AuditObjectResult, PublicationPointAudit};
|
||||
use crate::audit::{AuditObjectResult, ObjectAuditEntry, PublicationPointAudit};
|
||||
use crate::cir::encode::{CirEncodeError, encode_cir};
|
||||
use crate::cir::model::{
|
||||
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirRejectedObject,
|
||||
CirTrustAnchor, compute_reject_list_sha256,
|
||||
CanonicalInputRepresentation, CirObject, CirRejectedObject, CirTrustAnchor,
|
||||
};
|
||||
use crate::cir::static_pool::{
|
||||
CirStaticPoolError, CirStaticPoolExportSummary, export_hashes_from_store,
|
||||
@ -99,12 +98,45 @@ fn insert_consumed_object_hash(
|
||||
Ok(())
|
||||
}
|
||||
|
||||
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
|
||||
enum CirObjectSection {
|
||||
Fresh,
|
||||
Cached,
|
||||
}
|
||||
|
||||
fn publication_point_uses_explicit_cir_sections(pp: &PublicationPointAudit) -> bool {
|
||||
!pp.cir_fresh_objects.is_empty() || !pp.cir_cached_objects.is_empty()
|
||||
}
|
||||
|
||||
fn publication_point_is_cached_fallback(pp: &PublicationPointAudit) -> bool {
|
||||
pp.source == "vcir_current_instance" || pp.repo_terminal_state == "fallback_current_instance"
|
||||
}
|
||||
|
||||
fn publication_point_cir_entries<'a>(
|
||||
pp: &'a PublicationPointAudit,
|
||||
section: CirObjectSection,
|
||||
) -> &'a [ObjectAuditEntry] {
|
||||
if publication_point_uses_explicit_cir_sections(pp) {
|
||||
return match section {
|
||||
CirObjectSection::Fresh => &pp.cir_fresh_objects,
|
||||
CirObjectSection::Cached => &pp.cir_cached_objects,
|
||||
};
|
||||
}
|
||||
|
||||
match (section, publication_point_is_cached_fallback(pp)) {
|
||||
(CirObjectSection::Fresh, false) => &pp.objects,
|
||||
(CirObjectSection::Cached, true) => &pp.objects,
|
||||
_ => &[],
|
||||
}
|
||||
}
|
||||
|
||||
fn collect_cir_objects_from_validation_audit(
|
||||
publication_points: &[PublicationPointAudit],
|
||||
section: CirObjectSection,
|
||||
) -> Result<BTreeMap<String, String>, CirExportError> {
|
||||
let mut objects = BTreeMap::new();
|
||||
for pp in publication_points {
|
||||
for obj in &pp.objects {
|
||||
for obj in publication_point_cir_entries(pp, section) {
|
||||
if !matches!(obj.result, AuditObjectResult::Ok | AuditObjectResult::Error) {
|
||||
continue;
|
||||
}
|
||||
@ -114,6 +146,35 @@ fn collect_cir_objects_from_validation_audit(
|
||||
Ok(objects)
|
||||
}
|
||||
|
||||
fn collect_rejected_objects_from_validation_audit(
|
||||
publication_points: &[PublicationPointAudit],
|
||||
section: CirObjectSection,
|
||||
) -> Vec<CirRejectedObject> {
|
||||
let mut rejected_objects = publication_points
|
||||
.iter()
|
||||
.flat_map(|pp| publication_point_cir_entries(pp, section).iter())
|
||||
.filter(|item| item.result == AuditObjectResult::Error)
|
||||
.filter(|item| item.rsync_uri.starts_with("rsync://"))
|
||||
.map(|item| CirRejectedObject {
|
||||
object_uri: item.rsync_uri.clone(),
|
||||
reason: item.detail.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
rejected_objects.sort_by(|a, b| a.object_uri.cmp(&b.object_uri));
|
||||
rejected_objects.dedup_by(|a, b| a.object_uri == b.object_uri);
|
||||
rejected_objects
|
||||
}
|
||||
|
||||
fn cir_objects_from_hash_map(objects: BTreeMap<String, String>) -> Vec<CirObject> {
|
||||
objects
|
||||
.into_iter()
|
||||
.map(|(rsync_uri, sha256_hex)| CirObject {
|
||||
rsync_uri,
|
||||
sha256: hex::decode(sha256_hex).expect("validated hex"),
|
||||
})
|
||||
.collect()
|
||||
}
|
||||
|
||||
fn canonical_ta_rsync_uri(trust_anchor: &TrustAnchor) -> Result<String, CirExportError> {
|
||||
if let Some(uri) = &trust_anchor.resolved_ta_uri
|
||||
&& uri.scheme() == "rsync"
|
||||
@ -162,7 +223,10 @@ pub fn build_cir_from_run_multi(
|
||||
}
|
||||
}
|
||||
|
||||
let objects = collect_cir_objects_from_validation_audit(publication_points)?;
|
||||
let fresh_objects =
|
||||
collect_cir_objects_from_validation_audit(publication_points, CirObjectSection::Fresh)?;
|
||||
let cached_objects =
|
||||
collect_cir_objects_from_validation_audit(publication_points, CirObjectSection::Cached)?;
|
||||
|
||||
let mut trust_anchors = Vec::with_capacity(tal_bindings.len());
|
||||
for binding in tal_bindings {
|
||||
@ -178,41 +242,21 @@ pub fn build_cir_from_run_multi(
|
||||
}
|
||||
trust_anchors.sort_by(|a, b| a.ta_rsync_uri.cmp(&b.ta_rsync_uri));
|
||||
|
||||
let cir = CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: validation_time.to_offset(time::UtcOffset::UTC),
|
||||
objects: objects
|
||||
.into_iter()
|
||||
.map(|(rsync_uri, sha256_hex)| CirObject {
|
||||
rsync_uri,
|
||||
sha256: hex::decode(sha256_hex).expect("validated hex"),
|
||||
})
|
||||
.collect(),
|
||||
trust_anchors,
|
||||
reject_list_sha256: Vec::new(),
|
||||
rejected_objects: Vec::new(),
|
||||
};
|
||||
let mut rejected_objects = publication_points
|
||||
.iter()
|
||||
.flat_map(|pp| pp.objects.iter())
|
||||
.filter(|item| item.result == AuditObjectResult::Error)
|
||||
.filter(|item| item.rsync_uri.starts_with("rsync://"))
|
||||
.map(|item| CirRejectedObject {
|
||||
object_uri: item.rsync_uri.clone(),
|
||||
reason: item.detail.clone(),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
rejected_objects.sort_by(|a, b| a.object_uri.cmp(&b.object_uri));
|
||||
rejected_objects.dedup_by(|a, b| a.object_uri == b.object_uri);
|
||||
let fresh_rejected_objects =
|
||||
collect_rejected_objects_from_validation_audit(publication_points, CirObjectSection::Fresh);
|
||||
let cached_rejected_objects = collect_rejected_objects_from_validation_audit(
|
||||
publication_points,
|
||||
CirObjectSection::Cached,
|
||||
);
|
||||
|
||||
let cir = CanonicalInputRepresentation {
|
||||
reject_list_sha256: compute_reject_list_sha256(
|
||||
rejected_objects.iter().map(|item| item.object_uri.as_str()),
|
||||
),
|
||||
rejected_objects,
|
||||
..cir
|
||||
};
|
||||
let cir = CanonicalInputRepresentation::new_v4(
|
||||
validation_time,
|
||||
cir_objects_from_hash_map(fresh_objects),
|
||||
cir_objects_from_hash_map(cached_objects),
|
||||
trust_anchors,
|
||||
fresh_rejected_objects,
|
||||
cached_rejected_objects,
|
||||
);
|
||||
cir.validate().map_err(CirExportError::Validate)?;
|
||||
Ok(cir)
|
||||
}
|
||||
@ -239,8 +283,7 @@ pub fn export_cir_static_pool(
|
||||
) -> Result<CirStaticPoolExportSummary, CirExportError> {
|
||||
let _ = trust_anchors;
|
||||
let hashes = cir
|
||||
.objects
|
||||
.iter()
|
||||
.validated_objects()
|
||||
.map(|item| hex::encode(&item.sha256))
|
||||
.collect::<Vec<_>>();
|
||||
export_hashes_from_store(store, static_root, capture_date_utc, &hashes).map_err(Into::into)
|
||||
@ -254,8 +297,7 @@ pub fn export_cir_raw_store(
|
||||
) -> Result<CirRawStoreExportSummary, CirExportError> {
|
||||
let _ = trust_anchors;
|
||||
let unique: BTreeSet<String> = cir
|
||||
.objects
|
||||
.iter()
|
||||
.validated_objects()
|
||||
.map(|item| hex::encode(&item.sha256))
|
||||
.collect();
|
||||
|
||||
@ -337,7 +379,7 @@ pub fn export_cir_from_run_multi(
|
||||
let write_cir_ms = started.elapsed().as_millis() as u64;
|
||||
|
||||
Ok(CirExportSummary {
|
||||
object_count: cir.objects.len(),
|
||||
object_count: cir.validated_object_count(),
|
||||
trust_anchor_count: cir.trust_anchors.len(),
|
||||
timing: CirExportTiming {
|
||||
build_cir_ms,
|
||||
@ -437,19 +479,19 @@ mod tests {
|
||||
&publication_points,
|
||||
)
|
||||
.expect("build cir");
|
||||
assert_eq!(cir.version, CIR_VERSION_V3);
|
||||
assert_eq!(cir.version, crate::cir::model::CIR_VERSION_V4);
|
||||
assert_eq!(cir.trust_anchors.len(), 1);
|
||||
assert_eq!(
|
||||
cir.trust_anchors[0].tal_uri,
|
||||
"https://example.test/root.tal"
|
||||
);
|
||||
assert!(
|
||||
cir.objects
|
||||
cir.fresh_validated_objects
|
||||
.iter()
|
||||
.any(|item| item.rsync_uri == "rsync://example.test/repo/a.cer")
|
||||
);
|
||||
assert!(
|
||||
!cir.objects
|
||||
!cir.fresh_validated_objects
|
||||
.iter()
|
||||
.any(|item| item.rsync_uri == cir.trust_anchors[0].ta_rsync_uri)
|
||||
);
|
||||
@ -610,12 +652,12 @@ mod tests {
|
||||
.expect("build cir");
|
||||
|
||||
assert!(
|
||||
cir.objects
|
||||
cir.cached_validated_objects
|
||||
.iter()
|
||||
.any(|item| item.rsync_uri == "rsync://example.test/repo/fallback.mft")
|
||||
);
|
||||
assert!(
|
||||
cir.objects
|
||||
cir.cached_validated_objects
|
||||
.iter()
|
||||
.any(|item| item.rsync_uri == "rsync://example.test/repo/fallback.roa")
|
||||
);
|
||||
@ -664,19 +706,19 @@ mod tests {
|
||||
|
||||
assert_eq!(cir.trust_anchors.len(), 2);
|
||||
assert!(
|
||||
cir.objects
|
||||
cir.fresh_validated_objects
|
||||
.iter()
|
||||
.any(|item| item.rsync_uri == "rsync://example.test/repo/consumed.roa")
|
||||
);
|
||||
assert!(
|
||||
!cir.objects
|
||||
!cir.fresh_validated_objects
|
||||
.iter()
|
||||
.any(|item| item.rsync_uri == "rsync://example.test/repo/superfluous.roa"),
|
||||
"current repo objects must not be included unless validation consumed them",
|
||||
);
|
||||
for trust_anchor in &cir.trust_anchors {
|
||||
assert!(
|
||||
!cir.objects
|
||||
!cir.fresh_validated_objects
|
||||
.iter()
|
||||
.any(|item| item.rsync_uri == trust_anchor.ta_rsync_uri),
|
||||
"trust anchor rsync objects must not be included in CIR.objects",
|
||||
@ -765,33 +807,33 @@ mod tests {
|
||||
)
|
||||
.expect("build cir");
|
||||
|
||||
assert_eq!(cir.rejected_objects.len(), 2);
|
||||
assert_eq!(cir.rejected_object_count(), 2);
|
||||
assert_eq!(
|
||||
cir.rejected_objects[0].object_uri,
|
||||
cir.fresh_rejected_objects[0].object_uri,
|
||||
"rsync://example.test/repo/a.roa"
|
||||
);
|
||||
assert_eq!(
|
||||
cir.rejected_objects[0].reason.as_deref(),
|
||||
cir.fresh_rejected_objects[0].reason.as_deref(),
|
||||
Some("invalid roa")
|
||||
);
|
||||
assert_eq!(
|
||||
cir.rejected_objects[1].object_uri,
|
||||
cir.fresh_rejected_objects[1].object_uri,
|
||||
"rsync://example.test/repo/c.roa"
|
||||
);
|
||||
assert!(
|
||||
cir.objects
|
||||
cir.fresh_validated_objects
|
||||
.iter()
|
||||
.any(|item| item.rsync_uri == "rsync://example.test/repo/a.roa"),
|
||||
"rejected audit objects were still consumed as validation input",
|
||||
);
|
||||
assert!(
|
||||
cir.objects
|
||||
cir.fresh_validated_objects
|
||||
.iter()
|
||||
.any(|item| item.rsync_uri == "rsync://example.test/repo/c.roa"),
|
||||
"rejected audit objects were still consumed as validation input",
|
||||
);
|
||||
assert!(
|
||||
!cir.objects
|
||||
!cir.fresh_validated_objects
|
||||
.iter()
|
||||
.any(|item| item.rsync_uri == "rsync://example.test/repo/b.asa"),
|
||||
"skipped audit objects are not considered consumed input",
|
||||
@ -829,20 +871,20 @@ mod tests {
|
||||
.expect("build cir");
|
||||
|
||||
assert!(
|
||||
cir.objects
|
||||
cir.fresh_validated_objects
|
||||
.iter()
|
||||
.any(|item| item.rsync_uri == manifest_uri),
|
||||
"manifest rejected because issuer CRL is expired was still read as validation input",
|
||||
);
|
||||
assert_eq!(cir.rejected_objects.len(), 1);
|
||||
assert_eq!(cir.rejected_objects[0].object_uri, manifest_uri);
|
||||
assert_eq!(cir.rejected_object_count(), 1);
|
||||
assert_eq!(cir.fresh_rejected_objects[0].object_uri, manifest_uri);
|
||||
assert_eq!(
|
||||
cir.rejected_objects[0].reason.as_deref(),
|
||||
cir.fresh_rejected_objects[0].reason.as_deref(),
|
||||
Some(reject_reason)
|
||||
);
|
||||
assert_eq!(
|
||||
cir.reject_list_sha256,
|
||||
compute_reject_list_sha256([manifest_uri].into_iter())
|
||||
crate::cir::model::compute_reject_list_sha256([manifest_uri].into_iter())
|
||||
);
|
||||
}
|
||||
|
||||
@ -894,21 +936,25 @@ mod tests {
|
||||
.expect("build cir");
|
||||
|
||||
assert!(
|
||||
cir.objects
|
||||
cir.fresh_validated_objects
|
||||
.iter()
|
||||
.any(|item| item.rsync_uri == manifest_uri),
|
||||
"rejected manifest is still a current-run validation input",
|
||||
);
|
||||
assert!(
|
||||
!cir.objects.iter().any(|item| item.rsync_uri == roa_uri),
|
||||
!cir.fresh_validated_objects
|
||||
.iter()
|
||||
.any(|item| item.rsync_uri == roa_uri),
|
||||
"ROA listed by a rejected manifest must not enter CIR objects",
|
||||
);
|
||||
assert!(
|
||||
!cir.objects.iter().any(|item| item.rsync_uri == crl_uri),
|
||||
!cir.fresh_validated_objects
|
||||
.iter()
|
||||
.any(|item| item.rsync_uri == crl_uri),
|
||||
"CRL listed by a rejected manifest must not enter CIR objects",
|
||||
);
|
||||
assert_eq!(cir.rejected_objects.len(), 1);
|
||||
assert_eq!(cir.rejected_objects[0].object_uri, manifest_uri);
|
||||
assert_eq!(cir.rejected_object_count(), 1);
|
||||
assert_eq!(cir.fresh_rejected_objects[0].object_uri, manifest_uri);
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -953,8 +999,8 @@ mod tests {
|
||||
|
||||
assert_eq!(cir_a.reject_list_sha256, cir_b.reject_list_sha256);
|
||||
assert_ne!(
|
||||
cir_a.rejected_objects[0].reason,
|
||||
cir_b.rejected_objects[0].reason
|
||||
cir_a.fresh_rejected_objects[0].reason,
|
||||
cir_b.fresh_rejected_objects[0].reason
|
||||
);
|
||||
}
|
||||
|
||||
@ -1083,7 +1129,7 @@ mod tests {
|
||||
assert_eq!(summary.reused_entries, 0);
|
||||
|
||||
let mut cir_missing_object = cir_only_tas.clone();
|
||||
cir_missing_object.objects.push(CirObject {
|
||||
cir_missing_object.fresh_validated_objects.push(CirObject {
|
||||
rsync_uri: "rsync://example.test/repo/missing.roa".to_string(),
|
||||
sha256: vec![0x44; 32],
|
||||
});
|
||||
|
||||
@ -86,7 +86,7 @@ pub fn materialize_cir(
|
||||
let mut linked_files = 0usize;
|
||||
let mut copied_files = 0usize;
|
||||
|
||||
for object in &cir.objects {
|
||||
for object in cir.validated_objects() {
|
||||
let sha256_hex = hex::encode(&object.sha256);
|
||||
let source = resolve_static_pool_file(static_root, &sha256_hex)?;
|
||||
let relative = mirror_relative_path_for_rsync_uri(&object.rsync_uri)?;
|
||||
@ -140,7 +140,7 @@ pub fn materialize_cir(
|
||||
}
|
||||
|
||||
Ok(CirMaterializeSummary {
|
||||
object_count: cir.objects.len(),
|
||||
object_count: cir.validated_object_count(),
|
||||
trust_anchor_count: cir.trust_anchors.len(),
|
||||
materialized_file_count: expected.len(),
|
||||
linked_files,
|
||||
@ -165,7 +165,7 @@ pub fn materialize_cir_from_raw_store(
|
||||
})?;
|
||||
|
||||
let mut copied_files = 0usize;
|
||||
for object in &cir.objects {
|
||||
for object in cir.validated_objects() {
|
||||
let sha256_hex = hex::encode(&object.sha256);
|
||||
let bytes = raw_store
|
||||
.get_blob_bytes(&sha256_hex)
|
||||
@ -206,7 +206,7 @@ pub fn materialize_cir_from_raw_store(
|
||||
}
|
||||
|
||||
Ok(CirMaterializeSummary {
|
||||
object_count: cir.objects.len(),
|
||||
object_count: cir.validated_object_count(),
|
||||
trust_anchor_count: cir.trust_anchors.len(),
|
||||
materialized_file_count: expected.len(),
|
||||
linked_files: 0,
|
||||
@ -232,7 +232,7 @@ pub fn materialize_cir_from_repo_bytes(
|
||||
})?;
|
||||
|
||||
let mut copied_files = 0usize;
|
||||
for object in &cir.objects {
|
||||
for object in cir.validated_objects() {
|
||||
let sha256_hex = hex::encode(&object.sha256);
|
||||
let bytes = repo_bytes
|
||||
.get_blob_bytes(&sha256_hex)
|
||||
@ -273,7 +273,7 @@ pub fn materialize_cir_from_repo_bytes(
|
||||
}
|
||||
|
||||
Ok(CirMaterializeSummary {
|
||||
object_count: cir.objects.len(),
|
||||
object_count: cir.validated_object_count(),
|
||||
trust_anchor_count: cir.trust_anchors.len(),
|
||||
materialized_file_count: expected.len(),
|
||||
linked_files: 0,
|
||||
@ -325,8 +325,7 @@ fn write_bytes_to_mirror_uri(
|
||||
}
|
||||
|
||||
fn expected_materialized_uris(cir: &CanonicalInputRepresentation) -> BTreeSet<String> {
|
||||
cir.objects
|
||||
.iter()
|
||||
cir.validated_objects()
|
||||
.map(|item| item.rsync_uri.clone())
|
||||
.chain(
|
||||
cir.trust_anchors
|
||||
@ -435,10 +434,7 @@ mod tests {
|
||||
resolve_static_pool_file,
|
||||
};
|
||||
use crate::blob_store::{ExternalRawStoreDb, ExternalRepoBytesDb};
|
||||
use crate::cir::model::{
|
||||
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirObject,
|
||||
CirRejectedObject, CirTrustAnchor, compute_reject_list_sha256, sha256,
|
||||
};
|
||||
use crate::cir::model::{CanonicalInputRepresentation, CirObject, CirTrustAnchor, sha256};
|
||||
use sha2::Digest;
|
||||
use std::path::{Path, PathBuf};
|
||||
|
||||
@ -463,15 +459,9 @@ mod tests {
|
||||
}
|
||||
|
||||
fn sample_cir() -> CanonicalInputRepresentation {
|
||||
let rejected_objects = vec![CirRejectedObject {
|
||||
object_uri: "rsync://example.net/repo/rejected-a.roa".to_string(),
|
||||
reason: Some("invalid roa".to_string()),
|
||||
}];
|
||||
CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: sample_time(),
|
||||
objects: vec![
|
||||
CanonicalInputRepresentation::new_v4(
|
||||
sample_time(),
|
||||
vec![
|
||||
CirObject {
|
||||
rsync_uri: "rsync://example.net/repo/a.cer".to_string(),
|
||||
sha256: hex::decode(
|
||||
@ -487,20 +477,17 @@ mod tests {
|
||||
.unwrap(),
|
||||
},
|
||||
],
|
||||
trust_anchors: vec![sample_trust_anchor()],
|
||||
reject_list_sha256: compute_reject_list_sha256(
|
||||
rejected_objects.iter().map(|item| item.object_uri.as_str()),
|
||||
),
|
||||
rejected_objects,
|
||||
}
|
||||
Vec::new(),
|
||||
vec![sample_trust_anchor()],
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
)
|
||||
}
|
||||
|
||||
fn cir_with_real_hashes(a: &[u8], b: &[u8]) -> CanonicalInputRepresentation {
|
||||
CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: sample_time(),
|
||||
objects: vec![
|
||||
CanonicalInputRepresentation::new_v4(
|
||||
sample_time(),
|
||||
vec![
|
||||
CirObject {
|
||||
rsync_uri: "rsync://example.net/repo/a.cer".to_string(),
|
||||
sha256: sha2::Sha256::digest(a).to_vec(),
|
||||
@ -510,10 +497,11 @@ mod tests {
|
||||
sha256: sha2::Sha256::digest(b).to_vec(),
|
||||
},
|
||||
],
|
||||
trust_anchors: vec![sample_trust_anchor()],
|
||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||
rejected_objects: Vec::new(),
|
||||
}
|
||||
Vec::new(),
|
||||
vec![sample_trust_anchor()],
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
)
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -661,31 +649,14 @@ mod tests {
|
||||
let mirror_root = td.path().join("mirror");
|
||||
let a = b"a".to_vec();
|
||||
let b = b"b".to_vec();
|
||||
let cir = CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: sample_time(),
|
||||
objects: vec![
|
||||
CirObject {
|
||||
rsync_uri: "rsync://example.net/repo/a.cer".to_string(),
|
||||
sha256: sha2::Sha256::digest(&a).to_vec(),
|
||||
},
|
||||
CirObject {
|
||||
rsync_uri: "rsync://example.net/repo/nested/b.roa".to_string(),
|
||||
sha256: sha2::Sha256::digest(&b).to_vec(),
|
||||
},
|
||||
],
|
||||
trust_anchors: vec![sample_trust_anchor()],
|
||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||
rejected_objects: Vec::new(),
|
||||
};
|
||||
let cir = cir_with_real_hashes(&a, &b);
|
||||
|
||||
{
|
||||
let raw_store = ExternalRawStoreDb::open(&raw_store_path).unwrap();
|
||||
raw_store
|
||||
.put_blob_bytes_batch(&[
|
||||
(hex::encode(&cir.objects[0].sha256), a),
|
||||
(hex::encode(&cir.objects[1].sha256), b),
|
||||
(hex::encode(&cir.fresh_validated_objects[0].sha256), a),
|
||||
(hex::encode(&cir.fresh_validated_objects[1].sha256), b),
|
||||
])
|
||||
.unwrap();
|
||||
}
|
||||
@ -720,7 +691,10 @@ mod tests {
|
||||
{
|
||||
let raw_store = ExternalRawStoreDb::open(&raw_store_path).unwrap();
|
||||
raw_store
|
||||
.put_blob_bytes_batch(&[(hex::encode(&cir.objects[0].sha256), b"a".to_vec())])
|
||||
.put_blob_bytes_batch(&[(
|
||||
hex::encode(&cir.fresh_validated_objects[0].sha256),
|
||||
b"a".to_vec(),
|
||||
)])
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
@ -742,8 +716,14 @@ mod tests {
|
||||
let raw_store = ExternalRawStoreDb::open(&raw_store_path).unwrap();
|
||||
raw_store
|
||||
.put_blob_bytes_batch(&[
|
||||
(hex::encode(&cir.objects[0].sha256), b"a".to_vec()),
|
||||
(hex::encode(&cir.objects[1].sha256), b"b".to_vec()),
|
||||
(
|
||||
hex::encode(&cir.fresh_validated_objects[0].sha256),
|
||||
b"a".to_vec(),
|
||||
),
|
||||
(
|
||||
hex::encode(&cir.fresh_validated_objects[1].sha256),
|
||||
b"b".to_vec(),
|
||||
),
|
||||
])
|
||||
.unwrap();
|
||||
}
|
||||
@ -767,8 +747,14 @@ mod tests {
|
||||
let raw_store = ExternalRawStoreDb::open(&raw_store_path).unwrap();
|
||||
raw_store
|
||||
.put_blob_bytes_batch(&[
|
||||
(hex::encode(&cir.objects[0].sha256), a.clone()),
|
||||
(hex::encode(&cir.objects[1].sha256), b.clone()),
|
||||
(
|
||||
hex::encode(&cir.fresh_validated_objects[0].sha256),
|
||||
a.clone(),
|
||||
),
|
||||
(
|
||||
hex::encode(&cir.fresh_validated_objects[1].sha256),
|
||||
b.clone(),
|
||||
),
|
||||
])
|
||||
.unwrap();
|
||||
}
|
||||
@ -787,21 +773,20 @@ mod tests {
|
||||
let td = tempfile::tempdir().unwrap();
|
||||
let raw_store_path = td.path().join("raw-store.db");
|
||||
let mirror_root = td.path().join("mirror");
|
||||
let cir = CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: sample_time(),
|
||||
objects: vec![CirObject {
|
||||
let cir = CanonicalInputRepresentation::new_v4(
|
||||
sample_time(),
|
||||
vec![CirObject {
|
||||
rsync_uri: "rsync://example.net/repo/a.cer".to_string(),
|
||||
sha256: hex::decode(
|
||||
"1111111111111111111111111111111111111111111111111111111111111111",
|
||||
)
|
||||
.unwrap(),
|
||||
}],
|
||||
trust_anchors: vec![sample_trust_anchor()],
|
||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||
rejected_objects: Vec::new(),
|
||||
};
|
||||
Vec::new(),
|
||||
vec![sample_trust_anchor()],
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
);
|
||||
{
|
||||
let raw_store = ExternalRawStoreDb::open(&raw_store_path).unwrap();
|
||||
raw_store
|
||||
@ -841,31 +826,14 @@ mod tests {
|
||||
let mirror_root = td.path().join("mirror");
|
||||
let a = b"a".to_vec();
|
||||
let b = b"b".to_vec();
|
||||
let cir = CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: sample_time(),
|
||||
objects: vec![
|
||||
CirObject {
|
||||
rsync_uri: "rsync://example.net/repo/a.cer".to_string(),
|
||||
sha256: sha2::Sha256::digest(&a).to_vec(),
|
||||
},
|
||||
CirObject {
|
||||
rsync_uri: "rsync://example.net/repo/nested/b.roa".to_string(),
|
||||
sha256: sha2::Sha256::digest(&b).to_vec(),
|
||||
},
|
||||
],
|
||||
trust_anchors: vec![sample_trust_anchor()],
|
||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||
rejected_objects: Vec::new(),
|
||||
};
|
||||
let cir = cir_with_real_hashes(&a, &b);
|
||||
|
||||
{
|
||||
let repo_bytes = ExternalRepoBytesDb::open(&repo_bytes_db).unwrap();
|
||||
repo_bytes
|
||||
.put_blob_bytes_batch(&[
|
||||
(hex::encode(&cir.objects[0].sha256), a),
|
||||
(hex::encode(&cir.objects[1].sha256), b),
|
||||
(hex::encode(&cir.fresh_validated_objects[0].sha256), a),
|
||||
(hex::encode(&cir.fresh_validated_objects[1].sha256), b),
|
||||
])
|
||||
.unwrap();
|
||||
}
|
||||
|
||||
196
src/cir/mod.rs
196
src/cir/mod.rs
@ -22,8 +22,8 @@ pub use materialize::{
|
||||
materialize_cir_from_repo_bytes, mirror_relative_path_for_rsync_uri, resolve_static_pool_file,
|
||||
};
|
||||
pub use model::{
|
||||
CIR_VERSION_V1, CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirObject,
|
||||
CirRejectedObject, CirTrustAnchor, compute_reject_list_sha256, sha256,
|
||||
CIR_VERSION_V1, CIR_VERSION_V3, CIR_VERSION_V4, CanonicalInputRepresentation, CirHashAlgorithm,
|
||||
CirObject, CirRejectedObject, CirTrustAnchor, compute_reject_list_sha256, sha256,
|
||||
};
|
||||
pub use sequence::{CirSequenceManifest, CirSequenceStep, CirSequenceStepKind};
|
||||
#[cfg(feature = "full")]
|
||||
@ -36,8 +36,8 @@ pub use static_pool::{
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::{
|
||||
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirObject,
|
||||
CirRejectedObject, CirTrustAnchor, compute_reject_list_sha256, decode_cir, encode_cir,
|
||||
CIR_VERSION_V4, CanonicalInputRepresentation, CirObject, CirRejectedObject, CirTrustAnchor,
|
||||
decode_cir, encode_cir,
|
||||
};
|
||||
|
||||
fn sample_time() -> time::OffsetDateTime {
|
||||
@ -69,11 +69,9 @@ mod tests {
|
||||
reason: None,
|
||||
},
|
||||
];
|
||||
CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: sample_time(),
|
||||
objects: vec![
|
||||
CanonicalInputRepresentation::new_v4(
|
||||
sample_time(),
|
||||
vec![
|
||||
CirObject {
|
||||
rsync_uri: "rsync://example.net/repo/a.cer".to_string(),
|
||||
sha256: vec![0x11; 32],
|
||||
@ -82,17 +80,24 @@ mod tests {
|
||||
rsync_uri: "rsync://example.net/repo/b.roa".to_string(),
|
||||
sha256: vec![0x22; 32],
|
||||
},
|
||||
CirObject {
|
||||
rsync_uri: "rsync://example.net/repo/rejected-a.roa".to_string(),
|
||||
sha256: vec![0x33; 32],
|
||||
},
|
||||
CirObject {
|
||||
rsync_uri: "rsync://example.net/repo/rejected-b.asa".to_string(),
|
||||
sha256: vec![0x44; 32],
|
||||
},
|
||||
],
|
||||
trust_anchors: vec![sample_trust_anchor(
|
||||
Vec::new(),
|
||||
vec![sample_trust_anchor(
|
||||
"rsync://example.net/repo/ta.cer",
|
||||
"https://tal.example.net/root.tal",
|
||||
b"ta-der",
|
||||
)],
|
||||
reject_list_sha256: compute_reject_list_sha256(
|
||||
rejected_objects.iter().map(|item| item.object_uri.as_str()),
|
||||
),
|
||||
rejected_objects,
|
||||
}
|
||||
Vec::new(),
|
||||
)
|
||||
}
|
||||
|
||||
fn test_encode_tlv(tag: u8, value: &[u8]) -> Vec<u8> {
|
||||
@ -125,19 +130,18 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn cir_roundtrip_minimal_succeeds() {
|
||||
let cir = CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: sample_time(),
|
||||
objects: Vec::new(),
|
||||
trust_anchors: vec![sample_trust_anchor(
|
||||
let cir = CanonicalInputRepresentation::new_v4(
|
||||
sample_time(),
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
vec![sample_trust_anchor(
|
||||
"rsync://example.net/repo/minimal-ta.cer",
|
||||
"https://tal.example.net/minimal.tal",
|
||||
b"minimal-ta-der",
|
||||
)],
|
||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||
rejected_objects: Vec::new(),
|
||||
};
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
);
|
||||
let der = encode_cir(&cir).expect("encode minimal cir");
|
||||
let decoded = decode_cir(&der).expect("decode minimal cir");
|
||||
assert_eq!(decoded, cir);
|
||||
@ -145,11 +149,9 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn cir_model_rejects_unsorted_duplicate_objects() {
|
||||
let cir = CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: sample_time(),
|
||||
objects: vec![
|
||||
let cir = CanonicalInputRepresentation::new_v4(
|
||||
sample_time(),
|
||||
vec![
|
||||
CirObject {
|
||||
rsync_uri: "rsync://example.net/repo/z.roa".to_string(),
|
||||
sha256: vec![0x11; 32],
|
||||
@ -159,26 +161,29 @@ mod tests {
|
||||
sha256: vec![0x22; 32],
|
||||
},
|
||||
],
|
||||
trust_anchors: vec![sample_trust_anchor(
|
||||
Vec::new(),
|
||||
vec![sample_trust_anchor(
|
||||
"rsync://example.net/repo/ta.cer",
|
||||
"https://tal.example.net/root.tal",
|
||||
b"ta-der",
|
||||
)],
|
||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||
rejected_objects: Vec::new(),
|
||||
};
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
);
|
||||
let err = encode_cir(&cir).expect_err("unsorted objects must fail");
|
||||
assert!(err.to_string().contains("CIR.objects"), "{err}");
|
||||
assert!(
|
||||
err.to_string().contains("CIR.freshValidatedObjects"),
|
||||
"{err}"
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn cir_model_rejects_duplicate_tals() {
|
||||
let cir = CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: sample_time(),
|
||||
objects: Vec::new(),
|
||||
trust_anchors: vec![
|
||||
let cir = CanonicalInputRepresentation::new_v4(
|
||||
sample_time(),
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
vec![
|
||||
sample_trust_anchor(
|
||||
"rsync://example.net/repo/ta.cer",
|
||||
"https://tal.example.net/root.tal",
|
||||
@ -190,9 +195,9 @@ mod tests {
|
||||
b"ta-der-b",
|
||||
),
|
||||
],
|
||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||
rejected_objects: Vec::new(),
|
||||
};
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
);
|
||||
let err = encode_cir(&cir).expect_err("duplicate trust_anchors must fail");
|
||||
assert!(err.to_string().contains("CIR.trustAnchors"), "{err}");
|
||||
}
|
||||
@ -202,10 +207,10 @@ mod tests {
|
||||
let mut der = encode_cir(&sample_cir()).expect("encode cir");
|
||||
let pos = der
|
||||
.windows(3)
|
||||
.position(|window| window == [0x02, 0x01, CIR_VERSION_V3 as u8])
|
||||
.position(|window| window == [0x02, 0x01, CIR_VERSION_V4 as u8])
|
||||
.or_else(|| {
|
||||
der.windows(3)
|
||||
.position(|window| window == [0x02, 0x01, CIR_VERSION_V3 as u8])
|
||||
.position(|window| window == [0x02, 0x01, CIR_VERSION_V4 as u8])
|
||||
})
|
||||
.expect("find version integer");
|
||||
der[pos + 2] = 2;
|
||||
@ -243,34 +248,32 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn cir_model_rejects_non_rsync_object_uri_and_empty_tals() {
|
||||
let bad_object = CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: sample_time(),
|
||||
objects: vec![CirObject {
|
||||
let bad_object = CanonicalInputRepresentation::new_v4(
|
||||
sample_time(),
|
||||
vec![CirObject {
|
||||
rsync_uri: "https://example.net/repo/a.roa".to_string(),
|
||||
sha256: vec![0x11; 32],
|
||||
}],
|
||||
trust_anchors: vec![sample_trust_anchor(
|
||||
Vec::new(),
|
||||
vec![sample_trust_anchor(
|
||||
"rsync://example.net/repo/ta.cer",
|
||||
"https://tal.example.net/root.tal",
|
||||
b"ta-der",
|
||||
)],
|
||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||
rejected_objects: Vec::new(),
|
||||
};
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
);
|
||||
let err = encode_cir(&bad_object).expect_err("non-rsync object uri must fail");
|
||||
assert!(err.to_string().contains("rsync://"), "{err}");
|
||||
|
||||
let no_tals = CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: sample_time(),
|
||||
objects: Vec::new(),
|
||||
trust_anchors: Vec::new(),
|
||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||
rejected_objects: Vec::new(),
|
||||
};
|
||||
let no_tals = CanonicalInputRepresentation::new_v4(
|
||||
sample_time(),
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
);
|
||||
let err = encode_cir(&no_tals).expect_err("empty trust_anchors must fail");
|
||||
assert!(
|
||||
err.to_string()
|
||||
@ -281,54 +284,55 @@ mod tests {
|
||||
|
||||
#[test]
|
||||
fn cir_model_rejects_non_utc_time_bad_hash_len_and_non_http_tal_uri() {
|
||||
let bad_time = CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: sample_time().to_offset(time::UtcOffset::from_hms(8, 0, 0).unwrap()),
|
||||
objects: Vec::new(),
|
||||
trust_anchors: vec![sample_trust_anchor(
|
||||
let bad_time = CanonicalInputRepresentation::new_v4(
|
||||
sample_time().to_offset(time::UtcOffset::from_hms(8, 0, 0).unwrap()),
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
vec![sample_trust_anchor(
|
||||
"rsync://example.net/repo/ta.cer",
|
||||
"https://tal.example.net/root.tal",
|
||||
b"ta-der",
|
||||
)],
|
||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||
rejected_objects: Vec::new(),
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
);
|
||||
let bad_time = CanonicalInputRepresentation {
|
||||
validation_time: sample_time().to_offset(time::UtcOffset::from_hms(8, 0, 0).unwrap()),
|
||||
..bad_time
|
||||
};
|
||||
let err = encode_cir(&bad_time).expect_err("non-utc validation time must fail");
|
||||
assert!(err.to_string().contains("UTC"), "{err}");
|
||||
|
||||
let bad_hash = CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: sample_time(),
|
||||
objects: vec![CirObject {
|
||||
let bad_hash = CanonicalInputRepresentation::new_v4(
|
||||
sample_time(),
|
||||
vec![CirObject {
|
||||
rsync_uri: "rsync://example.net/repo/a.roa".to_string(),
|
||||
sha256: vec![0x11; 31],
|
||||
}],
|
||||
trust_anchors: vec![sample_trust_anchor(
|
||||
Vec::new(),
|
||||
vec![sample_trust_anchor(
|
||||
"rsync://example.net/repo/ta.cer",
|
||||
"https://tal.example.net/root.tal",
|
||||
b"ta-der",
|
||||
)],
|
||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||
rejected_objects: Vec::new(),
|
||||
};
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
);
|
||||
let err = encode_cir(&bad_hash).expect_err("bad digest len must fail");
|
||||
assert!(err.to_string().contains("32 bytes"), "{err}");
|
||||
|
||||
let bad_tal_uri = CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: sample_time(),
|
||||
objects: Vec::new(),
|
||||
trust_anchors: vec![sample_trust_anchor(
|
||||
let bad_tal_uri = CanonicalInputRepresentation::new_v4(
|
||||
sample_time(),
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
vec![sample_trust_anchor(
|
||||
"rsync://example.net/repo/ta.cer",
|
||||
"ftp://tal.example.net/root.tal",
|
||||
b"ta-der",
|
||||
)],
|
||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||
rejected_objects: Vec::new(),
|
||||
};
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
);
|
||||
let err = encode_cir(&bad_tal_uri).expect_err("bad tal uri must fail");
|
||||
assert!(err.to_string().contains("http:// or https://"), "{err}");
|
||||
}
|
||||
@ -367,12 +371,19 @@ mod tests {
|
||||
let bad = test_encode_tlv(
|
||||
0x30,
|
||||
&[
|
||||
test_encode_tlv(0x02, &[CIR_VERSION_V3 as u8]),
|
||||
test_encode_tlv(0x02, &[CIR_VERSION_V4 as u8]),
|
||||
test_encode_tlv(0x06, crate::data_model::oid::OID_SHA256_RAW),
|
||||
test_encode_tlv(0x18, b"20260407123456Z"),
|
||||
test_encode_tlv(0x30, &object),
|
||||
test_encode_tlv(0x30, &[]),
|
||||
test_encode_tlv(0x30, &trust_anchor),
|
||||
test_encode_tlv(0x04, &[0x33; 32]),
|
||||
test_encode_tlv(0x04, &[0x33; 32]),
|
||||
test_encode_tlv(0x04, &[0x33; 32]),
|
||||
test_encode_tlv(0x04, &[0x33; 32]),
|
||||
test_encode_tlv(0x04, &[0x33; 32]),
|
||||
test_encode_tlv(0x04, &[0x33; 32]),
|
||||
test_encode_tlv(0x30, &[]),
|
||||
test_encode_tlv(0x30, &[]),
|
||||
]
|
||||
.concat(),
|
||||
@ -412,9 +423,12 @@ mod tests {
|
||||
#[test]
|
||||
fn cir_model_rejects_unsorted_rejected_objects_and_bad_digest() {
|
||||
let mut cir = sample_cir();
|
||||
cir.rejected_objects.swap(0, 1);
|
||||
cir.fresh_rejected_objects.swap(0, 1);
|
||||
let err = encode_cir(&cir).expect_err("unsorted rejected objects must fail");
|
||||
assert!(err.to_string().contains("CIR.rejectedObjects"), "{err}");
|
||||
assert!(
|
||||
err.to_string().contains("CIR.freshRejectedObjects"),
|
||||
"{err}"
|
||||
);
|
||||
|
||||
let mut cir = sample_cir();
|
||||
cir.reject_list_sha256 = vec![0x55; 32];
|
||||
|
||||
247
src/cir/model.rs
247
src/cir/model.rs
@ -3,6 +3,7 @@ use crate::data_model::oid::OID_SHA256;
|
||||
pub const CIR_VERSION_V1: u32 = 1;
|
||||
pub const CIR_VERSION_V2: u32 = 2;
|
||||
pub const CIR_VERSION_V3: u32 = 3;
|
||||
pub const CIR_VERSION_V4: u32 = 4;
|
||||
pub const DIGEST_LEN_SHA256: usize = 32;
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
@ -23,17 +24,81 @@ pub struct CanonicalInputRepresentation {
|
||||
pub version: u32,
|
||||
pub hash_alg: CirHashAlgorithm,
|
||||
pub validation_time: time::OffsetDateTime,
|
||||
pub objects: Vec<CirObject>,
|
||||
pub fresh_validated_objects: Vec<CirObject>,
|
||||
pub cached_validated_objects: Vec<CirObject>,
|
||||
pub trust_anchors: Vec<CirTrustAnchor>,
|
||||
pub object_list_sha256: Vec<u8>,
|
||||
pub fresh_object_list_sha256: Vec<u8>,
|
||||
pub cached_object_list_sha256: Vec<u8>,
|
||||
pub reject_list_sha256: Vec<u8>,
|
||||
pub rejected_objects: Vec<CirRejectedObject>,
|
||||
pub fresh_reject_list_sha256: Vec<u8>,
|
||||
pub cached_reject_list_sha256: Vec<u8>,
|
||||
pub fresh_rejected_objects: Vec<CirRejectedObject>,
|
||||
pub cached_rejected_objects: Vec<CirRejectedObject>,
|
||||
}
|
||||
|
||||
impl CanonicalInputRepresentation {
|
||||
pub fn new_v4(
|
||||
validation_time: time::OffsetDateTime,
|
||||
fresh_validated_objects: Vec<CirObject>,
|
||||
cached_validated_objects: Vec<CirObject>,
|
||||
trust_anchors: Vec<CirTrustAnchor>,
|
||||
fresh_rejected_objects: Vec<CirRejectedObject>,
|
||||
cached_rejected_objects: Vec<CirRejectedObject>,
|
||||
) -> Self {
|
||||
let fresh_object_list_sha256 = compute_object_list_sha256(fresh_validated_objects.iter());
|
||||
let cached_object_list_sha256 = compute_object_list_sha256(cached_validated_objects.iter());
|
||||
let object_list_sha256 = compute_sectioned_object_list_sha256(
|
||||
fresh_validated_objects
|
||||
.iter()
|
||||
.map(|object| ("fresh", object))
|
||||
.chain(
|
||||
cached_validated_objects
|
||||
.iter()
|
||||
.map(|object| ("cached", object)),
|
||||
),
|
||||
);
|
||||
let fresh_reject_list_sha256 = compute_reject_list_sha256(
|
||||
fresh_rejected_objects
|
||||
.iter()
|
||||
.map(|item| item.object_uri.as_str()),
|
||||
);
|
||||
let cached_reject_list_sha256 = compute_reject_list_sha256(
|
||||
cached_rejected_objects
|
||||
.iter()
|
||||
.map(|item| item.object_uri.as_str()),
|
||||
);
|
||||
let mut all_rejected_uris = fresh_rejected_objects
|
||||
.iter()
|
||||
.chain(cached_rejected_objects.iter())
|
||||
.map(|item| item.object_uri.as_str())
|
||||
.collect::<Vec<_>>();
|
||||
all_rejected_uris.sort_unstable();
|
||||
all_rejected_uris.dedup();
|
||||
let reject_list_sha256 = compute_reject_list_sha256(all_rejected_uris.iter().copied());
|
||||
|
||||
Self {
|
||||
version: CIR_VERSION_V4,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: validation_time.to_offset(time::UtcOffset::UTC),
|
||||
fresh_validated_objects,
|
||||
cached_validated_objects,
|
||||
trust_anchors,
|
||||
object_list_sha256,
|
||||
fresh_object_list_sha256,
|
||||
cached_object_list_sha256,
|
||||
reject_list_sha256,
|
||||
fresh_reject_list_sha256,
|
||||
cached_reject_list_sha256,
|
||||
fresh_rejected_objects,
|
||||
cached_rejected_objects,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn validate(&self) -> Result<(), String> {
|
||||
if self.version != CIR_VERSION_V3 {
|
||||
if self.version != CIR_VERSION_V4 {
|
||||
return Err(format!(
|
||||
"CIR version must be {CIR_VERSION_V3}, got {}",
|
||||
"CIR version must be {CIR_VERSION_V4}, got {}",
|
||||
self.version
|
||||
));
|
||||
}
|
||||
@ -44,8 +109,16 @@ impl CanonicalInputRepresentation {
|
||||
return Err("CIR validationTime must be UTC".into());
|
||||
}
|
||||
validate_sorted_unique_strings(
|
||||
self.objects.iter().map(|item| item.rsync_uri.as_str()),
|
||||
"CIR.objects must be sorted by rsyncUri and unique",
|
||||
self.fresh_validated_objects
|
||||
.iter()
|
||||
.map(|item| item.rsync_uri.as_str()),
|
||||
"CIR.freshValidatedObjects must be sorted by rsyncUri and unique",
|
||||
)?;
|
||||
validate_sorted_unique_strings(
|
||||
self.cached_validated_objects
|
||||
.iter()
|
||||
.map(|item| item.rsync_uri.as_str()),
|
||||
"CIR.cachedValidatedObjects must be sorted by rsyncUri and unique",
|
||||
)?;
|
||||
validate_sorted_unique_strings(
|
||||
self.trust_anchors
|
||||
@ -54,14 +127,21 @@ impl CanonicalInputRepresentation {
|
||||
"CIR.trustAnchors must be sorted by taRsyncUri and unique",
|
||||
)?;
|
||||
validate_sorted_unique_strings(
|
||||
self.rejected_objects
|
||||
self.fresh_rejected_objects
|
||||
.iter()
|
||||
.map(|item| item.object_uri.as_str()),
|
||||
"CIR.rejectedObjects must be sorted by objectUri and unique",
|
||||
"CIR.freshRejectedObjects must be sorted by objectUri and unique",
|
||||
)?;
|
||||
validate_sorted_unique_strings(
|
||||
self.cached_rejected_objects
|
||||
.iter()
|
||||
.map(|item| item.object_uri.as_str()),
|
||||
"CIR.cachedRejectedObjects must be sorted by objectUri and unique",
|
||||
)?;
|
||||
let object_uris = self
|
||||
.objects
|
||||
.fresh_validated_objects
|
||||
.iter()
|
||||
.chain(self.cached_validated_objects.iter())
|
||||
.map(|item| item.rsync_uri.as_str())
|
||||
.collect::<std::collections::BTreeSet<_>>();
|
||||
for trust_anchor in &self.trust_anchors {
|
||||
@ -72,34 +152,136 @@ impl CanonicalInputRepresentation {
|
||||
));
|
||||
}
|
||||
}
|
||||
let fresh_object_uris = self
|
||||
.fresh_validated_objects
|
||||
.iter()
|
||||
.map(|item| item.rsync_uri.as_str())
|
||||
.collect::<std::collections::BTreeSet<_>>();
|
||||
for item in &self.fresh_rejected_objects {
|
||||
if !fresh_object_uris.contains(item.object_uri.as_str()) {
|
||||
return Err(format!(
|
||||
"CIR.freshRejectedObjects URI must exist in freshValidatedObjects: {}",
|
||||
item.object_uri
|
||||
));
|
||||
}
|
||||
}
|
||||
let cached_object_uris = self
|
||||
.cached_validated_objects
|
||||
.iter()
|
||||
.map(|item| item.rsync_uri.as_str())
|
||||
.collect::<std::collections::BTreeSet<_>>();
|
||||
for item in &self.cached_rejected_objects {
|
||||
if !cached_object_uris.contains(item.object_uri.as_str()) {
|
||||
return Err(format!(
|
||||
"CIR.cachedRejectedObjects URI must exist in cachedValidatedObjects: {}",
|
||||
item.object_uri
|
||||
));
|
||||
}
|
||||
}
|
||||
if self.trust_anchors.is_empty() {
|
||||
return Err("CIR.trustAnchors must be non-empty".into());
|
||||
}
|
||||
if self.reject_list_sha256.len() != DIGEST_LEN_SHA256 {
|
||||
return Err(format!(
|
||||
"CIR.rejectListSha256 must be {DIGEST_LEN_SHA256} bytes, got {}",
|
||||
self.reject_list_sha256.len()
|
||||
));
|
||||
for (label, digest) in [
|
||||
("CIR.objectListSha256", &self.object_list_sha256),
|
||||
("CIR.freshObjectListSha256", &self.fresh_object_list_sha256),
|
||||
(
|
||||
"CIR.cachedObjectListSha256",
|
||||
&self.cached_object_list_sha256,
|
||||
),
|
||||
("CIR.rejectListSha256", &self.reject_list_sha256),
|
||||
("CIR.freshRejectListSha256", &self.fresh_reject_list_sha256),
|
||||
(
|
||||
"CIR.cachedRejectListSha256",
|
||||
&self.cached_reject_list_sha256,
|
||||
),
|
||||
] {
|
||||
if digest.len() != DIGEST_LEN_SHA256 {
|
||||
return Err(format!(
|
||||
"{label} must be {DIGEST_LEN_SHA256} bytes, got {}",
|
||||
digest.len()
|
||||
));
|
||||
}
|
||||
}
|
||||
for object in &self.objects {
|
||||
for object in self.validated_objects() {
|
||||
object.validate()?;
|
||||
}
|
||||
for trust_anchor in &self.trust_anchors {
|
||||
trust_anchor.validate()?;
|
||||
}
|
||||
for item in &self.rejected_objects {
|
||||
for item in self.rejected_objects() {
|
||||
item.validate()?;
|
||||
}
|
||||
let expected_digest = compute_reject_list_sha256(
|
||||
self.rejected_objects
|
||||
let expected_fresh_object_digest =
|
||||
compute_object_list_sha256(self.fresh_validated_objects.iter());
|
||||
if self.fresh_object_list_sha256 != expected_fresh_object_digest {
|
||||
return Err("CIR.freshObjectListSha256 does not match freshValidatedObjects".into());
|
||||
}
|
||||
let expected_cached_object_digest =
|
||||
compute_object_list_sha256(self.cached_validated_objects.iter());
|
||||
if self.cached_object_list_sha256 != expected_cached_object_digest {
|
||||
return Err("CIR.cachedObjectListSha256 does not match cachedValidatedObjects".into());
|
||||
}
|
||||
let expected_object_digest = compute_sectioned_object_list_sha256(
|
||||
self.fresh_validated_objects
|
||||
.iter()
|
||||
.map(|object| ("fresh", object))
|
||||
.chain(
|
||||
self.cached_validated_objects
|
||||
.iter()
|
||||
.map(|object| ("cached", object)),
|
||||
),
|
||||
);
|
||||
if self.object_list_sha256 != expected_object_digest {
|
||||
return Err("CIR.objectListSha256 does not match fresh/cached objects".into());
|
||||
}
|
||||
let expected_fresh_reject_digest = compute_reject_list_sha256(
|
||||
self.fresh_rejected_objects
|
||||
.iter()
|
||||
.map(|item| item.object_uri.as_str()),
|
||||
);
|
||||
if self.fresh_reject_list_sha256 != expected_fresh_reject_digest {
|
||||
return Err("CIR.freshRejectListSha256 does not match freshRejectedObjects".into());
|
||||
}
|
||||
let expected_cached_reject_digest = compute_reject_list_sha256(
|
||||
self.cached_rejected_objects
|
||||
.iter()
|
||||
.map(|item| item.object_uri.as_str()),
|
||||
);
|
||||
if self.cached_reject_list_sha256 != expected_cached_reject_digest {
|
||||
return Err("CIR.cachedRejectListSha256 does not match cachedRejectedObjects".into());
|
||||
}
|
||||
let mut all_rejected_uris = self
|
||||
.rejected_objects()
|
||||
.map(|item| item.object_uri.as_str())
|
||||
.collect::<Vec<_>>();
|
||||
all_rejected_uris.sort_unstable();
|
||||
all_rejected_uris.dedup();
|
||||
let expected_digest = compute_reject_list_sha256(all_rejected_uris.iter().copied());
|
||||
if self.reject_list_sha256 != expected_digest {
|
||||
return Err("CIR.rejectListSha256 does not match rejectedObjects".into());
|
||||
return Err("CIR.rejectListSha256 does not match fresh/cached rejectedObjects".into());
|
||||
}
|
||||
Ok(())
|
||||
}
|
||||
|
||||
pub fn validated_objects(&self) -> impl Iterator<Item = &CirObject> {
|
||||
self.fresh_validated_objects
|
||||
.iter()
|
||||
.chain(self.cached_validated_objects.iter())
|
||||
}
|
||||
|
||||
pub fn rejected_objects(&self) -> impl Iterator<Item = &CirRejectedObject> {
|
||||
self.fresh_rejected_objects
|
||||
.iter()
|
||||
.chain(self.cached_rejected_objects.iter())
|
||||
}
|
||||
|
||||
pub fn validated_object_count(&self) -> usize {
|
||||
self.fresh_validated_objects.len() + self.cached_validated_objects.len()
|
||||
}
|
||||
|
||||
pub fn rejected_object_count(&self) -> usize {
|
||||
self.fresh_rejected_objects.len() + self.cached_rejected_objects.len()
|
||||
}
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||
@ -209,12 +391,39 @@ pub fn compute_reject_list_sha256<'a>(uris: impl IntoIterator<Item = &'a str>) -
|
||||
sha256(&body)
|
||||
}
|
||||
|
||||
pub fn compute_object_list_sha256<'a>(objects: impl IntoIterator<Item = &'a CirObject>) -> Vec<u8> {
|
||||
let mut body = Vec::new();
|
||||
for object in objects {
|
||||
append_digest_string(&mut body, &object.rsync_uri);
|
||||
body.extend_from_slice(&object.sha256);
|
||||
}
|
||||
sha256(&body)
|
||||
}
|
||||
|
||||
pub fn compute_sectioned_object_list_sha256<'a>(
|
||||
objects: impl IntoIterator<Item = (&'a str, &'a CirObject)>,
|
||||
) -> Vec<u8> {
|
||||
let mut body = Vec::new();
|
||||
for (section, object) in objects {
|
||||
append_digest_string(&mut body, section);
|
||||
append_digest_string(&mut body, &object.rsync_uri);
|
||||
body.extend_from_slice(&object.sha256);
|
||||
}
|
||||
sha256(&body)
|
||||
}
|
||||
|
||||
pub fn sha256(bytes: &[u8]) -> Vec<u8> {
|
||||
use sha2::Digest;
|
||||
|
||||
sha2::Sha256::digest(bytes).to_vec()
|
||||
}
|
||||
|
||||
fn append_digest_string(body: &mut Vec<u8>, value: &str) {
|
||||
let bytes = value.as_bytes();
|
||||
body.extend_from_slice(&(bytes.len() as u32).to_be_bytes());
|
||||
body.extend_from_slice(bytes);
|
||||
}
|
||||
|
||||
fn validate_sorted_unique_strings<'a>(
|
||||
items: impl IntoIterator<Item = &'a str>,
|
||||
message: &str,
|
||||
|
||||
@ -352,11 +352,24 @@ impl Histogram {
|
||||
struct CirMetrics {
|
||||
version: u32,
|
||||
objects: u64,
|
||||
fresh_objects: u64,
|
||||
cached_objects: u64,
|
||||
trust_anchors: u64,
|
||||
rejected_objects: u64,
|
||||
fresh_rejected_objects: u64,
|
||||
cached_rejected_objects: u64,
|
||||
object_list_sha256: String,
|
||||
fresh_object_list_sha256: String,
|
||||
cached_object_list_sha256: String,
|
||||
reject_list_sha256: String,
|
||||
fresh_reject_list_sha256: String,
|
||||
cached_reject_list_sha256: String,
|
||||
objects_by_type: BTreeMap<String, u64>,
|
||||
fresh_objects_by_type: BTreeMap<String, u64>,
|
||||
cached_objects_by_type: BTreeMap<String, u64>,
|
||||
rejected_objects_by_type: BTreeMap<String, u64>,
|
||||
fresh_rejected_objects_by_type: BTreeMap<String, u64>,
|
||||
cached_rejected_objects_by_type: BTreeMap<String, u64>,
|
||||
}
|
||||
|
||||
#[derive(Clone, Debug, Default, Serialize)]
|
||||
@ -973,25 +986,62 @@ fn parse_cir(path: &Path, snapshot: &mut MetricsSnapshot) {
|
||||
{
|
||||
Ok(cir) => {
|
||||
let mut objects_by_type = BTreeMap::new();
|
||||
for object in &cir.objects {
|
||||
for object in cir.validated_objects() {
|
||||
*objects_by_type
|
||||
.entry(object_type_from_uri(&object.rsync_uri))
|
||||
.or_default() += 1;
|
||||
}
|
||||
let mut fresh_objects_by_type = BTreeMap::new();
|
||||
for object in &cir.fresh_validated_objects {
|
||||
*fresh_objects_by_type
|
||||
.entry(object_type_from_uri(&object.rsync_uri))
|
||||
.or_default() += 1;
|
||||
}
|
||||
let mut cached_objects_by_type = BTreeMap::new();
|
||||
for object in &cir.cached_validated_objects {
|
||||
*cached_objects_by_type
|
||||
.entry(object_type_from_uri(&object.rsync_uri))
|
||||
.or_default() += 1;
|
||||
}
|
||||
let mut rejected_objects_by_type = BTreeMap::new();
|
||||
for object in &cir.rejected_objects {
|
||||
for object in cir.rejected_objects() {
|
||||
*rejected_objects_by_type
|
||||
.entry(object_type_from_uri(&object.object_uri))
|
||||
.or_default() += 1;
|
||||
}
|
||||
let mut fresh_rejected_objects_by_type = BTreeMap::new();
|
||||
for object in &cir.fresh_rejected_objects {
|
||||
*fresh_rejected_objects_by_type
|
||||
.entry(object_type_from_uri(&object.object_uri))
|
||||
.or_default() += 1;
|
||||
}
|
||||
let mut cached_rejected_objects_by_type = BTreeMap::new();
|
||||
for object in &cir.cached_rejected_objects {
|
||||
*cached_rejected_objects_by_type
|
||||
.entry(object_type_from_uri(&object.object_uri))
|
||||
.or_default() += 1;
|
||||
}
|
||||
snapshot.cir = Some(CirMetrics {
|
||||
version: cir.version,
|
||||
objects: cir.objects.len() as u64,
|
||||
objects: cir.validated_object_count() as u64,
|
||||
fresh_objects: cir.fresh_validated_objects.len() as u64,
|
||||
cached_objects: cir.cached_validated_objects.len() as u64,
|
||||
trust_anchors: cir.trust_anchors.len() as u64,
|
||||
rejected_objects: cir.rejected_objects.len() as u64,
|
||||
rejected_objects: cir.rejected_object_count() as u64,
|
||||
fresh_rejected_objects: cir.fresh_rejected_objects.len() as u64,
|
||||
cached_rejected_objects: cir.cached_rejected_objects.len() as u64,
|
||||
object_list_sha256: hex::encode(&cir.object_list_sha256),
|
||||
fresh_object_list_sha256: hex::encode(&cir.fresh_object_list_sha256),
|
||||
cached_object_list_sha256: hex::encode(&cir.cached_object_list_sha256),
|
||||
reject_list_sha256: hex::encode(&cir.reject_list_sha256),
|
||||
fresh_reject_list_sha256: hex::encode(&cir.fresh_reject_list_sha256),
|
||||
cached_reject_list_sha256: hex::encode(&cir.cached_reject_list_sha256),
|
||||
objects_by_type,
|
||||
fresh_objects_by_type,
|
||||
cached_objects_by_type,
|
||||
rejected_objects_by_type,
|
||||
fresh_rejected_objects_by_type,
|
||||
cached_rejected_objects_by_type,
|
||||
});
|
||||
}
|
||||
Err(err) => snapshot
|
||||
@ -1580,6 +1630,14 @@ fn render_cir_metrics(writer: &mut PromWriter<'_>, instance: &str, cir: &CirMetr
|
||||
&[label("instance", instance)],
|
||||
cir.objects as f64,
|
||||
);
|
||||
for (source, count) in [("fresh", cir.fresh_objects), ("cached", cir.cached_objects)] {
|
||||
writer.gauge(
|
||||
"ours_rp_cir_objects_by_source",
|
||||
"CIR object count by validation input source",
|
||||
&[label("instance", instance), label("source", source)],
|
||||
count as f64,
|
||||
);
|
||||
}
|
||||
writer.gauge(
|
||||
"ours_rp_cir_trust_anchors",
|
||||
"CIR trust anchor count",
|
||||
@ -1592,6 +1650,29 @@ fn render_cir_metrics(writer: &mut PromWriter<'_>, instance: &str, cir: &CirMetr
|
||||
&[label("instance", instance)],
|
||||
cir.rejected_objects as f64,
|
||||
);
|
||||
for (source, count) in [
|
||||
("fresh", cir.fresh_rejected_objects),
|
||||
("cached", cir.cached_rejected_objects),
|
||||
] {
|
||||
writer.gauge(
|
||||
"ours_rp_cir_rejected_objects_by_source",
|
||||
"CIR rejected object count by validation input source",
|
||||
&[label("instance", instance), label("source", source)],
|
||||
count as f64,
|
||||
);
|
||||
}
|
||||
for (source, digest) in [
|
||||
("merged", cir.object_list_sha256.as_str()),
|
||||
("fresh", cir.fresh_object_list_sha256.as_str()),
|
||||
("cached", cir.cached_object_list_sha256.as_str()),
|
||||
] {
|
||||
writer.gauge(
|
||||
"ours_rp_cir_object_list_digest_present",
|
||||
"CIR object list digest is present by validation input source",
|
||||
&[label("instance", instance), label("source", source)],
|
||||
if digest.len() == 64 { 1.0 } else { 0.0 },
|
||||
);
|
||||
}
|
||||
writer.gauge(
|
||||
"ours_rp_cir_reject_list_digest_present",
|
||||
"CIR reject list digest is present",
|
||||
@ -1602,6 +1683,18 @@ fn render_cir_metrics(writer: &mut PromWriter<'_>, instance: &str, cir: &CirMetr
|
||||
0.0
|
||||
},
|
||||
);
|
||||
for (source, digest) in [
|
||||
("merged", cir.reject_list_sha256.as_str()),
|
||||
("fresh", cir.fresh_reject_list_sha256.as_str()),
|
||||
("cached", cir.cached_reject_list_sha256.as_str()),
|
||||
] {
|
||||
writer.gauge(
|
||||
"ours_rp_cir_reject_list_digest_present_by_source",
|
||||
"CIR reject list digest is present by validation input source",
|
||||
&[label("instance", instance), label("source", source)],
|
||||
if digest.len() == 64 { 1.0 } else { 0.0 },
|
||||
);
|
||||
}
|
||||
for (object_type, count) in &cir.objects_by_type {
|
||||
writer.gauge(
|
||||
"ours_rp_cir_objects_by_type",
|
||||
@ -1613,6 +1706,23 @@ fn render_cir_metrics(writer: &mut PromWriter<'_>, instance: &str, cir: &CirMetr
|
||||
*count as f64,
|
||||
);
|
||||
}
|
||||
for (source, counts) in [
|
||||
("fresh", &cir.fresh_objects_by_type),
|
||||
("cached", &cir.cached_objects_by_type),
|
||||
] {
|
||||
for (object_type, count) in counts {
|
||||
writer.gauge(
|
||||
"ours_rp_cir_objects_by_source_type",
|
||||
"CIR object count by validation input source and file type",
|
||||
&[
|
||||
label("instance", instance),
|
||||
label("source", source),
|
||||
label("object_type", object_type),
|
||||
],
|
||||
*count as f64,
|
||||
);
|
||||
}
|
||||
}
|
||||
for (object_type, count) in &cir.rejected_objects_by_type {
|
||||
writer.gauge(
|
||||
"ours_rp_cir_rejected_objects_by_type",
|
||||
@ -1624,6 +1734,23 @@ fn render_cir_metrics(writer: &mut PromWriter<'_>, instance: &str, cir: &CirMetr
|
||||
*count as f64,
|
||||
);
|
||||
}
|
||||
for (source, counts) in [
|
||||
("fresh", &cir.fresh_rejected_objects_by_type),
|
||||
("cached", &cir.cached_rejected_objects_by_type),
|
||||
] {
|
||||
for (object_type, count) in counts {
|
||||
writer.gauge(
|
||||
"ours_rp_cir_rejected_objects_by_source_type",
|
||||
"CIR rejected object count by validation input source and file type",
|
||||
&[
|
||||
label("instance", instance),
|
||||
label("source", source),
|
||||
label("object_type", object_type),
|
||||
],
|
||||
*count as f64,
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_ccr_metrics(writer: &mut PromWriter<'_>, instance: &str, ccr: &CcrMetrics) {
|
||||
@ -2092,8 +2219,8 @@ mod tests {
|
||||
encode_content_info,
|
||||
};
|
||||
use crate::cir::{
|
||||
CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirRejectedObject,
|
||||
CirTrustAnchor, compute_reject_list_sha256, encode_cir, sha256,
|
||||
CanonicalInputRepresentation, CirObject, CirRejectedObject, CirTrustAnchor, encode_cir,
|
||||
sha256,
|
||||
};
|
||||
use tempfile::TempDir;
|
||||
|
||||
@ -2154,7 +2281,7 @@ mod tests {
|
||||
assert!(snapshot.repo_stats[0].sync_success);
|
||||
assert_eq!(snapshot.repo_stats[0].download_bytes, 333);
|
||||
assert_eq!(snapshot.top_pp_by_object_count[0].object_count, 2);
|
||||
assert_eq!(snapshot.cir.as_ref().unwrap().objects, 1);
|
||||
assert_eq!(snapshot.cir.as_ref().unwrap().objects, 2);
|
||||
assert_eq!(snapshot.ccr.as_ref().unwrap().state_items["tas"], 1);
|
||||
let metrics = render_metrics(&snapshot);
|
||||
assert!(metrics.contains("ours_rp_repository_info"));
|
||||
@ -2162,12 +2289,29 @@ mod tests {
|
||||
assert!(metrics.contains("ours_rp_repository_download_bytes"));
|
||||
assert!(metrics.contains("ours_rp_large_publication_points"));
|
||||
assert!(metrics.contains("ours_rp_cir_objects"));
|
||||
assert!(metrics.contains("ours_rp_cir_objects_by_source"));
|
||||
assert!(metrics.contains("ours_rp_cir_rejected_objects_by_source"));
|
||||
assert!(metrics.contains("ours_rp_cir_objects_by_source_type"));
|
||||
assert!(metrics.contains("ours_rp_cir_rejected_objects_by_source_type"));
|
||||
assert!(metrics.contains("ours_rp_cir_object_list_digest_present"));
|
||||
assert!(metrics.contains("ours_rp_cir_reject_list_digest_present_by_source"));
|
||||
assert!(metrics.contains("ours_rp_ccr_state_items"));
|
||||
assert!(
|
||||
metrics.contains(r#"ours_rp_cir_objects_by_source{instance="test",source="fresh"} 2"#)
|
||||
);
|
||||
assert!(
|
||||
metrics.contains(r#"ours_rp_cir_objects_by_source{instance="test",source="cached"} 0"#)
|
||||
);
|
||||
assert!(metrics.contains(
|
||||
r#"ours_rp_cir_rejected_objects_by_source{instance="test",source="fresh"} 1"#
|
||||
));
|
||||
assert!(metrics.contains(r#"ours_rp_vrps{instance="test",kind="total"} 3"#));
|
||||
assert!(metrics.contains(r#"ours_rp_vrps{instance="test",kind="unique"} 2"#));
|
||||
let status = render_status_json(&snapshot).expect("status");
|
||||
assert!(status.contains("topPublicationPointsByObjectCount"));
|
||||
assert!(status.contains(r#""vrpsUnique": 2"#));
|
||||
assert!(status.contains(r#""freshObjects": 2"#));
|
||||
assert!(status.contains(r#""cachedObjects": 0"#));
|
||||
}
|
||||
|
||||
#[test]
|
||||
@ -2186,30 +2330,33 @@ mod tests {
|
||||
object_uri: "rsync://repo.example/a/bad.roa".to_string(),
|
||||
reason: Some("bad".to_string()),
|
||||
}];
|
||||
let cir = CanonicalInputRepresentation {
|
||||
version: crate::cir::CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: time::OffsetDateTime::parse(
|
||||
let cir = CanonicalInputRepresentation::new_v4(
|
||||
time::OffsetDateTime::parse(
|
||||
"2026-05-25T00:00:00Z",
|
||||
&time::format_description::well_known::Rfc3339,
|
||||
)
|
||||
.unwrap(),
|
||||
objects: vec![CirObject {
|
||||
rsync_uri: "rsync://repo.example/a/a.roa".to_string(),
|
||||
sha256: vec![1; 32],
|
||||
}],
|
||||
trust_anchors: vec![CirTrustAnchor {
|
||||
vec![
|
||||
CirObject {
|
||||
rsync_uri: "rsync://repo.example/a/a.roa".to_string(),
|
||||
sha256: vec![1; 32],
|
||||
},
|
||||
CirObject {
|
||||
rsync_uri: "rsync://repo.example/a/bad.roa".to_string(),
|
||||
sha256: vec![2; 32],
|
||||
},
|
||||
],
|
||||
Vec::new(),
|
||||
vec![CirTrustAnchor {
|
||||
ta_rsync_uri: "rsync://repo.example/ta.cer".to_string(),
|
||||
tal_uri: "https://tal.example/tal.tal".to_string(),
|
||||
tal_bytes: b"rsync://repo.example/ta.cer\n\nAQID\n".to_vec(),
|
||||
ta_certificate_der: b"ta".to_vec(),
|
||||
ta_certificate_sha256: sha256(b"ta"),
|
||||
}],
|
||||
reject_list_sha256: compute_reject_list_sha256(
|
||||
rejected.iter().map(|item| item.object_uri.as_str()),
|
||||
),
|
||||
rejected_objects: rejected,
|
||||
};
|
||||
rejected,
|
||||
Vec::new(),
|
||||
);
|
||||
encode_cir(&cir).expect("encode cir")
|
||||
}
|
||||
|
||||
|
||||
@ -48,8 +48,8 @@ mod tests {
|
||||
build_aspa_payload_state, build_roa_payload_state, encode_content_info,
|
||||
};
|
||||
use crate::cir::{
|
||||
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirObject,
|
||||
CirRejectedObject, CirTrustAnchor, compute_reject_list_sha256, encode_cir, sha256,
|
||||
CanonicalInputRepresentation, CirObject, CirRejectedObject, CirTrustAnchor, encode_cir,
|
||||
sha256,
|
||||
};
|
||||
use crate::data_model::roa::{IpPrefix, RoaAfi};
|
||||
use crate::validation::objects::{AspaAttestation, Vrp};
|
||||
@ -100,6 +100,7 @@ mod tests {
|
||||
&[
|
||||
object("rsync://example.net/a.roa", 0x11),
|
||||
object("rsync://example.net/persistent.roa", 0x44),
|
||||
object("rsync://example.net/reject-old.roa", 0xee),
|
||||
],
|
||||
&[],
|
||||
64496,
|
||||
@ -485,7 +486,18 @@ mod tests {
|
||||
9,
|
||||
64496,
|
||||
);
|
||||
write_sample_with_ccr_seq(root, "right2", 2, &[peer_mismatch], &[], 10, 64497);
|
||||
write_sample_with_ccr_seq(
|
||||
root,
|
||||
"right2",
|
||||
2,
|
||||
&[
|
||||
peer_mismatch,
|
||||
object("rsync://example.net/pp/rejected.roa", 0xee),
|
||||
],
|
||||
&[],
|
||||
10,
|
||||
64497,
|
||||
);
|
||||
std::fs::write(
|
||||
root.join("left.jsonl"),
|
||||
jsonl(&[
|
||||
@ -845,7 +857,6 @@ mod tests {
|
||||
rejected: &[&str],
|
||||
) -> CanonicalInputRepresentation {
|
||||
let mut objects = objects.to_vec();
|
||||
objects.sort_by(|a, b| a.rsync_uri.cmp(&b.rsync_uri));
|
||||
let rejected_objects = rejected
|
||||
.iter()
|
||||
.map(|uri| CirRejectedObject {
|
||||
@ -853,15 +864,26 @@ mod tests {
|
||||
reason: Some("test".to_string()),
|
||||
})
|
||||
.collect::<Vec<_>>();
|
||||
CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: sample_time(seq),
|
||||
objects,
|
||||
trust_anchors: vec![sample_trust_anchor()],
|
||||
reject_list_sha256: compute_reject_list_sha256(rejected.iter().copied()),
|
||||
rejected_objects,
|
||||
for rejected_uri in rejected {
|
||||
if !objects
|
||||
.iter()
|
||||
.any(|object| object.rsync_uri == *rejected_uri)
|
||||
{
|
||||
objects.push(CirObject {
|
||||
rsync_uri: (*rejected_uri).to_string(),
|
||||
sha256: vec![0xee; 32],
|
||||
});
|
||||
}
|
||||
}
|
||||
objects.sort_by(|a, b| a.rsync_uri.cmp(&b.rsync_uri));
|
||||
CanonicalInputRepresentation::new_v4(
|
||||
sample_time(seq),
|
||||
objects,
|
||||
Vec::new(),
|
||||
vec![sample_trust_anchor()],
|
||||
rejected_objects,
|
||||
Vec::new(),
|
||||
)
|
||||
}
|
||||
|
||||
fn sample_trust_anchor() -> CirTrustAnchor {
|
||||
|
||||
@ -42,10 +42,12 @@ pub(super) fn load_sequence_meta(path: &Path, side: Side) -> Result<Vec<Sequence
|
||||
.transpose()?;
|
||||
let validation_time = cir.validation_time;
|
||||
samples.push(SequenceMeta {
|
||||
cir_object_count: raw.cir_object_count.or(Some(cir.objects.len() as u64)),
|
||||
cir_object_count: raw
|
||||
.cir_object_count
|
||||
.or(Some(cir.validated_object_count() as u64)),
|
||||
cir_reject_count: raw
|
||||
.cir_reject_count
|
||||
.or(Some(cir.rejected_objects.len() as u64)),
|
||||
.or(Some(cir.rejected_object_count() as u64)),
|
||||
cir_trust_anchor_count: raw
|
||||
.cir_trust_anchor_count
|
||||
.or(Some(cir.trust_anchors.len() as u64)),
|
||||
@ -89,8 +91,7 @@ fn load_sample_parts(
|
||||
)
|
||||
})?;
|
||||
objects = cir
|
||||
.objects
|
||||
.iter()
|
||||
.validated_objects()
|
||||
.map(|item| (item.rsync_uri.clone(), hex::encode(&item.sha256)))
|
||||
.collect::<BTreeMap<_, _>>();
|
||||
object_hashes = objects
|
||||
@ -98,8 +99,7 @@ fn load_sample_parts(
|
||||
.map(|(uri, hash)| object_hash_key(uri, hash))
|
||||
.collect::<BTreeSet<_>>();
|
||||
rejects = cir
|
||||
.rejected_objects
|
||||
.iter()
|
||||
.rejected_objects()
|
||||
.map(|item| item.object_uri.clone())
|
||||
.collect::<BTreeSet<_>>();
|
||||
}
|
||||
|
||||
@ -281,6 +281,19 @@ impl<'a> Rpkiv1PublicationPointRunner<'a> {
|
||||
})
|
||||
}
|
||||
|
||||
fn fresh_failure_audit_entries_for_cir(
|
||||
&self,
|
||||
ca: &CaInstanceHandle,
|
||||
fresh_err: &ManifestFreshError,
|
||||
) -> Vec<ObjectAuditEntry> {
|
||||
if !fresh_err.should_warn_when_current_instance_reused() {
|
||||
return Vec::new();
|
||||
}
|
||||
self.rejected_manifest_audit_entry_for_failed_fetch(ca, fresh_err)
|
||||
.into_iter()
|
||||
.collect()
|
||||
}
|
||||
|
||||
pub(crate) fn stage_fresh_publication_point_after_repo_ready(
|
||||
&self,
|
||||
ca: &CaInstanceHandle,
|
||||
@ -955,21 +968,15 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
|
||||
}
|
||||
crate::policy::CaFailedFetchPolicy::ReuseCurrentInstanceVcir => {
|
||||
let projection_started = std::time::Instant::now();
|
||||
let mut projection = project_current_instance_vcir_on_failed_fetch(
|
||||
let projection = project_current_instance_vcir_on_failed_fetch(
|
||||
self.store,
|
||||
ca,
|
||||
&fresh_err,
|
||||
self.validation_time,
|
||||
)
|
||||
.map_err(|e| format!("failed fetch VCIR projection failed: {e}"))?;
|
||||
if matches!(
|
||||
projection.source,
|
||||
PublicationPointSource::FailedFetchNoCache
|
||||
) && let Some(rejected_manifest) =
|
||||
self.rejected_manifest_audit_entry_for_failed_fetch(ca, &fresh_err)
|
||||
{
|
||||
projection.objects.audit.push(rejected_manifest);
|
||||
}
|
||||
let fresh_failure_audits =
|
||||
self.fresh_failure_audit_entries_for_cir(ca, &fresh_err);
|
||||
self.append_ccr_manifest_projection_from_reuse(&projection)?;
|
||||
let projection_ms = projection_started.elapsed().as_millis() as u64;
|
||||
warnings.extend(projection.warnings.clone());
|
||||
@ -986,6 +993,7 @@ impl<'a> PublicationPointRunner for Rpkiv1PublicationPointRunner<'a> {
|
||||
&warnings,
|
||||
&projection.objects,
|
||||
&projection.child_audits,
|
||||
&fresh_failure_audits,
|
||||
);
|
||||
let audit_build_ms = audit_build_started.elapsed().as_millis() as u64;
|
||||
let result = PublicationPointRunResult {
|
||||
@ -1922,7 +1930,9 @@ fn build_publication_point_audit_from_snapshot(
|
||||
next_update_rfc3339_utc: pack.next_update.rfc3339_utc.clone(),
|
||||
verified_at_rfc3339_utc: pack.verified_at.rfc3339_utc.clone(),
|
||||
warnings,
|
||||
objects: objects_out,
|
||||
objects: objects_out.clone(),
|
||||
cir_fresh_objects: objects_out,
|
||||
cir_cached_objects: Vec::new(),
|
||||
}
|
||||
}
|
||||
|
||||
@ -1938,6 +1948,7 @@ fn build_publication_point_audit_from_vcir(
|
||||
runner_warnings: &[Warning],
|
||||
objects: &crate::validation::objects::ObjectsOutput,
|
||||
child_audits: &[ObjectAuditEntry],
|
||||
fresh_failure_audits: &[ObjectAuditEntry],
|
||||
) -> PublicationPointAudit {
|
||||
if let Some(pack) = pack {
|
||||
return build_publication_point_audit_from_snapshot(
|
||||
@ -1977,14 +1988,19 @@ fn build_publication_point_audit_from_vcir(
|
||||
next_update_rfc3339_utc: String::new(),
|
||||
verified_at_rfc3339_utc: String::new(),
|
||||
warnings,
|
||||
objects: Vec::new(),
|
||||
objects: fresh_failure_audits.to_vec(),
|
||||
cir_fresh_objects: fresh_failure_audits.to_vec(),
|
||||
cir_cached_objects: Vec::new(),
|
||||
};
|
||||
};
|
||||
|
||||
if source == PublicationPointSource::FailedFetchNoCache {
|
||||
let mut objects_out = Vec::with_capacity(objects.audit.len() + child_audits.len());
|
||||
let mut objects_out = Vec::with_capacity(
|
||||
objects.audit.len() + child_audits.len() + fresh_failure_audits.len(),
|
||||
);
|
||||
objects_out.extend(child_audits.iter().cloned());
|
||||
objects_out.extend(objects.audit.iter().cloned());
|
||||
objects_out.extend(fresh_failure_audits.iter().cloned());
|
||||
return PublicationPointAudit {
|
||||
node_id: None,
|
||||
parent_node_id: None,
|
||||
@ -2011,7 +2027,9 @@ fn build_publication_point_audit_from_vcir(
|
||||
.clone(),
|
||||
verified_at_rfc3339_utc: vcir.last_successful_validation_time.rfc3339_utc.clone(),
|
||||
warnings,
|
||||
objects: objects_out,
|
||||
objects: objects_out.clone(),
|
||||
cir_fresh_objects: objects_out,
|
||||
cir_cached_objects: Vec::new(),
|
||||
};
|
||||
}
|
||||
|
||||
@ -2076,6 +2094,9 @@ fn build_publication_point_audit_from_vcir(
|
||||
}
|
||||
}
|
||||
|
||||
let mut audit_objects = objects_out.clone();
|
||||
audit_objects.extend(fresh_failure_audits.iter().cloned());
|
||||
|
||||
PublicationPointAudit {
|
||||
node_id: None,
|
||||
parent_node_id: None,
|
||||
@ -2102,7 +2123,9 @@ fn build_publication_point_audit_from_vcir(
|
||||
.clone(),
|
||||
verified_at_rfc3339_utc: vcir.last_successful_validation_time.rfc3339_utc.clone(),
|
||||
warnings,
|
||||
objects: objects_out,
|
||||
objects: audit_objects,
|
||||
cir_fresh_objects: fresh_failure_audits.to_vec(),
|
||||
cir_cached_objects: objects_out,
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@ -3324,6 +3324,7 @@ fn build_publication_point_audit_from_vcir_uses_vcir_metadata_and_overlays_child
|
||||
&runner_warnings,
|
||||
&objects,
|
||||
&child_audits,
|
||||
&[],
|
||||
);
|
||||
|
||||
assert_eq!(audit.source, "vcir_current_instance");
|
||||
@ -3413,6 +3414,7 @@ fn build_publication_point_audit_from_vcir_failed_no_cache_keeps_current_reject_
|
||||
&[Warning::new("latest VCIR instance_gate expired")],
|
||||
&objects,
|
||||
&[],
|
||||
&[],
|
||||
);
|
||||
|
||||
assert_eq!(audit.source, "failed_fetch_no_cache");
|
||||
@ -3537,6 +3539,7 @@ fn build_publication_point_audit_from_vcir_without_cached_inputs_returns_empty_l
|
||||
roa_cache_stats: crate::validation::objects::RoaValidationCacheStats::default(),
|
||||
},
|
||||
&[],
|
||||
&[],
|
||||
);
|
||||
|
||||
assert_eq!(audit.source, "failed_fetch_no_cache");
|
||||
|
||||
@ -5,10 +5,7 @@ use rpki::ccr::{
|
||||
CcrContentInfo, CcrDigestAlgorithm, RpkiCanonicalCacheRepresentation, TrustAnchorState,
|
||||
encode_content_info,
|
||||
};
|
||||
use rpki::cir::{
|
||||
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirTrustAnchor,
|
||||
compute_reject_list_sha256, encode_cir, sha256,
|
||||
};
|
||||
use rpki::cir::{CanonicalInputRepresentation, CirObject, CirTrustAnchor, encode_cir, sha256};
|
||||
|
||||
fn skip_heavy_blackbox_test() -> bool {
|
||||
std::env::var_os("RPKI_SKIP_HEAVY_BLACKBOX_TESTS").is_some()
|
||||
@ -44,31 +41,28 @@ fn cir_full_and_delta_pair_reuses_shared_repo_bytes_db() {
|
||||
};
|
||||
let trust_anchors = vec![test_trust_anchor()];
|
||||
|
||||
let full_cir = CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: time::OffsetDateTime::parse(
|
||||
let full_cir = CanonicalInputRepresentation::new_v4(
|
||||
time::OffsetDateTime::parse(
|
||||
"2026-03-16T11:49:15Z",
|
||||
&time::format_description::well_known::Rfc3339,
|
||||
)
|
||||
.unwrap(),
|
||||
objects: vec![CirObject {
|
||||
vec![CirObject {
|
||||
rsync_uri: "rsync://example.net/repo/full.roa".to_string(),
|
||||
sha256: hex::decode(&full_obj_hash).unwrap(),
|
||||
}],
|
||||
trust_anchors: trust_anchors.clone(),
|
||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||
rejected_objects: Vec::new(),
|
||||
};
|
||||
let delta_cir = CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: time::OffsetDateTime::parse(
|
||||
Vec::new(),
|
||||
trust_anchors.clone(),
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
);
|
||||
let delta_cir = CanonicalInputRepresentation::new_v4(
|
||||
time::OffsetDateTime::parse(
|
||||
"2026-03-16T11:50:15Z",
|
||||
&time::format_description::well_known::Rfc3339,
|
||||
)
|
||||
.unwrap(),
|
||||
objects: {
|
||||
{
|
||||
let mut objects = vec![
|
||||
CirObject {
|
||||
rsync_uri: "rsync://example.net/repo/full.roa".to_string(),
|
||||
@ -82,10 +76,11 @@ fn cir_full_and_delta_pair_reuses_shared_repo_bytes_db() {
|
||||
objects.sort_by(|a, b| a.rsync_uri.cmp(&b.rsync_uri));
|
||||
objects
|
||||
},
|
||||
Vec::new(),
|
||||
trust_anchors,
|
||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||
rejected_objects: Vec::new(),
|
||||
};
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
);
|
||||
let empty_ccr = CcrContentInfo::new(RpkiCanonicalCacheRepresentation {
|
||||
version: 0,
|
||||
hash_alg: CcrDigestAlgorithm::Sha256,
|
||||
@ -214,8 +209,8 @@ fi
|
||||
rpki::cir::decode_cir(&std::fs::read(out.join("delta-001").join("input.cir")).unwrap())
|
||||
.expect("decode delta cir");
|
||||
|
||||
assert!(!full_cir.objects.is_empty());
|
||||
assert!(!delta_cir.objects.is_empty());
|
||||
assert!(full_cir.validated_object_count() > 0);
|
||||
assert!(delta_cir.validated_object_count() > 0);
|
||||
|
||||
let summary: serde_json::Value =
|
||||
serde_json::from_slice(&std::fs::read(out.join("summary.json")).unwrap()).unwrap();
|
||||
|
||||
@ -6,10 +6,7 @@ use rpki::ccr::{
|
||||
CcrContentInfo, CcrDigestAlgorithm, RpkiCanonicalCacheRepresentation, TrustAnchorState,
|
||||
encode_content_info,
|
||||
};
|
||||
use rpki::cir::{
|
||||
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirTrustAnchor,
|
||||
compute_reject_list_sha256, encode_cir, sha256,
|
||||
};
|
||||
use rpki::cir::{CanonicalInputRepresentation, CirObject, CirTrustAnchor, encode_cir, sha256};
|
||||
|
||||
#[test]
|
||||
fn cir_drop_report_counts_dropped_roa_objects_and_vrps() {
|
||||
@ -33,22 +30,21 @@ fn cir_drop_report_counts_dropped_roa_objects_and_vrps() {
|
||||
.put_blob_bytes_batch(&[(hash.clone(), roa_bytes.clone())])
|
||||
.expect("write repo bytes");
|
||||
|
||||
let cir = CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: time::OffsetDateTime::parse(
|
||||
let cir = CanonicalInputRepresentation::new_v4(
|
||||
time::OffsetDateTime::parse(
|
||||
"2026-04-09T00:00:00Z",
|
||||
&time::format_description::well_known::Rfc3339,
|
||||
)
|
||||
.unwrap(),
|
||||
objects: vec![CirObject {
|
||||
vec![CirObject {
|
||||
rsync_uri: "rsync://example.net/repo/AS4538.roa".to_string(),
|
||||
sha256: hex::decode(&hash).unwrap(),
|
||||
}],
|
||||
trust_anchors: vec![test_trust_anchor()],
|
||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||
rejected_objects: Vec::new(),
|
||||
};
|
||||
Vec::new(),
|
||||
vec![test_trust_anchor()],
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
);
|
||||
std::fs::write(&cir_path, encode_cir(&cir).unwrap()).unwrap();
|
||||
|
||||
let ccr = CcrContentInfo::new(RpkiCanonicalCacheRepresentation {
|
||||
|
||||
@ -3,8 +3,8 @@ use std::process::Command;
|
||||
|
||||
use rpki::blob_store::ExternalRepoBytesDb;
|
||||
use rpki::cir::{
|
||||
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirTrustAnchor,
|
||||
compute_reject_list_sha256, encode_cir, materialize_cir_from_repo_bytes, sha256,
|
||||
CanonicalInputRepresentation, CirTrustAnchor, encode_cir, materialize_cir_from_repo_bytes,
|
||||
sha256,
|
||||
};
|
||||
|
||||
fn skip_heavy_script_replay_test() -> bool {
|
||||
@ -31,25 +31,24 @@ fn build_ta_only_cir() -> (CanonicalInputRepresentation, Vec<u8>) {
|
||||
.as_str()
|
||||
.to_string();
|
||||
(
|
||||
CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: time::OffsetDateTime::parse(
|
||||
CanonicalInputRepresentation::new_v4(
|
||||
time::OffsetDateTime::parse(
|
||||
"2026-04-07T00:00:00Z",
|
||||
&time::format_description::well_known::Rfc3339,
|
||||
)
|
||||
.unwrap(),
|
||||
objects: Vec::new(),
|
||||
trust_anchors: vec![CirTrustAnchor {
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
vec![CirTrustAnchor {
|
||||
ta_rsync_uri,
|
||||
tal_uri: "https://example.test/root.tal".to_string(),
|
||||
tal_bytes,
|
||||
ta_certificate_sha256: sha256(&ta_bytes),
|
||||
ta_certificate_der: ta_bytes.clone(),
|
||||
}],
|
||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||
rejected_objects: Vec::new(),
|
||||
},
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
),
|
||||
ta_bytes,
|
||||
)
|
||||
}
|
||||
|
||||
@ -3,8 +3,8 @@ use std::process::Command;
|
||||
|
||||
use rpki::blob_store::ExternalRepoBytesDb;
|
||||
use rpki::cir::{
|
||||
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirTrustAnchor,
|
||||
compute_reject_list_sha256, encode_cir, materialize_cir_from_repo_bytes, sha256,
|
||||
CanonicalInputRepresentation, CirTrustAnchor, encode_cir, materialize_cir_from_repo_bytes,
|
||||
sha256,
|
||||
};
|
||||
|
||||
fn skip_heavy_script_replay_test() -> bool {
|
||||
@ -31,25 +31,24 @@ fn build_ta_only_cir() -> (CanonicalInputRepresentation, Vec<u8>) {
|
||||
.as_str()
|
||||
.to_string();
|
||||
(
|
||||
CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: time::OffsetDateTime::parse(
|
||||
CanonicalInputRepresentation::new_v4(
|
||||
time::OffsetDateTime::parse(
|
||||
"2026-04-07T00:00:00Z",
|
||||
&time::format_description::well_known::Rfc3339,
|
||||
)
|
||||
.unwrap(),
|
||||
objects: Vec::new(),
|
||||
trust_anchors: vec![CirTrustAnchor {
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
vec![CirTrustAnchor {
|
||||
ta_rsync_uri,
|
||||
tal_uri: "https://example.test/root.tal".to_string(),
|
||||
tal_bytes,
|
||||
ta_certificate_sha256: sha256(&ta_bytes),
|
||||
ta_certificate_der: ta_bytes.clone(),
|
||||
}],
|
||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||
rejected_objects: Vec::new(),
|
||||
},
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
),
|
||||
ta_bytes,
|
||||
)
|
||||
}
|
||||
|
||||
@ -5,10 +5,7 @@ use rpki::ccr::{
|
||||
CcrContentInfo, CcrDigestAlgorithm, RpkiCanonicalCacheRepresentation, TrustAnchorState,
|
||||
encode_content_info,
|
||||
};
|
||||
use rpki::cir::{
|
||||
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirTrustAnchor,
|
||||
compute_reject_list_sha256, encode_cir, sha256,
|
||||
};
|
||||
use rpki::cir::{CanonicalInputRepresentation, CirObject, CirTrustAnchor, encode_cir, sha256};
|
||||
|
||||
fn skip_heavy_blackbox_test() -> bool {
|
||||
std::env::var_os("RPKI_SKIP_HEAVY_BLACKBOX_TESTS").is_some()
|
||||
@ -35,21 +32,19 @@ fn cir_offline_sequence_writes_parseable_sequence_json_and_steps() {
|
||||
)
|
||||
.unwrap();
|
||||
|
||||
let mk_cir = |uri: &str, hash_hex: &str, vt: &str| CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: time::OffsetDateTime::parse(
|
||||
vt,
|
||||
&time::format_description::well_known::Rfc3339,
|
||||
let mk_cir = |uri: &str, hash_hex: &str, vt: &str| {
|
||||
CanonicalInputRepresentation::new_v4(
|
||||
time::OffsetDateTime::parse(vt, &time::format_description::well_known::Rfc3339)
|
||||
.unwrap(),
|
||||
vec![CirObject {
|
||||
rsync_uri: uri.to_string(),
|
||||
sha256: hex::decode(hash_hex).unwrap(),
|
||||
}],
|
||||
Vec::new(),
|
||||
vec![test_trust_anchor()],
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
)
|
||||
.unwrap(),
|
||||
objects: vec![CirObject {
|
||||
rsync_uri: uri.to_string(),
|
||||
sha256: hex::decode(hash_hex).unwrap(),
|
||||
}],
|
||||
trust_anchors: vec![test_trust_anchor()],
|
||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||
rejected_objects: Vec::new(),
|
||||
};
|
||||
let full_hash = {
|
||||
use sha2::{Digest, Sha256};
|
||||
@ -64,17 +59,15 @@ fn cir_offline_sequence_writes_parseable_sequence_json_and_steps() {
|
||||
&full_hash,
|
||||
"2026-03-16T11:49:15Z",
|
||||
);
|
||||
let delta_cir = CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: time::OffsetDateTime::parse(
|
||||
let delta_cir = CanonicalInputRepresentation::new_v4(
|
||||
time::OffsetDateTime::parse(
|
||||
"2026-03-16T11:50:15Z",
|
||||
&time::format_description::well_known::Rfc3339,
|
||||
)
|
||||
.unwrap(),
|
||||
objects: {
|
||||
{
|
||||
let mut objects = vec![
|
||||
full_cir.objects[0].clone(),
|
||||
full_cir.fresh_validated_objects[0].clone(),
|
||||
CirObject {
|
||||
rsync_uri: "rsync://example.net/repo/delta.roa".to_string(),
|
||||
sha256: hex::decode(&delta_hash).unwrap(),
|
||||
@ -83,10 +76,11 @@ fn cir_offline_sequence_writes_parseable_sequence_json_and_steps() {
|
||||
objects.sort_by(|a, b| a.rsync_uri.cmp(&b.rsync_uri));
|
||||
objects
|
||||
},
|
||||
trust_anchors: full_cir.trust_anchors.clone(),
|
||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||
rejected_objects: Vec::new(),
|
||||
};
|
||||
Vec::new(),
|
||||
full_cir.trust_anchors.clone(),
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
);
|
||||
let empty_ccr = CcrContentInfo::new(RpkiCanonicalCacheRepresentation {
|
||||
version: 0,
|
||||
hash_alg: CcrDigestAlgorithm::Sha256,
|
||||
|
||||
@ -3,8 +3,8 @@ use std::process::Command;
|
||||
|
||||
use rpki::blob_store::ExternalRepoBytesDb;
|
||||
use rpki::cir::{
|
||||
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirTrustAnchor,
|
||||
compute_reject_list_sha256, encode_cir, materialize_cir_from_repo_bytes, sha256,
|
||||
CanonicalInputRepresentation, CirTrustAnchor, encode_cir, materialize_cir_from_repo_bytes,
|
||||
sha256,
|
||||
};
|
||||
|
||||
fn skip_heavy_script_replay_test() -> bool {
|
||||
@ -31,25 +31,24 @@ fn build_ta_only_cir() -> (CanonicalInputRepresentation, Vec<u8>) {
|
||||
.as_str()
|
||||
.to_string();
|
||||
(
|
||||
CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: time::OffsetDateTime::parse(
|
||||
CanonicalInputRepresentation::new_v4(
|
||||
time::OffsetDateTime::parse(
|
||||
"2026-04-07T00:00:00Z",
|
||||
&time::format_description::well_known::Rfc3339,
|
||||
)
|
||||
.unwrap(),
|
||||
objects: Vec::new(),
|
||||
trust_anchors: vec![CirTrustAnchor {
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
vec![CirTrustAnchor {
|
||||
ta_rsync_uri,
|
||||
tal_uri: "https://example.test/root.tal".to_string(),
|
||||
tal_bytes,
|
||||
ta_certificate_sha256: sha256(&ta_bytes),
|
||||
ta_certificate_der: ta_bytes.clone(),
|
||||
}],
|
||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||
rejected_objects: Vec::new(),
|
||||
},
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
),
|
||||
ta_bytes,
|
||||
)
|
||||
}
|
||||
|
||||
@ -3,8 +3,8 @@ use std::process::Command;
|
||||
|
||||
use rpki::blob_store::ExternalRepoBytesDb;
|
||||
use rpki::cir::{
|
||||
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirTrustAnchor,
|
||||
compute_reject_list_sha256, encode_cir, materialize_cir_from_repo_bytes, sha256,
|
||||
CanonicalInputRepresentation, CirTrustAnchor, encode_cir, materialize_cir_from_repo_bytes,
|
||||
sha256,
|
||||
};
|
||||
|
||||
fn skip_heavy_script_replay_test() -> bool {
|
||||
@ -31,25 +31,24 @@ fn build_ta_only_cir() -> (CanonicalInputRepresentation, Vec<u8>) {
|
||||
.as_str()
|
||||
.to_string();
|
||||
(
|
||||
CanonicalInputRepresentation {
|
||||
version: CIR_VERSION_V3,
|
||||
hash_alg: CirHashAlgorithm::Sha256,
|
||||
validation_time: time::OffsetDateTime::parse(
|
||||
CanonicalInputRepresentation::new_v4(
|
||||
time::OffsetDateTime::parse(
|
||||
"2026-04-07T00:00:00Z",
|
||||
&time::format_description::well_known::Rfc3339,
|
||||
)
|
||||
.unwrap(),
|
||||
objects: Vec::new(),
|
||||
trust_anchors: vec![CirTrustAnchor {
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
vec![CirTrustAnchor {
|
||||
ta_rsync_uri,
|
||||
tal_uri: "https://example.test/root.tal".to_string(),
|
||||
tal_bytes,
|
||||
ta_certificate_sha256: sha256(&ta_bytes),
|
||||
ta_certificate_der: ta_bytes.clone(),
|
||||
}],
|
||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||
rejected_objects: Vec::new(),
|
||||
},
|
||||
Vec::new(),
|
||||
Vec::new(),
|
||||
),
|
||||
ta_bytes,
|
||||
)
|
||||
}
|
||||
|
||||
@ -134,8 +134,7 @@ fn cli_run_offline_mode_writes_cir_and_static_pool() {
|
||||
);
|
||||
assert!(!cir.trust_anchors[0].ta_certificate_der.is_empty());
|
||||
assert!(
|
||||
!cir.objects
|
||||
.iter()
|
||||
!cir.validated_objects()
|
||||
.any(|item| item.rsync_uri == cir.trust_anchors[0].ta_rsync_uri)
|
||||
);
|
||||
}
|
||||
|
||||
Loading…
x
Reference in New Issue
Block a user