From f71c8682467a7a24a29c2b74738acfae2f167e49 Mon Sep 17 00:00:00 2001 From: occheung Date: Thu, 12 Nov 2020 16:32:27 +0800 Subject: [PATCH] cert ipv4/v6: segregation --- Cargo.toml | 2 +- src/certificate.rs | 642 +++++++++++++++++++++++++++++++++++++++++++-- 2 files changed, 628 insertions(+), 16 deletions(-) diff --git a/Cargo.toml b/Cargo.toml index 0d8dde5..05ddfa9 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -90,4 +90,4 @@ optional = true [features] default = [] -std = [ "simple_logger", "rand", "hex-literal" ] \ No newline at end of file +std = [ "rand", "hex-literal", "simple_logger" ] diff --git a/src/certificate.rs b/src/certificate.rs index 37a6aef..d276fe9 100644 --- a/src/certificate.rs +++ b/src/certificate.rs @@ -170,7 +170,7 @@ pub enum ExtensionValue<'a> { // Embedded value might be empty (&[]) // This means a reject-all/accept-none condition -#[derive(Debug, Clone, Eq, PartialEq)] +#[derive(Clone, Eq, PartialEq)] pub enum GeneralName<'a> { OtherName { type_id: &'a [u8], @@ -189,6 +189,60 @@ pub enum GeneralName<'a> { RegisteredID(&'a [u8]), } +impl<'a> core::fmt::Debug for GeneralName<'a> { + fn fmt(&self, f: &mut core::fmt::Formatter<'_>) -> core::fmt::Result { + match self { + Self::OtherName {type_id, value} => { + f.debug_struct("OtherName") + .field("type_id", type_id) + .field("value", value) + .finish() + }, + Self::RFC822Name(name) => { + f.debug_tuple("RFC822Name") + .field(&core::str::from_utf8(name).unwrap()) + .finish() + }, + Self::DNSName(name) => { + f.debug_tuple("DNSName") + .field(&core::str::from_utf8(name).unwrap()) + .finish() + }, + Self::X400Address(name) => { + f.debug_tuple("X400Address") + .field(&core::str::from_utf8(name).unwrap()) + .finish() + }, + Self::DirectoryName(name) => { + f.debug_tuple("DirectoryName") + .field(name) + .finish() + }, + Self::EDIPartyName {name_assigner, party_name} => { + f.debug_struct("EDIPartyName") + .field("name_assigner", name_assigner) + .field("party_name", party_name) + .finish() + }, + Self::URI(name) => { + f.debug_tuple("URI") + .field(&core::str::from_utf8(name).unwrap()) + .finish() + }, + Self::IPAddress(name) => { + f.debug_tuple("IPAddress") + .field(name) + .finish() + }, + Self::RegisteredID(name) => { + f.debug_tuple("RegisteredID") + .field(name) + .finish() + }, + } + } +} + // Set operation for General Name (X is a subset of Y, where X, Y are the same variant) // Will not handle `OtherName`, `X400Address`, `EDIPartyName`, `RegisteredID`, // as these restrictions of these variants are not suggested @@ -1160,21 +1214,32 @@ fn get_subtree_intersection<'a>( cert_subtree: &Vec> ) { // 1. Determine the variants that need to be preserved (i.e. body-count) + // This is to preserve general names that does not have any matching variant + // Intersecting or unioning onceself return the input value (by identity law) let mut has_self_uri_tree = false; let mut has_other_uri_tree = false; let mut has_self_rfc_822_name_tree = false; let mut has_other_rfc_822_name_tree = false; let mut has_self_dns_name_tree = false; let mut has_other_dns_name_tree = false; - let mut has_self_ip_address_tree = false; - let mut has_other_ip_address_tree = false; + let mut has_self_ipv4_address_tree = false; + let mut has_other_ipv4_address_tree = false; + let mut has_self_ipv6_address_tree = false; + let mut has_other_ipv6_address_tree = false; for general_name in state_subtree.iter() { match general_name { GeneralName::URI(..) => has_self_uri_tree = true, GeneralName::RFC822Name(..) => has_self_rfc_822_name_tree = true, GeneralName::DNSName(..) => has_self_dns_name_tree = true, - GeneralName::IPAddress(..) => has_self_ip_address_tree = true, + GeneralName::IPAddress(self_ip) => { + if self_ip.len() == 8 || self_ip.len() == 0 { + has_self_ipv4_address_tree = true; + } + if self_ip.len() == 32 || self_ip.len() == 0 { + has_self_ipv6_address_tree = true; + } + }, // Other general_name variants should not appear in this subtree _ => {}, } @@ -1185,7 +1250,14 @@ fn get_subtree_intersection<'a>( GeneralName::URI(..) => has_other_uri_tree = true, GeneralName::RFC822Name(..) => has_other_rfc_822_name_tree = true, GeneralName::DNSName(..) => has_other_dns_name_tree = true, - GeneralName::IPAddress(..) => has_other_ip_address_tree = true, + GeneralName::IPAddress(other_ip) => { + if other_ip.len() == 8 || other_ip.len() == 0 { + has_other_ipv4_address_tree = true; + } + if other_ip.len() == 32 || other_ip.len() == 0 { + has_other_ipv6_address_tree = true; + } + }, // Other general_name variants should not appear in this subtree _ => {}, } @@ -1205,17 +1277,20 @@ fn get_subtree_intersection<'a>( if !has_other_rfc_822_name_tree { preserved_subtrees.push((*general_name).clone()); } - } + }, GeneralName::DNSName(..) => { if !has_other_dns_name_tree { preserved_subtrees.push((*general_name).clone()); } - } - GeneralName::IPAddress(..) => { - if !has_other_ip_address_tree { + }, + GeneralName::IPAddress(ip) => { + if !has_other_ipv4_address_tree && ip.len() == 8 { preserved_subtrees.push((*general_name).clone()); } - } + else if !has_other_ipv6_address_tree && ip.len() == 32 { + preserved_subtrees.push((*general_name).clone()); + } + }, // Other general_name variants should not appear in this subtree _ => {}, } @@ -1232,17 +1307,20 @@ fn get_subtree_intersection<'a>( if !has_self_rfc_822_name_tree { preserved_subtrees.push((*general_name).clone()); } - } + }, GeneralName::DNSName(..) => { if !has_self_dns_name_tree { preserved_subtrees.push((*general_name).clone()); } - } - GeneralName::IPAddress(..) => { - if !has_self_ip_address_tree { + }, + GeneralName::IPAddress(ip) => { + if !has_self_ipv4_address_tree && ip.len() == 8 { preserved_subtrees.push((*general_name).clone()); } - } + else if !has_self_ipv6_address_tree && ip.len() == 32 { + preserved_subtrees.push((*general_name).clone()); + } + }, // Other general_name variants should not appear in this subtree _ => {}, } @@ -1378,3 +1456,537 @@ pub fn get_subtree_union<'a>( prune_subset(state_subtree, &mut merged_subtrees); } + +#[cfg(test)] +mod test { + use alloc::vec::Vec; + use super::*; + + const DNS_EXAMPLE_COM: GeneralName = GeneralName::DNSName( + b"example.com" + ); + const DNS_FOO_EXAMPLE: GeneralName = GeneralName::DNSName( + b"foo.example.com" + ); + const DNS_EXAMPLE_NET: GeneralName = GeneralName::DNSName( + b"example.net" + ); + const DNS_EMPTY: GeneralName = GeneralName::DNSName( + b"" + ); + + // Helper to init logger if necessary + fn init() { + simple_logger::SimpleLogger::new().init(); + } + + /* + * Simple example from RFC 5280, section 6.1.4, page 85, statement (g) + */ + + // The intersection between "foo.example.com" and "example.com" shall be + // "foo.example.com" + #[test] + fn test_foo_example_intersection() { + init(); + + let mut state_subtrees: Vec = Vec::new(); + state_subtrees.push(DNS_EXAMPLE_COM); + + let mut cert_subtrees: Vec = Vec::new(); + cert_subtrees.push(DNS_FOO_EXAMPLE); + + get_subtree_intersection(&mut state_subtrees, &cert_subtrees); + assert_eq!( + state_subtrees, + cert_subtrees + ); + } + + // The intersection between "example.com" and "example.net" shall be + // "" (empty set) + #[test] + fn test_example_com_net_intersection() { + init(); + + let mut state_subtrees: Vec = Vec::new(); + state_subtrees.push(DNS_EXAMPLE_COM); + + let mut cert_subtrees: Vec = Vec::new(); + cert_subtrees.push(DNS_EXAMPLE_NET); + + let mut expected_tree_with_empty_entry: Vec = Vec::new(); + expected_tree_with_empty_entry.push(DNS_EMPTY); + + get_subtree_intersection(&mut state_subtrees, &cert_subtrees); + assert_eq!( + state_subtrees, + expected_tree_with_empty_entry + ); + } + + // The union between "example.com" and "foo.example.com" shall be "example.com" + #[test] + fn test_foo_example_union() { + init(); + + let mut state_subtrees: Vec = Vec::new(); + state_subtrees.push(DNS_EXAMPLE_COM); + + let mut cert_subtrees: Vec = Vec::new(); + cert_subtrees.push(DNS_FOO_EXAMPLE); + + let mut expected_tree: Vec = Vec::new(); + expected_tree.push(DNS_EXAMPLE_COM); + + get_subtree_union(&mut state_subtrees, &cert_subtrees); + assert_eq!( + state_subtrees, + expected_tree + ); + } + + // The union between "example.com" and "example.net" shall be both namespaces + #[test] + fn test_example_com_net_union() { + init(); + + let mut state_subtrees: Vec = Vec::new(); + state_subtrees.push(DNS_EXAMPLE_COM); + + let mut cert_subtrees: Vec = Vec::new(); + cert_subtrees.push(DNS_EXAMPLE_NET); + + let mut expected_tree: Vec = Vec::new(); + expected_tree.push(DNS_EXAMPLE_COM); + expected_tree.push(DNS_EXAMPLE_NET); + + get_subtree_union(&mut state_subtrees, &cert_subtrees); + assert_eq!( + state_subtrees, + expected_tree + ); + } + + /* + * Behaviour of empty set within intersection and union operations + */ + + // Empty set intersects other set + // Empty intersects any set gives empty set + #[test] + fn test_empty_set_intersects_other_set() { + init(); + + let mut state_subtrees: Vec = Vec::new(); + state_subtrees.push(DNS_EMPTY); + + let mut cert_subtrees: Vec = Vec::new(); + cert_subtrees.push(DNS_EXAMPLE_COM); + cert_subtrees.push(DNS_EXAMPLE_NET); + cert_subtrees.push(DNS_FOO_EXAMPLE); + + let mut expected_tree: Vec = Vec::new(); + expected_tree.push(DNS_EMPTY); + + get_subtree_intersection(&mut state_subtrees, &cert_subtrees); + assert_eq!( + state_subtrees, + expected_tree + ); + } + + // Other set intersects empty set + // Anything intersects empty set gives empty set + #[test] + fn test_other_set_intersects_empty_set() { + init(); + + let mut state_subtrees: Vec = Vec::new(); + state_subtrees.push(DNS_EXAMPLE_COM); + state_subtrees.push(DNS_FOO_EXAMPLE); + + let mut cert_subtrees: Vec = Vec::new(); + cert_subtrees.push(DNS_EMPTY); + + let mut expected_tree: Vec = Vec::new(); + expected_tree.push(DNS_EMPTY); + + get_subtree_intersection(&mut state_subtrees, &cert_subtrees); + assert_eq!( + state_subtrees, + expected_tree + ); + } + + /* + * Behaviour of unspecificed variant + */ + #[test] + fn test_unspecified_DNS() { + init(); + + let mut state_subtrees: Vec = Vec::new(); + state_subtrees.push(DNS_EXAMPLE_COM); + state_subtrees.push(DNS_FOO_EXAMPLE); + + let mut cert_subtrees: Vec = Vec::new(); + + let mut expected_tree: Vec = Vec::new(); + expected_tree.push(DNS_EXAMPLE_COM); + + get_subtree_intersection(&mut state_subtrees, &cert_subtrees); + assert_eq!( + state_subtrees, + expected_tree + ); + } + + #[test] + fn test_specifying_DNS_from_unspecified() { + init(); + + let mut state_subtrees: Vec = Vec::new(); + + let mut cert_subtrees: Vec = Vec::new(); + cert_subtrees.push(DNS_EXAMPLE_COM); + cert_subtrees.push(DNS_FOO_EXAMPLE); + + let mut expected_tree: Vec = Vec::new(); + expected_tree.push(DNS_EXAMPLE_COM); + + get_subtree_intersection(&mut state_subtrees, &cert_subtrees); + assert_eq!( + state_subtrees, + expected_tree + ); + } + + /* + * Behaviour of IP intersection/union operation + */ + // 192.168.0.1/24 + const CIDR_IPv4_1: GeneralName = GeneralName::IPAddress( + &[192, 168, 0, 1, 255, 255, 255, 0] + ); + // 192.168.0.1/25 + const CIDR_IPv4_2: GeneralName = GeneralName::IPAddress( + &[192, 168, 0, 1, 255, 255, 255, 128] + ); + + #[test] + fn test_ip_24_25_intersection() { + init(); + + let mut state_subtrees: Vec = Vec::new(); + state_subtrees.push(CIDR_IPv4_1); + let mut cert_subtrees: Vec = Vec::new(); + cert_subtrees.push(CIDR_IPv4_2); + let mut expected_subtrees: Vec = Vec::new(); + expected_subtrees.push(CIDR_IPv4_2); + get_subtree_intersection(&mut state_subtrees, &cert_subtrees); + assert_eq!( + state_subtrees, + expected_subtrees + ); + } + + // 192.168.0.1/31 + const CIDR_IPv4_3: GeneralName = GeneralName::IPAddress( + &[192, 168, 0, 1, 255, 255, 255, 254] + ); + + #[test] + fn test_ip_24_31_intersection() { + init(); + + let mut state_subtrees: Vec = Vec::new(); + state_subtrees.push(CIDR_IPv4_1); + let mut cert_subtrees: Vec = Vec::new(); + cert_subtrees.push(CIDR_IPv4_3); + let mut expected_subtrees: Vec = Vec::new(); + expected_subtrees.push(CIDR_IPv4_3); + get_subtree_intersection(&mut state_subtrees, &cert_subtrees); + assert_eq!( + state_subtrees, + expected_subtrees + ); + } + + const CIDR_IPv4_4: GeneralName = GeneralName::IPAddress( + &[192, 72, 0, 1, 255, 255, 255, 0] + ); + + const CIDR_IPv4_NONE: GeneralName = GeneralName::IPAddress( + &[] + ); + + #[test] + fn test_ip_disjoint_intersection() { + init(); + + let mut state_subtrees: Vec = Vec::new(); + state_subtrees.push(CIDR_IPv4_1); + let mut cert_subtrees: Vec = Vec::new(); + cert_subtrees.push(CIDR_IPv4_4); + let mut expected_subtrees: Vec = Vec::new(); + expected_subtrees.push(CIDR_IPv4_NONE); + get_subtree_intersection(&mut state_subtrees, &cert_subtrees); + assert_eq!( + state_subtrees, + expected_subtrees + ); + } + + #[test] + fn test_ip_24_25_intersection_reversed() { + init(); + + let mut state_subtrees: Vec = Vec::new(); + state_subtrees.push(CIDR_IPv4_2); + let mut cert_subtrees: Vec = Vec::new(); + cert_subtrees.push(CIDR_IPv4_1); + let mut expected_subtrees: Vec = Vec::new(); + expected_subtrees.push(CIDR_IPv4_2); + get_subtree_intersection(&mut state_subtrees, &cert_subtrees); + assert_eq!( + state_subtrees, + expected_subtrees + ); + } + + #[test] + fn test_ip_24_31_intersection_reversed() { + init(); + + let mut state_subtrees: Vec = Vec::new(); + state_subtrees.push(CIDR_IPv4_3); + let mut cert_subtrees: Vec = Vec::new(); + cert_subtrees.push(CIDR_IPv4_1); + let mut expected_subtrees: Vec = Vec::new(); + expected_subtrees.push(CIDR_IPv4_3); + get_subtree_intersection(&mut state_subtrees, &cert_subtrees); + assert_eq!( + state_subtrees, + expected_subtrees + ); + } + + #[test] + fn test_ip_disjoint_intersection_reversed() { + init(); + + let mut state_subtrees: Vec = Vec::new(); + state_subtrees.push(CIDR_IPv4_4); + let mut cert_subtrees: Vec = Vec::new(); + cert_subtrees.push(CIDR_IPv4_1); + let mut expected_subtrees: Vec = Vec::new(); + expected_subtrees.push(CIDR_IPv4_NONE); + get_subtree_intersection(&mut state_subtrees, &cert_subtrees); + assert_eq!( + state_subtrees, + expected_subtrees + ); + } + + /* + * Empty set behaviour + */ + #[test] + fn test_ip_empty_intersect() { + init(); + + let mut state_subtrees: Vec = Vec::new(); + state_subtrees.push(CIDR_IPv4_NONE); + let mut cert_subtrees: Vec = Vec::new(); + cert_subtrees.push(CIDR_IPv4_1); + let mut expected_subtrees: Vec = Vec::new(); + expected_subtrees.push(CIDR_IPv4_NONE); + get_subtree_intersection(&mut state_subtrees, &cert_subtrees); + assert_eq!( + state_subtrees, + expected_subtrees + ); + + cert_subtrees.clear(); + cert_subtrees.push(CIDR_IPv4_2); + get_subtree_intersection(&mut state_subtrees, &cert_subtrees); + assert_eq!( + state_subtrees, + expected_subtrees + ); + + cert_subtrees.clear(); + cert_subtrees.push(CIDR_IPv4_3); + get_subtree_intersection(&mut state_subtrees, &cert_subtrees); + assert_eq!( + state_subtrees, + expected_subtrees + ); + + cert_subtrees.clear(); + cert_subtrees.push(CIDR_IPv4_NONE); + get_subtree_intersection(&mut state_subtrees, &cert_subtrees); + assert_eq!( + state_subtrees, + expected_subtrees + ); + } + + #[test] + fn test_ip_empty_union() { + init(); + + let mut state_subtrees: Vec = Vec::new(); + state_subtrees.push(CIDR_IPv4_NONE); + let mut cert_subtrees: Vec = Vec::new(); + cert_subtrees.push(CIDR_IPv4_1); + let mut expected_subtrees: Vec = Vec::new(); + expected_subtrees.push(CIDR_IPv4_1); + get_subtree_union(&mut state_subtrees, &cert_subtrees); + assert_eq!( + state_subtrees, + expected_subtrees + ); + + let mut state_subtrees: Vec = Vec::new(); + state_subtrees.push(CIDR_IPv4_NONE); + let mut cert_subtrees: Vec = Vec::new(); + cert_subtrees.push(CIDR_IPv4_2); + let mut expected_subtrees: Vec = Vec::new(); + expected_subtrees.push(CIDR_IPv4_2); + get_subtree_union(&mut state_subtrees, &cert_subtrees); + assert_eq!( + state_subtrees, + expected_subtrees + ); + + let mut state_subtrees: Vec = Vec::new(); + state_subtrees.push(CIDR_IPv4_NONE); + let mut cert_subtrees: Vec = Vec::new(); + cert_subtrees.push(CIDR_IPv4_3); + let mut expected_subtrees: Vec = Vec::new(); + expected_subtrees.push(CIDR_IPv4_3); + get_subtree_union(&mut state_subtrees, &cert_subtrees); + assert_eq!( + state_subtrees, + expected_subtrees + ); + + let mut state_subtrees: Vec = Vec::new(); + state_subtrees.push(CIDR_IPv4_NONE); + let mut cert_subtrees: Vec = Vec::new(); + cert_subtrees.push(CIDR_IPv4_4); + let mut expected_subtrees: Vec = Vec::new(); + expected_subtrees.push(CIDR_IPv4_4); + get_subtree_union(&mut state_subtrees, &cert_subtrees); + assert_eq!( + state_subtrees, + expected_subtrees + ); + } + + const CIDR_IPv4_5: GeneralName = GeneralName::IPAddress( + &[192, 72, 0, 0, 255, 255, 255, 0] + ); + const CIDR_IPv4_6: GeneralName = GeneralName::IPAddress( + &[192, 200, 103, 0, 255, 255, 255, 0] + ); + const CIDR_IPv4_7: GeneralName = GeneralName::IPAddress( + &[192, 200, 100, 0, 255, 255, 252, 0] + ); + const CIDR_IPv4_8: GeneralName = GeneralName::IPAddress( + &[200, 200, 100, 0, 255, 255, 255, 0] + ); + + /* + * Multiple IP set behaviour + */ + #[test] + fn test_multiple_ip_intersection() { + init(); + + let mut state_subtrees: Vec = Vec::new(); + state_subtrees.push(CIDR_IPv4_5); + state_subtrees.push(CIDR_IPv4_6); + let mut cert_subtrees: Vec = Vec::new(); + cert_subtrees.push(CIDR_IPv4_7); + cert_subtrees.push(CIDR_IPv4_8); + + // Basically, there are 3 disjoint sets + // CIDR_IPv4_5, (CIDR_IPv4_6, CIDR_IPv4_7), CIDR_IPv4_8 + // The only overlapping area between set 1 and set 2 is CIDR_IPv4_6, + // as CIDR_IPv4_6 is a subnet of CIDR_IPv4_7, while other networks + // are disjoint + let mut expected_subtrees: Vec = Vec::new(); + expected_subtrees.push(CIDR_IPv4_6); + get_subtree_intersection(&mut state_subtrees, &cert_subtrees); + assert_eq!( + state_subtrees, + expected_subtrees + ); + } + + #[test] + fn test_multiple_ip_union() { + init(); + + let mut state_subtrees: Vec = Vec::new(); + state_subtrees.push(CIDR_IPv4_5); + state_subtrees.push(CIDR_IPv4_6); + let mut cert_subtrees: Vec = Vec::new(); + cert_subtrees.push(CIDR_IPv4_7); + cert_subtrees.push(CIDR_IPv4_8); + let mut expected_subtrees: Vec = Vec::new(); + expected_subtrees.push(CIDR_IPv4_5); + expected_subtrees.push(CIDR_IPv4_7); + expected_subtrees.push(CIDR_IPv4_8); + get_subtree_union(&mut state_subtrees, &cert_subtrees); + assert_eq!( + state_subtrees, + expected_subtrees + ); + } + + const CIDR_IPv6_1: GeneralName = GeneralName::IPAddress( + &[0x20, 0x01, 0x0D, 0xB8, 0xAC, 0x10, 0xFE, 0x01, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + ); + const CIDR_IPv6_2: GeneralName = GeneralName::IPAddress( + &[0x20, 0x01, 0x0D, 0xB8, 0xAC, 0x10, 0xFE, 0x01, + 0x12, 0x24, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, + 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, + 0xFF, 0xFF, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00] + ); + // const CIDR_IPv6_3: GeneralName = GeneralName::IPAddress( + // &[192, 200, 100, 0, 255, 255, 252, 0] + // ); + // const CIDR_IPv6_4: GeneralName = GeneralName::IPAddress( + // &[200, 200, 100, 0, 255, 255, 255, 0] + // ); + + /* + * Heterogeneous IP intersection/union + */ + #[test] + fn test_ipv4_ipv6_mix_intersection() { + init(); + + let mut state_subtrees: Vec = Vec::new(); + state_subtrees.push(CIDR_IPv6_1); + state_subtrees.push(CIDR_IPv6_2); + let mut cert_subtrees: Vec = Vec::new(); + cert_subtrees.push(CIDR_IPv4_7); + cert_subtrees.push(CIDR_IPv4_6); + let mut expected_subtrees: Vec = Vec::new(); + expected_subtrees.push(CIDR_IPv6_1); + expected_subtrees.push(CIDR_IPv4_7); + get_subtree_intersection(&mut state_subtrees, &cert_subtrees); + assert_eq!( + state_subtrees, + expected_subtrees + ); + } +}