use rpki::data_model::rc::{AsResourceSet, IpResourceSet}; 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_sequence(children: Vec>) -> Vec { let mut content = Vec::new(); for c in children { content.extend(c); } tlv(0x30, &content) } fn der_octet_string(bytes: &[u8]) -> Vec { tlv(0x04, bytes) } fn der_null() -> Vec { vec![0x05, 0x00] } fn der_integer_bytes(bytes: &[u8]) -> Vec { tlv(0x02, bytes) } 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); } } der_integer_bytes(&bytes) } fn der_bit_string(unused: u8, bytes: &[u8]) -> Vec { let mut content = vec![unused]; content.extend_from_slice(bytes); tlv(0x03, &content) } fn cs_cons(tag_no: u8, inner_der: Vec) -> Vec { tlv(0xA0 | (tag_no & 0x1F), &inner_der) } #[test] fn ip_addr_blocks_decode_rejects_invalid_encodings() { // Not a SEQUENCE. assert!(IpResourceSet::decode_extn_value(&der_null()).is_err()); // IPAddressFamily wrong shape. let fam_wrong = der_sequence(vec![der_octet_string(&[0x00, 0x01])]); // missing choice assert!(IpResourceSet::decode_extn_value(&der_sequence(vec![fam_wrong])).is_err()); // addressFamily wrong length. let fam_wrong = der_sequence(vec![ der_octet_string(&[0x00]), // 1 byte der_null(), ]); assert!(IpResourceSet::decode_extn_value(&der_sequence(vec![fam_wrong])).is_err()); // unsupported AFI. let fam_wrong = der_sequence(vec![der_octet_string(&[0x00, 0x03]), der_null()]); assert!(IpResourceSet::decode_extn_value(&der_sequence(vec![fam_wrong])).is_err()); // ipAddressChoice wrong type. let fam_wrong = der_sequence(vec![ der_octet_string(&[0x00, 0x01]), der_integer_u64(1), ]); assert!(IpResourceSet::decode_extn_value(&der_sequence(vec![fam_wrong])).is_err()); // BitString with invalid unused-bits value (>7). let min = der_bit_string(0, &[0x0A, 0x00, 0x00, 0x00]); let max = der_bit_string(8, &[0x0A, 0xFF, 0xFF, 0xFF]); // invalid unused bits let range = der_sequence(vec![min, max]); let fam = der_sequence(vec![ der_octet_string(&[0x00, 0x01]), der_sequence(vec![range]), ]); assert!(IpResourceSet::decode_extn_value(&der_sequence(vec![fam])).is_err()); // BitString with non-zero bits in the unused tail. let min = der_bit_string(0, &[0x0A, 0x00, 0x00, 0x00]); let max = der_bit_string(1, &[0x0A, 0x00, 0x00, 0x01]); // LSB set, but unused=1 let range = der_sequence(vec![min, max]); let fam = der_sequence(vec![ der_octet_string(&[0x00, 0x01]), der_sequence(vec![range]), ]); assert!(IpResourceSet::decode_extn_value(&der_sequence(vec![fam])).is_err()); // Prefix length out of range for IPv4 (40 bits). let prefix = der_bit_string(0, &[0x0A, 0x00, 0x00, 0x00, 0x00]); // 40 bits let fam = der_sequence(vec![ der_octet_string(&[0x00, 0x01]), der_sequence(vec![prefix]), ]); assert!(IpResourceSet::decode_extn_value(&der_sequence(vec![fam])).is_err()); } #[test] fn autonomous_sys_ids_decode_rejects_invalid_encodings() { // Not a SEQUENCE. assert!(AsResourceSet::decode_extn_value(&der_null()).is_err()); // Wrong tag class (expects [0]/[1] context-specific). let as_ids = der_sequence(vec![der_integer_u64(64496)]); assert!(AsResourceSet::decode_extn_value(&as_ids).is_err()); // Duplicate [0] tags. let a0 = cs_cons(0, der_null()); let a0_dup = cs_cons(0, der_null()); assert!(AsResourceSet::decode_extn_value(&der_sequence(vec![a0, a0_dup])).is_err()); // Out-of-range ASID (> u32::MAX). let too_big = der_integer_bytes(&[0x01, 0x00, 0x00, 0x00, 0x00]); // 2^32 let asnum = cs_cons(0, der_sequence(vec![too_big])); assert!(AsResourceSet::decode_extn_value(&der_sequence(vec![asnum])).is_err()); // Range min > max. let bad_range = der_sequence(vec![der_integer_u64(64510), der_integer_u64(64500)]); let asnum = cs_cons(0, der_sequence(vec![bad_range])); assert!(AsResourceSet::decode_extn_value(&der_sequence(vec![asnum])).is_err()); // Unsupported element inside asIdsOrRanges. let asnum = cs_cons(0, der_sequence(vec![der_null()])); assert!(AsResourceSet::decode_extn_value(&der_sequence(vec![asnum])).is_err()); } #[test] fn ip_addr_blocks_prefix_bitstring_unused_bits_checks_are_enforced() { // Prefix BitString with non-zero bits in the unused tail (parse_ip_prefix path). let prefix = der_bit_string(1, &[0x0A, 0x00, 0x00, 0x01]); // LSB set, but unused=1 let fam = der_sequence(vec![ der_octet_string(&[0x00, 0x01]), der_sequence(vec![prefix]), ]); assert!(IpResourceSet::decode_extn_value(&der_sequence(vec![fam])).is_err()); // Prefix BitString with empty bytes but unused_bits != 0 is invalid. let prefix = der_bit_string(1, &[]); let fam = der_sequence(vec![ der_octet_string(&[0x00, 0x01]), der_sequence(vec![prefix]), ]); assert!(IpResourceSet::decode_extn_value(&der_sequence(vec![fam])).is_err()); } #[test] fn ip_addr_blocks_range_upper_bound_can_fill_all_ones_when_bit_len_zero() { // A BIT STRING with 0 bits (content is only the "unused bits" count octet) is allowed. // For an IPAddressRange upper bound, RFC 3779 interprets missing bits as 1s. let min = der_bit_string(0, &[]); let max = der_bit_string(0, &[]); let range = der_sequence(vec![min, max]); let fam = der_sequence(vec![ der_octet_string(&[0x00, 0x01]), der_sequence(vec![range]), ]); let set = IpResourceSet::decode_extn_value(&der_sequence(vec![fam])).expect("decode range with 0-bit endpoints"); let fam = set.families.iter().find(|f| f.afi == rpki::data_model::rc::Afi::Ipv4).unwrap(); let rpki::data_model::rc::IpAddressChoice::AddressesOrRanges(items) = &fam.choice else { panic!("expected explicit addressesOrRanges"); }; assert_eq!(items.len(), 1); let rpki::data_model::rc::IpAddressOrRange::Range(r) = &items[0] else { panic!("expected a range"); }; assert_eq!(r.min, vec![0, 0, 0, 0]); assert_eq!(r.max, vec![0xFF, 0xFF, 0xFF, 0xFF]); }