383 lines
16 KiB
Rust
383 lines
16 KiB
Rust
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<CcrContentInfo, CcrDecodeError> {
|
|
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<RpkiCanonicalCacheRepresentation, CcrDecodeError> {
|
|
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<ManifestState, CcrDecodeError> {
|
|
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<ManifestInstance, CcrDecodeError> {
|
|
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<RoaPayloadState, CcrDecodeError> {
|
|
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<RoaPayloadSet, CcrDecodeError> {
|
|
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<AspaPayloadState, CcrDecodeError> {
|
|
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<AspaPayloadSet, CcrDecodeError> {
|
|
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<TrustAnchorState, CcrDecodeError> {
|
|
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<RouterKeyState, CcrDecodeError> {
|
|
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<RouterKeySet, CcrDecodeError> {
|
|
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<RouterKey, CcrDecodeError> {
|
|
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<CcrDigestAlgorithm, CcrDecodeError> {
|
|
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<String, CcrDecodeError> {
|
|
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<time::OffsetDateTime, CcrDecodeError> {
|
|
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<usize>| -> Result<u32, CcrDecodeError> {
|
|
s[range]
|
|
.parse::<u32>()
|
|
.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<BigUnsigned, CcrDecodeError> {
|
|
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 })
|
|
}
|