20260511 CIR V3 trust anchors self-contained
This commit is contained in:
parent
51e483d924
commit
f2fbb20a29
@ -242,6 +242,30 @@ ta_file_for_rir() {
|
|||||||
esac
|
esac
|
||||||
}
|
}
|
||||||
|
|
||||||
|
refresh_ta_file_for_rir() {
|
||||||
|
local rir="$1"
|
||||||
|
local uri
|
||||||
|
local file
|
||||||
|
uri="$(tal_uri_for_rir "$rir")"
|
||||||
|
file="$(ta_file_for_rir "$rir")"
|
||||||
|
python3 - <<'PY' "$uri" "$file"
|
||||||
|
import sys
|
||||||
|
import urllib.request
|
||||||
|
uri, path = sys.argv[1:]
|
||||||
|
request = urllib.request.Request(uri, headers={"User-Agent": "rpki-dev/compare-fast-path"})
|
||||||
|
with urllib.request.urlopen(request, timeout=30) as response:
|
||||||
|
data = response.read()
|
||||||
|
if not data:
|
||||||
|
raise SystemExit(f"empty TA certificate response: {uri}")
|
||||||
|
with open(path, "wb") as output:
|
||||||
|
output.write(data)
|
||||||
|
PY
|
||||||
|
}
|
||||||
|
|
||||||
|
for rir in "${RIRS[@]}"; do
|
||||||
|
refresh_ta_file_for_rir "$rir"
|
||||||
|
done
|
||||||
|
|
||||||
OURS_TAL_ARGS=()
|
OURS_TAL_ARGS=()
|
||||||
CLIENT_TAL_ARGS=()
|
CLIENT_TAL_ARGS=()
|
||||||
OURS_CIR_TAL_ARGS=()
|
OURS_CIR_TAL_ARGS=()
|
||||||
@ -260,6 +284,8 @@ fi
|
|||||||
mkdir -p state/ours/work-db state/ours/raw-store.db state/ours/repo-bytes.db state/rpki-client/cache state/rpki-client/out state/rpki-client/ta state/rpki-client/.ta
|
mkdir -p state/ours/work-db state/ours/raw-store.db state/ours/repo-bytes.db state/rpki-client/cache state/rpki-client/out state/rpki-client/ta state/rpki-client/.ta
|
||||||
chmod 0777 state/ours/work-db state/ours/raw-store.db state/ours/repo-bytes.db
|
chmod 0777 state/ours/work-db state/ours/raw-store.db state/ours/repo-bytes.db
|
||||||
chmod -R 0777 state/rpki-client
|
chmod -R 0777 state/rpki-client
|
||||||
|
touch state/rpki-client/rpki-client-skiplist
|
||||||
|
chmod 0644 state/rpki-client/rpki-client-skiplist
|
||||||
|
|
||||||
START_EPOCH="$(python3 - <<'PY'
|
START_EPOCH="$(python3 - <<'PY'
|
||||||
import time
|
import time
|
||||||
@ -333,7 +359,7 @@ PY
|
|||||||
set +e
|
set +e
|
||||||
LD_LIBRARY_PATH="$REMOTE_ROOT/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" "$REMOTE_ROOT/bin/rpki-client" \
|
LD_LIBRARY_PATH="$REMOTE_ROOT/lib${LD_LIBRARY_PATH:+:$LD_LIBRARY_PATH}" "$REMOTE_ROOT/bin/rpki-client" \
|
||||||
-vv \
|
-vv \
|
||||||
-S "$REMOTE_ROOT/rpki-client-skiplist" \
|
-S rpki-client-skiplist \
|
||||||
"${CLIENT_TAL_ARGS[@]}" \
|
"${CLIENT_TAL_ARGS[@]}" \
|
||||||
-d cache out \
|
-d cache out \
|
||||||
> "$REMOTE_ROOT/steps/$STEP_ID/rpki-client/run.log" 2>&1
|
> "$REMOTE_ROOT/steps/$STEP_ID/rpki-client/run.log" 2>&1
|
||||||
|
|||||||
@ -114,8 +114,8 @@ else:
|
|||||||
diagnosis = "ccr_mismatch_cir_not_available"
|
diagnosis = "ccr_mismatch_cir_not_available"
|
||||||
elif not cir_compared:
|
elif not cir_compared:
|
||||||
diagnosis = "ccr_mismatch_cir_compare_skipped"
|
diagnosis = "ccr_mismatch_cir_compare_skipped"
|
||||||
elif cir.get("tals", {}).get("match") is not True:
|
elif cir.get("trustAnchors", {}).get("match") is not True:
|
||||||
diagnosis = "tal_input_difference"
|
diagnosis = "trust_anchor_input_difference"
|
||||||
elif cir.get("objects", {}).get("match") is not True:
|
elif cir.get("objects", {}).get("match") is not True:
|
||||||
diagnosis = "input_object_or_manifest_accepted_set_difference"
|
diagnosis = "input_object_or_manifest_accepted_set_difference"
|
||||||
elif (
|
elif (
|
||||||
@ -154,10 +154,13 @@ combined = {
|
|||||||
"allMatch": cir.get("allMatch") if cir else None,
|
"allMatch": cir.get("allMatch") if cir else None,
|
||||||
"objectsMatch": cir.get("objects", {}).get("match") if cir else None,
|
"objectsMatch": cir.get("objects", {}).get("match") if cir else None,
|
||||||
"rejectsMatch": cir.get("rejects", {}).get("match") if cir else None,
|
"rejectsMatch": cir.get("rejects", {}).get("match") if cir else None,
|
||||||
"talsMatch": cir.get("tals", {}).get("match") if cir else None,
|
"trustAnchorsMatch": cir.get("trustAnchors", {}).get("match") if cir else None,
|
||||||
|
"talsMatch": cir.get("trustAnchors", {}).get("match") if cir else None,
|
||||||
"rejectListSha256Match": cir.get("rejectListSha256Match") if cir else None,
|
"rejectListSha256Match": cir.get("rejectListSha256Match") if cir else None,
|
||||||
"oursObjectCount": cir.get("ours", {}).get("objectCount") if cir else None,
|
"oursObjectCount": cir.get("ours", {}).get("objectCount") if cir else None,
|
||||||
"rpkiClientObjectCount": cir.get("rpkiClient", {}).get("objectCount") if cir else None,
|
"rpkiClientObjectCount": cir.get("rpkiClient", {}).get("objectCount") if cir else None,
|
||||||
|
"oursTrustAnchorCount": cir.get("ours", {}).get("trustAnchorCount") if cir else None,
|
||||||
|
"rpkiClientTrustAnchorCount": cir.get("rpkiClient", {}).get("trustAnchorCount") if cir else None,
|
||||||
"oursRejectCount": cir.get("ours", {}).get("rejectCount") if cir else None,
|
"oursRejectCount": cir.get("ours", {}).get("rejectCount") if cir else None,
|
||||||
"rpkiClientRejectCount": cir.get("rpkiClient", {}).get("rejectCount") if cir else None,
|
"rpkiClientRejectCount": cir.get("rpkiClient", {}).get("rejectCount") if cir else None,
|
||||||
},
|
},
|
||||||
@ -178,8 +181,8 @@ if cir:
|
|||||||
lines.extend([
|
lines.extend([
|
||||||
f"- `cirObjectsMatch`: `{str(combined['cir']['objectsMatch']).lower()}`",
|
f"- `cirObjectsMatch`: `{str(combined['cir']['objectsMatch']).lower()}`",
|
||||||
f"- `cirRejectsMatch`: `{str(combined['cir']['rejectsMatch']).lower()}`",
|
f"- `cirRejectsMatch`: `{str(combined['cir']['rejectsMatch']).lower()}`",
|
||||||
f"- `cirTalsMatch`: `{str(combined['cir']['talsMatch']).lower()}`",
|
f"- `cirTrustAnchorsMatch`: `{str(combined['cir']['trustAnchorsMatch']).lower()}`",
|
||||||
f"- `cirCounts`: ours objects `{combined['cir']['oursObjectCount']}`, rpki-client objects `{combined['cir']['rpkiClientObjectCount']}`, ours rejects `{combined['cir']['oursRejectCount']}`, rpki-client rejects `{combined['cir']['rpkiClientRejectCount']}`",
|
f"- `cirCounts`: ours objects `{combined['cir']['oursObjectCount']}`, rpki-client objects `{combined['cir']['rpkiClientObjectCount']}`, ours trustAnchors `{combined['cir']['oursTrustAnchorCount']}`, rpki-client trustAnchors `{combined['cir']['rpkiClientTrustAnchorCount']}`, ours rejects `{combined['cir']['oursRejectCount']}`, rpki-client rejects `{combined['cir']['rpkiClientRejectCount']}`",
|
||||||
])
|
])
|
||||||
else:
|
else:
|
||||||
lines.append(f"- `cirSkippedReason`: `{'ccr matched' if ccr_match and not always_compare_cir else 'CIR inputs unavailable'}`")
|
lines.append(f"- `cirSkippedReason`: `{'ccr matched' if ccr_match and not always_compare_cir else 'CIR inputs unavailable'}`")
|
||||||
|
|||||||
@ -57,7 +57,7 @@ fn real_main() -> Result<(), String> {
|
|||||||
let cir = rpki::cir::decode_cir(&bytes).map_err(|e| format!("decode cir failed: {e}"))?;
|
let cir = rpki::cir::decode_cir(&bytes).map_err(|e| format!("decode cir failed: {e}"))?;
|
||||||
|
|
||||||
println!("object_count={}", cir.objects.len());
|
println!("object_count={}", cir.objects.len());
|
||||||
println!("tal_count={}", cir.tals.len());
|
println!("trust_anchor_count={}", cir.trust_anchors.len());
|
||||||
for (index, item) in cir.objects.iter().take(args.limit).enumerate() {
|
for (index, item) in cir.objects.iter().take(args.limit).enumerate() {
|
||||||
println!(
|
println!(
|
||||||
"{:04} object={} sha256={}",
|
"{:04} object={} sha256={}",
|
||||||
|
|||||||
@ -49,17 +49,23 @@ fn run(argv: Vec<String>) -> Result<(), String> {
|
|||||||
.map_err(|e| format!("read CIR failed: {}: {e}", cir_path.display()))?;
|
.map_err(|e| format!("read CIR failed: {}: {e}", cir_path.display()))?;
|
||||||
let cir = rpki::cir::decode_cir(&bytes).map_err(|e| e.to_string())?;
|
let cir = rpki::cir::decode_cir(&bytes).map_err(|e| e.to_string())?;
|
||||||
|
|
||||||
std::fs::create_dir_all(&tals_dir)
|
std::fs::create_dir_all(&tals_dir).map_err(|e| {
|
||||||
.map_err(|e| format!("create tals dir failed: {}: {e}", tals_dir.display()))?;
|
format!(
|
||||||
|
"create trust_anchors dir failed: {}: {e}",
|
||||||
|
tals_dir.display()
|
||||||
|
)
|
||||||
|
})?;
|
||||||
|
|
||||||
let mut tal_files = Vec::new();
|
let mut tal_files = Vec::new();
|
||||||
for (idx, tal) in cir.tals.iter().enumerate() {
|
for (idx, tal) in cir.trust_anchors.iter().enumerate() {
|
||||||
let filename = format!("tal-{:03}.tal", idx + 1);
|
let filename = format!("tal-{:03}.tal", idx + 1);
|
||||||
let path = tals_dir.join(filename);
|
let path = tals_dir.join(filename);
|
||||||
std::fs::write(&path, &tal.tal_bytes)
|
std::fs::write(&path, &tal.tal_bytes)
|
||||||
.map_err(|e| format!("write TAL failed: {}: {e}", path.display()))?;
|
.map_err(|e| format!("write TAL failed: {}: {e}", path.display()))?;
|
||||||
tal_files.push(serde_json::json!({
|
tal_files.push(serde_json::json!({
|
||||||
"talUri": tal.tal_uri,
|
"talUri": tal.tal_uri,
|
||||||
|
"taRsyncUri": tal.ta_rsync_uri,
|
||||||
|
"taCertificateSha256": hex::encode(&tal.ta_certificate_sha256),
|
||||||
"path": path,
|
"path": path,
|
||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
@ -70,6 +76,7 @@ fn run(argv: Vec<String>) -> Result<(), String> {
|
|||||||
.map_err(|e| format!("format validationTime failed: {e}"))?;
|
.map_err(|e| format!("format validationTime failed: {e}"))?;
|
||||||
let meta = serde_json::json!({
|
let meta = serde_json::json!({
|
||||||
"validationTime": validation_time,
|
"validationTime": validation_time,
|
||||||
|
"trustAnchorCount": cir.trust_anchors.len(),
|
||||||
"talFiles": tal_files,
|
"talFiles": tal_files,
|
||||||
});
|
});
|
||||||
if let Some(parent) = meta_json.parent() {
|
if let Some(parent) = meta_json.parent() {
|
||||||
|
|||||||
@ -59,9 +59,11 @@ fn run(argv: Vec<String>) -> Result<(), String> {
|
|||||||
match result {
|
match result {
|
||||||
Ok(summary) => {
|
Ok(summary) => {
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"materialized CIR: mirror={} objects={} linked={} copied={} keep_db={}",
|
"materialized CIR: mirror={} objects={} trust_anchors={} materialized_files={} linked={} copied={} keep_db={}",
|
||||||
mirror_root.display(),
|
mirror_root.display(),
|
||||||
summary.object_count,
|
summary.object_count,
|
||||||
|
summary.trust_anchor_count,
|
||||||
|
summary.materialized_file_count,
|
||||||
summary.linked_files,
|
summary.linked_files,
|
||||||
summary.copied_files,
|
summary.copied_files,
|
||||||
keep_db
|
keep_db
|
||||||
|
|||||||
@ -443,8 +443,8 @@ fn uri_extension(uri: &str) -> String {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use rpki::cir::{
|
use rpki::cir::{
|
||||||
CIR_VERSION_V2, CanonicalInputRepresentation, CirHashAlgorithm, CirObject,
|
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirObject,
|
||||||
CirRejectedObject, CirTal, compute_reject_list_sha256, encode_cir,
|
CirRejectedObject, CirTrustAnchor, compute_reject_list_sha256, encode_cir,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -664,20 +664,28 @@ mod tests {
|
|||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
objects.sort_by(|left, right| left.rsync_uri.cmp(&right.rsync_uri));
|
objects.sort_by(|left, right| left.rsync_uri.cmp(&right.rsync_uri));
|
||||||
CanonicalInputRepresentation {
|
CanonicalInputRepresentation {
|
||||||
version: CIR_VERSION_V2,
|
version: CIR_VERSION_V3,
|
||||||
hash_alg: CirHashAlgorithm::Sha256,
|
hash_alg: CirHashAlgorithm::Sha256,
|
||||||
validation_time: time::OffsetDateTime::UNIX_EPOCH,
|
validation_time: time::OffsetDateTime::UNIX_EPOCH,
|
||||||
objects,
|
objects,
|
||||||
tals: vec![CirTal {
|
trust_anchors: vec![sample_trust_anchor()],
|
||||||
tal_uri: "https://tal.example.test/apnic.tal".to_string(),
|
|
||||||
tal_bytes: b"https://tal.example.test/apnic.tal\nrsync://example.test/ta.cer\nMIIB"
|
|
||||||
.to_vec(),
|
|
||||||
}],
|
|
||||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||||
rejected_objects: Vec::<CirRejectedObject>::new(),
|
rejected_objects: Vec::<CirRejectedObject>::new(),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sample_trust_anchor() -> CirTrustAnchor {
|
||||||
|
let ta_rsync_uri = "rsync://example.test/ta.cer";
|
||||||
|
let ta_certificate_der = b"ta-der".to_vec();
|
||||||
|
CirTrustAnchor {
|
||||||
|
ta_rsync_uri: ta_rsync_uri.to_string(),
|
||||||
|
tal_uri: "https://tal.example.test/apnic.tal".to_string(),
|
||||||
|
tal_bytes: format!("{ta_rsync_uri}\n\nAQID\n").into_bytes(),
|
||||||
|
ta_certificate_sha256: Sha256::digest(&ta_certificate_der).to_vec(),
|
||||||
|
ta_certificate_der,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn write_cir(path: &Path, cir: &CanonicalInputRepresentation) {
|
fn write_cir(path: &Path, cir: &CanonicalInputRepresentation) {
|
||||||
std::fs::write(path, encode_cir(cir).expect("encode cir")).expect("write cir");
|
std::fs::write(path, encode_cir(cir).expect("encode cir")).expect("write cir");
|
||||||
}
|
}
|
||||||
|
|||||||
@ -114,40 +114,56 @@ fn run(args: Args) -> Result<(), String> {
|
|||||||
.iter()
|
.iter()
|
||||||
.map(|item| item.object_uri.clone())
|
.map(|item| item.object_uri.clone())
|
||||||
.collect::<BTreeSet<_>>();
|
.collect::<BTreeSet<_>>();
|
||||||
let ours_tals = ours
|
let ours_trust_anchors = ours
|
||||||
.tals
|
.trust_anchors
|
||||||
.iter()
|
.iter()
|
||||||
.map(|item| item.tal_uri.clone())
|
.map(|item| {
|
||||||
.collect::<BTreeSet<_>>();
|
(
|
||||||
let peer_tals = peer
|
item.ta_rsync_uri.clone(),
|
||||||
.tals
|
hex::encode(&item.ta_certificate_sha256),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<BTreeMap<_, _>>();
|
||||||
|
let peer_trust_anchors = peer
|
||||||
|
.trust_anchors
|
||||||
.iter()
|
.iter()
|
||||||
.map(|item| item.tal_uri.clone())
|
.map(|item| {
|
||||||
.collect::<BTreeSet<_>>();
|
(
|
||||||
|
item.ta_rsync_uri.clone(),
|
||||||
|
hex::encode(&item.ta_certificate_sha256),
|
||||||
|
)
|
||||||
|
})
|
||||||
|
.collect::<BTreeMap<_, _>>();
|
||||||
|
|
||||||
let object_summary = compare_object_maps(&ours_objects, &peer_objects, args.sample_limit);
|
let object_summary = compare_object_maps(&ours_objects, &peer_objects, args.sample_limit);
|
||||||
let reject_summary = compare_sets(&ours_rejects, &peer_rejects, args.sample_limit);
|
let reject_summary = compare_sets(&ours_rejects, &peer_rejects, args.sample_limit);
|
||||||
let tal_summary = compare_sets(&ours_tals, &peer_tals, args.sample_limit);
|
let trust_anchor_summary =
|
||||||
|
compare_object_maps(&ours_trust_anchors, &peer_trust_anchors, args.sample_limit);
|
||||||
let reject_hash_match = ours.reject_list_sha256 == peer.reject_list_sha256;
|
let reject_hash_match = ours.reject_list_sha256 == peer.reject_list_sha256;
|
||||||
let all_match =
|
let all_match = object_summary.match_
|
||||||
object_summary.match_ && reject_summary.match_ && tal_summary.match_ && reject_hash_match;
|
&& reject_summary.match_
|
||||||
|
&& trust_anchor_summary.match_
|
||||||
|
&& reject_hash_match;
|
||||||
|
|
||||||
let summary = json!({
|
let summary = json!({
|
||||||
"allMatch": all_match,
|
"allMatch": all_match,
|
||||||
"objects": object_summary.to_json(),
|
"objects": object_summary.to_json(),
|
||||||
"rejects": reject_summary.to_json(),
|
"rejects": reject_summary.to_json(),
|
||||||
"tals": tal_summary.to_json(),
|
"trustAnchors": trust_anchor_summary.to_json(),
|
||||||
|
"trust_anchors": trust_anchor_summary.to_json(),
|
||||||
"rejectListSha256Match": reject_hash_match,
|
"rejectListSha256Match": reject_hash_match,
|
||||||
"ours": {
|
"ours": {
|
||||||
"objectCount": ours.objects.len(),
|
"objectCount": ours.objects.len(),
|
||||||
"talCount": ours.tals.len(),
|
"talCount": ours.trust_anchors.len(),
|
||||||
|
"trustAnchorCount": ours.trust_anchors.len(),
|
||||||
"rejectCount": ours.rejected_objects.len(),
|
"rejectCount": ours.rejected_objects.len(),
|
||||||
"rejectListSha256": hex::encode(&ours.reject_list_sha256),
|
"rejectListSha256": hex::encode(&ours.reject_list_sha256),
|
||||||
"validationTime": ours.validation_time.to_string(),
|
"validationTime": ours.validation_time.to_string(),
|
||||||
},
|
},
|
||||||
"rpkiClient": {
|
"rpkiClient": {
|
||||||
"objectCount": peer.objects.len(),
|
"objectCount": peer.objects.len(),
|
||||||
"talCount": peer.tals.len(),
|
"talCount": peer.trust_anchors.len(),
|
||||||
|
"trustAnchorCount": peer.trust_anchors.len(),
|
||||||
"rejectCount": peer.rejected_objects.len(),
|
"rejectCount": peer.rejected_objects.len(),
|
||||||
"rejectListSha256": hex::encode(&peer.reject_list_sha256),
|
"rejectListSha256": hex::encode(&peer.reject_list_sha256),
|
||||||
"validationTime": peer.validation_time.to_string(),
|
"validationTime": peer.validation_time.to_string(),
|
||||||
@ -200,23 +216,25 @@ fn write_markdown(path: &Path, summary: &serde_json::Value) -> Result<(), String
|
|||||||
summary["rejects"]["match"].as_bool().unwrap_or(false)
|
summary["rejects"]["match"].as_bool().unwrap_or(false)
|
||||||
),
|
),
|
||||||
format!(
|
format!(
|
||||||
"- `talsMatch`: `{}`",
|
"- `trustAnchorsMatch`: `{}`",
|
||||||
summary["tals"]["match"].as_bool().unwrap_or(false)
|
summary["trustAnchors"]["match"].as_bool().unwrap_or(false)
|
||||||
),
|
),
|
||||||
format!(
|
format!(
|
||||||
"- `rejectListSha256Match`: `{}`",
|
"- `rejectListSha256Match`: `{}`",
|
||||||
summary["rejectListSha256Match"].as_bool().unwrap_or(false)
|
summary["rejectListSha256Match"].as_bool().unwrap_or(false)
|
||||||
),
|
),
|
||||||
format!(
|
format!(
|
||||||
"- `ours`: objects `{}`, tals `{}`, rejects `{}`",
|
"- `ours`: objects `{}`, trustAnchors `{}`, rejects `{}`",
|
||||||
summary["ours"]["objectCount"].as_u64().unwrap_or(0),
|
summary["ours"]["objectCount"].as_u64().unwrap_or(0),
|
||||||
summary["ours"]["talCount"].as_u64().unwrap_or(0),
|
summary["ours"]["trustAnchorCount"].as_u64().unwrap_or(0),
|
||||||
summary["ours"]["rejectCount"].as_u64().unwrap_or(0)
|
summary["ours"]["rejectCount"].as_u64().unwrap_or(0)
|
||||||
),
|
),
|
||||||
format!(
|
format!(
|
||||||
"- `rpkiClient`: objects `{}`, tals `{}`, rejects `{}`",
|
"- `rpkiClient`: objects `{}`, trustAnchors `{}`, rejects `{}`",
|
||||||
summary["rpkiClient"]["objectCount"].as_u64().unwrap_or(0),
|
summary["rpkiClient"]["objectCount"].as_u64().unwrap_or(0),
|
||||||
summary["rpkiClient"]["talCount"].as_u64().unwrap_or(0),
|
summary["rpkiClient"]["trustAnchorCount"]
|
||||||
|
.as_u64()
|
||||||
|
.unwrap_or(0),
|
||||||
summary["rpkiClient"]["rejectCount"].as_u64().unwrap_or(0)
|
summary["rpkiClient"]["rejectCount"].as_u64().unwrap_or(0)
|
||||||
),
|
),
|
||||||
];
|
];
|
||||||
@ -410,8 +428,8 @@ fn uri_extension(uri: &str) -> String {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use rpki::cir::{
|
use rpki::cir::{
|
||||||
CIR_VERSION_V2, CanonicalInputRepresentation, CirHashAlgorithm, CirObject,
|
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirObject,
|
||||||
CirRejectedObject, CirTal, compute_reject_list_sha256, encode_cir,
|
CirRejectedObject, CirTrustAnchor, compute_reject_list_sha256, encode_cir, sha256,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -513,7 +531,7 @@ mod tests {
|
|||||||
assert_eq!(summary["allMatch"], true);
|
assert_eq!(summary["allMatch"], true);
|
||||||
assert_eq!(summary["objects"]["match"], true);
|
assert_eq!(summary["objects"]["match"], true);
|
||||||
assert_eq!(summary["rejects"]["match"], true);
|
assert_eq!(summary["rejects"]["match"], true);
|
||||||
assert_eq!(summary["tals"]["match"], true);
|
assert_eq!(summary["trustAnchors"]["match"], true);
|
||||||
assert_eq!(summary["rejectListSha256Match"], true);
|
assert_eq!(summary["rejectListSha256Match"], true);
|
||||||
assert_eq!(summary["ours"]["objectCount"], 2);
|
assert_eq!(summary["ours"]["objectCount"], 2);
|
||||||
assert!(
|
assert!(
|
||||||
@ -524,7 +542,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn run_reports_object_reject_tal_differences_with_samples() {
|
fn run_reports_object_reject_trust_anchor_differences_with_samples() {
|
||||||
let temp = tempfile::tempdir().expect("tempdir");
|
let temp = tempfile::tempdir().expect("tempdir");
|
||||||
let ours_cir = sample_cir(
|
let ours_cir = sample_cir(
|
||||||
&[
|
&[
|
||||||
@ -581,7 +599,7 @@ mod tests {
|
|||||||
1
|
1
|
||||||
);
|
);
|
||||||
assert_eq!(summary["rejects"]["match"], false);
|
assert_eq!(summary["rejects"]["match"], false);
|
||||||
assert_eq!(summary["tals"]["match"], false);
|
assert_eq!(summary["trustAnchors"]["match"], false);
|
||||||
assert_eq!(summary["rejectListSha256Match"], false);
|
assert_eq!(summary["rejectListSha256Match"], false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -700,7 +718,7 @@ mod tests {
|
|||||||
|
|
||||||
fn sample_cir(
|
fn sample_cir(
|
||||||
objects: &[(&str, u8)],
|
objects: &[(&str, u8)],
|
||||||
tals: &[&str],
|
trust_anchors: &[&str],
|
||||||
rejected_objects: &[(&str, Option<&str>)],
|
rejected_objects: &[(&str, Option<&str>)],
|
||||||
) -> CanonicalInputRepresentation {
|
) -> CanonicalInputRepresentation {
|
||||||
let rejected_objects = rejected_objects
|
let rejected_objects = rejected_objects
|
||||||
@ -711,7 +729,7 @@ mod tests {
|
|||||||
})
|
})
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
CanonicalInputRepresentation {
|
CanonicalInputRepresentation {
|
||||||
version: CIR_VERSION_V2,
|
version: CIR_VERSION_V3,
|
||||||
hash_alg: CirHashAlgorithm::Sha256,
|
hash_alg: CirHashAlgorithm::Sha256,
|
||||||
validation_time: sample_time(),
|
validation_time: sample_time(),
|
||||||
objects: objects
|
objects: objects
|
||||||
@ -721,12 +739,23 @@ mod tests {
|
|||||||
sha256: vec![*fill; 32],
|
sha256: vec![*fill; 32],
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
tals: tals
|
trust_anchors: trust_anchors
|
||||||
.iter()
|
.iter()
|
||||||
.map(|tal_uri| CirTal {
|
.map(|tal_uri| {
|
||||||
tal_uri: (*tal_uri).to_string(),
|
let name = tal_uri
|
||||||
tal_bytes: format!("{tal_uri}\nrsync://example.net/repo/ta.cer\nMIIB")
|
.rsplit('/')
|
||||||
.into_bytes(),
|
.next()
|
||||||
|
.unwrap_or("root.tal")
|
||||||
|
.trim_end_matches(".tal");
|
||||||
|
let ta_rsync_uri = format!("rsync://example.net/repo/{name}.cer");
|
||||||
|
let ta_certificate_der = format!("ta-der-{name}").into_bytes();
|
||||||
|
CirTrustAnchor {
|
||||||
|
ta_rsync_uri: ta_rsync_uri.clone(),
|
||||||
|
tal_uri: (*tal_uri).to_string(),
|
||||||
|
tal_bytes: format!("{ta_rsync_uri}\n\nAQID\n").into_bytes(),
|
||||||
|
ta_certificate_sha256: sha256(&ta_certificate_der),
|
||||||
|
ta_certificate_der,
|
||||||
|
}
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
reject_list_sha256: compute_reject_list_sha256(
|
reject_list_sha256: compute_reject_list_sha256(
|
||||||
|
|||||||
@ -1,11 +1,9 @@
|
|||||||
use std::path::PathBuf;
|
use std::path::PathBuf;
|
||||||
|
|
||||||
use rpki::blob_store::ExternalRepoBytesDb;
|
|
||||||
use rpki::cir::{
|
use rpki::cir::{
|
||||||
CIR_VERSION_V2, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirTal,
|
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirTrustAnchor,
|
||||||
compute_reject_list_sha256, encode_cir,
|
compute_reject_list_sha256, encode_cir, sha256,
|
||||||
};
|
};
|
||||||
use sha2::Digest;
|
|
||||||
|
|
||||||
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>";
|
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>";
|
||||||
|
|
||||||
@ -88,6 +86,7 @@ fn parse_args(
|
|||||||
fn main() -> Result<(), String> {
|
fn main() -> Result<(), String> {
|
||||||
let argv: Vec<String> = std::env::args().collect();
|
let argv: Vec<String> = std::env::args().collect();
|
||||||
let (tal_path, ta_path, tal_uri, validation_time, cir_out, repo_bytes_db) = parse_args(&argv)?;
|
let (tal_path, ta_path, tal_uri, validation_time, cir_out, repo_bytes_db) = parse_args(&argv)?;
|
||||||
|
let _ = repo_bytes_db;
|
||||||
|
|
||||||
let tal_bytes = std::fs::read(&tal_path).map_err(|e| format!("read tal failed: {e}"))?;
|
let tal_bytes = std::fs::read(&tal_path).map_err(|e| format!("read tal failed: {e}"))?;
|
||||||
let ta_bytes = std::fs::read(&ta_path).map_err(|e| format!("read ta failed: {e}"))?;
|
let ta_bytes = std::fs::read(&ta_path).map_err(|e| format!("read ta failed: {e}"))?;
|
||||||
@ -101,22 +100,20 @@ fn main() -> Result<(), String> {
|
|||||||
.as_str()
|
.as_str()
|
||||||
.to_string();
|
.to_string();
|
||||||
|
|
||||||
let sha = sha2::Sha256::digest(&ta_bytes);
|
let ta_certificate_sha256 = sha256(&ta_bytes);
|
||||||
let hash_hex = hex::encode(sha);
|
|
||||||
ExternalRepoBytesDb::open(&repo_bytes_db)
|
|
||||||
.map_err(|e| format!("open repo bytes db failed: {e}"))?
|
|
||||||
.put_blob_bytes_batch(&[(hash_hex, ta_bytes.clone())])
|
|
||||||
.map_err(|e| format!("write repo bytes db failed: {e}"))?;
|
|
||||||
|
|
||||||
let cir = CanonicalInputRepresentation {
|
let cir = CanonicalInputRepresentation {
|
||||||
version: CIR_VERSION_V2,
|
version: CIR_VERSION_V3,
|
||||||
hash_alg: CirHashAlgorithm::Sha256,
|
hash_alg: CirHashAlgorithm::Sha256,
|
||||||
validation_time,
|
validation_time,
|
||||||
objects: vec![CirObject {
|
objects: Vec::new(),
|
||||||
rsync_uri: ta_rsync_uri,
|
trust_anchors: vec![CirTrustAnchor {
|
||||||
sha256: sha.to_vec(),
|
ta_rsync_uri,
|
||||||
|
tal_uri,
|
||||||
|
tal_bytes,
|
||||||
|
ta_certificate_der: ta_bytes,
|
||||||
|
ta_certificate_sha256,
|
||||||
}],
|
}],
|
||||||
tals: vec![CirTal { tal_uri, tal_bytes }],
|
|
||||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||||
rejected_objects: Vec::new(),
|
rejected_objects: Vec::new(),
|
||||||
};
|
};
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use crate::cir::model::{
|
use crate::cir::model::{
|
||||||
CIR_VERSION_V2, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirRejectedObject,
|
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirRejectedObject,
|
||||||
CirTal,
|
CirTrustAnchor,
|
||||||
};
|
};
|
||||||
use crate::data_model::common::DerReader;
|
use crate::data_model::common::DerReader;
|
||||||
use crate::data_model::oid::{OID_SHA256, OID_SHA256_RAW};
|
use crate::data_model::oid::{OID_SHA256, OID_SHA256_RAW};
|
||||||
@ -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;
|
let version = seq.take_uint_u64().map_err(CirDecodeError::Parse)? as u32;
|
||||||
if version != CIR_VERSION_V2 {
|
if version != CIR_VERSION_V3 {
|
||||||
return Err(CirDecodeError::UnexpectedVersion {
|
return Err(CirDecodeError::UnexpectedVersion {
|
||||||
expected: CIR_VERSION_V2,
|
expected: CIR_VERSION_V3,
|
||||||
actual: version,
|
actual: version,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
@ -52,12 +52,14 @@ pub fn decode_cir(der: &[u8]) -> Result<CanonicalInputRepresentation, CirDecodeE
|
|||||||
objects.push(decode_object(full)?);
|
objects.push(decode_object(full)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
let tals_der = seq.take_tag(0x30).map_err(CirDecodeError::Parse)?;
|
let trust_anchors_der = seq.take_tag(0x30).map_err(CirDecodeError::Parse)?;
|
||||||
let mut tals_reader = DerReader::new(tals_der);
|
let mut trust_anchors_reader = DerReader::new(trust_anchors_der);
|
||||||
let mut tals = Vec::new();
|
let mut trust_anchors = Vec::new();
|
||||||
while !tals_reader.is_empty() {
|
while !trust_anchors_reader.is_empty() {
|
||||||
let (_tag, full, _value) = tals_reader.take_any_full().map_err(CirDecodeError::Parse)?;
|
let (_tag, full, _value) = trust_anchors_reader
|
||||||
tals.push(decode_tal(full)?);
|
.take_any_full()
|
||||||
|
.map_err(CirDecodeError::Parse)?;
|
||||||
|
trust_anchors.push(decode_trust_anchor(full)?);
|
||||||
}
|
}
|
||||||
|
|
||||||
let reject_list_sha256 = seq
|
let reject_list_sha256 = seq
|
||||||
@ -84,7 +86,7 @@ pub fn decode_cir(der: &[u8]) -> Result<CanonicalInputRepresentation, CirDecodeE
|
|||||||
hash_alg,
|
hash_alg,
|
||||||
validation_time,
|
validation_time,
|
||||||
objects,
|
objects,
|
||||||
tals,
|
trust_anchors,
|
||||||
reject_list_sha256,
|
reject_list_sha256,
|
||||||
rejected_objects,
|
rejected_objects,
|
||||||
};
|
};
|
||||||
@ -123,12 +125,17 @@ fn decode_object(der: &[u8]) -> Result<CirObject, CirDecodeError> {
|
|||||||
Ok(CirObject { rsync_uri, sha256 })
|
Ok(CirObject { rsync_uri, sha256 })
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode_tal(der: &[u8]) -> Result<CirTal, CirDecodeError> {
|
fn decode_trust_anchor(der: &[u8]) -> Result<CirTrustAnchor, CirDecodeError> {
|
||||||
let mut top = DerReader::new(der);
|
let mut top = DerReader::new(der);
|
||||||
let mut seq = top.take_sequence().map_err(CirDecodeError::Parse)?;
|
let mut seq = top.take_sequence().map_err(CirDecodeError::Parse)?;
|
||||||
if !top.is_empty() {
|
if !top.is_empty() {
|
||||||
return Err(CirDecodeError::Parse("trailing bytes after CirTal".into()));
|
return Err(CirDecodeError::Parse(
|
||||||
|
"trailing bytes after CirTrustAnchor".into(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
|
let ta_rsync_uri = std::str::from_utf8(seq.take_tag(0x16).map_err(CirDecodeError::Parse)?)
|
||||||
|
.map_err(|e| CirDecodeError::Parse(e.to_string()))?
|
||||||
|
.to_string();
|
||||||
let tal_uri = std::str::from_utf8(seq.take_tag(0x16).map_err(CirDecodeError::Parse)?)
|
let tal_uri = std::str::from_utf8(seq.take_tag(0x16).map_err(CirDecodeError::Parse)?)
|
||||||
.map_err(|e| CirDecodeError::Parse(e.to_string()))?
|
.map_err(|e| CirDecodeError::Parse(e.to_string()))?
|
||||||
.to_string();
|
.to_string();
|
||||||
@ -136,10 +143,26 @@ fn decode_tal(der: &[u8]) -> Result<CirTal, CirDecodeError> {
|
|||||||
.take_octet_string()
|
.take_octet_string()
|
||||||
.map_err(CirDecodeError::Parse)?
|
.map_err(CirDecodeError::Parse)?
|
||||||
.to_vec();
|
.to_vec();
|
||||||
|
let ta_certificate_der = seq
|
||||||
|
.take_octet_string()
|
||||||
|
.map_err(CirDecodeError::Parse)?
|
||||||
|
.to_vec();
|
||||||
|
let ta_certificate_sha256 = seq
|
||||||
|
.take_octet_string()
|
||||||
|
.map_err(CirDecodeError::Parse)?
|
||||||
|
.to_vec();
|
||||||
if !seq.is_empty() {
|
if !seq.is_empty() {
|
||||||
return Err(CirDecodeError::Parse("trailing fields in CirTal".into()));
|
return Err(CirDecodeError::Parse(
|
||||||
|
"trailing fields in CirTrustAnchor".into(),
|
||||||
|
));
|
||||||
}
|
}
|
||||||
Ok(CirTal { tal_uri, tal_bytes })
|
Ok(CirTrustAnchor {
|
||||||
|
ta_rsync_uri,
|
||||||
|
tal_uri,
|
||||||
|
tal_bytes,
|
||||||
|
ta_certificate_der,
|
||||||
|
ta_certificate_sha256,
|
||||||
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn decode_rejected_object(der: &[u8]) -> Result<CirRejectedObject, CirDecodeError> {
|
fn decode_rejected_object(der: &[u8]) -> Result<CirRejectedObject, CirDecodeError> {
|
||||||
|
|||||||
@ -1,6 +1,6 @@
|
|||||||
use crate::cir::model::{
|
use crate::cir::model::{
|
||||||
CIR_VERSION_V2, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirRejectedObject,
|
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirRejectedObject,
|
||||||
CirTal,
|
CirTrustAnchor,
|
||||||
};
|
};
|
||||||
use crate::data_model::oid::OID_SHA256_RAW;
|
use crate::data_model::oid::OID_SHA256_RAW;
|
||||||
|
|
||||||
@ -25,9 +25,9 @@ pub fn encode_cir(cir: &CanonicalInputRepresentation) -> Result<Vec<u8>, CirEnco
|
|||||||
.collect::<Result<Vec<_>, _>>()?,
|
.collect::<Result<Vec<_>, _>>()?,
|
||||||
),
|
),
|
||||||
encode_sequence(
|
encode_sequence(
|
||||||
&cir.tals
|
&cir.trust_anchors
|
||||||
.iter()
|
.iter()
|
||||||
.map(encode_tal)
|
.map(encode_trust_anchor)
|
||||||
.collect::<Result<Vec<_>, _>>()?,
|
.collect::<Result<Vec<_>, _>>()?,
|
||||||
),
|
),
|
||||||
encode_octet_string(&cir.reject_list_sha256),
|
encode_octet_string(&cir.reject_list_sha256),
|
||||||
@ -48,11 +48,14 @@ fn encode_object(object: &CirObject) -> Result<Vec<u8>, CirEncodeError> {
|
|||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
|
||||||
fn encode_tal(tal: &CirTal) -> Result<Vec<u8>, CirEncodeError> {
|
fn encode_trust_anchor(trust_anchor: &CirTrustAnchor) -> Result<Vec<u8>, CirEncodeError> {
|
||||||
tal.validate().map_err(CirEncodeError::Validate)?;
|
trust_anchor.validate().map_err(CirEncodeError::Validate)?;
|
||||||
Ok(encode_sequence(&[
|
Ok(encode_sequence(&[
|
||||||
encode_ia5_string(tal.tal_uri.as_bytes()),
|
encode_ia5_string(trust_anchor.ta_rsync_uri.as_bytes()),
|
||||||
encode_octet_string(&tal.tal_bytes),
|
encode_ia5_string(trust_anchor.tal_uri.as_bytes()),
|
||||||
|
encode_octet_string(&trust_anchor.tal_bytes),
|
||||||
|
encode_octet_string(&trust_anchor.ta_certificate_der),
|
||||||
|
encode_octet_string(&trust_anchor.ta_certificate_sha256),
|
||||||
]))
|
]))
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -160,5 +163,5 @@ fn encode_len_into(len: usize, out: &mut Vec<u8>) {
|
|||||||
|
|
||||||
#[allow(dead_code)]
|
#[allow(dead_code)]
|
||||||
const _: () = {
|
const _: () = {
|
||||||
let _ = CIR_VERSION_V2;
|
let _ = CIR_VERSION_V3;
|
||||||
};
|
};
|
||||||
|
|||||||
@ -5,12 +5,11 @@ use std::path::Path;
|
|||||||
use crate::audit::{AuditObjectResult, PublicationPointAudit};
|
use crate::audit::{AuditObjectResult, PublicationPointAudit};
|
||||||
use crate::cir::encode::{CirEncodeError, encode_cir};
|
use crate::cir::encode::{CirEncodeError, encode_cir};
|
||||||
use crate::cir::model::{
|
use crate::cir::model::{
|
||||||
CIR_VERSION_V2, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirRejectedObject,
|
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirRejectedObject,
|
||||||
CirTal, compute_reject_list_sha256,
|
CirTrustAnchor, compute_reject_list_sha256,
|
||||||
};
|
};
|
||||||
use crate::cir::static_pool::{
|
use crate::cir::static_pool::{
|
||||||
CirStaticPoolError, CirStaticPoolExportSummary, export_hashes_from_store,
|
CirStaticPoolError, CirStaticPoolExportSummary, export_hashes_from_store,
|
||||||
write_bytes_to_static_pool,
|
|
||||||
};
|
};
|
||||||
use crate::current_repo_index::CurrentRepoObject;
|
use crate::current_repo_index::CurrentRepoObject;
|
||||||
use crate::data_model::ta::TrustAnchor;
|
use crate::data_model::ta::TrustAnchor;
|
||||||
@ -49,9 +48,6 @@ pub enum CirExportError {
|
|||||||
|
|
||||||
#[error("write CIR file failed: {0}: {1}")]
|
#[error("write CIR file failed: {0}: {1}")]
|
||||||
Write(String, String),
|
Write(String, String),
|
||||||
|
|
||||||
#[error("write CIR trust anchor bytes to repo store failed: {0}")]
|
|
||||||
WriteRepoBytes(String),
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
@ -64,12 +60,12 @@ pub struct CirRawStoreExportSummary {
|
|||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct CirExportSummary {
|
pub struct CirExportSummary {
|
||||||
pub object_count: usize,
|
pub object_count: usize,
|
||||||
pub tal_count: usize,
|
pub trust_anchor_count: usize,
|
||||||
pub timing: CirExportTiming,
|
pub timing: CirExportTiming,
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Copy, Debug)]
|
#[derive(Clone, Copy, Debug)]
|
||||||
pub struct CirTalBinding<'a> {
|
pub struct CirTrustAnchorBinding<'a> {
|
||||||
pub trust_anchor: &'a TrustAnchor,
|
pub trust_anchor: &'a TrustAnchor,
|
||||||
pub tal_uri: &'a str,
|
pub tal_uri: &'a str,
|
||||||
}
|
}
|
||||||
@ -118,6 +114,22 @@ fn collect_cir_objects_from_validation_audit(
|
|||||||
Ok(objects)
|
Ok(objects)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn canonical_ta_rsync_uri(trust_anchor: &TrustAnchor) -> Result<String, CirExportError> {
|
||||||
|
if let Some(uri) = &trust_anchor.resolved_ta_uri
|
||||||
|
&& uri.scheme() == "rsync"
|
||||||
|
{
|
||||||
|
return Ok(uri.as_str().to_string());
|
||||||
|
}
|
||||||
|
trust_anchor
|
||||||
|
.tal
|
||||||
|
.ta_uris
|
||||||
|
.iter()
|
||||||
|
.filter(|uri| uri.scheme() == "rsync")
|
||||||
|
.map(|uri| uri.as_str().to_string())
|
||||||
|
.min()
|
||||||
|
.ok_or(CirExportError::MissingTaRsyncUri)
|
||||||
|
}
|
||||||
|
|
||||||
pub fn build_cir_from_run(
|
pub fn build_cir_from_run(
|
||||||
store: &RocksStore,
|
store: &RocksStore,
|
||||||
trust_anchor: &TrustAnchor,
|
trust_anchor: &TrustAnchor,
|
||||||
@ -127,7 +139,7 @@ pub fn build_cir_from_run(
|
|||||||
) -> Result<CanonicalInputRepresentation, CirExportError> {
|
) -> Result<CanonicalInputRepresentation, CirExportError> {
|
||||||
build_cir_from_run_multi(
|
build_cir_from_run_multi(
|
||||||
store,
|
store,
|
||||||
&[CirTalBinding {
|
&[CirTrustAnchorBinding {
|
||||||
trust_anchor,
|
trust_anchor,
|
||||||
tal_uri,
|
tal_uri,
|
||||||
}],
|
}],
|
||||||
@ -139,7 +151,7 @@ pub fn build_cir_from_run(
|
|||||||
|
|
||||||
pub fn build_cir_from_run_multi(
|
pub fn build_cir_from_run_multi(
|
||||||
_store: &RocksStore,
|
_store: &RocksStore,
|
||||||
tal_bindings: &[CirTalBinding<'_>],
|
tal_bindings: &[CirTrustAnchorBinding<'_>],
|
||||||
validation_time: time::OffsetDateTime,
|
validation_time: time::OffsetDateTime,
|
||||||
publication_points: &[PublicationPointAudit],
|
publication_points: &[PublicationPointAudit],
|
||||||
_current_repo_objects: Option<&[CurrentRepoObject]>,
|
_current_repo_objects: Option<&[CurrentRepoObject]>,
|
||||||
@ -150,30 +162,24 @@ pub fn build_cir_from_run_multi(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
let mut objects = collect_cir_objects_from_validation_audit(publication_points)?;
|
let objects = collect_cir_objects_from_validation_audit(publication_points)?;
|
||||||
|
|
||||||
let mut tals = Vec::with_capacity(tal_bindings.len());
|
let mut trust_anchors = Vec::with_capacity(tal_bindings.len());
|
||||||
for binding in tal_bindings {
|
for binding in tal_bindings {
|
||||||
let ta_hash = ta_sha256_hex(&binding.trust_anchor.ta_certificate.raw_der);
|
let ta_rsync_uri = canonical_ta_rsync_uri(binding.trust_anchor)?;
|
||||||
let mut saw_rsync_uri = false;
|
let ta_certificate_der = binding.trust_anchor.ta_certificate.raw_der.clone();
|
||||||
for uri in &binding.trust_anchor.tal.ta_uris {
|
trust_anchors.push(CirTrustAnchor {
|
||||||
if uri.scheme() == "rsync" {
|
ta_rsync_uri,
|
||||||
saw_rsync_uri = true;
|
|
||||||
objects.insert(uri.as_str().to_string(), ta_hash.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if !saw_rsync_uri {
|
|
||||||
return Err(CirExportError::MissingTaRsyncUri);
|
|
||||||
}
|
|
||||||
tals.push(CirTal {
|
|
||||||
tal_uri: binding.tal_uri.to_string(),
|
tal_uri: binding.tal_uri.to_string(),
|
||||||
tal_bytes: binding.trust_anchor.tal.raw.clone(),
|
tal_bytes: binding.trust_anchor.tal.raw.clone(),
|
||||||
|
ta_certificate_sha256: crate::cir::model::sha256(&ta_certificate_der),
|
||||||
|
ta_certificate_der,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
tals.sort_by(|a, b| a.tal_uri.cmp(&b.tal_uri));
|
trust_anchors.sort_by(|a, b| a.ta_rsync_uri.cmp(&b.ta_rsync_uri));
|
||||||
|
|
||||||
let cir = CanonicalInputRepresentation {
|
let cir = CanonicalInputRepresentation {
|
||||||
version: CIR_VERSION_V2,
|
version: CIR_VERSION_V3,
|
||||||
hash_alg: CirHashAlgorithm::Sha256,
|
hash_alg: CirHashAlgorithm::Sha256,
|
||||||
validation_time: validation_time.to_offset(time::UtcOffset::UTC),
|
validation_time: validation_time.to_offset(time::UtcOffset::UTC),
|
||||||
objects: objects
|
objects: objects
|
||||||
@ -183,7 +189,7 @@ pub fn build_cir_from_run_multi(
|
|||||||
sha256: hex::decode(sha256_hex).expect("validated hex"),
|
sha256: hex::decode(sha256_hex).expect("validated hex"),
|
||||||
})
|
})
|
||||||
.collect(),
|
.collect(),
|
||||||
tals,
|
trust_anchors,
|
||||||
reject_list_sha256: Vec::new(),
|
reject_list_sha256: Vec::new(),
|
||||||
rejected_objects: Vec::new(),
|
rejected_objects: Vec::new(),
|
||||||
};
|
};
|
||||||
@ -238,36 +244,13 @@ pub fn export_cir_static_pool(
|
|||||||
cir: &CanonicalInputRepresentation,
|
cir: &CanonicalInputRepresentation,
|
||||||
trust_anchors: &[&TrustAnchor],
|
trust_anchors: &[&TrustAnchor],
|
||||||
) -> Result<CirStaticPoolExportSummary, CirExportError> {
|
) -> Result<CirStaticPoolExportSummary, CirExportError> {
|
||||||
let ta_hashes = trust_anchors
|
let _ = trust_anchors;
|
||||||
.iter()
|
|
||||||
.map(|ta| ta_sha256_hex(&ta.ta_certificate.raw_der))
|
|
||||||
.collect::<BTreeSet<_>>();
|
|
||||||
let hashes = cir
|
let hashes = cir
|
||||||
.objects
|
.objects
|
||||||
.iter()
|
.iter()
|
||||||
.map(|item| hex::encode(&item.sha256))
|
.map(|item| hex::encode(&item.sha256))
|
||||||
.filter(|hash| !ta_hashes.contains(hash))
|
|
||||||
.collect::<Vec<_>>();
|
.collect::<Vec<_>>();
|
||||||
let mut summary = export_hashes_from_store(store, static_root, capture_date_utc, &hashes)?;
|
export_hashes_from_store(store, static_root, capture_date_utc, &hashes).map_err(Into::into)
|
||||||
|
|
||||||
let mut unique = hashes.iter().cloned().collect::<BTreeSet<_>>();
|
|
||||||
for trust_anchor in trust_anchors {
|
|
||||||
let ta_hash = ta_sha256_hex(&trust_anchor.ta_certificate.raw_der);
|
|
||||||
let ta_result = write_bytes_to_static_pool(
|
|
||||||
static_root,
|
|
||||||
capture_date_utc,
|
|
||||||
&ta_hash,
|
|
||||||
&trust_anchor.ta_certificate.raw_der,
|
|
||||||
)?;
|
|
||||||
unique.insert(ta_hash);
|
|
||||||
if ta_result.written {
|
|
||||||
summary.written_files += 1;
|
|
||||||
} else {
|
|
||||||
summary.reused_files += 1;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
summary.unique_hashes = unique.len();
|
|
||||||
Ok(summary)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn export_cir_raw_store(
|
pub fn export_cir_raw_store(
|
||||||
@ -276,17 +259,14 @@ pub fn export_cir_raw_store(
|
|||||||
cir: &CanonicalInputRepresentation,
|
cir: &CanonicalInputRepresentation,
|
||||||
trust_anchors: &[&TrustAnchor],
|
trust_anchors: &[&TrustAnchor],
|
||||||
) -> Result<CirRawStoreExportSummary, CirExportError> {
|
) -> Result<CirRawStoreExportSummary, CirExportError> {
|
||||||
let ta_by_hash = trust_anchors
|
let _ = trust_anchors;
|
||||||
.iter()
|
|
||||||
.map(|ta| (ta_sha256_hex(&ta.ta_certificate.raw_der), *ta))
|
|
||||||
.collect::<BTreeMap<_, _>>();
|
|
||||||
let unique: BTreeSet<String> = cir
|
let unique: BTreeSet<String> = cir
|
||||||
.objects
|
.objects
|
||||||
.iter()
|
.iter()
|
||||||
.map(|item| hex::encode(&item.sha256))
|
.map(|item| hex::encode(&item.sha256))
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
let mut written_entries = 0usize;
|
let written_entries = 0usize;
|
||||||
let mut reused_entries = 0usize;
|
let mut reused_entries = 0usize;
|
||||||
for sha256_hex in &unique {
|
for sha256_hex in &unique {
|
||||||
if store
|
if store
|
||||||
@ -299,23 +279,6 @@ pub fn export_cir_raw_store(
|
|||||||
reused_entries += 1;
|
reused_entries += 1;
|
||||||
continue;
|
continue;
|
||||||
}
|
}
|
||||||
if let Some(trust_anchor) = ta_by_hash.get(sha256_hex) {
|
|
||||||
let mut entry = crate::storage::RawByHashEntry::from_bytes(
|
|
||||||
sha256_hex.clone(),
|
|
||||||
trust_anchor.ta_certificate.raw_der.clone(),
|
|
||||||
);
|
|
||||||
entry.object_type = Some("cer".to_string());
|
|
||||||
for object in &cir.objects {
|
|
||||||
if hex::encode(&object.sha256) == *sha256_hex {
|
|
||||||
entry.origin_uris.push(object.rsync_uri.clone());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
store.put_raw_by_hash_entry(&entry).map_err(|e| {
|
|
||||||
CirExportError::Write(raw_store_path.display().to_string(), e.to_string())
|
|
||||||
})?;
|
|
||||||
written_entries += 1;
|
|
||||||
continue;
|
|
||||||
}
|
|
||||||
return Err(CirExportError::Write(
|
return Err(CirExportError::Write(
|
||||||
raw_store_path.display().to_string(),
|
raw_store_path.display().to_string(),
|
||||||
format!("raw store missing object for sha256={sha256_hex}"),
|
format!("raw store missing object for sha256={sha256_hex}"),
|
||||||
@ -340,7 +303,7 @@ pub fn export_cir_from_run(
|
|||||||
) -> Result<CirExportSummary, CirExportError> {
|
) -> Result<CirExportSummary, CirExportError> {
|
||||||
export_cir_from_run_multi(
|
export_cir_from_run_multi(
|
||||||
store,
|
store,
|
||||||
&[CirTalBinding {
|
&[CirTrustAnchorBinding {
|
||||||
trust_anchor,
|
trust_anchor,
|
||||||
tal_uri,
|
tal_uri,
|
||||||
}],
|
}],
|
||||||
@ -354,7 +317,7 @@ pub fn export_cir_from_run(
|
|||||||
|
|
||||||
pub fn export_cir_from_run_multi(
|
pub fn export_cir_from_run_multi(
|
||||||
store: &RocksStore,
|
store: &RocksStore,
|
||||||
tal_bindings: &[CirTalBinding<'_>],
|
tal_bindings: &[CirTrustAnchorBinding<'_>],
|
||||||
validation_time: time::OffsetDateTime,
|
validation_time: time::OffsetDateTime,
|
||||||
publication_points: &[PublicationPointAudit],
|
publication_points: &[PublicationPointAudit],
|
||||||
cir_out: &Path,
|
cir_out: &Path,
|
||||||
@ -374,18 +337,7 @@ pub fn export_cir_from_run_multi(
|
|||||||
)?;
|
)?;
|
||||||
let build_cir_ms = started.elapsed().as_millis() as u64;
|
let build_cir_ms = started.elapsed().as_millis() as u64;
|
||||||
|
|
||||||
let ta_blobs = tal_bindings
|
let _ = store;
|
||||||
.iter()
|
|
||||||
.map(|binding| {
|
|
||||||
(
|
|
||||||
ta_sha256_hex(&binding.trust_anchor.ta_certificate.raw_der),
|
|
||||||
binding.trust_anchor.ta_certificate.raw_der.clone(),
|
|
||||||
)
|
|
||||||
})
|
|
||||||
.collect::<Vec<_>>();
|
|
||||||
store
|
|
||||||
.put_blob_bytes_batch(&ta_blobs)
|
|
||||||
.map_err(|e| CirExportError::WriteRepoBytes(e.to_string()))?;
|
|
||||||
|
|
||||||
let started = std::time::Instant::now();
|
let started = std::time::Instant::now();
|
||||||
write_cir_file(cir_out, &cir)?;
|
write_cir_file(cir_out, &cir)?;
|
||||||
@ -393,7 +345,7 @@ pub fn export_cir_from_run_multi(
|
|||||||
|
|
||||||
Ok(CirExportSummary {
|
Ok(CirExportSummary {
|
||||||
object_count: cir.objects.len(),
|
object_count: cir.objects.len(),
|
||||||
tal_count: cir.tals.len(),
|
trust_anchor_count: cir.trust_anchors.len(),
|
||||||
timing: CirExportTiming {
|
timing: CirExportTiming {
|
||||||
build_cir_ms,
|
build_cir_ms,
|
||||||
write_cir_ms,
|
write_cir_ms,
|
||||||
@ -402,11 +354,6 @@ pub fn export_cir_from_run_multi(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn ta_sha256_hex(bytes: &[u8]) -> String {
|
|
||||||
use sha2::{Digest, Sha256};
|
|
||||||
hex::encode(Sha256::digest(bytes))
|
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
@ -497,19 +444,23 @@ mod tests {
|
|||||||
&publication_points,
|
&publication_points,
|
||||||
)
|
)
|
||||||
.expect("build cir");
|
.expect("build cir");
|
||||||
assert_eq!(cir.version, CIR_VERSION_V2);
|
assert_eq!(cir.version, CIR_VERSION_V3);
|
||||||
assert_eq!(cir.tals.len(), 1);
|
assert_eq!(cir.trust_anchors.len(), 1);
|
||||||
assert_eq!(cir.tals[0].tal_uri, "https://example.test/root.tal");
|
assert_eq!(
|
||||||
|
cir.trust_anchors[0].tal_uri,
|
||||||
|
"https://example.test/root.tal"
|
||||||
|
);
|
||||||
assert!(
|
assert!(
|
||||||
cir.objects
|
cir.objects
|
||||||
.iter()
|
.iter()
|
||||||
.any(|item| item.rsync_uri == "rsync://example.test/repo/a.cer")
|
.any(|item| item.rsync_uri == "rsync://example.test/repo/a.cer")
|
||||||
);
|
);
|
||||||
assert!(
|
assert!(
|
||||||
cir.objects
|
!cir.objects
|
||||||
.iter()
|
.iter()
|
||||||
.any(|item| item.rsync_uri.contains("apnic-rpki-root-iana-origin.cer"))
|
.any(|item| item.rsync_uri == cir.trust_anchors[0].ta_rsync_uri)
|
||||||
);
|
);
|
||||||
|
assert!(!cir.trust_anchors[0].ta_certificate_der.is_empty());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -549,13 +500,16 @@ mod tests {
|
|||||||
sample_date(),
|
sample_date(),
|
||||||
)
|
)
|
||||||
.expect("export cir");
|
.expect("export cir");
|
||||||
assert_eq!(summary.tal_count, 1);
|
assert_eq!(summary.trust_anchor_count, 1);
|
||||||
assert!(summary.object_count >= 2);
|
assert_eq!(summary.object_count, 1);
|
||||||
assert!(summary.timing.total_ms >= summary.timing.build_cir_ms);
|
assert!(summary.timing.total_ms >= summary.timing.build_cir_ms);
|
||||||
|
|
||||||
let der = std::fs::read(&cir_path).unwrap();
|
let der = std::fs::read(&cir_path).unwrap();
|
||||||
let cir = decode_cir(&der).unwrap();
|
let cir = decode_cir(&der).unwrap();
|
||||||
assert_eq!(cir.tals[0].tal_uri, "https://example.test/root.tal");
|
assert_eq!(
|
||||||
|
cir.trust_anchors[0].tal_uri,
|
||||||
|
"https://example.test/root.tal"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -595,13 +549,13 @@ mod tests {
|
|||||||
sample_date(),
|
sample_date(),
|
||||||
)
|
)
|
||||||
.expect("export cir");
|
.expect("export cir");
|
||||||
assert!(summary.object_count >= 2);
|
assert_eq!(summary.object_count, 1);
|
||||||
assert!(raw_store.exists());
|
assert!(raw_store.exists());
|
||||||
assert!(cir_path.exists());
|
assert!(cir_path.exists());
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn export_cir_from_run_writes_ta_bytes_to_repo_bytes_store() {
|
fn export_cir_from_run_does_not_write_ta_bytes_to_repo_bytes_store() {
|
||||||
let td = tempfile::tempdir().unwrap();
|
let td = tempfile::tempdir().unwrap();
|
||||||
let store = RocksStore::open_with_external_repo_bytes(
|
let store = RocksStore::open_with_external_repo_bytes(
|
||||||
&td.path().join("db"),
|
&td.path().join("db"),
|
||||||
@ -623,10 +577,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.expect("export cir");
|
.expect("export cir");
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(store.get_blob_bytes(&ta_hash).unwrap(), None);
|
||||||
store.get_blob_bytes(&ta_hash).unwrap(),
|
|
||||||
Some(ta.ta_certificate.raw_der.clone())
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -703,11 +654,11 @@ mod tests {
|
|||||||
let cir = build_cir_from_run_multi(
|
let cir = build_cir_from_run_multi(
|
||||||
&store,
|
&store,
|
||||||
&[
|
&[
|
||||||
CirTalBinding {
|
CirTrustAnchorBinding {
|
||||||
trust_anchor: &ta1,
|
trust_anchor: &ta1,
|
||||||
tal_uri: "https://example.test/apnic.tal",
|
tal_uri: "https://example.test/apnic.tal",
|
||||||
},
|
},
|
||||||
CirTalBinding {
|
CirTrustAnchorBinding {
|
||||||
trust_anchor: &ta2,
|
trust_anchor: &ta2,
|
||||||
tal_uri: "https://example.test/arin.tal",
|
tal_uri: "https://example.test/arin.tal",
|
||||||
},
|
},
|
||||||
@ -718,7 +669,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.expect("build cir from consumed audit objects");
|
.expect("build cir from consumed audit objects");
|
||||||
|
|
||||||
assert_eq!(cir.tals.len(), 2);
|
assert_eq!(cir.trust_anchors.len(), 2);
|
||||||
assert!(
|
assert!(
|
||||||
cir.objects
|
cir.objects
|
||||||
.iter()
|
.iter()
|
||||||
@ -730,17 +681,18 @@ mod tests {
|
|||||||
.any(|item| item.rsync_uri == "rsync://example.test/repo/superfluous.roa"),
|
.any(|item| item.rsync_uri == "rsync://example.test/repo/superfluous.roa"),
|
||||||
"current repo objects must not be included unless validation consumed them",
|
"current repo objects must not be included unless validation consumed them",
|
||||||
);
|
);
|
||||||
assert!(
|
for trust_anchor in &cir.trust_anchors {
|
||||||
cir.objects.iter().any(|item| {
|
assert!(
|
||||||
item.rsync_uri.contains("apnic-rpki-root-iana-origin.cer")
|
!cir.objects
|
||||||
|| item.rsync_uri.contains("arin-rpki-ta.cer")
|
.iter()
|
||||||
}),
|
.any(|item| item.rsync_uri == trust_anchor.ta_rsync_uri),
|
||||||
"trust anchor rsync objects must be included",
|
"trust anchor rsync objects must not be included in CIR.objects",
|
||||||
);
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn build_cir_from_run_multi_sorts_tals_by_tal_uri() {
|
fn build_cir_from_run_multi_sorts_trust_anchors_by_ta_rsync_uri() {
|
||||||
let td = tempfile::tempdir().unwrap();
|
let td = tempfile::tempdir().unwrap();
|
||||||
let store = RocksStore::open(td.path()).unwrap();
|
let store = RocksStore::open(td.path()).unwrap();
|
||||||
let apnic = sample_trust_anchor();
|
let apnic = sample_trust_anchor();
|
||||||
@ -749,11 +701,11 @@ mod tests {
|
|||||||
let cir = build_cir_from_run_multi(
|
let cir = build_cir_from_run_multi(
|
||||||
&store,
|
&store,
|
||||||
&[
|
&[
|
||||||
CirTalBinding {
|
CirTrustAnchorBinding {
|
||||||
trust_anchor: &apnic,
|
trust_anchor: &apnic,
|
||||||
tal_uri: "https://rpki.apnic.net/repository/apnic-rpki-root-iana-origin.cer",
|
tal_uri: "https://rpki.apnic.net/repository/apnic-rpki-root-iana-origin.cer",
|
||||||
},
|
},
|
||||||
CirTalBinding {
|
CirTrustAnchorBinding {
|
||||||
trust_anchor: &arin,
|
trust_anchor: &arin,
|
||||||
tal_uri: "https://rrdp.arin.net/arin-rpki-ta.cer",
|
tal_uri: "https://rrdp.arin.net/arin-rpki-ta.cer",
|
||||||
},
|
},
|
||||||
@ -765,13 +717,13 @@ mod tests {
|
|||||||
.expect("build cir with unsorted input bindings");
|
.expect("build cir with unsorted input bindings");
|
||||||
|
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
cir.tals
|
cir.trust_anchors
|
||||||
.iter()
|
.iter()
|
||||||
.map(|tal| tal.tal_uri.as_str())
|
.map(|trust_anchor| trust_anchor.ta_rsync_uri.as_str())
|
||||||
.collect::<Vec<_>>(),
|
.collect::<Vec<_>>(),
|
||||||
vec![
|
vec![
|
||||||
"https://rpki.apnic.net/repository/apnic-rpki-root-iana-origin.cer",
|
"rsync://rpki.apnic.net/repository/apnic-rpki-root-iana-origin.cer",
|
||||||
"https://rrdp.arin.net/arin-rpki-ta.cer",
|
"rsync://rpki.arin.net/repository/arin-rpki-ta.cer",
|
||||||
]
|
]
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -810,7 +762,7 @@ mod tests {
|
|||||||
|
|
||||||
let cir = build_cir_from_run_multi(
|
let cir = build_cir_from_run_multi(
|
||||||
&store,
|
&store,
|
||||||
&[CirTalBinding {
|
&[CirTrustAnchorBinding {
|
||||||
trust_anchor: &ta,
|
trust_anchor: &ta,
|
||||||
tal_uri: "https://example.test/root.tal",
|
tal_uri: "https://example.test/root.tal",
|
||||||
}],
|
}],
|
||||||
@ -860,7 +812,7 @@ mod tests {
|
|||||||
|
|
||||||
let cir_a = build_cir_from_run_multi(
|
let cir_a = build_cir_from_run_multi(
|
||||||
&store,
|
&store,
|
||||||
&[CirTalBinding {
|
&[CirTrustAnchorBinding {
|
||||||
trust_anchor: &ta,
|
trust_anchor: &ta,
|
||||||
tal_uri: "https://example.test/root.tal",
|
tal_uri: "https://example.test/root.tal",
|
||||||
}],
|
}],
|
||||||
@ -871,7 +823,7 @@ mod tests {
|
|||||||
.expect("build cir a");
|
.expect("build cir a");
|
||||||
let cir_b = build_cir_from_run_multi(
|
let cir_b = build_cir_from_run_multi(
|
||||||
&store,
|
&store,
|
||||||
&[CirTalBinding {
|
&[CirTrustAnchorBinding {
|
||||||
trust_anchor: &ta,
|
trust_anchor: &ta,
|
||||||
tal_uri: "https://example.test/root.tal",
|
tal_uri: "https://example.test/root.tal",
|
||||||
}],
|
}],
|
||||||
@ -895,7 +847,7 @@ mod tests {
|
|||||||
|
|
||||||
let err = build_cir_from_run_multi(
|
let err = build_cir_from_run_multi(
|
||||||
&store,
|
&store,
|
||||||
&[CirTalBinding {
|
&[CirTrustAnchorBinding {
|
||||||
trust_anchor: &sample_trust_anchor(),
|
trust_anchor: &sample_trust_anchor(),
|
||||||
tal_uri: "file:///not-supported.tal",
|
tal_uri: "file:///not-supported.tal",
|
||||||
}],
|
}],
|
||||||
@ -908,7 +860,7 @@ mod tests {
|
|||||||
|
|
||||||
let err = build_cir_from_run_multi(
|
let err = build_cir_from_run_multi(
|
||||||
&store,
|
&store,
|
||||||
&[CirTalBinding {
|
&[CirTrustAnchorBinding {
|
||||||
trust_anchor: &sample_trust_anchor_without_rsync_uri(),
|
trust_anchor: &sample_trust_anchor_without_rsync_uri(),
|
||||||
tal_uri: "https://example.test/root.tal",
|
tal_uri: "https://example.test/root.tal",
|
||||||
}],
|
}],
|
||||||
@ -921,7 +873,7 @@ mod tests {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn export_cir_static_pool_writes_objects_and_multiple_tas() {
|
fn export_cir_static_pool_writes_repository_objects_only() {
|
||||||
let td = tempfile::tempdir().unwrap();
|
let td = tempfile::tempdir().unwrap();
|
||||||
let store = RocksStore::open(&td.path().join("db")).unwrap();
|
let store = RocksStore::open(&td.path().join("db")).unwrap();
|
||||||
let static_root = td.path().join("static");
|
let static_root = td.path().join("static");
|
||||||
@ -948,11 +900,11 @@ mod tests {
|
|||||||
let cir = build_cir_from_run_multi(
|
let cir = build_cir_from_run_multi(
|
||||||
&store,
|
&store,
|
||||||
&[
|
&[
|
||||||
CirTalBinding {
|
CirTrustAnchorBinding {
|
||||||
trust_anchor: &ta1,
|
trust_anchor: &ta1,
|
||||||
tal_uri: "https://example.test/apnic.tal",
|
tal_uri: "https://example.test/apnic.tal",
|
||||||
},
|
},
|
||||||
CirTalBinding {
|
CirTrustAnchorBinding {
|
||||||
trust_anchor: &ta2,
|
trust_anchor: &ta2,
|
||||||
tal_uri: "https://example.test/arin.tal",
|
tal_uri: "https://example.test/arin.tal",
|
||||||
},
|
},
|
||||||
@ -966,12 +918,20 @@ mod tests {
|
|||||||
let summary =
|
let summary =
|
||||||
export_cir_static_pool(&store, &static_root, sample_date(), &cir, &[&ta1, &ta2])
|
export_cir_static_pool(&store, &static_root, sample_date(), &cir, &[&ta1, &ta2])
|
||||||
.expect("export static pool");
|
.expect("export static pool");
|
||||||
assert!(summary.unique_hashes >= 3);
|
assert_eq!(summary.unique_hashes, 1);
|
||||||
assert!(summary.written_files >= 3);
|
assert_eq!(summary.written_files, 1);
|
||||||
|
for trust_anchor in &cir.trust_anchors {
|
||||||
|
let ta_hash = hex::encode(&trust_anchor.ta_certificate_sha256);
|
||||||
|
assert!(
|
||||||
|
!crate::cir::static_pool::static_pool_path(&static_root, sample_date(), &ta_hash)
|
||||||
|
.expect("static pool ta path")
|
||||||
|
.exists()
|
||||||
|
);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn export_cir_raw_store_reports_missing_non_ta_object_and_writes_ta_entries() {
|
fn export_cir_raw_store_reports_missing_non_ta_object_only() {
|
||||||
let td = tempfile::tempdir().unwrap();
|
let td = tempfile::tempdir().unwrap();
|
||||||
let raw_store_path = td.path().join("raw-store.db");
|
let raw_store_path = td.path().join("raw-store.db");
|
||||||
let store =
|
let store =
|
||||||
@ -983,11 +943,11 @@ mod tests {
|
|||||||
let cir_only_tas = build_cir_from_run_multi(
|
let cir_only_tas = build_cir_from_run_multi(
|
||||||
&store,
|
&store,
|
||||||
&[
|
&[
|
||||||
CirTalBinding {
|
CirTrustAnchorBinding {
|
||||||
trust_anchor: &ta1,
|
trust_anchor: &ta1,
|
||||||
tal_uri: "https://example.test/apnic.tal",
|
tal_uri: "https://example.test/apnic.tal",
|
||||||
},
|
},
|
||||||
CirTalBinding {
|
CirTrustAnchorBinding {
|
||||||
trust_anchor: &ta2,
|
trust_anchor: &ta2,
|
||||||
tal_uri: "https://example.test/arin.tal",
|
tal_uri: "https://example.test/arin.tal",
|
||||||
},
|
},
|
||||||
@ -1000,8 +960,9 @@ mod tests {
|
|||||||
|
|
||||||
let summary = export_cir_raw_store(&store, &raw_store_path, &cir_only_tas, &[&ta1, &ta2])
|
let summary = export_cir_raw_store(&store, &raw_store_path, &cir_only_tas, &[&ta1, &ta2])
|
||||||
.expect("export raw store");
|
.expect("export raw store");
|
||||||
assert!(summary.unique_hashes >= 2);
|
assert_eq!(summary.unique_hashes, 0);
|
||||||
assert!(summary.written_entries >= 2 || summary.reused_entries >= 2);
|
assert_eq!(summary.written_entries, 0);
|
||||||
|
assert_eq!(summary.reused_entries, 0);
|
||||||
|
|
||||||
let mut cir_missing_object = cir_only_tas.clone();
|
let mut cir_missing_object = cir_only_tas.clone();
|
||||||
cir_missing_object.objects.push(CirObject {
|
cir_missing_object.objects.push(CirObject {
|
||||||
|
|||||||
@ -1,3 +1,4 @@
|
|||||||
|
use std::collections::BTreeSet;
|
||||||
use std::fs;
|
use std::fs;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
@ -66,6 +67,8 @@ pub enum CirMaterializeError {
|
|||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct CirMaterializeSummary {
|
pub struct CirMaterializeSummary {
|
||||||
pub object_count: usize,
|
pub object_count: usize,
|
||||||
|
pub trust_anchor_count: usize,
|
||||||
|
pub materialized_file_count: usize,
|
||||||
pub linked_files: usize,
|
pub linked_files: usize,
|
||||||
pub copied_files: usize,
|
pub copied_files: usize,
|
||||||
}
|
}
|
||||||
@ -78,16 +81,7 @@ pub fn materialize_cir(
|
|||||||
) -> Result<CirMaterializeSummary, CirMaterializeError> {
|
) -> Result<CirMaterializeSummary, CirMaterializeError> {
|
||||||
cir.validate().map_err(CirMaterializeError::TreeMismatch)?;
|
cir.validate().map_err(CirMaterializeError::TreeMismatch)?;
|
||||||
|
|
||||||
if clean_rebuild && mirror_root.exists() {
|
prepare_mirror_root(mirror_root, clean_rebuild)?;
|
||||||
fs::remove_dir_all(mirror_root).map_err(|e| CirMaterializeError::RemoveMirrorRoot {
|
|
||||||
path: mirror_root.display().to_string(),
|
|
||||||
detail: e.to_string(),
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
fs::create_dir_all(mirror_root).map_err(|e| CirMaterializeError::CreateMirrorRoot {
|
|
||||||
path: mirror_root.display().to_string(),
|
|
||||||
detail: e.to_string(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let mut linked_files = 0usize;
|
let mut linked_files = 0usize;
|
||||||
let mut copied_files = 0usize;
|
let mut copied_files = 0usize;
|
||||||
@ -125,12 +119,18 @@ pub fn materialize_cir(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for trust_anchor in &cir.trust_anchors {
|
||||||
|
write_bytes_to_mirror_uri(
|
||||||
|
mirror_root,
|
||||||
|
&trust_anchor.ta_rsync_uri,
|
||||||
|
&trust_anchor.ta_certificate_der,
|
||||||
|
"cir trust anchor",
|
||||||
|
)?;
|
||||||
|
copied_files += 1;
|
||||||
|
}
|
||||||
|
|
||||||
let actual = collect_materialized_uris(mirror_root)?;
|
let actual = collect_materialized_uris(mirror_root)?;
|
||||||
let expected = cir
|
let expected = expected_materialized_uris(cir);
|
||||||
.objects
|
|
||||||
.iter()
|
|
||||||
.map(|item| item.rsync_uri.clone())
|
|
||||||
.collect::<std::collections::BTreeSet<_>>();
|
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
return Err(CirMaterializeError::TreeMismatch(format!(
|
return Err(CirMaterializeError::TreeMismatch(format!(
|
||||||
"expected {} files, got {} files",
|
"expected {} files, got {} files",
|
||||||
@ -141,6 +141,8 @@ pub fn materialize_cir(
|
|||||||
|
|
||||||
Ok(CirMaterializeSummary {
|
Ok(CirMaterializeSummary {
|
||||||
object_count: cir.objects.len(),
|
object_count: cir.objects.len(),
|
||||||
|
trust_anchor_count: cir.trust_anchors.len(),
|
||||||
|
materialized_file_count: expected.len(),
|
||||||
linked_files,
|
linked_files,
|
||||||
copied_files,
|
copied_files,
|
||||||
})
|
})
|
||||||
@ -154,16 +156,7 @@ pub fn materialize_cir_from_raw_store(
|
|||||||
) -> Result<CirMaterializeSummary, CirMaterializeError> {
|
) -> Result<CirMaterializeSummary, CirMaterializeError> {
|
||||||
cir.validate().map_err(CirMaterializeError::TreeMismatch)?;
|
cir.validate().map_err(CirMaterializeError::TreeMismatch)?;
|
||||||
|
|
||||||
if clean_rebuild && mirror_root.exists() {
|
prepare_mirror_root(mirror_root, clean_rebuild)?;
|
||||||
fs::remove_dir_all(mirror_root).map_err(|e| CirMaterializeError::RemoveMirrorRoot {
|
|
||||||
path: mirror_root.display().to_string(),
|
|
||||||
detail: e.to_string(),
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
fs::create_dir_all(mirror_root).map_err(|e| CirMaterializeError::CreateMirrorRoot {
|
|
||||||
path: mirror_root.display().to_string(),
|
|
||||||
detail: e.to_string(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let raw_store =
|
let raw_store =
|
||||||
ExternalRawStoreDb::open(raw_store_db).map_err(|e| CirMaterializeError::OpenRawStore {
|
ExternalRawStoreDb::open(raw_store_db).map_err(|e| CirMaterializeError::OpenRawStore {
|
||||||
@ -183,37 +176,27 @@ pub fn materialize_cir_from_raw_store(
|
|||||||
.ok_or_else(|| CirMaterializeError::MissingRawStoreObject {
|
.ok_or_else(|| CirMaterializeError::MissingRawStoreObject {
|
||||||
sha256_hex: sha256_hex.clone(),
|
sha256_hex: sha256_hex.clone(),
|
||||||
})?;
|
})?;
|
||||||
let relative = mirror_relative_path_for_rsync_uri(&object.rsync_uri)?;
|
write_bytes_to_mirror_uri(
|
||||||
let target = mirror_root.join(&relative);
|
mirror_root,
|
||||||
|
&object.rsync_uri,
|
||||||
|
&bytes,
|
||||||
|
&raw_store_db.display().to_string(),
|
||||||
|
)?;
|
||||||
|
copied_files += 1;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(parent) = target.parent() {
|
for trust_anchor in &cir.trust_anchors {
|
||||||
fs::create_dir_all(parent).map_err(|e| CirMaterializeError::CreateParent {
|
write_bytes_to_mirror_uri(
|
||||||
path: parent.display().to_string(),
|
mirror_root,
|
||||||
detail: e.to_string(),
|
&trust_anchor.ta_rsync_uri,
|
||||||
})?;
|
&trust_anchor.ta_certificate_der,
|
||||||
}
|
"cir trust anchor",
|
||||||
|
)?;
|
||||||
if target.exists() {
|
|
||||||
fs::remove_file(&target).map_err(|e| CirMaterializeError::RemoveExistingTarget {
|
|
||||||
path: target.display().to_string(),
|
|
||||||
detail: e.to_string(),
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
fs::write(&target, &bytes).map_err(|e| CirMaterializeError::Copy {
|
|
||||||
src: raw_store_db.display().to_string(),
|
|
||||||
dst: target.display().to_string(),
|
|
||||||
detail: e.to_string(),
|
|
||||||
})?;
|
|
||||||
copied_files += 1;
|
copied_files += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let actual = collect_materialized_uris(mirror_root)?;
|
let actual = collect_materialized_uris(mirror_root)?;
|
||||||
let expected = cir
|
let expected = expected_materialized_uris(cir);
|
||||||
.objects
|
|
||||||
.iter()
|
|
||||||
.map(|item| item.rsync_uri.clone())
|
|
||||||
.collect::<std::collections::BTreeSet<_>>();
|
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
return Err(CirMaterializeError::TreeMismatch(format!(
|
return Err(CirMaterializeError::TreeMismatch(format!(
|
||||||
"expected {} files, got {} files",
|
"expected {} files, got {} files",
|
||||||
@ -224,6 +207,8 @@ pub fn materialize_cir_from_raw_store(
|
|||||||
|
|
||||||
Ok(CirMaterializeSummary {
|
Ok(CirMaterializeSummary {
|
||||||
object_count: cir.objects.len(),
|
object_count: cir.objects.len(),
|
||||||
|
trust_anchor_count: cir.trust_anchors.len(),
|
||||||
|
materialized_file_count: expected.len(),
|
||||||
linked_files: 0,
|
linked_files: 0,
|
||||||
copied_files,
|
copied_files,
|
||||||
})
|
})
|
||||||
@ -237,16 +222,7 @@ pub fn materialize_cir_from_repo_bytes(
|
|||||||
) -> Result<CirMaterializeSummary, CirMaterializeError> {
|
) -> Result<CirMaterializeSummary, CirMaterializeError> {
|
||||||
cir.validate().map_err(CirMaterializeError::TreeMismatch)?;
|
cir.validate().map_err(CirMaterializeError::TreeMismatch)?;
|
||||||
|
|
||||||
if clean_rebuild && mirror_root.exists() {
|
prepare_mirror_root(mirror_root, clean_rebuild)?;
|
||||||
fs::remove_dir_all(mirror_root).map_err(|e| CirMaterializeError::RemoveMirrorRoot {
|
|
||||||
path: mirror_root.display().to_string(),
|
|
||||||
detail: e.to_string(),
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
fs::create_dir_all(mirror_root).map_err(|e| CirMaterializeError::CreateMirrorRoot {
|
|
||||||
path: mirror_root.display().to_string(),
|
|
||||||
detail: e.to_string(),
|
|
||||||
})?;
|
|
||||||
|
|
||||||
let repo_bytes = ExternalRepoBytesDb::open(repo_bytes_db).map_err(|e| {
|
let repo_bytes = ExternalRepoBytesDb::open(repo_bytes_db).map_err(|e| {
|
||||||
CirMaterializeError::OpenRepoBytesStore {
|
CirMaterializeError::OpenRepoBytesStore {
|
||||||
@ -267,37 +243,27 @@ pub fn materialize_cir_from_repo_bytes(
|
|||||||
.ok_or_else(|| CirMaterializeError::MissingRepoBytesObject {
|
.ok_or_else(|| CirMaterializeError::MissingRepoBytesObject {
|
||||||
sha256_hex: sha256_hex.clone(),
|
sha256_hex: sha256_hex.clone(),
|
||||||
})?;
|
})?;
|
||||||
let relative = mirror_relative_path_for_rsync_uri(&object.rsync_uri)?;
|
write_bytes_to_mirror_uri(
|
||||||
let target = mirror_root.join(&relative);
|
mirror_root,
|
||||||
|
&object.rsync_uri,
|
||||||
|
&bytes,
|
||||||
|
&repo_bytes_db.display().to_string(),
|
||||||
|
)?;
|
||||||
|
copied_files += 1;
|
||||||
|
}
|
||||||
|
|
||||||
if let Some(parent) = target.parent() {
|
for trust_anchor in &cir.trust_anchors {
|
||||||
fs::create_dir_all(parent).map_err(|e| CirMaterializeError::CreateParent {
|
write_bytes_to_mirror_uri(
|
||||||
path: parent.display().to_string(),
|
mirror_root,
|
||||||
detail: e.to_string(),
|
&trust_anchor.ta_rsync_uri,
|
||||||
})?;
|
&trust_anchor.ta_certificate_der,
|
||||||
}
|
"cir trust anchor",
|
||||||
|
)?;
|
||||||
if target.exists() {
|
|
||||||
fs::remove_file(&target).map_err(|e| CirMaterializeError::RemoveExistingTarget {
|
|
||||||
path: target.display().to_string(),
|
|
||||||
detail: e.to_string(),
|
|
||||||
})?;
|
|
||||||
}
|
|
||||||
|
|
||||||
fs::write(&target, &bytes).map_err(|e| CirMaterializeError::Copy {
|
|
||||||
src: repo_bytes_db.display().to_string(),
|
|
||||||
dst: target.display().to_string(),
|
|
||||||
detail: e.to_string(),
|
|
||||||
})?;
|
|
||||||
copied_files += 1;
|
copied_files += 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
let actual = collect_materialized_uris(mirror_root)?;
|
let actual = collect_materialized_uris(mirror_root)?;
|
||||||
let expected = cir
|
let expected = expected_materialized_uris(cir);
|
||||||
.objects
|
|
||||||
.iter()
|
|
||||||
.map(|item| item.rsync_uri.clone())
|
|
||||||
.collect::<std::collections::BTreeSet<_>>();
|
|
||||||
if actual != expected {
|
if actual != expected {
|
||||||
return Err(CirMaterializeError::TreeMismatch(format!(
|
return Err(CirMaterializeError::TreeMismatch(format!(
|
||||||
"expected {} files, got {} files",
|
"expected {} files, got {} files",
|
||||||
@ -308,11 +274,68 @@ pub fn materialize_cir_from_repo_bytes(
|
|||||||
|
|
||||||
Ok(CirMaterializeSummary {
|
Ok(CirMaterializeSummary {
|
||||||
object_count: cir.objects.len(),
|
object_count: cir.objects.len(),
|
||||||
|
trust_anchor_count: cir.trust_anchors.len(),
|
||||||
|
materialized_file_count: expected.len(),
|
||||||
linked_files: 0,
|
linked_files: 0,
|
||||||
copied_files,
|
copied_files,
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn prepare_mirror_root(mirror_root: &Path, clean_rebuild: bool) -> Result<(), CirMaterializeError> {
|
||||||
|
if clean_rebuild && mirror_root.exists() {
|
||||||
|
fs::remove_dir_all(mirror_root).map_err(|e| CirMaterializeError::RemoveMirrorRoot {
|
||||||
|
path: mirror_root.display().to_string(),
|
||||||
|
detail: e.to_string(),
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
fs::create_dir_all(mirror_root).map_err(|e| CirMaterializeError::CreateMirrorRoot {
|
||||||
|
path: mirror_root.display().to_string(),
|
||||||
|
detail: e.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn write_bytes_to_mirror_uri(
|
||||||
|
mirror_root: &Path,
|
||||||
|
rsync_uri: &str,
|
||||||
|
bytes: &[u8],
|
||||||
|
src_label: &str,
|
||||||
|
) -> Result<(), CirMaterializeError> {
|
||||||
|
let relative = mirror_relative_path_for_rsync_uri(rsync_uri)?;
|
||||||
|
let target = mirror_root.join(&relative);
|
||||||
|
|
||||||
|
if let Some(parent) = target.parent() {
|
||||||
|
fs::create_dir_all(parent).map_err(|e| CirMaterializeError::CreateParent {
|
||||||
|
path: parent.display().to_string(),
|
||||||
|
detail: e.to_string(),
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
if target.exists() {
|
||||||
|
fs::remove_file(&target).map_err(|e| CirMaterializeError::RemoveExistingTarget {
|
||||||
|
path: target.display().to_string(),
|
||||||
|
detail: e.to_string(),
|
||||||
|
})?;
|
||||||
|
}
|
||||||
|
|
||||||
|
fs::write(&target, bytes).map_err(|e| CirMaterializeError::Copy {
|
||||||
|
src: src_label.to_string(),
|
||||||
|
dst: target.display().to_string(),
|
||||||
|
detail: e.to_string(),
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
|
fn expected_materialized_uris(cir: &CanonicalInputRepresentation) -> BTreeSet<String> {
|
||||||
|
cir.objects
|
||||||
|
.iter()
|
||||||
|
.map(|item| item.rsync_uri.clone())
|
||||||
|
.chain(
|
||||||
|
cir.trust_anchors
|
||||||
|
.iter()
|
||||||
|
.map(|item| item.ta_rsync_uri.clone()),
|
||||||
|
)
|
||||||
|
.collect()
|
||||||
|
}
|
||||||
|
|
||||||
pub fn mirror_relative_path_for_rsync_uri(rsync_uri: &str) -> Result<PathBuf, CirMaterializeError> {
|
pub fn mirror_relative_path_for_rsync_uri(rsync_uri: &str) -> Result<PathBuf, CirMaterializeError> {
|
||||||
let url = url::Url::parse(rsync_uri)
|
let url = url::Url::parse(rsync_uri)
|
||||||
.map_err(|_| CirMaterializeError::InvalidRsyncUri(rsync_uri.to_string()))?;
|
.map_err(|_| CirMaterializeError::InvalidRsyncUri(rsync_uri.to_string()))?;
|
||||||
@ -375,10 +398,8 @@ pub fn resolve_static_pool_file(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
fn collect_materialized_uris(
|
fn collect_materialized_uris(mirror_root: &Path) -> Result<BTreeSet<String>, CirMaterializeError> {
|
||||||
mirror_root: &Path,
|
let mut out = BTreeSet::new();
|
||||||
) -> Result<std::collections::BTreeSet<String>, CirMaterializeError> {
|
|
||||||
let mut out = std::collections::BTreeSet::new();
|
|
||||||
let mut stack = vec![mirror_root.to_path_buf()];
|
let mut stack = vec![mirror_root.to_path_buf()];
|
||||||
while let Some(path) = stack.pop() {
|
while let Some(path) = stack.pop() {
|
||||||
for entry in fs::read_dir(&path).map_err(|e| CirMaterializeError::CreateMirrorRoot {
|
for entry in fs::read_dir(&path).map_err(|e| CirMaterializeError::CreateMirrorRoot {
|
||||||
@ -415,8 +436,8 @@ mod tests {
|
|||||||
};
|
};
|
||||||
use crate::blob_store::{ExternalRawStoreDb, ExternalRepoBytesDb};
|
use crate::blob_store::{ExternalRawStoreDb, ExternalRepoBytesDb};
|
||||||
use crate::cir::model::{
|
use crate::cir::model::{
|
||||||
CIR_VERSION_V2, CanonicalInputRepresentation, CirHashAlgorithm, CirObject,
|
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirObject,
|
||||||
CirRejectedObject, CirTal, compute_reject_list_sha256,
|
CirRejectedObject, CirTrustAnchor, compute_reject_list_sha256, sha256,
|
||||||
};
|
};
|
||||||
use sha2::Digest;
|
use sha2::Digest;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
@ -429,13 +450,25 @@ mod tests {
|
|||||||
.unwrap()
|
.unwrap()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sample_trust_anchor() -> CirTrustAnchor {
|
||||||
|
let ta_rsync_uri = "rsync://example.net/repo/ta.cer";
|
||||||
|
let ta_certificate_der = b"ta-der".to_vec();
|
||||||
|
CirTrustAnchor {
|
||||||
|
ta_rsync_uri: ta_rsync_uri.to_string(),
|
||||||
|
tal_uri: "https://tal.example.net/root.tal".to_string(),
|
||||||
|
tal_bytes: format!("{ta_rsync_uri}\n\nAQID\n").into_bytes(),
|
||||||
|
ta_certificate_sha256: sha256(&ta_certificate_der),
|
||||||
|
ta_certificate_der,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn sample_cir() -> CanonicalInputRepresentation {
|
fn sample_cir() -> CanonicalInputRepresentation {
|
||||||
let rejected_objects = vec![CirRejectedObject {
|
let rejected_objects = vec![CirRejectedObject {
|
||||||
object_uri: "rsync://example.net/repo/rejected-a.roa".to_string(),
|
object_uri: "rsync://example.net/repo/rejected-a.roa".to_string(),
|
||||||
reason: Some("invalid roa".to_string()),
|
reason: Some("invalid roa".to_string()),
|
||||||
}];
|
}];
|
||||||
CanonicalInputRepresentation {
|
CanonicalInputRepresentation {
|
||||||
version: CIR_VERSION_V2,
|
version: CIR_VERSION_V3,
|
||||||
hash_alg: CirHashAlgorithm::Sha256,
|
hash_alg: CirHashAlgorithm::Sha256,
|
||||||
validation_time: sample_time(),
|
validation_time: sample_time(),
|
||||||
objects: vec![
|
objects: vec![
|
||||||
@ -454,10 +487,7 @@ mod tests {
|
|||||||
.unwrap(),
|
.unwrap(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
tals: vec![CirTal {
|
trust_anchors: vec![sample_trust_anchor()],
|
||||||
tal_uri: "https://tal.example.net/root.tal".to_string(),
|
|
||||||
tal_bytes: b"x".to_vec(),
|
|
||||||
}],
|
|
||||||
reject_list_sha256: compute_reject_list_sha256(
|
reject_list_sha256: compute_reject_list_sha256(
|
||||||
rejected_objects.iter().map(|item| item.object_uri.as_str()),
|
rejected_objects.iter().map(|item| item.object_uri.as_str()),
|
||||||
),
|
),
|
||||||
@ -467,7 +497,7 @@ mod tests {
|
|||||||
|
|
||||||
fn cir_with_real_hashes(a: &[u8], b: &[u8]) -> CanonicalInputRepresentation {
|
fn cir_with_real_hashes(a: &[u8], b: &[u8]) -> CanonicalInputRepresentation {
|
||||||
CanonicalInputRepresentation {
|
CanonicalInputRepresentation {
|
||||||
version: CIR_VERSION_V2,
|
version: CIR_VERSION_V3,
|
||||||
hash_alg: CirHashAlgorithm::Sha256,
|
hash_alg: CirHashAlgorithm::Sha256,
|
||||||
validation_time: sample_time(),
|
validation_time: sample_time(),
|
||||||
objects: vec![
|
objects: vec![
|
||||||
@ -480,10 +510,7 @@ mod tests {
|
|||||||
sha256: sha2::Sha256::digest(b).to_vec(),
|
sha256: sha2::Sha256::digest(b).to_vec(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
tals: vec![CirTal {
|
trust_anchors: vec![sample_trust_anchor()],
|
||||||
tal_uri: "https://tal.example.net/root.tal".to_string(),
|
|
||||||
tal_bytes: b"x".to_vec(),
|
|
||||||
}],
|
|
||||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||||
rejected_objects: Vec::new(),
|
rejected_objects: Vec::new(),
|
||||||
}
|
}
|
||||||
@ -573,6 +600,8 @@ mod tests {
|
|||||||
|
|
||||||
let summary = materialize_cir(&sample_cir(), &static_root, &mirror_root, true).unwrap();
|
let summary = materialize_cir(&sample_cir(), &static_root, &mirror_root, true).unwrap();
|
||||||
assert_eq!(summary.object_count, 2);
|
assert_eq!(summary.object_count, 2);
|
||||||
|
assert_eq!(summary.trust_anchor_count, 1);
|
||||||
|
assert_eq!(summary.materialized_file_count, 3);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
std::fs::read(mirror_root.join("example.net/repo/a.cer")).unwrap(),
|
std::fs::read(mirror_root.join("example.net/repo/a.cer")).unwrap(),
|
||||||
b"a"
|
b"a"
|
||||||
@ -581,6 +610,10 @@ mod tests {
|
|||||||
std::fs::read(mirror_root.join("example.net/repo/nested/b.roa")).unwrap(),
|
std::fs::read(mirror_root.join("example.net/repo/nested/b.roa")).unwrap(),
|
||||||
b"b"
|
b"b"
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
std::fs::read(mirror_root.join("example.net/repo/ta.cer")).unwrap(),
|
||||||
|
b"ta-der"
|
||||||
|
);
|
||||||
assert!(!mirror_root.join("stale/old.txt").exists());
|
assert!(!mirror_root.join("stale/old.txt").exists());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -629,7 +662,7 @@ mod tests {
|
|||||||
let a = b"a".to_vec();
|
let a = b"a".to_vec();
|
||||||
let b = b"b".to_vec();
|
let b = b"b".to_vec();
|
||||||
let cir = CanonicalInputRepresentation {
|
let cir = CanonicalInputRepresentation {
|
||||||
version: CIR_VERSION_V2,
|
version: CIR_VERSION_V3,
|
||||||
hash_alg: CirHashAlgorithm::Sha256,
|
hash_alg: CirHashAlgorithm::Sha256,
|
||||||
validation_time: sample_time(),
|
validation_time: sample_time(),
|
||||||
objects: vec![
|
objects: vec![
|
||||||
@ -642,10 +675,7 @@ mod tests {
|
|||||||
sha256: sha2::Sha256::digest(&b).to_vec(),
|
sha256: sha2::Sha256::digest(&b).to_vec(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
tals: vec![CirTal {
|
trust_anchors: vec![sample_trust_anchor()],
|
||||||
tal_uri: "https://tal.example.net/root.tal".to_string(),
|
|
||||||
tal_bytes: b"x".to_vec(),
|
|
||||||
}],
|
|
||||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||||
rejected_objects: Vec::new(),
|
rejected_objects: Vec::new(),
|
||||||
};
|
};
|
||||||
@ -663,8 +693,10 @@ mod tests {
|
|||||||
let summary =
|
let summary =
|
||||||
materialize_cir_from_raw_store(&cir, &raw_store_path, &mirror_root, true).unwrap();
|
materialize_cir_from_raw_store(&cir, &raw_store_path, &mirror_root, true).unwrap();
|
||||||
assert_eq!(summary.object_count, 2);
|
assert_eq!(summary.object_count, 2);
|
||||||
|
assert_eq!(summary.trust_anchor_count, 1);
|
||||||
|
assert_eq!(summary.materialized_file_count, 3);
|
||||||
assert_eq!(summary.linked_files, 0);
|
assert_eq!(summary.linked_files, 0);
|
||||||
assert_eq!(summary.copied_files, 2);
|
assert_eq!(summary.copied_files, 3);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
std::fs::read(mirror_root.join("example.net/repo/a.cer")).unwrap(),
|
std::fs::read(mirror_root.join("example.net/repo/a.cer")).unwrap(),
|
||||||
b"a"
|
b"a"
|
||||||
@ -673,6 +705,10 @@ mod tests {
|
|||||||
std::fs::read(mirror_root.join("example.net/repo/nested/b.roa")).unwrap(),
|
std::fs::read(mirror_root.join("example.net/repo/nested/b.roa")).unwrap(),
|
||||||
b"b"
|
b"b"
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
std::fs::read(mirror_root.join("example.net/repo/ta.cer")).unwrap(),
|
||||||
|
b"ta-der"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -742,7 +778,7 @@ mod tests {
|
|||||||
|
|
||||||
let summary =
|
let summary =
|
||||||
materialize_cir_from_raw_store(&cir, &raw_store_path, &mirror_root, false).unwrap();
|
materialize_cir_from_raw_store(&cir, &raw_store_path, &mirror_root, false).unwrap();
|
||||||
assert_eq!(summary.copied_files, 2);
|
assert_eq!(summary.copied_files, 3);
|
||||||
assert_eq!(std::fs::read(&target).unwrap(), a);
|
assert_eq!(std::fs::read(&target).unwrap(), a);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -752,7 +788,7 @@ mod tests {
|
|||||||
let raw_store_path = td.path().join("raw-store.db");
|
let raw_store_path = td.path().join("raw-store.db");
|
||||||
let mirror_root = td.path().join("mirror");
|
let mirror_root = td.path().join("mirror");
|
||||||
let cir = CanonicalInputRepresentation {
|
let cir = CanonicalInputRepresentation {
|
||||||
version: CIR_VERSION_V2,
|
version: CIR_VERSION_V3,
|
||||||
hash_alg: CirHashAlgorithm::Sha256,
|
hash_alg: CirHashAlgorithm::Sha256,
|
||||||
validation_time: sample_time(),
|
validation_time: sample_time(),
|
||||||
objects: vec![CirObject {
|
objects: vec![CirObject {
|
||||||
@ -762,10 +798,7 @@ mod tests {
|
|||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
}],
|
}],
|
||||||
tals: vec![CirTal {
|
trust_anchors: vec![sample_trust_anchor()],
|
||||||
tal_uri: "https://tal.example.net/root.tal".to_string(),
|
|
||||||
tal_bytes: b"x".to_vec(),
|
|
||||||
}],
|
|
||||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||||
rejected_objects: Vec::new(),
|
rejected_objects: Vec::new(),
|
||||||
};
|
};
|
||||||
@ -790,10 +823,15 @@ mod tests {
|
|||||||
let summary =
|
let summary =
|
||||||
materialize_cir_from_raw_store(&cir, &raw_store_path, &mirror_root, true).unwrap();
|
materialize_cir_from_raw_store(&cir, &raw_store_path, &mirror_root, true).unwrap();
|
||||||
assert_eq!(summary.object_count, 1);
|
assert_eq!(summary.object_count, 1);
|
||||||
|
assert_eq!(summary.materialized_file_count, 2);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
std::fs::read(mirror_root.join("example.net/repo/a.cer")).unwrap(),
|
std::fs::read(mirror_root.join("example.net/repo/a.cer")).unwrap(),
|
||||||
b"blob-a"
|
b"blob-a"
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
std::fs::read(mirror_root.join("example.net/repo/ta.cer")).unwrap(),
|
||||||
|
b"ta-der"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -804,7 +842,7 @@ mod tests {
|
|||||||
let a = b"a".to_vec();
|
let a = b"a".to_vec();
|
||||||
let b = b"b".to_vec();
|
let b = b"b".to_vec();
|
||||||
let cir = CanonicalInputRepresentation {
|
let cir = CanonicalInputRepresentation {
|
||||||
version: CIR_VERSION_V2,
|
version: CIR_VERSION_V3,
|
||||||
hash_alg: CirHashAlgorithm::Sha256,
|
hash_alg: CirHashAlgorithm::Sha256,
|
||||||
validation_time: sample_time(),
|
validation_time: sample_time(),
|
||||||
objects: vec![
|
objects: vec![
|
||||||
@ -817,10 +855,7 @@ mod tests {
|
|||||||
sha256: sha2::Sha256::digest(&b).to_vec(),
|
sha256: sha2::Sha256::digest(&b).to_vec(),
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
tals: vec![CirTal {
|
trust_anchors: vec![sample_trust_anchor()],
|
||||||
tal_uri: "https://tal.example.net/root.tal".to_string(),
|
|
||||||
tal_bytes: b"x".to_vec(),
|
|
||||||
}],
|
|
||||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||||
rejected_objects: Vec::new(),
|
rejected_objects: Vec::new(),
|
||||||
};
|
};
|
||||||
@ -838,8 +873,10 @@ mod tests {
|
|||||||
let summary =
|
let summary =
|
||||||
materialize_cir_from_repo_bytes(&cir, &repo_bytes_db, &mirror_root, true).unwrap();
|
materialize_cir_from_repo_bytes(&cir, &repo_bytes_db, &mirror_root, true).unwrap();
|
||||||
assert_eq!(summary.object_count, 2);
|
assert_eq!(summary.object_count, 2);
|
||||||
|
assert_eq!(summary.trust_anchor_count, 1);
|
||||||
|
assert_eq!(summary.materialized_file_count, 3);
|
||||||
assert_eq!(summary.linked_files, 0);
|
assert_eq!(summary.linked_files, 0);
|
||||||
assert_eq!(summary.copied_files, 2);
|
assert_eq!(summary.copied_files, 3);
|
||||||
assert_eq!(
|
assert_eq!(
|
||||||
std::fs::read(mirror_root.join("example.net/repo/a.cer")).unwrap(),
|
std::fs::read(mirror_root.join("example.net/repo/a.cer")).unwrap(),
|
||||||
b"a"
|
b"a"
|
||||||
@ -848,6 +885,10 @@ mod tests {
|
|||||||
std::fs::read(mirror_root.join("example.net/repo/nested/b.roa")).unwrap(),
|
std::fs::read(mirror_root.join("example.net/repo/nested/b.roa")).unwrap(),
|
||||||
b"b"
|
b"b"
|
||||||
);
|
);
|
||||||
|
assert_eq!(
|
||||||
|
std::fs::read(mirror_root.join("example.net/repo/ta.cer")).unwrap(),
|
||||||
|
b"ta-der"
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
fn write_static(root: &Path, date: &str, hash: &str, bytes: &[u8]) {
|
fn write_static(root: &Path, date: &str, hash: &str, bytes: &[u8]) {
|
||||||
|
|||||||
152
src/cir/mod.rs
152
src/cir/mod.rs
@ -12,16 +12,16 @@ pub use decode::{CirDecodeError, decode_cir};
|
|||||||
pub use encode::{CirEncodeError, encode_cir};
|
pub use encode::{CirEncodeError, encode_cir};
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
pub use export::{
|
pub use export::{
|
||||||
CirExportError, CirExportSummary, CirTalBinding, build_cir_from_run, build_cir_from_run_multi,
|
CirExportError, CirExportSummary, CirTrustAnchorBinding, build_cir_from_run,
|
||||||
export_cir_from_run, export_cir_from_run_multi, write_cir_file,
|
build_cir_from_run_multi, export_cir_from_run, export_cir_from_run_multi, write_cir_file,
|
||||||
};
|
};
|
||||||
pub use materialize::{
|
pub use materialize::{
|
||||||
CirMaterializeError, CirMaterializeSummary, materialize_cir, materialize_cir_from_raw_store,
|
CirMaterializeError, CirMaterializeSummary, materialize_cir, materialize_cir_from_raw_store,
|
||||||
materialize_cir_from_repo_bytes, mirror_relative_path_for_rsync_uri, resolve_static_pool_file,
|
materialize_cir_from_repo_bytes, mirror_relative_path_for_rsync_uri, resolve_static_pool_file,
|
||||||
};
|
};
|
||||||
pub use model::{
|
pub use model::{
|
||||||
CIR_VERSION_V1, CIR_VERSION_V2, CanonicalInputRepresentation, CirHashAlgorithm, CirObject,
|
CIR_VERSION_V1, CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirObject,
|
||||||
CirRejectedObject, CirTal, compute_reject_list_sha256,
|
CirRejectedObject, CirTrustAnchor, compute_reject_list_sha256, sha256,
|
||||||
};
|
};
|
||||||
pub use sequence::{CirSequenceManifest, CirSequenceStep, CirSequenceStepKind};
|
pub use sequence::{CirSequenceManifest, CirSequenceStep, CirSequenceStepKind};
|
||||||
#[cfg(feature = "full")]
|
#[cfg(feature = "full")]
|
||||||
@ -34,8 +34,8 @@ pub use static_pool::{
|
|||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::{
|
use super::{
|
||||||
CIR_VERSION_V2, CanonicalInputRepresentation, CirHashAlgorithm, CirObject,
|
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirObject,
|
||||||
CirRejectedObject, CirTal, compute_reject_list_sha256, decode_cir, encode_cir,
|
CirRejectedObject, CirTrustAnchor, compute_reject_list_sha256, decode_cir, encode_cir,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn sample_time() -> time::OffsetDateTime {
|
fn sample_time() -> time::OffsetDateTime {
|
||||||
@ -46,6 +46,16 @@ mod tests {
|
|||||||
.expect("valid rfc3339")
|
.expect("valid rfc3339")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn sample_trust_anchor(ta_rsync_uri: &str, tal_uri: &str, ta_der: &[u8]) -> CirTrustAnchor {
|
||||||
|
CirTrustAnchor {
|
||||||
|
ta_rsync_uri: ta_rsync_uri.to_string(),
|
||||||
|
tal_uri: tal_uri.to_string(),
|
||||||
|
tal_bytes: format!("{ta_rsync_uri}\n\nAQID\n").into_bytes(),
|
||||||
|
ta_certificate_der: ta_der.to_vec(),
|
||||||
|
ta_certificate_sha256: super::sha256(ta_der),
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
fn sample_cir() -> CanonicalInputRepresentation {
|
fn sample_cir() -> CanonicalInputRepresentation {
|
||||||
let rejected_objects = vec![
|
let rejected_objects = vec![
|
||||||
CirRejectedObject {
|
CirRejectedObject {
|
||||||
@ -58,7 +68,7 @@ mod tests {
|
|||||||
},
|
},
|
||||||
];
|
];
|
||||||
CanonicalInputRepresentation {
|
CanonicalInputRepresentation {
|
||||||
version: CIR_VERSION_V2,
|
version: CIR_VERSION_V3,
|
||||||
hash_alg: CirHashAlgorithm::Sha256,
|
hash_alg: CirHashAlgorithm::Sha256,
|
||||||
validation_time: sample_time(),
|
validation_time: sample_time(),
|
||||||
objects: vec![
|
objects: vec![
|
||||||
@ -71,11 +81,11 @@ mod tests {
|
|||||||
sha256: vec![0x22; 32],
|
sha256: vec![0x22; 32],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
tals: vec![CirTal {
|
trust_anchors: vec![sample_trust_anchor(
|
||||||
tal_uri: "https://tal.example.net/root.tal".to_string(),
|
"rsync://example.net/repo/ta.cer",
|
||||||
tal_bytes: b"https://tal.example.net/ta.cer\nrsync://example.net/repo/ta.cer\nMIIB"
|
"https://tal.example.net/root.tal",
|
||||||
.to_vec(),
|
b"ta-der",
|
||||||
}],
|
)],
|
||||||
reject_list_sha256: compute_reject_list_sha256(
|
reject_list_sha256: compute_reject_list_sha256(
|
||||||
rejected_objects.iter().map(|item| item.object_uri.as_str()),
|
rejected_objects.iter().map(|item| item.object_uri.as_str()),
|
||||||
),
|
),
|
||||||
@ -114,14 +124,15 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn cir_roundtrip_minimal_succeeds() {
|
fn cir_roundtrip_minimal_succeeds() {
|
||||||
let cir = CanonicalInputRepresentation {
|
let cir = CanonicalInputRepresentation {
|
||||||
version: CIR_VERSION_V2,
|
version: CIR_VERSION_V3,
|
||||||
hash_alg: CirHashAlgorithm::Sha256,
|
hash_alg: CirHashAlgorithm::Sha256,
|
||||||
validation_time: sample_time(),
|
validation_time: sample_time(),
|
||||||
objects: Vec::new(),
|
objects: Vec::new(),
|
||||||
tals: vec![CirTal {
|
trust_anchors: vec![sample_trust_anchor(
|
||||||
tal_uri: "https://tal.example.net/minimal.tal".to_string(),
|
"rsync://example.net/repo/minimal-ta.cer",
|
||||||
tal_bytes: b"rsync://example.net/repo/ta.cer\nMIIB".to_vec(),
|
"https://tal.example.net/minimal.tal",
|
||||||
}],
|
b"minimal-ta-der",
|
||||||
|
)],
|
||||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||||
rejected_objects: Vec::new(),
|
rejected_objects: Vec::new(),
|
||||||
};
|
};
|
||||||
@ -133,7 +144,7 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn cir_model_rejects_unsorted_duplicate_objects() {
|
fn cir_model_rejects_unsorted_duplicate_objects() {
|
||||||
let cir = CanonicalInputRepresentation {
|
let cir = CanonicalInputRepresentation {
|
||||||
version: CIR_VERSION_V2,
|
version: CIR_VERSION_V3,
|
||||||
hash_alg: CirHashAlgorithm::Sha256,
|
hash_alg: CirHashAlgorithm::Sha256,
|
||||||
validation_time: sample_time(),
|
validation_time: sample_time(),
|
||||||
objects: vec![
|
objects: vec![
|
||||||
@ -146,10 +157,11 @@ mod tests {
|
|||||||
sha256: vec![0x22; 32],
|
sha256: vec![0x22; 32],
|
||||||
},
|
},
|
||||||
],
|
],
|
||||||
tals: vec![CirTal {
|
trust_anchors: vec![sample_trust_anchor(
|
||||||
tal_uri: "https://tal.example.net/root.tal".to_string(),
|
"rsync://example.net/repo/ta.cer",
|
||||||
tal_bytes: b"x".to_vec(),
|
"https://tal.example.net/root.tal",
|
||||||
}],
|
b"ta-der",
|
||||||
|
)],
|
||||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||||
rejected_objects: Vec::new(),
|
rejected_objects: Vec::new(),
|
||||||
};
|
};
|
||||||
@ -160,25 +172,27 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn cir_model_rejects_duplicate_tals() {
|
fn cir_model_rejects_duplicate_tals() {
|
||||||
let cir = CanonicalInputRepresentation {
|
let cir = CanonicalInputRepresentation {
|
||||||
version: CIR_VERSION_V2,
|
version: CIR_VERSION_V3,
|
||||||
hash_alg: CirHashAlgorithm::Sha256,
|
hash_alg: CirHashAlgorithm::Sha256,
|
||||||
validation_time: sample_time(),
|
validation_time: sample_time(),
|
||||||
objects: Vec::new(),
|
objects: Vec::new(),
|
||||||
tals: vec![
|
trust_anchors: vec![
|
||||||
CirTal {
|
sample_trust_anchor(
|
||||||
tal_uri: "https://tal.example.net/root.tal".to_string(),
|
"rsync://example.net/repo/ta.cer",
|
||||||
tal_bytes: b"a".to_vec(),
|
"https://tal.example.net/root.tal",
|
||||||
},
|
b"ta-der-a",
|
||||||
CirTal {
|
),
|
||||||
tal_uri: "https://tal.example.net/root.tal".to_string(),
|
sample_trust_anchor(
|
||||||
tal_bytes: b"b".to_vec(),
|
"rsync://example.net/repo/ta.cer",
|
||||||
},
|
"https://tal.example.net/root.tal",
|
||||||
|
b"ta-der-b",
|
||||||
|
),
|
||||||
],
|
],
|
||||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||||
rejected_objects: Vec::new(),
|
rejected_objects: Vec::new(),
|
||||||
};
|
};
|
||||||
let err = encode_cir(&cir).expect_err("duplicate tals must fail");
|
let err = encode_cir(&cir).expect_err("duplicate trust_anchors must fail");
|
||||||
assert!(err.to_string().contains("CIR.tals"), "{err}");
|
assert!(err.to_string().contains("CIR.trustAnchors"), "{err}");
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -186,13 +200,13 @@ mod tests {
|
|||||||
let mut der = encode_cir(&sample_cir()).expect("encode cir");
|
let mut der = encode_cir(&sample_cir()).expect("encode cir");
|
||||||
let pos = der
|
let pos = der
|
||||||
.windows(3)
|
.windows(3)
|
||||||
.position(|window| window == [0x02, 0x01, CIR_VERSION_V2 as u8])
|
.position(|window| window == [0x02, 0x01, CIR_VERSION_V3 as u8])
|
||||||
.or_else(|| {
|
.or_else(|| {
|
||||||
der.windows(3)
|
der.windows(3)
|
||||||
.position(|window| window == [0x02, 0x01, CIR_VERSION_V2 as u8])
|
.position(|window| window == [0x02, 0x01, CIR_VERSION_V3 as u8])
|
||||||
})
|
})
|
||||||
.expect("find version integer");
|
.expect("find version integer");
|
||||||
der[pos + 2] = 3;
|
der[pos + 2] = 2;
|
||||||
let err = decode_cir(&der).expect_err("wrong version must fail");
|
let err = decode_cir(&der).expect_err("wrong version must fail");
|
||||||
assert!(err.to_string().contains("unexpected CIR version"), "{err}");
|
assert!(err.to_string().contains("unexpected CIR version"), "{err}");
|
||||||
}
|
}
|
||||||
@ -228,17 +242,18 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn cir_model_rejects_non_rsync_object_uri_and_empty_tals() {
|
fn cir_model_rejects_non_rsync_object_uri_and_empty_tals() {
|
||||||
let bad_object = CanonicalInputRepresentation {
|
let bad_object = CanonicalInputRepresentation {
|
||||||
version: CIR_VERSION_V2,
|
version: CIR_VERSION_V3,
|
||||||
hash_alg: CirHashAlgorithm::Sha256,
|
hash_alg: CirHashAlgorithm::Sha256,
|
||||||
validation_time: sample_time(),
|
validation_time: sample_time(),
|
||||||
objects: vec![CirObject {
|
objects: vec![CirObject {
|
||||||
rsync_uri: "https://example.net/repo/a.roa".to_string(),
|
rsync_uri: "https://example.net/repo/a.roa".to_string(),
|
||||||
sha256: vec![0x11; 32],
|
sha256: vec![0x11; 32],
|
||||||
}],
|
}],
|
||||||
tals: vec![CirTal {
|
trust_anchors: vec![sample_trust_anchor(
|
||||||
tal_uri: "https://tal.example.net/root.tal".to_string(),
|
"rsync://example.net/repo/ta.cer",
|
||||||
tal_bytes: b"x".to_vec(),
|
"https://tal.example.net/root.tal",
|
||||||
}],
|
b"ta-der",
|
||||||
|
)],
|
||||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||||
rejected_objects: Vec::new(),
|
rejected_objects: Vec::new(),
|
||||||
};
|
};
|
||||||
@ -246,17 +261,18 @@ mod tests {
|
|||||||
assert!(err.to_string().contains("rsync://"), "{err}");
|
assert!(err.to_string().contains("rsync://"), "{err}");
|
||||||
|
|
||||||
let no_tals = CanonicalInputRepresentation {
|
let no_tals = CanonicalInputRepresentation {
|
||||||
version: CIR_VERSION_V2,
|
version: CIR_VERSION_V3,
|
||||||
hash_alg: CirHashAlgorithm::Sha256,
|
hash_alg: CirHashAlgorithm::Sha256,
|
||||||
validation_time: sample_time(),
|
validation_time: sample_time(),
|
||||||
objects: Vec::new(),
|
objects: Vec::new(),
|
||||||
tals: Vec::new(),
|
trust_anchors: Vec::new(),
|
||||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||||
rejected_objects: Vec::new(),
|
rejected_objects: Vec::new(),
|
||||||
};
|
};
|
||||||
let err = encode_cir(&no_tals).expect_err("empty tals must fail");
|
let err = encode_cir(&no_tals).expect_err("empty trust_anchors must fail");
|
||||||
assert!(
|
assert!(
|
||||||
err.to_string().contains("CIR.tals must be non-empty"),
|
err.to_string()
|
||||||
|
.contains("CIR.trustAnchors must be non-empty"),
|
||||||
"{err}"
|
"{err}"
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
@ -264,14 +280,15 @@ mod tests {
|
|||||||
#[test]
|
#[test]
|
||||||
fn cir_model_rejects_non_utc_time_bad_hash_len_and_non_http_tal_uri() {
|
fn cir_model_rejects_non_utc_time_bad_hash_len_and_non_http_tal_uri() {
|
||||||
let bad_time = CanonicalInputRepresentation {
|
let bad_time = CanonicalInputRepresentation {
|
||||||
version: CIR_VERSION_V2,
|
version: CIR_VERSION_V3,
|
||||||
hash_alg: CirHashAlgorithm::Sha256,
|
hash_alg: CirHashAlgorithm::Sha256,
|
||||||
validation_time: sample_time().to_offset(time::UtcOffset::from_hms(8, 0, 0).unwrap()),
|
validation_time: sample_time().to_offset(time::UtcOffset::from_hms(8, 0, 0).unwrap()),
|
||||||
objects: Vec::new(),
|
objects: Vec::new(),
|
||||||
tals: vec![CirTal {
|
trust_anchors: vec![sample_trust_anchor(
|
||||||
tal_uri: "https://tal.example.net/root.tal".to_string(),
|
"rsync://example.net/repo/ta.cer",
|
||||||
tal_bytes: b"x".to_vec(),
|
"https://tal.example.net/root.tal",
|
||||||
}],
|
b"ta-der",
|
||||||
|
)],
|
||||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||||
rejected_objects: Vec::new(),
|
rejected_objects: Vec::new(),
|
||||||
};
|
};
|
||||||
@ -279,17 +296,18 @@ mod tests {
|
|||||||
assert!(err.to_string().contains("UTC"), "{err}");
|
assert!(err.to_string().contains("UTC"), "{err}");
|
||||||
|
|
||||||
let bad_hash = CanonicalInputRepresentation {
|
let bad_hash = CanonicalInputRepresentation {
|
||||||
version: CIR_VERSION_V2,
|
version: CIR_VERSION_V3,
|
||||||
hash_alg: CirHashAlgorithm::Sha256,
|
hash_alg: CirHashAlgorithm::Sha256,
|
||||||
validation_time: sample_time(),
|
validation_time: sample_time(),
|
||||||
objects: vec![CirObject {
|
objects: vec![CirObject {
|
||||||
rsync_uri: "rsync://example.net/repo/a.roa".to_string(),
|
rsync_uri: "rsync://example.net/repo/a.roa".to_string(),
|
||||||
sha256: vec![0x11; 31],
|
sha256: vec![0x11; 31],
|
||||||
}],
|
}],
|
||||||
tals: vec![CirTal {
|
trust_anchors: vec![sample_trust_anchor(
|
||||||
tal_uri: "https://tal.example.net/root.tal".to_string(),
|
"rsync://example.net/repo/ta.cer",
|
||||||
tal_bytes: b"x".to_vec(),
|
"https://tal.example.net/root.tal",
|
||||||
}],
|
b"ta-der",
|
||||||
|
)],
|
||||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||||
rejected_objects: Vec::new(),
|
rejected_objects: Vec::new(),
|
||||||
};
|
};
|
||||||
@ -297,14 +315,15 @@ mod tests {
|
|||||||
assert!(err.to_string().contains("32 bytes"), "{err}");
|
assert!(err.to_string().contains("32 bytes"), "{err}");
|
||||||
|
|
||||||
let bad_tal_uri = CanonicalInputRepresentation {
|
let bad_tal_uri = CanonicalInputRepresentation {
|
||||||
version: CIR_VERSION_V2,
|
version: CIR_VERSION_V3,
|
||||||
hash_alg: CirHashAlgorithm::Sha256,
|
hash_alg: CirHashAlgorithm::Sha256,
|
||||||
validation_time: sample_time(),
|
validation_time: sample_time(),
|
||||||
objects: Vec::new(),
|
objects: Vec::new(),
|
||||||
tals: vec![CirTal {
|
trust_anchors: vec![sample_trust_anchor(
|
||||||
tal_uri: "ftp://tal.example.net/root.tal".to_string(),
|
"rsync://example.net/repo/ta.cer",
|
||||||
tal_bytes: b"x".to_vec(),
|
"ftp://tal.example.net/root.tal",
|
||||||
}],
|
b"ta-der",
|
||||||
|
)],
|
||||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||||
rejected_objects: Vec::new(),
|
rejected_objects: Vec::new(),
|
||||||
};
|
};
|
||||||
@ -332,22 +351,25 @@ mod tests {
|
|||||||
]
|
]
|
||||||
.concat(),
|
.concat(),
|
||||||
);
|
);
|
||||||
let tal = test_encode_tlv(
|
let trust_anchor = test_encode_tlv(
|
||||||
0x30,
|
0x30,
|
||||||
&[
|
&[
|
||||||
|
test_encode_tlv(0x16, b"rsync://example.net/repo/ta.cer"),
|
||||||
test_encode_tlv(0x16, b"https://tal.example.net/root.tal"),
|
test_encode_tlv(0x16, b"https://tal.example.net/root.tal"),
|
||||||
test_encode_tlv(0x04, b"x"),
|
test_encode_tlv(0x04, b"rsync://example.net/repo/ta.cer\n\nAQID\n"),
|
||||||
|
test_encode_tlv(0x04, b"ta-der"),
|
||||||
|
test_encode_tlv(0x04, &super::sha256(b"ta-der")),
|
||||||
]
|
]
|
||||||
.concat(),
|
.concat(),
|
||||||
);
|
);
|
||||||
let bad = test_encode_tlv(
|
let bad = test_encode_tlv(
|
||||||
0x30,
|
0x30,
|
||||||
&[
|
&[
|
||||||
test_encode_tlv(0x02, &[CIR_VERSION_V2 as u8]),
|
test_encode_tlv(0x02, &[CIR_VERSION_V3 as u8]),
|
||||||
test_encode_tlv(0x06, crate::data_model::oid::OID_SHA256_RAW),
|
test_encode_tlv(0x06, crate::data_model::oid::OID_SHA256_RAW),
|
||||||
test_encode_tlv(0x18, b"20260407123456Z"),
|
test_encode_tlv(0x18, b"20260407123456Z"),
|
||||||
test_encode_tlv(0x30, &object),
|
test_encode_tlv(0x30, &object),
|
||||||
test_encode_tlv(0x30, &tal),
|
test_encode_tlv(0x30, &trust_anchor),
|
||||||
test_encode_tlv(0x04, &[0x33; 32]),
|
test_encode_tlv(0x04, &[0x33; 32]),
|
||||||
test_encode_tlv(0x30, &[]),
|
test_encode_tlv(0x30, &[]),
|
||||||
]
|
]
|
||||||
|
|||||||
@ -2,6 +2,7 @@ use crate::data_model::oid::OID_SHA256;
|
|||||||
|
|
||||||
pub const CIR_VERSION_V1: u32 = 1;
|
pub const CIR_VERSION_V1: u32 = 1;
|
||||||
pub const CIR_VERSION_V2: u32 = 2;
|
pub const CIR_VERSION_V2: u32 = 2;
|
||||||
|
pub const CIR_VERSION_V3: u32 = 3;
|
||||||
pub const DIGEST_LEN_SHA256: usize = 32;
|
pub const DIGEST_LEN_SHA256: usize = 32;
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
@ -23,16 +24,16 @@ pub struct CanonicalInputRepresentation {
|
|||||||
pub hash_alg: CirHashAlgorithm,
|
pub hash_alg: CirHashAlgorithm,
|
||||||
pub validation_time: time::OffsetDateTime,
|
pub validation_time: time::OffsetDateTime,
|
||||||
pub objects: Vec<CirObject>,
|
pub objects: Vec<CirObject>,
|
||||||
pub tals: Vec<CirTal>,
|
pub trust_anchors: Vec<CirTrustAnchor>,
|
||||||
pub reject_list_sha256: Vec<u8>,
|
pub reject_list_sha256: Vec<u8>,
|
||||||
pub rejected_objects: Vec<CirRejectedObject>,
|
pub rejected_objects: Vec<CirRejectedObject>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CanonicalInputRepresentation {
|
impl CanonicalInputRepresentation {
|
||||||
pub fn validate(&self) -> Result<(), String> {
|
pub fn validate(&self) -> Result<(), String> {
|
||||||
if self.version != CIR_VERSION_V2 {
|
if self.version != CIR_VERSION_V3 {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"CIR version must be {CIR_VERSION_V2}, got {}",
|
"CIR version must be {CIR_VERSION_V3}, got {}",
|
||||||
self.version
|
self.version
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
@ -47,8 +48,10 @@ impl CanonicalInputRepresentation {
|
|||||||
"CIR.objects must be sorted by rsyncUri and unique",
|
"CIR.objects must be sorted by rsyncUri and unique",
|
||||||
)?;
|
)?;
|
||||||
validate_sorted_unique_strings(
|
validate_sorted_unique_strings(
|
||||||
self.tals.iter().map(|item| item.tal_uri.as_str()),
|
self.trust_anchors
|
||||||
"CIR.tals must be sorted by talUri and unique",
|
.iter()
|
||||||
|
.map(|item| item.ta_rsync_uri.as_str()),
|
||||||
|
"CIR.trustAnchors must be sorted by taRsyncUri and unique",
|
||||||
)?;
|
)?;
|
||||||
validate_sorted_unique_strings(
|
validate_sorted_unique_strings(
|
||||||
self.rejected_objects
|
self.rejected_objects
|
||||||
@ -56,8 +59,21 @@ impl CanonicalInputRepresentation {
|
|||||||
.map(|item| item.object_uri.as_str()),
|
.map(|item| item.object_uri.as_str()),
|
||||||
"CIR.rejectedObjects must be sorted by objectUri and unique",
|
"CIR.rejectedObjects must be sorted by objectUri and unique",
|
||||||
)?;
|
)?;
|
||||||
if self.tals.is_empty() {
|
let object_uris = self
|
||||||
return Err("CIR.tals must be non-empty".into());
|
.objects
|
||||||
|
.iter()
|
||||||
|
.map(|item| item.rsync_uri.as_str())
|
||||||
|
.collect::<std::collections::BTreeSet<_>>();
|
||||||
|
for trust_anchor in &self.trust_anchors {
|
||||||
|
if object_uris.contains(trust_anchor.ta_rsync_uri.as_str()) {
|
||||||
|
return Err(format!(
|
||||||
|
"CIR.objects must not include trust anchor URI {}",
|
||||||
|
trust_anchor.ta_rsync_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 {
|
if self.reject_list_sha256.len() != DIGEST_LEN_SHA256 {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
@ -68,8 +84,8 @@ impl CanonicalInputRepresentation {
|
|||||||
for object in &self.objects {
|
for object in &self.objects {
|
||||||
object.validate()?;
|
object.validate()?;
|
||||||
}
|
}
|
||||||
for tal in &self.tals {
|
for trust_anchor in &self.trust_anchors {
|
||||||
tal.validate()?;
|
trust_anchor.validate()?;
|
||||||
}
|
}
|
||||||
for item in &self.rejected_objects {
|
for item in &self.rejected_objects {
|
||||||
item.validate()?;
|
item.validate()?;
|
||||||
@ -111,21 +127,55 @@ impl CirObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Clone, Debug, PartialEq, Eq)]
|
#[derive(Clone, Debug, PartialEq, Eq)]
|
||||||
pub struct CirTal {
|
pub struct CirTrustAnchor {
|
||||||
|
pub ta_rsync_uri: String,
|
||||||
pub tal_uri: String,
|
pub tal_uri: String,
|
||||||
pub tal_bytes: Vec<u8>,
|
pub tal_bytes: Vec<u8>,
|
||||||
|
pub ta_certificate_der: Vec<u8>,
|
||||||
|
pub ta_certificate_sha256: Vec<u8>,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl CirTal {
|
impl CirTrustAnchor {
|
||||||
pub fn validate(&self) -> Result<(), String> {
|
pub fn validate(&self) -> Result<(), String> {
|
||||||
|
if !self.ta_rsync_uri.starts_with("rsync://") {
|
||||||
|
return Err(format!(
|
||||||
|
"CirTrustAnchor.ta_rsync_uri must start with rsync://, got {}",
|
||||||
|
self.ta_rsync_uri
|
||||||
|
));
|
||||||
|
}
|
||||||
if !(self.tal_uri.starts_with("https://") || self.tal_uri.starts_with("http://")) {
|
if !(self.tal_uri.starts_with("https://") || self.tal_uri.starts_with("http://")) {
|
||||||
return Err(format!(
|
return Err(format!(
|
||||||
"CirTal.tal_uri must start with http:// or https://, got {}",
|
"CirTrustAnchor.tal_uri must start with http:// or https://, got {}",
|
||||||
self.tal_uri
|
self.tal_uri
|
||||||
));
|
));
|
||||||
}
|
}
|
||||||
if self.tal_bytes.is_empty() {
|
if self.tal_bytes.is_empty() {
|
||||||
return Err("CirTal.tal_bytes must be non-empty".into());
|
return Err("CirTrustAnchor.tal_bytes must be non-empty".into());
|
||||||
|
}
|
||||||
|
let tal = crate::data_model::tal::Tal::decode_bytes(&self.tal_bytes)
|
||||||
|
.map_err(|e| format!("CirTrustAnchor.tal_bytes must decode as TAL: {e}"))?;
|
||||||
|
if !tal
|
||||||
|
.ta_uris
|
||||||
|
.iter()
|
||||||
|
.any(|uri| uri.as_str() == self.ta_rsync_uri)
|
||||||
|
{
|
||||||
|
return Err(format!(
|
||||||
|
"CirTrustAnchor.ta_rsync_uri must be listed in TAL bytes: {}",
|
||||||
|
self.ta_rsync_uri
|
||||||
|
));
|
||||||
|
}
|
||||||
|
if self.ta_certificate_der.is_empty() {
|
||||||
|
return Err("CirTrustAnchor.ta_certificate_der must be non-empty".into());
|
||||||
|
}
|
||||||
|
if self.ta_certificate_sha256.len() != DIGEST_LEN_SHA256 {
|
||||||
|
return Err(format!(
|
||||||
|
"CirTrustAnchor.ta_certificate_sha256 must be {DIGEST_LEN_SHA256} bytes, got {}",
|
||||||
|
self.ta_certificate_sha256.len()
|
||||||
|
));
|
||||||
|
}
|
||||||
|
let expected = sha256(&self.ta_certificate_der);
|
||||||
|
if self.ta_certificate_sha256 != expected {
|
||||||
|
return Err("CirTrustAnchor.ta_certificate_sha256 does not match DER bytes".into());
|
||||||
}
|
}
|
||||||
Ok(())
|
Ok(())
|
||||||
}
|
}
|
||||||
@ -150,15 +200,19 @@ impl CirRejectedObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
pub fn compute_reject_list_sha256<'a>(uris: impl IntoIterator<Item = &'a str>) -> Vec<u8> {
|
pub fn compute_reject_list_sha256<'a>(uris: impl IntoIterator<Item = &'a str>) -> Vec<u8> {
|
||||||
use sha2::Digest;
|
|
||||||
|
|
||||||
let mut body = Vec::new();
|
let mut body = Vec::new();
|
||||||
for uri in uris {
|
for uri in uris {
|
||||||
let bytes = uri.as_bytes();
|
let bytes = uri.as_bytes();
|
||||||
body.extend_from_slice(&(bytes.len() as u32).to_be_bytes());
|
body.extend_from_slice(&(bytes.len() as u32).to_be_bytes());
|
||||||
body.extend_from_slice(bytes);
|
body.extend_from_slice(bytes);
|
||||||
}
|
}
|
||||||
sha2::Sha256::digest(body).to_vec()
|
sha256(&body)
|
||||||
|
}
|
||||||
|
|
||||||
|
pub fn sha256(bytes: &[u8]) -> Vec<u8> {
|
||||||
|
use sha2::Digest;
|
||||||
|
|
||||||
|
sha2::Sha256::digest(bytes).to_vec()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn validate_sorted_unique_strings<'a>(
|
fn validate_sorted_unique_strings<'a>(
|
||||||
|
|||||||
@ -1,7 +1,7 @@
|
|||||||
use crate::ccr::{
|
use crate::ccr::{
|
||||||
CcrAccumulator, CcrBuildBreakdown, build_ccr_from_run_with_breakdown, write_ccr_file,
|
CcrAccumulator, CcrBuildBreakdown, build_ccr_from_run_with_breakdown, write_ccr_file,
|
||||||
};
|
};
|
||||||
use crate::cir::{CirTalBinding, export_cir_from_run_multi};
|
use crate::cir::{CirTrustAnchorBinding, export_cir_from_run_multi};
|
||||||
use std::io::BufWriter;
|
use std::io::BufWriter;
|
||||||
use std::path::{Path, PathBuf};
|
use std::path::{Path, PathBuf};
|
||||||
|
|
||||||
@ -1870,7 +1870,7 @@ pub fn run(argv: &[String]) -> Result<(), String> {
|
|||||||
.discoveries
|
.discoveries
|
||||||
.iter()
|
.iter()
|
||||||
.zip(cir_tal_uris.iter())
|
.zip(cir_tal_uris.iter())
|
||||||
.map(|(discovery, tal_uri)| CirTalBinding {
|
.map(|(discovery, tal_uri)| CirTrustAnchorBinding {
|
||||||
trust_anchor: &discovery.trust_anchor,
|
trust_anchor: &discovery.trust_anchor,
|
||||||
tal_uri: tal_uri.as_str(),
|
tal_uri: tal_uri.as_str(),
|
||||||
})
|
})
|
||||||
@ -1889,10 +1889,10 @@ pub fn run(argv: &[String]) -> Result<(), String> {
|
|||||||
cir_write_cir_ms = Some(summary.timing.write_cir_ms);
|
cir_write_cir_ms = Some(summary.timing.write_cir_ms);
|
||||||
cir_total_ms = Some(summary.timing.total_ms);
|
cir_total_ms = Some(summary.timing.total_ms);
|
||||||
eprintln!(
|
eprintln!(
|
||||||
"wrote CIR: {} (objects={}, tals={}, build_cir_ms={}, write_cir_ms={}, total_ms={})",
|
"wrote CIR: {} (objects={}, trust_anchors={}, build_cir_ms={}, write_cir_ms={}, total_ms={})",
|
||||||
cir_out_path.display(),
|
cir_out_path.display(),
|
||||||
summary.object_count,
|
summary.object_count,
|
||||||
summary.tal_count,
|
summary.trust_anchor_count,
|
||||||
summary.timing.build_cir_ms,
|
summary.timing.build_cir_ms,
|
||||||
summary.timing.write_cir_ms,
|
summary.timing.write_cir_ms,
|
||||||
summary.timing.total_ms
|
summary.timing.total_ms
|
||||||
|
|||||||
@ -319,11 +319,30 @@ fn discover_root_ca_instance_from_tal_and_ta_der_impl(
|
|||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn canonical_tal_rsync_uri_from_bytes(tal_bytes: &[u8]) -> Result<Url, FromTalError> {
|
||||||
|
let tal = Tal::decode_bytes(tal_bytes)?;
|
||||||
|
tal.ta_uris
|
||||||
|
.iter()
|
||||||
|
.find(|uri| uri.scheme() == "rsync")
|
||||||
|
.cloned()
|
||||||
|
.ok_or_else(|| {
|
||||||
|
FromTalError::TaFetch("TAL contains no rsync TA URI for offline TA binding".to_string())
|
||||||
|
})
|
||||||
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::fetch::rsync::LocalDirRsyncFetcher;
|
use crate::fetch::rsync::LocalDirRsyncFetcher;
|
||||||
|
|
||||||
|
struct FailingHttpFetcher;
|
||||||
|
|
||||||
|
impl Fetcher for FailingHttpFetcher {
|
||||||
|
fn fetch(&self, uri: &str) -> Result<Vec<u8>, String> {
|
||||||
|
Err(format!("blocked test HTTP fetch: {uri}"))
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn discover_root_ca_instance_from_tal_with_fetchers_supports_rsync_ta_uri() {
|
fn discover_root_ca_instance_from_tal_with_fetchers_supports_rsync_ta_uri() {
|
||||||
let tal_bytes = std::fs::read(
|
let tal_bytes = std::fs::read(
|
||||||
@ -352,17 +371,18 @@ mod tests {
|
|||||||
std::fs::create_dir_all(&mirror_root).unwrap();
|
std::fs::create_dir_all(&mirror_root).unwrap();
|
||||||
std::fs::write(mirror_root.join("apnic-rpki-root-iana-origin.cer"), ta_der).unwrap();
|
std::fs::write(mirror_root.join("apnic-rpki-root-iana-origin.cer"), ta_der).unwrap();
|
||||||
|
|
||||||
let http = crate::fetch::http::BlockingHttpFetcher::new(
|
|
||||||
crate::fetch::http::HttpFetcherConfig::default(),
|
|
||||||
)
|
|
||||||
.unwrap();
|
|
||||||
let rsync = LocalDirRsyncFetcher::new(
|
let rsync = LocalDirRsyncFetcher::new(
|
||||||
td.path()
|
td.path()
|
||||||
.join(rsync_uri.host_str().unwrap())
|
.join(rsync_uri.host_str().unwrap())
|
||||||
.join("repository"),
|
.join("repository"),
|
||||||
);
|
);
|
||||||
let discovery = discover_root_ca_instance_from_tal_with_fetchers(&http, &rsync, tal, None)
|
let discovery = discover_root_ca_instance_from_tal_with_fetchers(
|
||||||
.expect("discover via rsync TA");
|
&FailingHttpFetcher,
|
||||||
|
&rsync,
|
||||||
|
tal,
|
||||||
|
None,
|
||||||
|
)
|
||||||
|
.expect("discover via rsync TA fallback");
|
||||||
assert!(
|
assert!(
|
||||||
discovery
|
discovery
|
||||||
.trust_anchor
|
.trust_anchor
|
||||||
|
|||||||
@ -23,7 +23,8 @@ use crate::replay::fetch_http::PayloadReplayHttpFetcher;
|
|||||||
use crate::replay::fetch_rsync::PayloadReplayRsyncFetcher;
|
use crate::replay::fetch_rsync::PayloadReplayRsyncFetcher;
|
||||||
use crate::sync::rrdp::Fetcher;
|
use crate::sync::rrdp::Fetcher;
|
||||||
use crate::validation::from_tal::{
|
use crate::validation::from_tal::{
|
||||||
DiscoveredRootCaInstance, FromTalError, discover_root_ca_instance_from_tal_and_ta_der,
|
DiscoveredRootCaInstance, FromTalError, canonical_tal_rsync_uri_from_bytes,
|
||||||
|
discover_root_ca_instance_from_tal_and_ta_der,
|
||||||
discover_root_ca_instance_from_tal_and_ta_der_with_strict_name,
|
discover_root_ca_instance_from_tal_and_ta_der_with_strict_name,
|
||||||
discover_root_ca_instance_from_tal_url,
|
discover_root_ca_instance_from_tal_url,
|
||||||
discover_root_ca_instance_from_tal_url_with_strict_name,
|
discover_root_ca_instance_from_tal_url_with_strict_name,
|
||||||
@ -251,12 +252,19 @@ fn root_discovery_from_tal_input(
|
|||||||
let ta_der = std::fs::read(ta_path).map_err(|e| {
|
let ta_der = std::fs::read(ta_path).map_err(|e| {
|
||||||
FromTalError::TaFetch(format!("read TA file failed: {}: {e}", ta_path.display()))
|
FromTalError::TaFetch(format!("read TA file failed: {}: {e}", ta_path.display()))
|
||||||
})?;
|
})?;
|
||||||
|
let resolved_ta_uri = canonical_tal_rsync_uri_from_bytes(&tal_bytes)?;
|
||||||
if strict_name {
|
if strict_name {
|
||||||
discover_root_ca_instance_from_tal_and_ta_der_with_strict_name(
|
discover_root_ca_instance_from_tal_and_ta_der_with_strict_name(
|
||||||
&tal_bytes, &ta_der, None,
|
&tal_bytes,
|
||||||
|
&ta_der,
|
||||||
|
Some(&resolved_ta_uri),
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
discover_root_ca_instance_from_tal_and_ta_der(&tal_bytes, &ta_der, None)
|
discover_root_ca_instance_from_tal_and_ta_der(
|
||||||
|
&tal_bytes,
|
||||||
|
&ta_der,
|
||||||
|
Some(&resolved_ta_uri),
|
||||||
|
)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -6,8 +6,8 @@ use rpki::ccr::{
|
|||||||
encode_content_info,
|
encode_content_info,
|
||||||
};
|
};
|
||||||
use rpki::cir::{
|
use rpki::cir::{
|
||||||
CIR_VERSION_V2, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirTal,
|
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirTrustAnchor,
|
||||||
compute_reject_list_sha256, encode_cir,
|
compute_reject_list_sha256, encode_cir, sha256,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn skip_heavy_blackbox_test() -> bool {
|
fn skip_heavy_blackbox_test() -> bool {
|
||||||
@ -42,9 +42,10 @@ fn cir_full_and_delta_pair_reuses_shared_repo_bytes_db() {
|
|||||||
use sha2::{Digest, Sha256};
|
use sha2::{Digest, Sha256};
|
||||||
hex::encode(Sha256::digest(b"delta-object"))
|
hex::encode(Sha256::digest(b"delta-object"))
|
||||||
};
|
};
|
||||||
|
let trust_anchors = vec![test_trust_anchor()];
|
||||||
|
|
||||||
let full_cir = CanonicalInputRepresentation {
|
let full_cir = CanonicalInputRepresentation {
|
||||||
version: CIR_VERSION_V2,
|
version: CIR_VERSION_V3,
|
||||||
hash_alg: CirHashAlgorithm::Sha256,
|
hash_alg: CirHashAlgorithm::Sha256,
|
||||||
validation_time: time::OffsetDateTime::parse(
|
validation_time: time::OffsetDateTime::parse(
|
||||||
"2026-03-16T11:49:15Z",
|
"2026-03-16T11:49:15Z",
|
||||||
@ -55,15 +56,12 @@ fn cir_full_and_delta_pair_reuses_shared_repo_bytes_db() {
|
|||||||
rsync_uri: "rsync://example.net/repo/full.roa".to_string(),
|
rsync_uri: "rsync://example.net/repo/full.roa".to_string(),
|
||||||
sha256: hex::decode(&full_obj_hash).unwrap(),
|
sha256: hex::decode(&full_obj_hash).unwrap(),
|
||||||
}],
|
}],
|
||||||
tals: vec![CirTal {
|
trust_anchors: trust_anchors.clone(),
|
||||||
tal_uri: "https://rpki.apnic.net/tal/apnic-rfc7730-https.tal".to_string(),
|
|
||||||
tal_bytes: b"rsync://example.net/repo/root.cer\nMIIB".to_vec(),
|
|
||||||
}],
|
|
||||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||||
rejected_objects: Vec::new(),
|
rejected_objects: Vec::new(),
|
||||||
};
|
};
|
||||||
let delta_cir = CanonicalInputRepresentation {
|
let delta_cir = CanonicalInputRepresentation {
|
||||||
version: CIR_VERSION_V2,
|
version: CIR_VERSION_V3,
|
||||||
hash_alg: CirHashAlgorithm::Sha256,
|
hash_alg: CirHashAlgorithm::Sha256,
|
||||||
validation_time: time::OffsetDateTime::parse(
|
validation_time: time::OffsetDateTime::parse(
|
||||||
"2026-03-16T11:50:15Z",
|
"2026-03-16T11:50:15Z",
|
||||||
@ -84,7 +82,7 @@ fn cir_full_and_delta_pair_reuses_shared_repo_bytes_db() {
|
|||||||
objects.sort_by(|a, b| a.rsync_uri.cmp(&b.rsync_uri));
|
objects.sort_by(|a, b| a.rsync_uri.cmp(&b.rsync_uri));
|
||||||
objects
|
objects
|
||||||
},
|
},
|
||||||
tals: full_cir.tals.clone(),
|
trust_anchors,
|
||||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||||
rejected_objects: Vec::new(),
|
rejected_objects: Vec::new(),
|
||||||
};
|
};
|
||||||
@ -226,3 +224,15 @@ fi
|
|||||||
assert!(out.join("full").join("result.ccr").is_file());
|
assert!(out.join("full").join("result.ccr").is_file());
|
||||||
assert!(out.join("delta-001").join("result.ccr").is_file());
|
assert!(out.join("delta-001").join("result.ccr").is_file());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_trust_anchor() -> CirTrustAnchor {
|
||||||
|
let ta_rsync_uri = "rsync://example.net/repo/root.cer";
|
||||||
|
let ta_certificate_der = b"ta-der".to_vec();
|
||||||
|
CirTrustAnchor {
|
||||||
|
ta_rsync_uri: ta_rsync_uri.to_string(),
|
||||||
|
tal_uri: "https://rpki.apnic.net/tal/apnic-rfc7730-https.tal".to_string(),
|
||||||
|
tal_bytes: format!("{ta_rsync_uri}\n\nAQID\n").into_bytes(),
|
||||||
|
ta_certificate_sha256: sha256(&ta_certificate_der),
|
||||||
|
ta_certificate_der,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -7,8 +7,8 @@ use rpki::ccr::{
|
|||||||
encode_content_info,
|
encode_content_info,
|
||||||
};
|
};
|
||||||
use rpki::cir::{
|
use rpki::cir::{
|
||||||
CIR_VERSION_V2, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirTal,
|
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirTrustAnchor,
|
||||||
compute_reject_list_sha256, encode_cir,
|
compute_reject_list_sha256, encode_cir, sha256,
|
||||||
};
|
};
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
@ -34,7 +34,7 @@ fn cir_drop_report_counts_dropped_roa_objects_and_vrps() {
|
|||||||
.expect("write repo bytes");
|
.expect("write repo bytes");
|
||||||
|
|
||||||
let cir = CanonicalInputRepresentation {
|
let cir = CanonicalInputRepresentation {
|
||||||
version: CIR_VERSION_V2,
|
version: CIR_VERSION_V3,
|
||||||
hash_alg: CirHashAlgorithm::Sha256,
|
hash_alg: CirHashAlgorithm::Sha256,
|
||||||
validation_time: time::OffsetDateTime::parse(
|
validation_time: time::OffsetDateTime::parse(
|
||||||
"2026-04-09T00:00:00Z",
|
"2026-04-09T00:00:00Z",
|
||||||
@ -45,10 +45,7 @@ fn cir_drop_report_counts_dropped_roa_objects_and_vrps() {
|
|||||||
rsync_uri: "rsync://example.net/repo/AS4538.roa".to_string(),
|
rsync_uri: "rsync://example.net/repo/AS4538.roa".to_string(),
|
||||||
sha256: hex::decode(&hash).unwrap(),
|
sha256: hex::decode(&hash).unwrap(),
|
||||||
}],
|
}],
|
||||||
tals: vec![CirTal {
|
trust_anchors: vec![test_trust_anchor()],
|
||||||
tal_uri: "https://example.test/root.tal".to_string(),
|
|
||||||
tal_bytes: b"rsync://example.net/repo/root.cer\nMIIB".to_vec(),
|
|
||||||
}],
|
|
||||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||||
rejected_objects: Vec::new(),
|
rejected_objects: Vec::new(),
|
||||||
};
|
};
|
||||||
@ -124,3 +121,15 @@ fn cir_drop_report_counts_dropped_roa_objects_and_vrps() {
|
|||||||
.contains("Dropped By Reason")
|
.contains("Dropped By Reason")
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_trust_anchor() -> CirTrustAnchor {
|
||||||
|
let ta_rsync_uri = "rsync://example.net/repo/root.cer";
|
||||||
|
let ta_certificate_der = b"ta-der".to_vec();
|
||||||
|
CirTrustAnchor {
|
||||||
|
ta_rsync_uri: ta_rsync_uri.to_string(),
|
||||||
|
tal_uri: "https://example.test/root.tal".to_string(),
|
||||||
|
tal_bytes: format!("{ta_rsync_uri}\n\nAQID\n").into_bytes(),
|
||||||
|
ta_certificate_sha256: sha256(&ta_certificate_der),
|
||||||
|
ta_certificate_der,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -3,8 +3,8 @@ use std::process::Command;
|
|||||||
|
|
||||||
use rpki::blob_store::ExternalRepoBytesDb;
|
use rpki::blob_store::ExternalRepoBytesDb;
|
||||||
use rpki::cir::{
|
use rpki::cir::{
|
||||||
CIR_VERSION_V2, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirTal,
|
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirTrustAnchor,
|
||||||
compute_reject_list_sha256, encode_cir, materialize_cir_from_repo_bytes,
|
compute_reject_list_sha256, encode_cir, materialize_cir_from_repo_bytes, sha256,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn skip_heavy_script_replay_test() -> bool {
|
fn skip_heavy_script_replay_test() -> bool {
|
||||||
@ -30,26 +30,22 @@ fn build_ta_only_cir() -> (CanonicalInputRepresentation, Vec<u8>) {
|
|||||||
.expect("tal has rsync uri")
|
.expect("tal has rsync uri")
|
||||||
.as_str()
|
.as_str()
|
||||||
.to_string();
|
.to_string();
|
||||||
let ta_hash = {
|
|
||||||
use sha2::{Digest, Sha256};
|
|
||||||
Sha256::digest(&ta_bytes).to_vec()
|
|
||||||
};
|
|
||||||
(
|
(
|
||||||
CanonicalInputRepresentation {
|
CanonicalInputRepresentation {
|
||||||
version: CIR_VERSION_V2,
|
version: CIR_VERSION_V3,
|
||||||
hash_alg: CirHashAlgorithm::Sha256,
|
hash_alg: CirHashAlgorithm::Sha256,
|
||||||
validation_time: time::OffsetDateTime::parse(
|
validation_time: time::OffsetDateTime::parse(
|
||||||
"2026-04-07T00:00:00Z",
|
"2026-04-07T00:00:00Z",
|
||||||
&time::format_description::well_known::Rfc3339,
|
&time::format_description::well_known::Rfc3339,
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
objects: vec![CirObject {
|
objects: Vec::new(),
|
||||||
rsync_uri: ta_rsync_uri,
|
trust_anchors: vec![CirTrustAnchor {
|
||||||
sha256: ta_hash,
|
ta_rsync_uri,
|
||||||
}],
|
|
||||||
tals: vec![CirTal {
|
|
||||||
tal_uri: "https://example.test/root.tal".to_string(),
|
tal_uri: "https://example.test/root.tal".to_string(),
|
||||||
tal_bytes,
|
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>()),
|
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||||
rejected_objects: Vec::new(),
|
rejected_objects: Vec::new(),
|
||||||
|
|||||||
@ -3,8 +3,8 @@ use std::process::Command;
|
|||||||
|
|
||||||
use rpki::blob_store::ExternalRepoBytesDb;
|
use rpki::blob_store::ExternalRepoBytesDb;
|
||||||
use rpki::cir::{
|
use rpki::cir::{
|
||||||
CIR_VERSION_V2, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirTal,
|
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirTrustAnchor,
|
||||||
compute_reject_list_sha256, encode_cir, materialize_cir_from_repo_bytes,
|
compute_reject_list_sha256, encode_cir, materialize_cir_from_repo_bytes, sha256,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn skip_heavy_script_replay_test() -> bool {
|
fn skip_heavy_script_replay_test() -> bool {
|
||||||
@ -30,26 +30,22 @@ fn build_ta_only_cir() -> (CanonicalInputRepresentation, Vec<u8>) {
|
|||||||
.expect("tal has rsync uri")
|
.expect("tal has rsync uri")
|
||||||
.as_str()
|
.as_str()
|
||||||
.to_string();
|
.to_string();
|
||||||
let ta_hash = {
|
|
||||||
use sha2::{Digest, Sha256};
|
|
||||||
Sha256::digest(&ta_bytes).to_vec()
|
|
||||||
};
|
|
||||||
(
|
(
|
||||||
CanonicalInputRepresentation {
|
CanonicalInputRepresentation {
|
||||||
version: CIR_VERSION_V2,
|
version: CIR_VERSION_V3,
|
||||||
hash_alg: CirHashAlgorithm::Sha256,
|
hash_alg: CirHashAlgorithm::Sha256,
|
||||||
validation_time: time::OffsetDateTime::parse(
|
validation_time: time::OffsetDateTime::parse(
|
||||||
"2026-04-07T00:00:00Z",
|
"2026-04-07T00:00:00Z",
|
||||||
&time::format_description::well_known::Rfc3339,
|
&time::format_description::well_known::Rfc3339,
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
objects: vec![CirObject {
|
objects: Vec::new(),
|
||||||
rsync_uri: ta_rsync_uri,
|
trust_anchors: vec![CirTrustAnchor {
|
||||||
sha256: ta_hash,
|
ta_rsync_uri,
|
||||||
}],
|
|
||||||
tals: vec![CirTal {
|
|
||||||
tal_uri: "https://example.test/root.tal".to_string(),
|
tal_uri: "https://example.test/root.tal".to_string(),
|
||||||
tal_bytes,
|
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>()),
|
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||||
rejected_objects: Vec::new(),
|
rejected_objects: Vec::new(),
|
||||||
|
|||||||
@ -6,8 +6,8 @@ use rpki::ccr::{
|
|||||||
encode_content_info,
|
encode_content_info,
|
||||||
};
|
};
|
||||||
use rpki::cir::{
|
use rpki::cir::{
|
||||||
CIR_VERSION_V2, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirTal,
|
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirTrustAnchor,
|
||||||
compute_reject_list_sha256, encode_cir,
|
compute_reject_list_sha256, encode_cir, sha256,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn skip_heavy_blackbox_test() -> bool {
|
fn skip_heavy_blackbox_test() -> bool {
|
||||||
@ -36,7 +36,7 @@ fn cir_offline_sequence_writes_parseable_sequence_json_and_steps() {
|
|||||||
.unwrap();
|
.unwrap();
|
||||||
|
|
||||||
let mk_cir = |uri: &str, hash_hex: &str, vt: &str| CanonicalInputRepresentation {
|
let mk_cir = |uri: &str, hash_hex: &str, vt: &str| CanonicalInputRepresentation {
|
||||||
version: CIR_VERSION_V2,
|
version: CIR_VERSION_V3,
|
||||||
hash_alg: CirHashAlgorithm::Sha256,
|
hash_alg: CirHashAlgorithm::Sha256,
|
||||||
validation_time: time::OffsetDateTime::parse(
|
validation_time: time::OffsetDateTime::parse(
|
||||||
vt,
|
vt,
|
||||||
@ -47,10 +47,7 @@ fn cir_offline_sequence_writes_parseable_sequence_json_and_steps() {
|
|||||||
rsync_uri: uri.to_string(),
|
rsync_uri: uri.to_string(),
|
||||||
sha256: hex::decode(hash_hex).unwrap(),
|
sha256: hex::decode(hash_hex).unwrap(),
|
||||||
}],
|
}],
|
||||||
tals: vec![CirTal {
|
trust_anchors: vec![test_trust_anchor()],
|
||||||
tal_uri: "https://rpki.apnic.net/tal/apnic-rfc7730-https.tal".to_string(),
|
|
||||||
tal_bytes: b"rsync://example.net/repo/root.cer\nMIIB".to_vec(),
|
|
||||||
}],
|
|
||||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||||
rejected_objects: Vec::new(),
|
rejected_objects: Vec::new(),
|
||||||
};
|
};
|
||||||
@ -68,7 +65,7 @@ fn cir_offline_sequence_writes_parseable_sequence_json_and_steps() {
|
|||||||
"2026-03-16T11:49:15Z",
|
"2026-03-16T11:49:15Z",
|
||||||
);
|
);
|
||||||
let delta_cir = CanonicalInputRepresentation {
|
let delta_cir = CanonicalInputRepresentation {
|
||||||
version: CIR_VERSION_V2,
|
version: CIR_VERSION_V3,
|
||||||
hash_alg: CirHashAlgorithm::Sha256,
|
hash_alg: CirHashAlgorithm::Sha256,
|
||||||
validation_time: time::OffsetDateTime::parse(
|
validation_time: time::OffsetDateTime::parse(
|
||||||
"2026-03-16T11:50:15Z",
|
"2026-03-16T11:50:15Z",
|
||||||
@ -86,7 +83,7 @@ fn cir_offline_sequence_writes_parseable_sequence_json_and_steps() {
|
|||||||
objects.sort_by(|a, b| a.rsync_uri.cmp(&b.rsync_uri));
|
objects.sort_by(|a, b| a.rsync_uri.cmp(&b.rsync_uri));
|
||||||
objects
|
objects
|
||||||
},
|
},
|
||||||
tals: full_cir.tals.clone(),
|
trust_anchors: full_cir.trust_anchors.clone(),
|
||||||
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||||
rejected_objects: Vec::new(),
|
rejected_objects: Vec::new(),
|
||||||
};
|
};
|
||||||
@ -244,3 +241,15 @@ fi
|
|||||||
assert!(out.join(rel).is_file(), "missing {}", rel);
|
assert!(out.join(rel).is_file(), "missing {}", rel);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
fn test_trust_anchor() -> CirTrustAnchor {
|
||||||
|
let ta_rsync_uri = "rsync://example.net/repo/root.cer";
|
||||||
|
let ta_certificate_der = b"ta-der".to_vec();
|
||||||
|
CirTrustAnchor {
|
||||||
|
ta_rsync_uri: ta_rsync_uri.to_string(),
|
||||||
|
tal_uri: "https://rpki.apnic.net/tal/apnic-rfc7730-https.tal".to_string(),
|
||||||
|
tal_bytes: format!("{ta_rsync_uri}\n\nAQID\n").into_bytes(),
|
||||||
|
ta_certificate_sha256: sha256(&ta_certificate_der),
|
||||||
|
ta_certificate_der,
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|||||||
@ -3,8 +3,8 @@ use std::process::Command;
|
|||||||
|
|
||||||
use rpki::blob_store::ExternalRepoBytesDb;
|
use rpki::blob_store::ExternalRepoBytesDb;
|
||||||
use rpki::cir::{
|
use rpki::cir::{
|
||||||
CIR_VERSION_V2, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirTal,
|
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirTrustAnchor,
|
||||||
compute_reject_list_sha256, encode_cir, materialize_cir_from_repo_bytes,
|
compute_reject_list_sha256, encode_cir, materialize_cir_from_repo_bytes, sha256,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn skip_heavy_script_replay_test() -> bool {
|
fn skip_heavy_script_replay_test() -> bool {
|
||||||
@ -30,26 +30,22 @@ fn build_ta_only_cir() -> (CanonicalInputRepresentation, Vec<u8>) {
|
|||||||
.expect("tal has rsync uri")
|
.expect("tal has rsync uri")
|
||||||
.as_str()
|
.as_str()
|
||||||
.to_string();
|
.to_string();
|
||||||
let ta_hash = {
|
|
||||||
use sha2::{Digest, Sha256};
|
|
||||||
Sha256::digest(&ta_bytes).to_vec()
|
|
||||||
};
|
|
||||||
(
|
(
|
||||||
CanonicalInputRepresentation {
|
CanonicalInputRepresentation {
|
||||||
version: CIR_VERSION_V2,
|
version: CIR_VERSION_V3,
|
||||||
hash_alg: CirHashAlgorithm::Sha256,
|
hash_alg: CirHashAlgorithm::Sha256,
|
||||||
validation_time: time::OffsetDateTime::parse(
|
validation_time: time::OffsetDateTime::parse(
|
||||||
"2026-04-07T00:00:00Z",
|
"2026-04-07T00:00:00Z",
|
||||||
&time::format_description::well_known::Rfc3339,
|
&time::format_description::well_known::Rfc3339,
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
objects: vec![CirObject {
|
objects: Vec::new(),
|
||||||
rsync_uri: ta_rsync_uri,
|
trust_anchors: vec![CirTrustAnchor {
|
||||||
sha256: ta_hash,
|
ta_rsync_uri,
|
||||||
}],
|
|
||||||
tals: vec![CirTal {
|
|
||||||
tal_uri: "https://example.test/root.tal".to_string(),
|
tal_uri: "https://example.test/root.tal".to_string(),
|
||||||
tal_bytes,
|
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>()),
|
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||||
rejected_objects: Vec::new(),
|
rejected_objects: Vec::new(),
|
||||||
|
|||||||
@ -3,8 +3,8 @@ use std::process::Command;
|
|||||||
|
|
||||||
use rpki::blob_store::ExternalRepoBytesDb;
|
use rpki::blob_store::ExternalRepoBytesDb;
|
||||||
use rpki::cir::{
|
use rpki::cir::{
|
||||||
CIR_VERSION_V2, CanonicalInputRepresentation, CirHashAlgorithm, CirObject, CirTal,
|
CIR_VERSION_V3, CanonicalInputRepresentation, CirHashAlgorithm, CirTrustAnchor,
|
||||||
compute_reject_list_sha256, encode_cir, materialize_cir_from_repo_bytes,
|
compute_reject_list_sha256, encode_cir, materialize_cir_from_repo_bytes, sha256,
|
||||||
};
|
};
|
||||||
|
|
||||||
fn skip_heavy_script_replay_test() -> bool {
|
fn skip_heavy_script_replay_test() -> bool {
|
||||||
@ -30,26 +30,22 @@ fn build_ta_only_cir() -> (CanonicalInputRepresentation, Vec<u8>) {
|
|||||||
.expect("tal has rsync uri")
|
.expect("tal has rsync uri")
|
||||||
.as_str()
|
.as_str()
|
||||||
.to_string();
|
.to_string();
|
||||||
let ta_hash = {
|
|
||||||
use sha2::{Digest, Sha256};
|
|
||||||
Sha256::digest(&ta_bytes).to_vec()
|
|
||||||
};
|
|
||||||
(
|
(
|
||||||
CanonicalInputRepresentation {
|
CanonicalInputRepresentation {
|
||||||
version: CIR_VERSION_V2,
|
version: CIR_VERSION_V3,
|
||||||
hash_alg: CirHashAlgorithm::Sha256,
|
hash_alg: CirHashAlgorithm::Sha256,
|
||||||
validation_time: time::OffsetDateTime::parse(
|
validation_time: time::OffsetDateTime::parse(
|
||||||
"2026-04-07T00:00:00Z",
|
"2026-04-07T00:00:00Z",
|
||||||
&time::format_description::well_known::Rfc3339,
|
&time::format_description::well_known::Rfc3339,
|
||||||
)
|
)
|
||||||
.unwrap(),
|
.unwrap(),
|
||||||
objects: vec![CirObject {
|
objects: Vec::new(),
|
||||||
rsync_uri: ta_rsync_uri,
|
trust_anchors: vec![CirTrustAnchor {
|
||||||
sha256: ta_hash,
|
ta_rsync_uri,
|
||||||
}],
|
|
||||||
tals: vec![CirTal {
|
|
||||||
tal_uri: "https://example.test/root.tal".to_string(),
|
tal_uri: "https://example.test/root.tal".to_string(),
|
||||||
tal_bytes,
|
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>()),
|
reject_list_sha256: compute_reject_list_sha256(std::iter::empty::<&str>()),
|
||||||
rejected_objects: Vec::new(),
|
rejected_objects: Vec::new(),
|
||||||
|
|||||||
@ -127,12 +127,16 @@ fn cli_run_offline_mode_writes_cir_and_static_pool() {
|
|||||||
|
|
||||||
let bytes = std::fs::read(&cir_path).expect("read cir");
|
let bytes = std::fs::read(&cir_path).expect("read cir");
|
||||||
let cir = rpki::cir::decode_cir(&bytes).expect("decode cir");
|
let cir = rpki::cir::decode_cir(&bytes).expect("decode cir");
|
||||||
assert_eq!(cir.tals.len(), 1);
|
assert_eq!(cir.trust_anchors.len(), 1);
|
||||||
assert_eq!(cir.tals[0].tal_uri, "https://example.test/root.tal");
|
assert_eq!(
|
||||||
|
cir.trust_anchors[0].tal_uri,
|
||||||
|
"https://example.test/root.tal"
|
||||||
|
);
|
||||||
|
assert!(!cir.trust_anchors[0].ta_certificate_der.is_empty());
|
||||||
assert!(
|
assert!(
|
||||||
cir.objects
|
!cir.objects
|
||||||
.iter()
|
.iter()
|
||||||
.any(|item| item.rsync_uri.contains("apnic-rpki-root-iana-origin.cer"))
|
.any(|item| item.rsync_uri == cir.trust_anchors[0].ta_rsync_uri)
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
Loading…
x
Reference in New Issue
Block a user