use rpki::data_model::roa::{RoaDecodeError, RoaEContent, RoaParseError, RoaProfileError}; fn len_bytes(len: usize) -> Vec { if len < 128 { vec![len as u8] } else { let mut tmp = Vec::new(); let mut n = len; while n > 0 { tmp.push((n & 0xFF) as u8); n >>= 8; } tmp.reverse(); let mut out = vec![0x80 | (tmp.len() as u8)]; out.extend(tmp); out } } fn tlv(tag: u8, content: &[u8]) -> Vec { let mut out = vec![tag]; out.extend(len_bytes(content.len())); out.extend_from_slice(content); out } fn der_integer_u64(v: u64) -> Vec { let mut bytes = Vec::new(); let mut n = v; if n == 0 { bytes.push(0); } else { while n > 0 { bytes.push((n & 0xFF) as u8); n >>= 8; } bytes.reverse(); if bytes[0] & 0x80 != 0 { bytes.insert(0, 0); } } tlv(0x02, &bytes) } fn der_octet_string(bytes: &[u8]) -> Vec { tlv(0x04, bytes) } fn der_bit_string(unused: u8, bytes: &[u8]) -> Vec { let mut content = vec![unused]; content.extend_from_slice(bytes); tlv(0x03, &content) } fn der_sequence(children: Vec>) -> Vec { let mut content = Vec::new(); for c in children { content.extend(c); } tlv(0x30, &content) } fn cs_explicit(tag_no: u8, inner_der: Vec) -> Vec { tlv(0xA0 | (tag_no & 0x1F), &inner_der) } fn roa_ip_address(prefix_bs: Vec, max_len: Option) -> Vec { let mut fields = vec![prefix_bs]; if let Some(m) = max_len { fields.push(der_integer_u64(m)); } der_sequence(fields) } fn roa_ip_family(afi: [u8; 2], addresses: Vec>) -> Vec { der_sequence(vec![der_octet_string(&afi), der_sequence(addresses)]) } fn roa_attestation(version: Option, as_id: u64, families: Vec>) -> Vec { let mut fields = Vec::new(); if let Some(v) = version { fields.push(cs_explicit(0, der_integer_u64(v))); } fields.push(der_integer_u64(as_id)); fields.push(der_sequence(families)); der_sequence(fields) } #[test] fn trailing_bytes_are_rejected_in_parse_step() { let der = roa_attestation( None, 64496, vec![roa_ip_family( [0, 1], vec![roa_ip_address(der_bit_string(0, &[]), None)], )], ); let mut bad = der.clone(); bad.push(0); let err = RoaEContent::decode_der(&bad).unwrap_err(); assert!(matches!( err, RoaDecodeError::Parse(RoaParseError::TrailingBytes(1)) )); } #[test] fn attestation_sequence_len_is_validated() { let der = der_sequence(vec![der_integer_u64(64496)]); let err = RoaEContent::decode_der(&der).unwrap_err(); assert!(matches!( err, RoaDecodeError::Validate(RoaProfileError::InvalidAttestationSequenceLen(1)) )); } #[test] fn version_tag_must_be_context_specific_0() { let der = { let mut fields = Vec::new(); fields.push(cs_explicit(1, der_integer_u64(0))); // wrong tag number [1] fields.push(der_integer_u64(64496)); fields.push(der_sequence(vec![roa_ip_family( [0, 1], vec![roa_ip_address(der_bit_string(0, &[]), None)], )])); der_sequence(fields) }; let err = RoaEContent::decode_der(&der).unwrap_err(); assert!(matches!( err, RoaDecodeError::Validate(RoaProfileError::ProfileDecode(_)) )); } #[test] fn version_explicit_tag_rejects_trailing_bytes_inside_inner_der() { let mut version_inner = der_integer_u64(0); version_inner.extend(tlv(0x05, &[])); // NULL after INTEGER let der = { let mut fields = Vec::new(); fields.push(cs_explicit(0, version_inner)); fields.push(der_integer_u64(64496)); fields.push(der_sequence(vec![roa_ip_family( [0, 1], vec![roa_ip_address(der_bit_string(0, &[]), None)], )])); der_sequence(fields) }; let err = RoaEContent::decode_der(&der).unwrap_err(); assert!(matches!( err, RoaDecodeError::Validate(RoaProfileError::ProfileDecode(_)) )); } #[test] fn version_zero_is_accepted_when_explicitly_encoded() { let der = roa_attestation( Some(0), 64496, vec![roa_ip_family( [0, 1], vec![roa_ip_address(der_bit_string(7, &[0x80]), None)], // prefix_len=1 )], ); let roa = RoaEContent::decode_der(&der).expect("ROA must decode"); assert_eq!(roa.version, 0); assert_eq!(roa.as_id, 64496); } #[test] fn ip_address_family_shape_and_address_family_length_are_validated() { // family SEQUENCE has wrong length (1) let bad_family = der_sequence(vec![der_octet_string(&[0, 1])]); let der = roa_attestation(None, 64496, vec![bad_family]); let err = RoaEContent::decode_der(&der).unwrap_err(); assert!(matches!( err, RoaDecodeError::Validate(RoaProfileError::InvalidIpAddressFamily) )); // addressFamily OCTET STRING has invalid length (3) let family = der_sequence(vec![ der_octet_string(&[0, 1, 2]), der_sequence(vec![roa_ip_address(der_bit_string(0, &[]), None)]), ]); let der = roa_attestation(None, 64496, vec![family]); let err = RoaEContent::decode_der(&der).unwrap_err(); assert!(matches!( err, RoaDecodeError::Validate(RoaProfileError::InvalidAddressFamily) )); } #[test] fn roa_ip_address_shape_and_prefix_encoding_are_validated() { // ROAIPAddress must have 1..2 elements. let family = roa_ip_family([0, 1], vec![der_sequence(vec![])]); let der = roa_attestation(None, 64496, vec![family]); let err = RoaEContent::decode_der(&der).unwrap_err(); assert!(matches!( err, RoaDecodeError::Validate(RoaProfileError::InvalidRoaIpAddress) )); // ROAIPAddress.address must be BIT STRING, not OCTET STRING. let family = roa_ip_family([0, 1], vec![der_sequence(vec![der_octet_string(&[0])])]); let der = roa_attestation(None, 64496, vec![family]); let err = RoaEContent::decode_der(&der).unwrap_err(); assert!(matches!( err, RoaDecodeError::Validate(RoaProfileError::InvalidPrefixBitString) )); // unusedBits > 7 is rejected. let family = roa_ip_family([0, 1], vec![roa_ip_address(der_bit_string(8, &[0]), None)]); let der = roa_attestation(None, 64496, vec![family]); let err = RoaEContent::decode_der(&der).unwrap_err(); assert!(matches!( err, RoaDecodeError::Validate(RoaProfileError::InvalidPrefixUnusedBits) )); // empty BIT STRING with unusedBits != 0 is rejected. let family = roa_ip_family([0, 1], vec![roa_ip_address(der_bit_string(1, &[]), None)]); let der = roa_attestation(None, 64496, vec![family]); let err = RoaEContent::decode_der(&der).unwrap_err(); assert!(matches!( err, RoaDecodeError::Validate(RoaProfileError::InvalidPrefixUnusedBits) )); } #[test] fn max_length_integer_range_is_validated() { // maxLength too large to fit u16 triggers the try_into() error path. let family = roa_ip_family( [0, 1], vec![roa_ip_address(der_bit_string(0, &[0x0A]), Some(70000))], ); let der = roa_attestation(None, 64496, vec![family]); let err = RoaEContent::decode_der(&der).unwrap_err(); assert!(matches!( err, RoaDecodeError::Validate(RoaProfileError::InvalidMaxLength { .. }) )); } #[test] fn version_must_be_zero_when_present() { let der = roa_attestation( Some(1), 64496, vec![roa_ip_family( [0, 1], vec![roa_ip_address(der_bit_string(0, &[]), None)], )], ); let err = RoaEContent::decode_der(&der).unwrap_err(); assert!(matches!( err, RoaDecodeError::Validate(RoaProfileError::InvalidVersion(1)) )); } #[test] fn as_id_out_of_range_is_rejected() { let der = roa_attestation( None, (u32::MAX as u64) + 1, vec![roa_ip_family( [0, 1], vec![roa_ip_address(der_bit_string(0, &[]), None)], )], ); let err = RoaEContent::decode_der(&der).unwrap_err(); assert!(matches!( err, RoaDecodeError::Validate(RoaProfileError::AsIdOutOfRange(_)) )); } #[test] fn ip_addr_blocks_len_is_validated() { let der = roa_attestation(None, 64496, vec![]); let err = RoaEContent::decode_der(&der).unwrap_err(); assert!(matches!( err, RoaDecodeError::Validate(RoaProfileError::InvalidIpAddrBlocksLen(0)) )); let families = vec![ roa_ip_family([0, 1], vec![roa_ip_address(der_bit_string(0, &[]), None)]), roa_ip_family([0, 2], vec![roa_ip_address(der_bit_string(0, &[]), None)]), roa_ip_family([0, 1], vec![roa_ip_address(der_bit_string(0, &[]), None)]), ]; let der = roa_attestation(None, 64496, families); let err = RoaEContent::decode_der(&der).unwrap_err(); assert!(matches!( err, RoaDecodeError::Validate(RoaProfileError::InvalidIpAddrBlocksLen(3)) )); } #[test] fn duplicate_afi_is_rejected() { let families = vec![ roa_ip_family([0, 1], vec![roa_ip_address(der_bit_string(0, &[]), None)]), roa_ip_family([0, 1], vec![roa_ip_address(der_bit_string(0, &[]), None)]), ]; let der = roa_attestation(None, 64496, families); let err = RoaEContent::decode_der(&der).unwrap_err(); assert!(matches!( err, RoaDecodeError::Validate(RoaProfileError::DuplicateAfi(_)) )); } #[test] fn unsupported_afi_is_rejected() { let der = roa_attestation( None, 64496, vec![roa_ip_family( [0x12, 0x34], vec![roa_ip_address(der_bit_string(0, &[]), None)], )], ); let err = RoaEContent::decode_der(&der).unwrap_err(); assert!(matches!( err, RoaDecodeError::Validate(RoaProfileError::UnsupportedAfi(_)) )); } #[test] fn empty_address_list_is_rejected() { let der = roa_attestation(None, 64496, vec![roa_ip_family([0, 1], vec![])]); let err = RoaEContent::decode_der(&der).unwrap_err(); assert!(matches!( err, RoaDecodeError::Validate(RoaProfileError::EmptyAddressList) )); } #[test] fn prefix_unused_bits_must_be_zeroed() { // prefix_len=1 => unused_bits=7, last byte must have lower 7 bits zero. // Use 0b1000_0001 which has a non-zero unused bit. let bs = der_bit_string(7, &[0b1000_0001]); let der = roa_attestation( None, 64496, vec![roa_ip_family([0, 1], vec![roa_ip_address(bs, None)])], ); let err = RoaEContent::decode_der(&der).unwrap_err(); assert!(matches!( err, RoaDecodeError::Validate(RoaProfileError::InvalidPrefixUnusedBits) )); } #[test] fn prefix_len_out_of_range_is_rejected() { // IPv4 ub=32, encode 33 bits: 5 bytes with unused_bits=7 => 40-7=33. let bs = der_bit_string(7, &[0u8; 5]); let der = roa_attestation( None, 64496, vec![roa_ip_family([0, 1], vec![roa_ip_address(bs, None)])], ); let err = RoaEContent::decode_der(&der).unwrap_err(); assert!(matches!( err, RoaDecodeError::Validate(RoaProfileError::PrefixLenOutOfRange { .. }) )); } #[test] fn max_length_range_and_relation_are_validated() { // IPv4, prefix_len=8 let bs = der_bit_string(0, &[0x0A]); // maxLength < prefix_len let der = roa_attestation( None, 64496, vec![roa_ip_family( [0, 1], vec![roa_ip_address(bs.clone(), Some(7))], )], ); let err = RoaEContent::decode_der(&der).unwrap_err(); assert!(matches!( err, RoaDecodeError::Validate(RoaProfileError::InvalidMaxLength { .. }) )); // maxLength > ub let der = roa_attestation( None, 64496, vec![roa_ip_family([0, 1], vec![roa_ip_address(bs, Some(33))])], ); let err = RoaEContent::decode_der(&der).unwrap_err(); assert!(matches!( err, RoaDecodeError::Validate(RoaProfileError::InvalidMaxLength { .. }) )); }