| 1 | // Copyright 2015-2020 Brian Smith. |
| 2 | // |
| 3 | // Permission to use, copy, modify, and/or distribute this software for any |
| 4 | // purpose with or without fee is hereby granted, provided that the above |
| 5 | // copyright notice and this permission notice appear in all copies. |
| 6 | // |
| 7 | // THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES |
| 8 | // WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF |
| 9 | // MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR |
| 10 | // ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES |
| 11 | // WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN |
| 12 | // ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF |
| 13 | // OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. |
| 14 | |
| 15 | #[cfg (feature = "alloc" )] |
| 16 | use alloc::format; |
| 17 | |
| 18 | use pki_types::IpAddr; |
| 19 | #[cfg (feature = "alloc" )] |
| 20 | use pki_types::ServerName; |
| 21 | |
| 22 | use super::{GeneralName, NameIterator}; |
| 23 | use crate::cert::Cert; |
| 24 | use crate::error::{Error, InvalidNameContext}; |
| 25 | |
| 26 | pub(crate) fn verify_ip_address_names(reference: &IpAddr, cert: &Cert<'_>) -> Result<(), Error> { |
| 27 | let ip_address = match reference { |
| 28 | IpAddr::V4(ip) => untrusted::Input::from(ip.as_ref()), |
| 29 | IpAddr::V6(ip) => untrusted::Input::from(ip.as_ref()), |
| 30 | }; |
| 31 | |
| 32 | let result = NameIterator::new(cert.subject_alt_name).find_map(|result| { |
| 33 | let name = match result { |
| 34 | Ok(name) => name, |
| 35 | Err(err) => return Some(Err(err)), |
| 36 | }; |
| 37 | |
| 38 | let presented_id = match name { |
| 39 | GeneralName::IpAddress(presented) => presented, |
| 40 | _ => return None, |
| 41 | }; |
| 42 | |
| 43 | match presented_id_matches_reference_id(presented_id, ip_address) { |
| 44 | true => Some(Ok(())), |
| 45 | false => None, |
| 46 | } |
| 47 | }); |
| 48 | |
| 49 | match result { |
| 50 | Some(result) => return result, |
| 51 | #[cfg (feature = "alloc" )] |
| 52 | None => {} |
| 53 | #[cfg (not(feature = "alloc" ))] |
| 54 | None => Err(Error::CertNotValidForName(InvalidNameContext {})), |
| 55 | } |
| 56 | |
| 57 | #[cfg (feature = "alloc" )] |
| 58 | { |
| 59 | Err(Error::CertNotValidForName(InvalidNameContext { |
| 60 | expected: ServerName::from(*reference), |
| 61 | presented: NameIterator::new(cert.subject_alt_name) |
| 62 | .filter_map(|result| Some(format!(" {:?}" , result.ok()?))) |
| 63 | .collect(), |
| 64 | })) |
| 65 | } |
| 66 | } |
| 67 | |
| 68 | // https://tools.ietf.org/html/rfc5280#section-4.2.1.6 says: |
| 69 | // When the subjectAltName extension contains an iPAddress, the address |
| 70 | // MUST be stored in the octet string in "network byte order", as |
| 71 | // specified in [RFC791]. The least significant bit (LSB) of each octet |
| 72 | // is the LSB of the corresponding byte in the network address. For IP |
| 73 | // version 4, as specified in [RFC791], the octet string MUST contain |
| 74 | // exactly four octets. For IP version 6, as specified in |
| 75 | // [RFC2460], the octet string MUST contain exactly sixteen octets. |
| 76 | fn presented_id_matches_reference_id( |
| 77 | presented_id: untrusted::Input<'_>, |
| 78 | reference_id: untrusted::Input<'_>, |
| 79 | ) -> bool { |
| 80 | match (presented_id.len(), reference_id.len()) { |
| 81 | (4, 4) => (), |
| 82 | (16, 16) => (), |
| 83 | _ => { |
| 84 | return false; |
| 85 | } |
| 86 | }; |
| 87 | |
| 88 | let mut presented_ip_address: Reader<'_> = untrusted::Reader::new(input:presented_id); |
| 89 | let mut reference_ip_address: Reader<'_> = untrusted::Reader::new(input:reference_id); |
| 90 | while !presented_ip_address.at_end() { |
| 91 | let presented_ip_address_byte: u8 = presented_ip_address.read_byte().unwrap(); |
| 92 | let reference_ip_address_byte: u8 = reference_ip_address.read_byte().unwrap(); |
| 93 | if presented_ip_address_byte != reference_ip_address_byte { |
| 94 | return false; |
| 95 | } |
| 96 | } |
| 97 | |
| 98 | true |
| 99 | } |
| 100 | |
| 101 | // https://tools.ietf.org/html/rfc5280#section-4.2.1.10 says: |
| 102 | // |
| 103 | // For IPv4 addresses, the iPAddress field of GeneralName MUST contain |
| 104 | // eight (8) octets, encoded in the style of RFC 4632 (CIDR) to represent |
| 105 | // an address range [RFC4632]. For IPv6 addresses, the iPAddress field |
| 106 | // MUST contain 32 octets similarly encoded. For example, a name |
| 107 | // constraint for "class C" subnet 192.0.2.0 is represented as the |
| 108 | // octets C0 00 02 00 FF FF FF 00, representing the CIDR notation |
| 109 | // 192.0.2.0/24 (mask 255.255.255.0). |
| 110 | pub(super) fn presented_id_matches_constraint( |
| 111 | name: untrusted::Input<'_>, |
| 112 | constraint: untrusted::Input<'_>, |
| 113 | ) -> Result<bool, Error> { |
| 114 | match (name.len(), constraint.len()) { |
| 115 | (4, 8) => (), |
| 116 | (16, 32) => (), |
| 117 | |
| 118 | // an IPv4 address never matches an IPv6 constraint, and vice versa. |
| 119 | (4, 32) | (16, 8) => { |
| 120 | return Ok(false); |
| 121 | } |
| 122 | |
| 123 | // invalid constraint length |
| 124 | (4, _) | (16, _) => { |
| 125 | return Err(Error::InvalidNetworkMaskConstraint); |
| 126 | } |
| 127 | |
| 128 | // invalid name length, or anything else |
| 129 | _ => { |
| 130 | return Err(Error::BadDer); |
| 131 | } |
| 132 | }; |
| 133 | |
| 134 | let (constraint_address, constraint_mask) = constraint.read_all(Error::BadDer, |value| { |
| 135 | let address = value.read_bytes(constraint.len() / 2).unwrap(); |
| 136 | let mask = value.read_bytes(constraint.len() / 2).unwrap(); |
| 137 | Ok((address, mask)) |
| 138 | })?; |
| 139 | |
| 140 | let mut name = untrusted::Reader::new(name); |
| 141 | let mut constraint_address = untrusted::Reader::new(constraint_address); |
| 142 | let mut constraint_mask = untrusted::Reader::new(constraint_mask); |
| 143 | let mut seen_zero_bit = false; |
| 144 | |
| 145 | loop { |
| 146 | // Iterate through the name, constraint address, and constraint mask |
| 147 | // a byte at a time. |
| 148 | let name_byte = name.read_byte().unwrap(); |
| 149 | let constraint_address_byte = constraint_address.read_byte().unwrap(); |
| 150 | let constraint_mask_byte = constraint_mask.read_byte().unwrap(); |
| 151 | |
| 152 | // A valid mask consists of a sequence of 1 bits, followed by a |
| 153 | // sequence of 0 bits. Either sequence could be empty. |
| 154 | |
| 155 | let leading = constraint_mask_byte.leading_ones(); |
| 156 | let trailing = constraint_mask_byte.trailing_zeros(); |
| 157 | |
| 158 | // At the resolution of a single octet, a valid mask is one where |
| 159 | // leading_ones() and trailing_zeros() sums to 8. |
| 160 | // This includes all-ones and all-zeroes. |
| 161 | if leading + trailing != 8 { |
| 162 | return Err(Error::InvalidNetworkMaskConstraint); |
| 163 | } |
| 164 | |
| 165 | // There should be no bits set after the first octet with a zero bit is seen. |
| 166 | if seen_zero_bit && constraint_mask_byte != 0x00 { |
| 167 | return Err(Error::InvalidNetworkMaskConstraint); |
| 168 | } |
| 169 | |
| 170 | // Note when a zero bit is seen for later octets. |
| 171 | if constraint_mask_byte != 0xff { |
| 172 | seen_zero_bit = true; |
| 173 | } |
| 174 | |
| 175 | if ((name_byte ^ constraint_address_byte) & constraint_mask_byte) != 0 { |
| 176 | return Ok(false); |
| 177 | } |
| 178 | if name.at_end() { |
| 179 | break; |
| 180 | } |
| 181 | } |
| 182 | |
| 183 | Ok(true) |
| 184 | } |
| 185 | |
| 186 | #[cfg (test)] |
| 187 | mod tests { |
| 188 | use super::*; |
| 189 | |
| 190 | #[test ] |
| 191 | fn presented_id_matches_constraint_ipv4_test() { |
| 192 | let names_and_constraints = vec![ |
| 193 | ( |
| 194 | // 192.0.2.0 matches constraint 192.0.2.0/24 |
| 195 | [0xC0, 0x00, 0x02, 0x00], |
| 196 | [0xC0, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0xFF, 0x00], |
| 197 | Ok(true), |
| 198 | ), |
| 199 | ( |
| 200 | // 192.0.2.1 matches constraint 192.0.2.0/24 |
| 201 | [0xC0, 0x00, 0x02, 0x01], |
| 202 | [0xC0, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0xFF, 0x00], |
| 203 | Ok(true), |
| 204 | ), |
| 205 | ( |
| 206 | // 192.0.2.255 matches constraint 192.0.2.0/24 |
| 207 | [0xC0, 0x00, 0x02, 0xFF], |
| 208 | [0xC0, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0xFF, 0x00], |
| 209 | Ok(true), |
| 210 | ), |
| 211 | ( |
| 212 | // 192.0.1.255 does not match constraint 192.0.2.0/24 |
| 213 | [0xC0, 0x00, 0x01, 0xFF], |
| 214 | [0xC0, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0xFF, 0x00], |
| 215 | Ok(false), |
| 216 | ), |
| 217 | ( |
| 218 | // 192.0.3.0 does not match constraint 192.0.2.0/24 |
| 219 | [0xC0, 0x00, 0x03, 0x00], |
| 220 | [0xC0, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0xFF, 0x00], |
| 221 | Ok(false), |
| 222 | ), |
| 223 | ]; |
| 224 | for (name, constraint, match_result) in names_and_constraints { |
| 225 | assert_eq!( |
| 226 | presented_id_matches_constraint( |
| 227 | untrusted::Input::from(&name), |
| 228 | untrusted::Input::from(&constraint), |
| 229 | ), |
| 230 | match_result |
| 231 | ) |
| 232 | } |
| 233 | |
| 234 | // Invalid name length (shorter) |
| 235 | assert_eq!( |
| 236 | presented_id_matches_constraint( |
| 237 | untrusted::Input::from(&[0xC0, 0x00, 0x02]), |
| 238 | untrusted::Input::from(&[0xC0, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0xFF, 0x00]), |
| 239 | ), |
| 240 | Err(Error::BadDer), |
| 241 | ); |
| 242 | |
| 243 | // Invalid name length (longer) |
| 244 | assert_eq!( |
| 245 | presented_id_matches_constraint( |
| 246 | untrusted::Input::from(&[0xC0, 0x00, 0x02, 0x00, 0x00]), |
| 247 | untrusted::Input::from(&[0xC0, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0xFF, 0x00]), |
| 248 | ), |
| 249 | Err(Error::BadDer), |
| 250 | ); |
| 251 | |
| 252 | // Unmatching constraint size (shorter) |
| 253 | assert_eq!( |
| 254 | presented_id_matches_constraint( |
| 255 | untrusted::Input::from(&[0xC0, 0x00, 0x02, 0x00]), |
| 256 | untrusted::Input::from(&[0xC0, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0xFF]), |
| 257 | ), |
| 258 | Err(Error::InvalidNetworkMaskConstraint), |
| 259 | ); |
| 260 | |
| 261 | // Unmatching constraint size (longer) |
| 262 | assert_eq!( |
| 263 | presented_id_matches_constraint( |
| 264 | untrusted::Input::from(&[0xC0, 0x00, 0x02, 0x00]), |
| 265 | untrusted::Input::from(&[0xC0, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0xFF, 0x00, 0x00]), |
| 266 | ), |
| 267 | Err(Error::InvalidNetworkMaskConstraint), |
| 268 | ); |
| 269 | |
| 270 | // Unmatching constraint size (IPv6 constraint for IPv4 address) |
| 271 | assert_eq!( |
| 272 | presented_id_matches_constraint( |
| 273 | untrusted::Input::from(&[0xC0, 0x00, 0x02, 0x00]), |
| 274 | untrusted::Input::from(&[ |
| 275 | 0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 276 | 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, |
| 277 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 |
| 278 | ]), |
| 279 | ), |
| 280 | Ok(false), |
| 281 | ); |
| 282 | } |
| 283 | |
| 284 | #[test ] |
| 285 | fn presented_id_matches_constraint_ipv6_test() { |
| 286 | let names_and_constraints = vec![ |
| 287 | ( |
| 288 | // 2001:0DB8:ABCD:0012:0000:0000:0000:0000 matches constraint |
| 289 | // 2001:0DB8:ABCD:0012:0000:0000:0000:0000/64 |
| 290 | [ |
| 291 | 0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 292 | 0x00, 0x00, 0x00, |
| 293 | ], |
| 294 | [ |
| 295 | 0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 296 | 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, |
| 297 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 298 | ], |
| 299 | Ok(true), |
| 300 | ), |
| 301 | ( |
| 302 | // 2001:0DB8:ABCD:0012:0000:0000:0000:0001 matches constraint |
| 303 | // 2001:0DB8:ABCD:0012:0000:0000:0000:0000/64 |
| 304 | [ |
| 305 | 0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 306 | 0x00, 0x00, 0x01, |
| 307 | ], |
| 308 | [ |
| 309 | 0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 310 | 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, |
| 311 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 312 | ], |
| 313 | Ok(true), |
| 314 | ), |
| 315 | ( |
| 316 | // 2001:0DB8:ABCD:0012:FFFF:FFFF:FFFF:FFFF matches constraint |
| 317 | // 2001:0DB8:ABCD:0012:0000:0000:0000:0000/64 |
| 318 | [ |
| 319 | 0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, |
| 320 | 0xFF, 0xFF, 0xFF, |
| 321 | ], |
| 322 | [ |
| 323 | 0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 324 | 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, |
| 325 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 326 | ], |
| 327 | Ok(true), |
| 328 | ), |
| 329 | ( |
| 330 | // 2001:0DB8:ABCD:0011:0000:0000:0000:0000 does not match constraint |
| 331 | // 2001:0DB8:ABCD:0012:0000:0000:0000:0000/64 |
| 332 | [ |
| 333 | 0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x11, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 334 | 0x00, 0x00, 0x00, |
| 335 | ], |
| 336 | [ |
| 337 | 0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 338 | 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, |
| 339 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 340 | ], |
| 341 | Ok(false), |
| 342 | ), |
| 343 | ( |
| 344 | // 2001:0DB8:ABCD:0013:0000:0000:0000:0000 does not match constraint |
| 345 | // 2001:0DB8:ABCD:0012:0000:0000:0000:0000/64 |
| 346 | [ |
| 347 | 0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x13, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 348 | 0x00, 0x00, 0x00, |
| 349 | ], |
| 350 | [ |
| 351 | 0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 352 | 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, |
| 353 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 354 | ], |
| 355 | Ok(false), |
| 356 | ), |
| 357 | ]; |
| 358 | for (name, constraint, match_result) in names_and_constraints { |
| 359 | assert_eq!( |
| 360 | presented_id_matches_constraint( |
| 361 | untrusted::Input::from(&name), |
| 362 | untrusted::Input::from(&constraint), |
| 363 | ), |
| 364 | match_result |
| 365 | ) |
| 366 | } |
| 367 | |
| 368 | // Invalid name length (shorter) |
| 369 | assert_eq!( |
| 370 | presented_id_matches_constraint( |
| 371 | untrusted::Input::from(&[ |
| 372 | 0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 373 | 0x00, 0x00 |
| 374 | ]), |
| 375 | untrusted::Input::from(&[ |
| 376 | 0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 377 | 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, |
| 378 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 |
| 379 | ]), |
| 380 | ), |
| 381 | Err(Error::BadDer), |
| 382 | ); |
| 383 | |
| 384 | // Invalid name length (longer) |
| 385 | assert_eq!( |
| 386 | presented_id_matches_constraint( |
| 387 | untrusted::Input::from(&[ |
| 388 | 0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 389 | 0x00, 0x00, 0x00, 0x00 |
| 390 | ]), |
| 391 | untrusted::Input::from(&[ |
| 392 | 0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 393 | 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, |
| 394 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 |
| 395 | ]), |
| 396 | ), |
| 397 | Err(Error::BadDer), |
| 398 | ); |
| 399 | |
| 400 | // Unmatching constraint size (shorter) |
| 401 | assert_eq!( |
| 402 | presented_id_matches_constraint( |
| 403 | untrusted::Input::from(&[ |
| 404 | 0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 405 | 0x00, 0x00, 0x00, |
| 406 | ]), |
| 407 | untrusted::Input::from(&[ |
| 408 | 0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 409 | 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, |
| 410 | 0x00, 0x00, 0x00, 0x00, 0x00 |
| 411 | ]), |
| 412 | ), |
| 413 | Err(Error::InvalidNetworkMaskConstraint), |
| 414 | ); |
| 415 | |
| 416 | // Unmatching constraint size (longer) |
| 417 | assert_eq!( |
| 418 | presented_id_matches_constraint( |
| 419 | untrusted::Input::from(&[ |
| 420 | 0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 421 | 0x00, 0x00, 0x00, |
| 422 | ]), |
| 423 | untrusted::Input::from(&[ |
| 424 | 0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 425 | 0x00, 0x00, 0x00, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0x00, 0x00, |
| 426 | 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00 |
| 427 | ]), |
| 428 | ), |
| 429 | Err(Error::InvalidNetworkMaskConstraint), |
| 430 | ); |
| 431 | |
| 432 | // Unmatching constraint size (IPv4 constraint for IPv6 address) |
| 433 | assert_eq!( |
| 434 | presented_id_matches_constraint( |
| 435 | untrusted::Input::from(&[ |
| 436 | 0x20, 0x01, 0x0D, 0xB8, 0xAB, 0xCD, 0x00, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, |
| 437 | 0x00, 0x00, 0x00, |
| 438 | ]), |
| 439 | untrusted::Input::from(&[0xC0, 0x00, 0x02, 0x00, 0xFF, 0xFF, 0xFF, 0x00]), |
| 440 | ), |
| 441 | Ok(false), |
| 442 | ); |
| 443 | } |
| 444 | |
| 445 | #[test ] |
| 446 | fn test_presented_id_matches_reference_id() { |
| 447 | assert!(!presented_id_matches_reference_id( |
| 448 | untrusted::Input::from(&[]), |
| 449 | untrusted::Input::from(&[]), |
| 450 | )); |
| 451 | |
| 452 | assert!(!presented_id_matches_reference_id( |
| 453 | untrusted::Input::from(&[0x01]), |
| 454 | untrusted::Input::from(&[]) |
| 455 | )); |
| 456 | |
| 457 | assert!(!presented_id_matches_reference_id( |
| 458 | untrusted::Input::from(&[]), |
| 459 | untrusted::Input::from(&[0x01]) |
| 460 | )); |
| 461 | |
| 462 | assert!(presented_id_matches_reference_id( |
| 463 | untrusted::Input::from(&[1, 2, 3, 4]), |
| 464 | untrusted::Input::from(&[1, 2, 3, 4]) |
| 465 | )); |
| 466 | |
| 467 | assert!(!presented_id_matches_reference_id( |
| 468 | untrusted::Input::from(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]), |
| 469 | untrusted::Input::from(&[1, 2, 3, 4]) |
| 470 | )); |
| 471 | |
| 472 | assert!(!presented_id_matches_reference_id( |
| 473 | untrusted::Input::from(&[1, 2, 3, 4]), |
| 474 | untrusted::Input::from(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]) |
| 475 | )); |
| 476 | |
| 477 | assert!(presented_id_matches_reference_id( |
| 478 | untrusted::Input::from(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]), |
| 479 | untrusted::Input::from(&[1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]) |
| 480 | )); |
| 481 | } |
| 482 | |
| 483 | #[test ] |
| 484 | fn presented_id_matches_constraint_rejects_incorrect_length_arguments() { |
| 485 | // wrong length names |
| 486 | assert_eq!( |
| 487 | presented_id_matches_constraint( |
| 488 | untrusted::Input::from(b" \x00\x00\x00" ), |
| 489 | untrusted::Input::from(b"" ) |
| 490 | ), |
| 491 | Err(Error::BadDer) |
| 492 | ); |
| 493 | assert_eq!( |
| 494 | presented_id_matches_constraint( |
| 495 | untrusted::Input::from(b" \x00\x00\x00\x00\x00" ), |
| 496 | untrusted::Input::from(b"" ) |
| 497 | ), |
| 498 | Err(Error::BadDer) |
| 499 | ); |
| 500 | |
| 501 | assert_eq!( |
| 502 | presented_id_matches_constraint( |
| 503 | untrusted::Input::from( |
| 504 | b" \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" |
| 505 | ), |
| 506 | untrusted::Input::from(b"" ) |
| 507 | ), |
| 508 | Err(Error::BadDer) |
| 509 | ); |
| 510 | assert_eq!( |
| 511 | presented_id_matches_constraint( |
| 512 | untrusted::Input::from( |
| 513 | b" \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" |
| 514 | ), |
| 515 | untrusted::Input::from(b"" ) |
| 516 | ), |
| 517 | Err(Error::BadDer) |
| 518 | ); |
| 519 | |
| 520 | // wrong length constraints |
| 521 | assert_eq!( |
| 522 | presented_id_matches_constraint( |
| 523 | untrusted::Input::from(b" \x00\x00\x00\x00" ), |
| 524 | untrusted::Input::from(b" \x00\x00\x00\x00\xff\xff\xff" ) |
| 525 | ), |
| 526 | Err(Error::InvalidNetworkMaskConstraint) |
| 527 | ); |
| 528 | assert_eq!( |
| 529 | presented_id_matches_constraint( |
| 530 | untrusted::Input::from(b" \x00\x00\x00\x00" ), |
| 531 | untrusted::Input::from(b" \x00\x00\x00\x00\xff\xff\xff\xff\x00" ) |
| 532 | ), |
| 533 | Err(Error::InvalidNetworkMaskConstraint) |
| 534 | ); |
| 535 | assert_eq!( |
| 536 | presented_id_matches_constraint(untrusted::Input::from(b" \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" ), |
| 537 | untrusted::Input::from(b" \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ |
| 538 | \xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" )), |
| 539 | Err(Error::InvalidNetworkMaskConstraint) |
| 540 | ); |
| 541 | assert_eq!( |
| 542 | presented_id_matches_constraint(untrusted::Input::from(b" \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" ), |
| 543 | untrusted::Input::from(b" \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ |
| 544 | \xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" )), |
| 545 | Err(Error::InvalidNetworkMaskConstraint) |
| 546 | ); |
| 547 | |
| 548 | // ipv4-length not considered for ipv6-length name, and vv |
| 549 | assert_eq!( |
| 550 | presented_id_matches_constraint(untrusted::Input::from(b" \x00\x00\x00\x00" ), |
| 551 | untrusted::Input::from(b" \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\ |
| 552 | \xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff\xff" )), |
| 553 | Ok(false) |
| 554 | ); |
| 555 | assert_eq!( |
| 556 | presented_id_matches_constraint( |
| 557 | untrusted::Input::from( |
| 558 | b" \x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00\x00" |
| 559 | ), |
| 560 | untrusted::Input::from(b" \x00\x00\x00\x00\xff\xff\xff\xff" ) |
| 561 | ), |
| 562 | Ok(false) |
| 563 | ); |
| 564 | } |
| 565 | } |
| 566 | |
| 567 | #[cfg (all(test, feature = "alloc" ))] |
| 568 | mod alloc_tests { |
| 569 | use super::*; |
| 570 | |
| 571 | // (presented_address, constraint_address, constraint_mask, expected_result) |
| 572 | const PRESENTED_MATCHES_CONSTRAINT: &[(&str, &str, &str, Result<bool, Error>)] = &[ |
| 573 | // Cannot mix IpV4 with IpV6 and viceversa |
| 574 | ("2001:db8::" , "8.8.8.8" , "255.255.255.255" , Ok(false)), |
| 575 | ("8.8.8.8" , "2001:db8::" , "ffff::" , Ok(false)), |
| 576 | // IpV4 non-contiguous masks |
| 577 | ( |
| 578 | "8.8.8.8" , |
| 579 | "8.8.8.8" , |
| 580 | "255.255.255.1" , |
| 581 | Err(Error::InvalidNetworkMaskConstraint), |
| 582 | ), |
| 583 | ( |
| 584 | "8.8.8.8" , |
| 585 | "8.8.8.8" , |
| 586 | "255.255.0.255" , |
| 587 | Err(Error::InvalidNetworkMaskConstraint), |
| 588 | ), |
| 589 | ( |
| 590 | "8.8.8.8" , |
| 591 | "8.8.8.8" , |
| 592 | "255.0.255.255" , |
| 593 | Err(Error::InvalidNetworkMaskConstraint), |
| 594 | ), |
| 595 | ( |
| 596 | "8.8.8.8" , |
| 597 | "8.8.8.8" , |
| 598 | "0.255.255.255" , |
| 599 | Err(Error::InvalidNetworkMaskConstraint), |
| 600 | ), |
| 601 | ( |
| 602 | "8.8.8.8" , |
| 603 | "8.8.8.8" , |
| 604 | "1.255.255.255" , |
| 605 | Err(Error::InvalidNetworkMaskConstraint), |
| 606 | ), |
| 607 | ( |
| 608 | "8.8.8.8" , |
| 609 | "8.8.8.8" , |
| 610 | "128.128.128.128" , |
| 611 | Err(Error::InvalidNetworkMaskConstraint), |
| 612 | ), |
| 613 | // IpV4 |
| 614 | ("8.8.8.8" , "8.8.8.8" , "255.255.255.255" , Ok(true)), |
| 615 | ("8.8.8.9" , "8.8.8.8" , "255.255.255.255" , Ok(false)), |
| 616 | ("8.8.8.9" , "8.8.8.8" , "255.255.255.254" , Ok(true)), |
| 617 | ("8.8.8.10" , "8.8.8.8" , "255.255.255.254" , Ok(false)), |
| 618 | ("8.8.8.10" , "8.8.8.8" , "255.255.255.0" , Ok(true)), |
| 619 | ("8.8.15.10" , "8.8.8.8" , "255.255.248.0" , Ok(true)), |
| 620 | ("8.8.16.10" , "8.8.8.8" , "255.255.248.0" , Ok(false)), |
| 621 | ("8.8.16.10" , "8.8.8.8" , "255.255.0.0" , Ok(true)), |
| 622 | ("8.31.16.10" , "8.8.8.8" , "255.224.0.0" , Ok(true)), |
| 623 | ("8.32.16.10" , "8.8.8.8" , "255.224.0.0" , Ok(false)), |
| 624 | ("8.32.16.10" , "8.8.8.8" , "255.0.0.0" , Ok(true)), |
| 625 | ("63.32.16.10" , "8.8.8.8" , "192.0.0.0" , Ok(true)), |
| 626 | ("64.32.16.10" , "8.8.8.8" , "192.0.0.0" , Ok(false)), |
| 627 | ("64.32.16.10" , "8.8.8.8" , "0.0.0.0" , Ok(true)), |
| 628 | // IpV6 non-contiguous masks |
| 629 | ( |
| 630 | "2001:db8::" , |
| 631 | "2001:db8::" , |
| 632 | "fffe:ffff::" , |
| 633 | Err(Error::InvalidNetworkMaskConstraint), |
| 634 | ), |
| 635 | ( |
| 636 | "2001:db8::" , |
| 637 | "2001:db8::" , |
| 638 | "ffff:fdff::" , |
| 639 | Err(Error::InvalidNetworkMaskConstraint), |
| 640 | ), |
| 641 | ( |
| 642 | "2001:db8::" , |
| 643 | "2001:db8::" , |
| 644 | "ffff:feff::" , |
| 645 | Err(Error::InvalidNetworkMaskConstraint), |
| 646 | ), |
| 647 | ( |
| 648 | "2001:db8::" , |
| 649 | "2001:db8::" , |
| 650 | "ffff:fcff::" , |
| 651 | Err(Error::InvalidNetworkMaskConstraint), |
| 652 | ), |
| 653 | ( |
| 654 | "2001:db8::" , |
| 655 | "2001:db8::" , |
| 656 | "7fff:ffff::" , |
| 657 | Err(Error::InvalidNetworkMaskConstraint), |
| 658 | ), |
| 659 | // IpV6 |
| 660 | ("2001:db8::" , "2001:db8::" , "ffff:ffff::" , Ok(true)), |
| 661 | ("2001:db9::" , "2001:db8::" , "ffff:ffff::" , Ok(false)), |
| 662 | ("2001:db9::" , "2001:db8::" , "ffff:fffe::" , Ok(true)), |
| 663 | ("2001:dba::" , "2001:db8::" , "ffff:fffe::" , Ok(false)), |
| 664 | ("2001:dba::" , "2001:db8::" , "ffff:ff00::" , Ok(true)), |
| 665 | ("2001:dca::" , "2001:db8::" , "ffff:fe00::" , Ok(true)), |
| 666 | ("2001:fca::" , "2001:db8::" , "ffff:fe00::" , Ok(false)), |
| 667 | ("2001:fca::" , "2001:db8::" , "ffff:0000::" , Ok(true)), |
| 668 | ("2000:fca::" , "2001:db8::" , "fffe:0000::" , Ok(true)), |
| 669 | ("2003:fca::" , "2001:db8::" , "fffe:0000::" , Ok(false)), |
| 670 | ("2003:fca::" , "2001:db8::" , "ff00:0000::" , Ok(true)), |
| 671 | ("1003:fca::" , "2001:db8::" , "e000:0000::" , Ok(false)), |
| 672 | ("1003:fca::" , "2001:db8::" , "0000:0000::" , Ok(true)), |
| 673 | ]; |
| 674 | |
| 675 | #[cfg (feature = "std" )] |
| 676 | #[test ] |
| 677 | fn presented_matches_constraint_test() { |
| 678 | use std::boxed::Box; |
| 679 | use std::net::IpAddr; |
| 680 | |
| 681 | for (presented, constraint_address, constraint_mask, expected_result) in |
| 682 | PRESENTED_MATCHES_CONSTRAINT |
| 683 | { |
| 684 | let presented_bytes: Box<[u8]> = match presented.parse::<IpAddr>().unwrap() { |
| 685 | IpAddr::V4(p) => Box::new(p.octets()), |
| 686 | IpAddr::V6(p) => Box::new(p.octets()), |
| 687 | }; |
| 688 | let ca_bytes: Box<[u8]> = match constraint_address.parse::<IpAddr>().unwrap() { |
| 689 | IpAddr::V4(ca) => Box::new(ca.octets()), |
| 690 | IpAddr::V6(ca) => Box::new(ca.octets()), |
| 691 | }; |
| 692 | let cm_bytes: Box<[u8]> = match constraint_mask.parse::<IpAddr>().unwrap() { |
| 693 | IpAddr::V4(cm) => Box::new(cm.octets()), |
| 694 | IpAddr::V6(cm) => Box::new(cm.octets()), |
| 695 | }; |
| 696 | let constraint_bytes = [ca_bytes, cm_bytes].concat(); |
| 697 | let actual_result = presented_id_matches_constraint( |
| 698 | untrusted::Input::from(&presented_bytes), |
| 699 | untrusted::Input::from(&constraint_bytes), |
| 700 | ); |
| 701 | assert_eq!( |
| 702 | &actual_result, expected_result, |
| 703 | "presented_id_matches_constraint( \"{:?} \", \"{:?} \")" , |
| 704 | presented_bytes, constraint_bytes |
| 705 | ); |
| 706 | } |
| 707 | } |
| 708 | } |
| 709 | |