309 lines
13 KiB
Rust
309 lines
13 KiB
Rust
use crate::ccr::build::{
|
|
build_aspa_payload_state, build_manifest_state_from_vcirs_with_breakdown,
|
|
build_roa_payload_state, build_router_key_state_from_runtime, build_trust_anchor_state,
|
|
CcrBuildError, ManifestStateBuildBreakdown,
|
|
};
|
|
use crate::ccr::encode::{encode_content_info, CcrEncodeError};
|
|
use crate::ccr::model::{CcrContentInfo, CcrDigestAlgorithm, RpkiCanonicalCacheRepresentation};
|
|
use crate::data_model::ta::TrustAnchor;
|
|
use crate::storage::RocksStore;
|
|
use crate::validation::objects::{AspaAttestation, RouterKeyPayload, Vrp};
|
|
use serde::Serialize;
|
|
use std::path::Path;
|
|
|
|
#[derive(Debug, thiserror::Error)]
|
|
pub enum CcrExportError {
|
|
#[error("list VCIRs failed: {0}")]
|
|
ListVcirs(String),
|
|
|
|
#[error("build CCR state failed: {0}")]
|
|
Build(#[from] CcrBuildError),
|
|
|
|
#[error("encode CCR failed: {0}")]
|
|
Encode(#[from] CcrEncodeError),
|
|
|
|
#[error("write CCR file failed: {0}: {1}")]
|
|
Write(String, String),
|
|
}
|
|
|
|
#[derive(Clone, Debug, Default, PartialEq, Eq, Serialize)]
|
|
pub struct CcrBuildBreakdown {
|
|
pub vcir_count: usize,
|
|
pub list_vcirs_ms: u64,
|
|
pub manifest_state_ms: u64,
|
|
pub manifest_state_breakdown: ManifestStateBuildBreakdown,
|
|
pub roa_payload_state_ms: u64,
|
|
pub aspa_payload_state_ms: u64,
|
|
pub trust_anchor_state_ms: u64,
|
|
pub router_key_state_ms: u64,
|
|
pub total_ms: u64,
|
|
}
|
|
|
|
pub fn build_ccr_from_run(
|
|
store: &RocksStore,
|
|
trust_anchors: &[TrustAnchor],
|
|
vrps: &[Vrp],
|
|
aspas: &[AspaAttestation],
|
|
router_keys: &[RouterKeyPayload],
|
|
produced_at: time::OffsetDateTime,
|
|
) -> Result<RpkiCanonicalCacheRepresentation, CcrExportError> {
|
|
build_ccr_from_run_with_breakdown(store, trust_anchors, vrps, aspas, router_keys, produced_at)
|
|
.map(|(ccr, _)| ccr)
|
|
}
|
|
|
|
pub fn build_ccr_from_run_with_breakdown(
|
|
store: &RocksStore,
|
|
trust_anchors: &[TrustAnchor],
|
|
vrps: &[Vrp],
|
|
aspas: &[AspaAttestation],
|
|
router_keys: &[RouterKeyPayload],
|
|
produced_at: time::OffsetDateTime,
|
|
) -> Result<(RpkiCanonicalCacheRepresentation, CcrBuildBreakdown), CcrExportError> {
|
|
let total_started = std::time::Instant::now();
|
|
let mut breakdown = CcrBuildBreakdown::default();
|
|
|
|
let started = std::time::Instant::now();
|
|
let vcirs = store
|
|
.list_vcirs()
|
|
.map_err(|e| CcrExportError::ListVcirs(e.to_string()))?;
|
|
breakdown.list_vcirs_ms = started.elapsed().as_millis() as u64;
|
|
breakdown.vcir_count = vcirs.len();
|
|
|
|
let started = std::time::Instant::now();
|
|
let (mfts, manifest_state_breakdown) =
|
|
build_manifest_state_from_vcirs_with_breakdown(store, &vcirs)?;
|
|
breakdown.manifest_state_ms = started.elapsed().as_millis() as u64;
|
|
breakdown.manifest_state_breakdown = manifest_state_breakdown;
|
|
|
|
let started = std::time::Instant::now();
|
|
let vrps = build_roa_payload_state(vrps)?;
|
|
breakdown.roa_payload_state_ms = started.elapsed().as_millis() as u64;
|
|
|
|
let started = std::time::Instant::now();
|
|
let vaps = build_aspa_payload_state(aspas)?;
|
|
breakdown.aspa_payload_state_ms = started.elapsed().as_millis() as u64;
|
|
|
|
let started = std::time::Instant::now();
|
|
let tas = build_trust_anchor_state(trust_anchors)?;
|
|
breakdown.trust_anchor_state_ms = started.elapsed().as_millis() as u64;
|
|
|
|
let started = std::time::Instant::now();
|
|
let rks = build_router_key_state_from_runtime(router_keys)?;
|
|
breakdown.router_key_state_ms = started.elapsed().as_millis() as u64;
|
|
|
|
let ccr = RpkiCanonicalCacheRepresentation {
|
|
version: 0,
|
|
hash_alg: CcrDigestAlgorithm::Sha256,
|
|
produced_at,
|
|
mfts: Some(mfts),
|
|
vrps: Some(vrps),
|
|
vaps: Some(vaps),
|
|
tas: Some(tas),
|
|
rks: Some(rks),
|
|
};
|
|
breakdown.total_ms = total_started.elapsed().as_millis() as u64;
|
|
|
|
Ok((ccr, breakdown))
|
|
}
|
|
|
|
pub fn write_ccr_file(
|
|
path: &Path,
|
|
ccr: &RpkiCanonicalCacheRepresentation,
|
|
) -> Result<(), CcrExportError> {
|
|
let der = encode_content_info(&CcrContentInfo::new(ccr.clone()))?;
|
|
if let Some(parent) = path.parent() {
|
|
std::fs::create_dir_all(parent)
|
|
.map_err(|e| CcrExportError::Write(path.display().to_string(), e.to_string()))?;
|
|
}
|
|
std::fs::write(path, der)
|
|
.map_err(|e| CcrExportError::Write(path.display().to_string(), e.to_string()))
|
|
}
|
|
|
|
#[cfg(test)]
|
|
mod tests {
|
|
use super::*;
|
|
use crate::ccr::decode::decode_content_info;
|
|
use crate::data_model::manifest::ManifestObject;
|
|
use crate::data_model::roa::{IpPrefix, RoaAfi};
|
|
use crate::data_model::ta::TrustAnchor;
|
|
use crate::data_model::tal::Tal;
|
|
use crate::storage::{
|
|
PackTime, RawByHashEntry, RocksStore, ValidatedCaInstanceResult, ValidatedManifestMeta,
|
|
VcirArtifactKind, VcirArtifactRole, VcirArtifactValidationStatus, VcirAuditSummary,
|
|
VcirCcrManifestProjection, VcirChildEntry, VcirInstanceGate, VcirRelatedArtifact,
|
|
VcirSummary,
|
|
};
|
|
use crate::validation::objects::{AspaAttestation, RouterKeyPayload, Vrp};
|
|
use sha2::Digest;
|
|
|
|
fn sample_trust_anchor() -> TrustAnchor {
|
|
let base = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
let tal_bytes = std::fs::read(base.join("tests/fixtures/tal/apnic-rfc7730-https.tal"))
|
|
.expect("read tal");
|
|
let ta_der = std::fs::read(base.join("tests/fixtures/ta/apnic-ta.cer")).expect("read ta");
|
|
let tal = Tal::decode_bytes(&tal_bytes).expect("decode tal");
|
|
TrustAnchor::bind_der(tal, &ta_der, None).expect("bind ta")
|
|
}
|
|
|
|
fn sample_vcir_and_raw(store: &RocksStore) -> ValidatedCaInstanceResult {
|
|
let base = std::path::PathBuf::from(env!("CARGO_MANIFEST_DIR"));
|
|
let manifest_der = std::fs::read(base.join("tests/fixtures/repository/rpki.cernet.net/repo/cernet/0/05FC9C5B88506F7C0D3F862C8895BED67E9F8EBA.mft")).expect("read manifest");
|
|
let manifest = ManifestObject::decode_der(&manifest_der).expect("decode manifest");
|
|
let hash = hex::encode(sha2::Sha256::digest(&manifest_der));
|
|
let mut raw = RawByHashEntry::from_bytes(hash.clone(), manifest_der.clone());
|
|
raw.origin_uris
|
|
.push("rsync://example.test/repo/current.mft".to_string());
|
|
raw.object_type = Some("mft".to_string());
|
|
raw.encoding = Some("der".to_string());
|
|
store.put_raw_by_hash_entry(&raw).expect("put raw");
|
|
let projection = VcirCcrManifestProjection {
|
|
manifest_rsync_uri: "rsync://example.test/repo/current.mft".to_string(),
|
|
manifest_sha256: sha2::Sha256::digest(&manifest_der).to_vec(),
|
|
manifest_size: manifest_der.len() as u64,
|
|
manifest_ee_aki: manifest.signed_object.signed_data.certificates[0]
|
|
.resource_cert
|
|
.tbs
|
|
.extensions
|
|
.authority_key_identifier
|
|
.clone()
|
|
.expect("manifest aki"),
|
|
manifest_number_be: manifest.manifest.manifest_number.bytes_be.clone(),
|
|
manifest_this_update: PackTime::from_utc_offset_datetime(manifest.manifest.this_update),
|
|
manifest_sia_locations_der: vec![vec![
|
|
0x30, 0x11, 0x06, 0x08, 0x2b, 0x06, 0x01, 0x05, 0x05, 0x07, 0x30, 0x05, 0x86, 0x05,
|
|
b'r', b's', b'y', b'n', b'c',
|
|
]],
|
|
subordinate_skis: vec![vec![0x33; 20]],
|
|
};
|
|
ValidatedCaInstanceResult {
|
|
manifest_rsync_uri: "rsync://example.test/repo/current.mft".to_string(),
|
|
parent_manifest_rsync_uri: None,
|
|
tal_id: "apnic".to_string(),
|
|
ca_subject_name: "CN=test".to_string(),
|
|
ca_ski: "11".repeat(20),
|
|
issuer_ski: "22".repeat(20),
|
|
last_successful_validation_time: PackTime::from_utc_offset_datetime(
|
|
manifest.manifest.this_update,
|
|
),
|
|
current_manifest_rsync_uri: "rsync://example.test/repo/current.mft".to_string(),
|
|
current_crl_rsync_uri: "rsync://example.test/repo/current.crl".to_string(),
|
|
validated_manifest_meta: ValidatedManifestMeta {
|
|
validated_manifest_number: manifest.manifest.manifest_number.bytes_be.clone(),
|
|
validated_manifest_this_update: PackTime::from_utc_offset_datetime(
|
|
manifest.manifest.this_update,
|
|
),
|
|
validated_manifest_next_update: PackTime::from_utc_offset_datetime(
|
|
manifest.manifest.next_update,
|
|
),
|
|
},
|
|
ccr_manifest_projection: projection,
|
|
instance_gate: VcirInstanceGate {
|
|
manifest_next_update: PackTime::from_utc_offset_datetime(
|
|
manifest.manifest.next_update,
|
|
),
|
|
current_crl_next_update: PackTime::from_utc_offset_datetime(
|
|
manifest.manifest.next_update,
|
|
),
|
|
self_ca_not_after: PackTime::from_utc_offset_datetime(
|
|
manifest.manifest.next_update,
|
|
),
|
|
instance_effective_until: PackTime::from_utc_offset_datetime(
|
|
manifest.manifest.next_update,
|
|
),
|
|
},
|
|
child_entries: vec![VcirChildEntry {
|
|
child_manifest_rsync_uri: "rsync://example.test/repo/child.mft".to_string(),
|
|
child_cert_rsync_uri: "rsync://example.test/repo/child.cer".to_string(),
|
|
child_cert_hash: "aa".repeat(32),
|
|
child_ski: "33".repeat(20),
|
|
child_rsync_base_uri: "rsync://example.test/repo/".to_string(),
|
|
child_publication_point_rsync_uri: "rsync://example.test/repo/".to_string(),
|
|
child_rrdp_notification_uri: None,
|
|
child_effective_ip_resources: None,
|
|
child_effective_as_resources: None,
|
|
accepted_at_validation_time: PackTime::from_utc_offset_datetime(
|
|
manifest.manifest.this_update,
|
|
),
|
|
}],
|
|
local_outputs: Vec::new(),
|
|
related_artifacts: vec![VcirRelatedArtifact {
|
|
artifact_role: VcirArtifactRole::Manifest,
|
|
artifact_kind: VcirArtifactKind::Mft,
|
|
uri: Some("rsync://example.test/repo/current.mft".to_string()),
|
|
sha256: hash,
|
|
object_type: Some("mft".to_string()),
|
|
validation_status: VcirArtifactValidationStatus::Accepted,
|
|
}],
|
|
summary: VcirSummary {
|
|
local_vrp_count: 0,
|
|
local_aspa_count: 0,
|
|
local_router_key_count: 0,
|
|
child_count: 1,
|
|
accepted_object_count: 1,
|
|
rejected_object_count: 0,
|
|
},
|
|
audit_summary: VcirAuditSummary {
|
|
failed_fetch_eligible: true,
|
|
last_failed_fetch_reason: None,
|
|
warning_count: 0,
|
|
audit_flags: Vec::new(),
|
|
},
|
|
}
|
|
}
|
|
|
|
#[test]
|
|
fn build_and_write_ccr_from_run_exports_der_content_info() {
|
|
let td = tempfile::tempdir().expect("tempdir");
|
|
let db_path = td.path().join("db");
|
|
let store = RocksStore::open(&db_path).expect("open rocksdb");
|
|
let vcir = sample_vcir_and_raw(&store);
|
|
store.put_vcir(&vcir).expect("put vcir");
|
|
let trust_anchor = sample_trust_anchor();
|
|
let vrps = vec![Vrp {
|
|
asn: 64496,
|
|
prefix: IpPrefix {
|
|
afi: RoaAfi::Ipv4,
|
|
prefix_len: 8,
|
|
addr: [10, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0],
|
|
},
|
|
max_length: 8,
|
|
}];
|
|
let aspas = vec![AspaAttestation {
|
|
customer_as_id: 64496,
|
|
provider_as_ids: vec![64497],
|
|
}];
|
|
let router_keys = vec![RouterKeyPayload {
|
|
as_id: 64496,
|
|
ski: vec![0x11; 20],
|
|
spki_der: vec![0x30, 0x00],
|
|
source_object_uri: "rsync://example.test/repo/router.cer".to_string(),
|
|
source_object_hash: hex::encode([0x11; 32]),
|
|
source_ee_cert_hash: hex::encode([0x11; 32]),
|
|
item_effective_until: PackTime::from_utc_offset_datetime(
|
|
time::OffsetDateTime::now_utc() + time::Duration::hours(1),
|
|
),
|
|
}];
|
|
let ccr = build_ccr_from_run(
|
|
&store,
|
|
&[trust_anchor],
|
|
&vrps,
|
|
&aspas,
|
|
&router_keys,
|
|
time::OffsetDateTime::now_utc(),
|
|
)
|
|
.expect("build ccr");
|
|
assert!(ccr.mfts.is_some());
|
|
assert!(ccr.vrps.is_some());
|
|
assert!(ccr.vaps.is_some());
|
|
assert!(ccr.tas.is_some());
|
|
assert!(ccr.rks.is_some());
|
|
assert_eq!(ccr.rks.as_ref().unwrap().rksets.len(), 1);
|
|
let out = td.path().join("out/example.ccr");
|
|
write_ccr_file(&out, &ccr).expect("write ccr");
|
|
let der = std::fs::read(&out).expect("read ccr file");
|
|
let decoded = decode_content_info(&der).expect("decode ccr");
|
|
assert_eq!(decoded.content.version, 0);
|
|
assert!(decoded.content.mfts.is_some());
|
|
}
|
|
}
|