use rpki::ccr::{ AspaPayloadSet, AspaPayloadState, CcrContentInfo, CcrDigestAlgorithm, ManifestInstance, ManifestState, RoaPayloadSet, RoaPayloadState, RouterKey, RouterKeySet, RouterKeyState, RpkiCanonicalCacheRepresentation, TrustAnchorState, compute_state_hash, decode_content_info, encode::{ encode_manifest_state_payload_der, encode_router_key_state_payload_der, encode_trust_anchor_state_payload_der, }, encode_content_info, verify_state_hash, }; use rpki::data_model::common::BigUnsigned; use rpki::data_model::oid::{OID_CT_RPKI_CCR, OID_CT_RPKI_CCR_RAW}; fn sample_time() -> time::OffsetDateTime { time::OffsetDateTime::parse( "2026-03-24T00:00:00Z", &time::format_description::well_known::Rfc3339, ) .expect("valid rfc3339") } #[test] fn minimal_trust_anchor_ccr_roundtrips() { let skis = vec![vec![0x11; 20], vec![0x22; 20]]; let skis_der = encode_trust_anchor_state_payload_der(&skis).expect("encode trust anchor payload"); let state = TrustAnchorState { skis, hash: compute_state_hash(&skis_der), }; let ccr = RpkiCanonicalCacheRepresentation { version: 0, hash_alg: CcrDigestAlgorithm::Sha256, produced_at: sample_time(), mfts: None, vrps: None, vaps: None, tas: Some(state), rks: None, }; let content_info = CcrContentInfo::new(ccr.clone()); let der = encode_content_info(&content_info).expect("encode ccr"); let decoded = decode_content_info(&der).expect("decode ccr"); assert_eq!(decoded, content_info); assert_eq!(decoded.content_type_oid, OID_CT_RPKI_CCR); } #[test] fn decode_rejects_wrong_content_type_oid() { let skis = vec![vec![0x11; 20]]; let skis_der = encode_trust_anchor_state_payload_der(&skis).expect("encode trust anchor payload"); let content_info = CcrContentInfo::new(RpkiCanonicalCacheRepresentation { version: 0, hash_alg: CcrDigestAlgorithm::Sha256, produced_at: sample_time(), mfts: None, vrps: None, vaps: None, tas: Some(TrustAnchorState { skis, hash: compute_state_hash(&skis_der), }), rks: None, }); let mut der = encode_content_info(&content_info).expect("encode ccr"); let needle = OID_CT_RPKI_CCR_RAW; let pos = der .windows(needle.len()) .position(|w| w == needle) .expect("oid present"); der[pos + needle.len() - 1] ^= 0x01; let err = decode_content_info(&der).expect_err("wrong content type must fail"); assert!(err.to_string().contains("unexpected contentType OID"), "{err}"); } #[test] fn ccr_requires_at_least_one_state_aspect() { let ccr = CcrContentInfo::new(RpkiCanonicalCacheRepresentation { version: 0, hash_alg: CcrDigestAlgorithm::Sha256, produced_at: sample_time(), mfts: None, vrps: None, vaps: None, tas: None, rks: None, }); let err = encode_content_info(&ccr).expect_err("empty state aspects must fail"); assert!(err.to_string().contains("at least one of mfts/vrps/vaps/tas/rks")); } #[test] fn state_hash_helpers_accept_matching_and_reject_tampered_payload() { let skis = vec![vec![0x11; 20]]; let payload_der = encode_trust_anchor_state_payload_der(&skis).expect("encode trust anchor payload"); let hash = compute_state_hash(&payload_der); assert!(verify_state_hash(&hash, &payload_der)); let mut tampered = payload_der.clone(); *tampered.last_mut().expect("non-empty der") ^= 0x01; assert!(!verify_state_hash(&hash, &tampered)); } #[test] fn manifest_and_router_key_skeletons_encode_payloads_and_validate_sorting() { let manifest_instances = vec![ManifestInstance { hash: vec![0x33; 32], size: 2048, aki: vec![0x44; 20], manifest_number: BigUnsigned { bytes_be: vec![0x01] }, this_update: sample_time(), locations: vec![vec![0x30, 0x00]], subordinates: vec![vec![0x55; 20]], }]; let mis_der = encode_manifest_state_payload_der(&manifest_instances).expect("encode manifest state payload"); let manifest_state = ManifestState { mis: manifest_instances, most_recent_update: sample_time(), hash: compute_state_hash(&mis_der), }; manifest_state.validate().expect("manifest state validate"); let rksets = vec![RouterKeySet { as_id: 64496, router_keys: vec![RouterKey { ski: vec![0x66; 20], spki_der: vec![0x30, 0x00], }], }]; let rk_der = encode_router_key_state_payload_der(&rksets).expect("encode router key payload"); let rks = RouterKeyState { rksets, hash: compute_state_hash(&rk_der), }; rks.validate().expect("router key state validate"); } fn sample_manifest_state() -> ManifestState { let mis = vec![ManifestInstance { hash: vec![0x10; 32], size: 2048, aki: vec![0x20; 20], manifest_number: BigUnsigned { bytes_be: vec![1] }, this_update: sample_time(), locations: vec![vec![0x30, 0x00]], subordinates: vec![vec![0x30; 20], vec![0x40; 20]], }]; let mis_der = encode_manifest_state_payload_der(&mis).expect("encode mis"); ManifestState { mis, most_recent_update: sample_time(), hash: compute_state_hash(&mis_der), } } fn sample_roa_state() -> RoaPayloadState { let rps = vec![RoaPayloadSet { as_id: 64496, ip_addr_blocks: vec![vec![0x30, 0x00]], }]; let der = rpki::ccr::encode::encode_roa_payload_state_payload_der(&rps).expect("encode rps"); RoaPayloadState { rps, hash: compute_state_hash(&der), } } fn sample_aspa_state() -> AspaPayloadState { let aps = vec![AspaPayloadSet { customer_as_id: 64496, providers: vec![64497, 64498], }]; let der = rpki::ccr::encode::encode_aspa_payload_state_payload_der(&aps).expect("encode aps"); AspaPayloadState { aps, hash: compute_state_hash(&der), } } fn sample_ta_state() -> TrustAnchorState { let skis = vec![vec![0x11; 20], vec![0x22; 20]]; let der = encode_trust_anchor_state_payload_der(&skis).expect("encode skis"); TrustAnchorState { skis, hash: compute_state_hash(&der), } } fn sample_router_key_state() -> RouterKeyState { let rksets = vec![ RouterKeySet { as_id: 64496, router_keys: vec![RouterKey { ski: vec![0x41; 20], spki_der: vec![0x30, 0x00], }], }, RouterKeySet { as_id: 64497, router_keys: vec![RouterKey { ski: vec![0x42; 20], spki_der: vec![0x30, 0x00], }], }, ]; let der = encode_router_key_state_payload_der(&rksets).expect("encode rksets"); RouterKeyState { rksets, hash: compute_state_hash(&der), } } #[test] fn full_ccr_roundtrips_all_supported_state_aspects() { let ccr = RpkiCanonicalCacheRepresentation { version: 0, hash_alg: CcrDigestAlgorithm::Sha256, produced_at: sample_time(), mfts: Some(sample_manifest_state()), vrps: Some(sample_roa_state()), vaps: Some(sample_aspa_state()), tas: Some(sample_ta_state()), rks: Some(sample_router_key_state()), }; let encoded = encode_content_info(&CcrContentInfo::new(ccr.clone())).expect("encode full ccr"); let decoded = decode_content_info(&encoded).expect("decode full ccr"); assert_eq!(decoded.content, ccr); } #[test] fn decode_rejects_wrong_digest_algorithm_oid() { let ccr = CcrContentInfo::new(RpkiCanonicalCacheRepresentation { version: 0, hash_alg: CcrDigestAlgorithm::Sha256, produced_at: sample_time(), mfts: None, vrps: None, vaps: None, tas: Some(sample_ta_state()), rks: None, }); let mut der = encode_content_info(&ccr).expect("encode ccr"); let oid = rpki::data_model::oid::OID_SHA256_RAW; let pos = der.windows(oid.len()).position(|w| w == oid).expect("sha256 oid present"); der[pos + oid.len() - 1] ^= 0x01; let err = decode_content_info(&der).expect_err("decode must reject wrong digest oid"); assert!(err.to_string().contains("unexpected digest algorithm OID"), "{err}"); } #[test] fn decode_rejects_bad_generalized_time() { let ccr = CcrContentInfo::new(RpkiCanonicalCacheRepresentation { version: 0, hash_alg: CcrDigestAlgorithm::Sha256, produced_at: sample_time(), mfts: None, vrps: None, vaps: None, tas: Some(sample_ta_state()), rks: None, }); let mut der = encode_content_info(&ccr).expect("encode ccr"); let pos = der.windows(15).position(|w| w == b"20260324000000Z").expect("time present"); der[pos + 14] = b'X'; let err = decode_content_info(&der).expect_err("bad time must fail"); assert!(err.to_string().contains("GeneralizedTime"), "{err}"); } #[test] fn manifest_state_validate_rejects_unsorted_subordinates() { let mut state = sample_manifest_state(); state.mis[0].subordinates = vec![vec![0x40; 20], vec![0x30; 20]]; let err = state.validate().expect_err("unsorted subordinates must fail"); assert!(err.to_string().contains("subordinates"), "{err}"); } #[test] fn roa_payload_state_validate_rejects_duplicate_asn_sets() { let state = RoaPayloadState { rps: vec![ RoaPayloadSet { as_id: 64496, ip_addr_blocks: vec![vec![0x30, 0x00]] }, RoaPayloadSet { as_id: 64496, ip_addr_blocks: vec![vec![0x30, 0x00]] }, ], hash: vec![0u8; 32], }; let err = state.validate().expect_err("duplicate roa sets must fail"); assert!(err.to_string().contains("ROAPayloadState.rps"), "{err}"); } #[test] fn aspa_payload_state_validate_rejects_unsorted_providers() { let state = AspaPayloadState { aps: vec![AspaPayloadSet { customer_as_id: 64496, providers: vec![64498, 64497] }], hash: vec![0u8; 32], }; let err = state.validate().expect_err("unsorted providers must fail"); assert!(err.to_string().contains("providers"), "{err}"); } #[test] fn trust_anchor_state_validate_rejects_invalid_ski_length() { let state = TrustAnchorState { skis: vec![vec![0x11; 19]], hash: vec![0u8; 32], }; let err = state.validate().expect_err("bad ski len must fail"); assert!(err.to_string().contains("TrustAnchorState.skis"), "{err}"); } #[test] fn router_key_state_validate_rejects_unsorted_router_keys() { let state = RouterKeyState { rksets: vec![RouterKeySet { as_id: 64496, router_keys: vec![ RouterKey { ski: vec![0x42; 20], spki_der: vec![0x30, 0x00] }, RouterKey { ski: vec![0x41; 20], spki_der: vec![0x30, 0x00] }, ], }], hash: vec![0u8; 32], }; let err = state.validate().expect_err("unsorted router keys must fail"); assert!(err.to_string().contains("router_keys"), "{err}"); } #[test] fn manifest_instance_validate_rejects_bad_location_tag() { let instance = ManifestInstance { hash: vec![0x10; 32], size: 2048, aki: vec![0x20; 20], manifest_number: BigUnsigned { bytes_be: vec![1] }, this_update: sample_time(), locations: vec![vec![0x04, 0x00]], subordinates: vec![], }; let err = instance.validate().expect_err("bad AccessDescription tag must fail"); assert!(err.to_string().contains("unexpected tag"), "{err}"); }