rpki/tests/test_roa_econtent_decode_errors.rs
2026-02-04 17:02:17 +08:00

413 lines
12 KiB
Rust

use rpki::data_model::roa::{RoaDecodeError, RoaEContent, RoaParseError, RoaProfileError};
fn len_bytes(len: usize) -> Vec<u8> {
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<u8> {
let mut out = vec![tag];
out.extend(len_bytes(content.len()));
out.extend_from_slice(content);
out
}
fn der_integer_u64(v: u64) -> Vec<u8> {
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<u8> {
tlv(0x04, bytes)
}
fn der_bit_string(unused: u8, bytes: &[u8]) -> Vec<u8> {
let mut content = vec![unused];
content.extend_from_slice(bytes);
tlv(0x03, &content)
}
fn der_sequence(children: Vec<Vec<u8>>) -> Vec<u8> {
let mut content = Vec::new();
for c in children {
content.extend(c);
}
tlv(0x30, &content)
}
fn cs_explicit(tag_no: u8, inner_der: Vec<u8>) -> Vec<u8> {
tlv(0xA0 | (tag_no & 0x1F), &inner_der)
}
fn roa_ip_address(prefix_bs: Vec<u8>, max_len: Option<u64>) -> Vec<u8> {
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<u8>>) -> Vec<u8> {
der_sequence(vec![der_octet_string(&afi), der_sequence(addresses)])
}
fn roa_attestation(version: Option<u64>, as_id: u64, families: Vec<Vec<u8>>) -> Vec<u8> {
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 { .. })
));
}