use crate::ccr::model::{ AspaPayloadSet, AspaPayloadState, CCR_VERSION_V0, CcrContentInfo, CcrDigestAlgorithm, ManifestInstance, ManifestState, RoaPayloadSet, RoaPayloadState, RouterKey, RouterKeySet, RouterKeyState, RpkiCanonicalCacheRepresentation, TrustAnchorState, }; use crate::data_model::common::{BigUnsigned, DerReader}; use crate::data_model::oid::{OID_CT_RPKI_CCR, OID_CT_RPKI_CCR_RAW, OID_SHA256, OID_SHA256_RAW}; use der_parser::der::parse_der_oid; #[derive(Debug, thiserror::Error)] pub enum CcrDecodeError { #[error("DER parse error: {0}")] Parse(String), #[error("unexpected contentType OID: expected {expected}, got {actual}")] UnexpectedContentType { expected: &'static str, actual: String }, #[error("unexpected digest algorithm OID: expected {expected}, got {actual}")] UnexpectedDigestAlgorithm { expected: &'static str, actual: String }, #[error("CCR model validation failed after decode: {0}")] Validate(String), } pub fn decode_content_info(der: &[u8]) -> Result { let mut top = DerReader::new(der); let mut seq = top.take_sequence().map_err(CcrDecodeError::Parse)?; if !top.is_empty() { return Err(CcrDecodeError::Parse("trailing bytes after ContentInfo".into())); } let content_type_raw = seq.take_tag(0x06).map_err(CcrDecodeError::Parse)?; if content_type_raw != OID_CT_RPKI_CCR_RAW { return Err(CcrDecodeError::UnexpectedContentType { expected: OID_CT_RPKI_CCR, actual: oid_string(content_type_raw)?, }); } let inner = seq.take_tag(0xA0).map_err(CcrDecodeError::Parse)?; if !seq.is_empty() { return Err(CcrDecodeError::Parse("trailing fields in ContentInfo".into())); } let content = decode_ccr(inner)?; let ci = CcrContentInfo::new(content); ci.validate().map_err(CcrDecodeError::Validate)?; Ok(ci) } pub fn decode_ccr(der: &[u8]) -> Result { let mut top = DerReader::new(der); let mut seq = top.take_sequence().map_err(CcrDecodeError::Parse)?; if !top.is_empty() { return Err(CcrDecodeError::Parse("trailing bytes after CCR".into())); } let version = if !seq.is_empty() && seq.peek_tag().map_err(CcrDecodeError::Parse)? == 0xA0 { let explicit = seq.take_tag(0xA0).map_err(CcrDecodeError::Parse)?; let mut inner = DerReader::new(explicit); let version = inner.take_uint_u64().map_err(CcrDecodeError::Parse)? as u32; if !inner.is_empty() { return Err(CcrDecodeError::Parse( "trailing bytes inside CCR version EXPLICIT".into(), )); } version } else { CCR_VERSION_V0 }; let hash_alg = decode_digest_algorithm(seq.take_sequence().map_err(CcrDecodeError::Parse)?)?; let produced_at = parse_generalized_time(seq.take_tag(0x18).map_err(CcrDecodeError::Parse)?)?; let mut mfts = None; let mut vrps = None; let mut vaps = None; let mut tas = None; let mut rks = None; while !seq.is_empty() { let tag = seq.peek_tag().map_err(CcrDecodeError::Parse)?; let (tag_read, value) = seq.take_any().map_err(CcrDecodeError::Parse)?; debug_assert_eq!(tag, tag_read); match tag { 0xA1 => mfts = Some(decode_manifest_state(value)?), 0xA2 => vrps = Some(decode_roa_payload_state(value)?), 0xA3 => vaps = Some(decode_aspa_payload_state(value)?), 0xA4 => tas = Some(decode_trust_anchor_state(value)?), 0xA5 => rks = Some(decode_router_key_state(value)?), _ => { return Err(CcrDecodeError::Parse(format!( "unexpected CCR field tag 0x{tag:02X}" ))) } } } let ccr = RpkiCanonicalCacheRepresentation { version, hash_alg, produced_at, mfts, vrps, vaps, tas, rks, }; ccr.validate().map_err(CcrDecodeError::Validate)?; Ok(ccr) } fn decode_manifest_state(explicit_der: &[u8]) -> Result { let mut outer = DerReader::new(explicit_der); let mut seq = outer.take_sequence().map_err(CcrDecodeError::Parse)?; if !outer.is_empty() { return Err(CcrDecodeError::Parse("trailing bytes after ManifestState".into())); } let mis_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?; let mut mis_reader = DerReader::new(mis_der); let mut mis = Vec::new(); while !mis_reader.is_empty() { let (_tag, full, _value) = mis_reader.take_any_full().map_err(CcrDecodeError::Parse)?; mis.push(decode_manifest_instance(full)?); } let most_recent_update = parse_generalized_time(seq.take_tag(0x18).map_err(CcrDecodeError::Parse)?)?; let hash = seq.take_octet_string().map_err(CcrDecodeError::Parse)?.to_vec(); if !seq.is_empty() { return Err(CcrDecodeError::Parse("trailing fields in ManifestState".into())); } Ok(ManifestState { mis, most_recent_update, hash }) } fn decode_manifest_instance(der: &[u8]) -> Result { let mut top = DerReader::new(der); let mut seq = top.take_sequence().map_err(CcrDecodeError::Parse)?; if !top.is_empty() { return Err(CcrDecodeError::Parse("trailing bytes after ManifestInstance".into())); } let hash = seq.take_octet_string().map_err(CcrDecodeError::Parse)?.to_vec(); let size = seq.take_uint_u64().map_err(CcrDecodeError::Parse)?; let aki = seq.take_octet_string().map_err(CcrDecodeError::Parse)?.to_vec(); let manifest_number = decode_big_unsigned(seq.take_tag(0x02).map_err(CcrDecodeError::Parse)?)?; let this_update = parse_generalized_time(seq.take_tag(0x18).map_err(CcrDecodeError::Parse)?)?; let locations_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?; let mut locations_reader = DerReader::new(locations_der); let mut locations = Vec::new(); while !locations_reader.is_empty() { let (_tag, full, _value) = locations_reader.take_any_full().map_err(CcrDecodeError::Parse)?; locations.push(full.to_vec()); } let subordinates = if !seq.is_empty() { let subordinate_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?; let mut reader = DerReader::new(subordinate_der); let mut out = Vec::new(); while !reader.is_empty() { out.push(reader.take_octet_string().map_err(CcrDecodeError::Parse)?.to_vec()); } out } else { Vec::new() }; Ok(ManifestInstance { hash, size, aki, manifest_number, this_update, locations, subordinates }) } fn decode_roa_payload_state(explicit_der: &[u8]) -> Result { let mut outer = DerReader::new(explicit_der); let mut seq = outer.take_sequence().map_err(CcrDecodeError::Parse)?; if !outer.is_empty() { return Err(CcrDecodeError::Parse("trailing bytes after ROAPayloadState".into())); } let payload_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?; let mut reader = DerReader::new(payload_der); let mut rps = Vec::new(); while !reader.is_empty() { let (_tag, full, _value) = reader.take_any_full().map_err(CcrDecodeError::Parse)?; rps.push(decode_roa_payload_set(full)?); } let hash = seq.take_octet_string().map_err(CcrDecodeError::Parse)?.to_vec(); Ok(RoaPayloadState { rps, hash }) } fn decode_roa_payload_set(der: &[u8]) -> Result { let mut top = DerReader::new(der); let mut seq = top.take_sequence().map_err(CcrDecodeError::Parse)?; if !top.is_empty() { return Err(CcrDecodeError::Parse("trailing bytes after ROAPayloadSet".into())); } let as_id = seq.take_uint_u64().map_err(CcrDecodeError::Parse)? as u32; let blocks_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?; let mut reader = DerReader::new(blocks_der); let mut ip_addr_blocks = Vec::new(); while !reader.is_empty() { let (_tag, full, _value) = reader.take_any_full().map_err(CcrDecodeError::Parse)?; ip_addr_blocks.push(full.to_vec()); } Ok(RoaPayloadSet { as_id, ip_addr_blocks }) } fn decode_aspa_payload_state(explicit_der: &[u8]) -> Result { let mut outer = DerReader::new(explicit_der); let mut seq = outer.take_sequence().map_err(CcrDecodeError::Parse)?; if !outer.is_empty() { return Err(CcrDecodeError::Parse("trailing bytes after ASPAPayloadState".into())); } let payload_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?; let mut reader = DerReader::new(payload_der); let mut aps = Vec::new(); while !reader.is_empty() { let (_tag, full, _value) = reader.take_any_full().map_err(CcrDecodeError::Parse)?; aps.push(decode_aspa_payload_set(full)?); } let hash = seq.take_octet_string().map_err(CcrDecodeError::Parse)?.to_vec(); Ok(AspaPayloadState { aps, hash }) } fn decode_aspa_payload_set(der: &[u8]) -> Result { let mut top = DerReader::new(der); let mut seq = top.take_sequence().map_err(CcrDecodeError::Parse)?; if !top.is_empty() { return Err(CcrDecodeError::Parse("trailing bytes after ASPAPayloadSet".into())); } let customer_as_id = seq.take_uint_u64().map_err(CcrDecodeError::Parse)? as u32; let providers_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?; let mut reader = DerReader::new(providers_der); let mut providers = Vec::new(); while !reader.is_empty() { providers.push(reader.take_uint_u64().map_err(CcrDecodeError::Parse)? as u32); } Ok(AspaPayloadSet { customer_as_id, providers }) } fn decode_trust_anchor_state(explicit_der: &[u8]) -> Result { let mut outer = DerReader::new(explicit_der); let mut seq = outer.take_sequence().map_err(CcrDecodeError::Parse)?; if !outer.is_empty() { return Err(CcrDecodeError::Parse("trailing bytes after TrustAnchorState".into())); } let skis_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?; let mut reader = DerReader::new(skis_der); let mut skis = Vec::new(); while !reader.is_empty() { skis.push(reader.take_octet_string().map_err(CcrDecodeError::Parse)?.to_vec()); } let hash = seq.take_octet_string().map_err(CcrDecodeError::Parse)?.to_vec(); Ok(TrustAnchorState { skis, hash }) } fn decode_router_key_state(explicit_der: &[u8]) -> Result { let mut outer = DerReader::new(explicit_der); let mut seq = outer.take_sequence().map_err(CcrDecodeError::Parse)?; if !outer.is_empty() { return Err(CcrDecodeError::Parse("trailing bytes after RouterKeyState".into())); } let sets_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?; let mut reader = DerReader::new(sets_der); let mut rksets = Vec::new(); while !reader.is_empty() { let (_tag, full, _value) = reader.take_any_full().map_err(CcrDecodeError::Parse)?; rksets.push(decode_router_key_set(full)?); } let hash = seq.take_octet_string().map_err(CcrDecodeError::Parse)?.to_vec(); Ok(RouterKeyState { rksets, hash }) } fn decode_router_key_set(der: &[u8]) -> Result { let mut top = DerReader::new(der); let mut seq = top.take_sequence().map_err(CcrDecodeError::Parse)?; if !top.is_empty() { return Err(CcrDecodeError::Parse("trailing bytes after RouterKeySet".into())); } let as_id = seq.take_uint_u64().map_err(CcrDecodeError::Parse)? as u32; let keys_der = seq.take_tag(0x30).map_err(CcrDecodeError::Parse)?; let mut reader = DerReader::new(keys_der); let mut router_keys = Vec::new(); while !reader.is_empty() { let (_tag, full, _value) = reader.take_any_full().map_err(CcrDecodeError::Parse)?; router_keys.push(decode_router_key(full)?); } Ok(RouterKeySet { as_id, router_keys }) } fn decode_router_key(der: &[u8]) -> Result { let mut top = DerReader::new(der); let mut seq = top.take_sequence().map_err(CcrDecodeError::Parse)?; if !top.is_empty() { return Err(CcrDecodeError::Parse("trailing bytes after RouterKey".into())); } let ski = seq.take_octet_string().map_err(CcrDecodeError::Parse)?.to_vec(); let (_tag, full, _value) = seq.take_any_full().map_err(CcrDecodeError::Parse)?; if !seq.is_empty() { return Err(CcrDecodeError::Parse("trailing fields in RouterKey".into())); } Ok(RouterKey { ski, spki_der: full.to_vec() }) } fn decode_digest_algorithm(mut seq: DerReader<'_>) -> Result { let oid_raw = seq.take_tag(0x06).map_err(CcrDecodeError::Parse)?; if oid_raw != OID_SHA256_RAW { return Err(CcrDecodeError::UnexpectedDigestAlgorithm { expected: OID_SHA256, actual: oid_string(oid_raw)?, }); } if !seq.is_empty() { let tag = seq.peek_tag().map_err(CcrDecodeError::Parse)?; if tag == 0x05 { let null = seq.take_tag(0x05).map_err(CcrDecodeError::Parse)?; if !null.is_empty() { return Err(CcrDecodeError::Parse( "AlgorithmIdentifier NULL parameters must be empty".into(), )); } } } if !seq.is_empty() { return Err(CcrDecodeError::Parse( "trailing fields in DigestAlgorithmIdentifier".into(), )); } Ok(CcrDigestAlgorithm::Sha256) } fn oid_string(raw_body: &[u8]) -> Result { let der = { let mut out = Vec::with_capacity(raw_body.len() + 2); out.push(0x06); if raw_body.len() < 0x80 { out.push(raw_body.len() as u8); } else { return Err(CcrDecodeError::Parse("OID too long".into())); } out.extend_from_slice(raw_body); out }; let (_rem, oid) = parse_der_oid(&der).map_err(|e| CcrDecodeError::Parse(e.to_string()))?; let oid = oid .as_oid_val() .map_err(|e| CcrDecodeError::Parse(e.to_string()))?; Ok(oid.to_string()) } fn parse_generalized_time(bytes: &[u8]) -> Result { let s = std::str::from_utf8(bytes).map_err(|e| CcrDecodeError::Parse(e.to_string()))?; if s.len() != 15 || !s.ends_with('Z') { return Err(CcrDecodeError::Parse( "GeneralizedTime must be YYYYMMDDHHMMSSZ".into(), )); } let parse = |range: std::ops::Range| -> Result { s[range] .parse::() .map_err(|e| CcrDecodeError::Parse(e.to_string())) }; let year = parse(0..4)? as i32; let month = parse(4..6)? as u8; let day = parse(6..8)? as u8; let hour = parse(8..10)? as u8; let minute = parse(10..12)? as u8; let second = parse(12..14)? as u8; let month = time::Month::try_from(month) .map_err(|e| CcrDecodeError::Parse(e.to_string()))?; let date = time::Date::from_calendar_date(year, month, day) .map_err(|e| CcrDecodeError::Parse(e.to_string()))?; let timev = time::Time::from_hms(hour, minute, second) .map_err(|e| CcrDecodeError::Parse(e.to_string()))?; Ok(time::PrimitiveDateTime::new(date, timev).assume_utc()) } fn decode_big_unsigned(bytes: &[u8]) -> Result { if bytes.is_empty() { return Err(CcrDecodeError::Parse("INTEGER has empty content".into())); } if bytes[0] & 0x80 != 0 { return Err(CcrDecodeError::Parse("INTEGER must be non-negative".into())); } if bytes.len() > 1 && bytes[0] == 0x00 && (bytes[1] & 0x80) == 0 { return Err(CcrDecodeError::Parse("INTEGER not minimally encoded".into())); } let bytes_be = if bytes.len() > 1 && bytes[0] == 0x00 { bytes[1..].to_vec() } else { bytes.to_vec() }; Ok(BigUnsigned { bytes_be }) }