1#[cfg(feature = "alloc")]
16use alloc::string::String;
17#[cfg(feature = "alloc")]
18use core::fmt;
19
20use crate::der::{self, FromDer};
21use crate::error::{DerTypeId, Error};
22use crate::verify_cert::{Budget, PathNode};
23
24mod dns_name;
25use dns_name::IdRole;
26pub(crate) use dns_name::{WildcardDnsNameRef, verify_dns_names};
27
28mod ip_address;
29pub(crate) use ip_address::verify_ip_address_names;
30
31pub(crate) fn check_name_constraints(
33 constraints: Option<&mut untrusted::Reader<'_>>,
34 path: &PathNode<'_>,
35 budget: &mut Budget,
36) -> Result<(), Error> {
37 let constraints = match constraints {
38 Some(input) => input,
39 None => return Ok(()),
40 };
41
42 fn parse_subtrees<'b>(
43 inner: &mut untrusted::Reader<'b>,
44 subtrees_tag: der::Tag,
45 ) -> Result<Option<untrusted::Input<'b>>, Error> {
46 if !inner.peek(subtrees_tag.into()) {
47 return Ok(None);
48 }
49 der::expect_tag(inner, subtrees_tag).map(Some)
50 }
51
52 let permitted_subtrees = parse_subtrees(constraints, der::Tag::ContextSpecificConstructed0)?;
53 let excluded_subtrees = parse_subtrees(constraints, der::Tag::ContextSpecificConstructed1)?;
54
55 for path in path.iter() {
56 let result = NameIterator::new(path.cert.subject_alt_name).find_map(|result| {
57 let name = match result {
58 Ok(name) => name,
59 Err(err) => return Some(Err(err)),
60 };
61
62 check_presented_id_conforms_to_constraints(
63 name,
64 permitted_subtrees,
65 excluded_subtrees,
66 budget,
67 )
68 });
69
70 if let Some(Err(err)) = result {
71 return Err(err);
72 }
73
74 let result = check_presented_id_conforms_to_constraints(
75 GeneralName::DirectoryName,
76 permitted_subtrees,
77 excluded_subtrees,
78 budget,
79 );
80
81 if let Some(Err(err)) = result {
82 return Err(err);
83 }
84 }
85
86 Ok(())
87}
88
89fn check_presented_id_conforms_to_constraints(
90 name: GeneralName<'_>,
91 permitted_subtrees: Option<untrusted::Input<'_>>,
92 excluded_subtrees: Option<untrusted::Input<'_>>,
93 budget: &mut Budget,
94) -> Option<Result<(), Error>> {
95 let subtrees = [
96 (Subtrees::Permitted, permitted_subtrees),
97 (Subtrees::Excluded, excluded_subtrees),
98 ];
99
100 fn general_subtree<'b>(input: &mut untrusted::Reader<'b>) -> Result<GeneralName<'b>, Error> {
101 der::read_all(der::expect_tag(input, der::Tag::Sequence)?)
102 }
103
104 for (subtrees, constraints) in subtrees {
105 let mut constraints = match constraints {
106 Some(constraints) => untrusted::Reader::new(constraints),
107 None => continue,
108 };
109
110 let mut has_permitted_subtrees_match = false;
111 let mut has_permitted_subtrees_mismatch = false;
112 while !constraints.at_end() {
113 if let Err(e) = budget.consume_name_constraint_comparison() {
114 return Some(Err(e));
115 }
116
117 let base = match general_subtree(&mut constraints) {
125 Ok(base) => base,
126 Err(err) => return Some(Err(err)),
127 };
128
129 let matches = match (name, base) {
131 (GeneralName::DnsName(name), GeneralName::DnsName(base)) => {
132 dns_name::presented_id_matches_reference_id(
133 name,
134 IdRole::NameConstraint(subtrees),
135 base,
136 )
137 }
138 (GeneralName::DnsName(_), _) => continue,
139
140 (GeneralName::DirectoryName, GeneralName::DirectoryName) => Ok(
141 match subtrees {
155 Subtrees::Permitted => false,
156 Subtrees::Excluded => true,
157 },
158 ),
159 (GeneralName::DirectoryName, _) => continue,
160
161 (GeneralName::IpAddress(name), GeneralName::IpAddress(base)) => {
162 ip_address::presented_id_matches_constraint(name, base)
163 }
164 (GeneralName::IpAddress(_), _) => continue,
165
166 (
171 GeneralName::UniformResourceIdentifier(_),
172 GeneralName::UniformResourceIdentifier(_),
173 ) => Ok(match subtrees {
174 Subtrees::Permitted => false,
175 Subtrees::Excluded => true,
176 }),
177 (GeneralName::UniformResourceIdentifier(_), _) => continue,
178
179 (GeneralName::Unsupported(name_tag), GeneralName::Unsupported(base_tag))
188 if name_tag == base_tag =>
189 {
190 Err(Error::NameConstraintViolation)
191 }
192 (GeneralName::Unsupported(_), _) => continue,
193 };
194
195 match (subtrees, matches) {
196 (Subtrees::Permitted, Ok(true)) => {
197 has_permitted_subtrees_match = true;
198 }
199
200 (Subtrees::Permitted, Ok(false)) => {
201 has_permitted_subtrees_mismatch = true;
202 }
203
204 (Subtrees::Excluded, Ok(true)) => {
205 return Some(Err(Error::NameConstraintViolation));
206 }
207
208 (Subtrees::Excluded, Ok(false)) => (),
209 (_, Err(err)) => return Some(Err(err)),
210 }
211 }
212
213 if has_permitted_subtrees_mismatch && !has_permitted_subtrees_match {
214 return Some(Err(Error::NameConstraintViolation));
218 }
219 }
220
221 None
222}
223
224#[derive(Clone, Copy, PartialEq)]
225enum Subtrees {
226 Permitted,
227 Excluded,
228}
229
230pub(crate) struct NameIterator<'a> {
231 subject_alt_name: Option<untrusted::Reader<'a>>,
232}
233
234impl<'a> NameIterator<'a> {
235 pub(crate) fn new(subject_alt_name: Option<untrusted::Input<'a>>) -> Self {
236 Self {
237 subject_alt_name: subject_alt_name.map(untrusted::Reader::new),
238 }
239 }
240}
241
242impl<'a> Iterator for NameIterator<'a> {
243 type Item = Result<GeneralName<'a>, Error>;
244
245 fn next(&mut self) -> Option<Self::Item> {
246 let subject_alt_name = self.subject_alt_name.as_mut()?;
247 if subject_alt_name.at_end() {
255 self.subject_alt_name = None;
256 return None;
257 }
258
259 let err = match GeneralName::from_der(subject_alt_name) {
260 Ok(name) => return Some(Ok(name)),
261 Err(err) => err,
262 };
263
264 self.subject_alt_name = None;
266 Some(Err(err))
267 }
268}
269
270#[derive(Clone, Copy)]
276pub(crate) enum GeneralName<'a> {
277 DnsName(untrusted::Input<'a>),
278 DirectoryName,
279 IpAddress(untrusted::Input<'a>),
280 UniformResourceIdentifier(untrusted::Input<'a>),
281
282 Unsupported(u8),
286}
287
288impl<'a> FromDer<'a> for GeneralName<'a> {
289 fn from_der(reader: &mut untrusted::Reader<'a>) -> Result<Self, Error> {
290 use GeneralName::*;
291 use der::{CONSTRUCTED, CONTEXT_SPECIFIC};
292
293 #[allow(clippy::identity_op)]
294 const OTHER_NAME_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 0;
295 const RFC822_NAME_TAG: u8 = CONTEXT_SPECIFIC | 1;
296 const DNS_NAME_TAG: u8 = CONTEXT_SPECIFIC | 2;
297 const X400_ADDRESS_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 3;
298 const DIRECTORY_NAME_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 4;
299 const EDI_PARTY_NAME_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 5;
300 const UNIFORM_RESOURCE_IDENTIFIER_TAG: u8 = CONTEXT_SPECIFIC | 6;
301 const IP_ADDRESS_TAG: u8 = CONTEXT_SPECIFIC | 7;
302 const REGISTERED_ID_TAG: u8 = CONTEXT_SPECIFIC | 8;
303
304 let (tag, value) = der::read_tag_and_get_value(reader)?;
305 Ok(match tag {
306 DNS_NAME_TAG => DnsName(value),
307 DIRECTORY_NAME_TAG => DirectoryName,
308 IP_ADDRESS_TAG => IpAddress(value),
309 UNIFORM_RESOURCE_IDENTIFIER_TAG => UniformResourceIdentifier(value),
310
311 OTHER_NAME_TAG | RFC822_NAME_TAG | X400_ADDRESS_TAG | EDI_PARTY_NAME_TAG
312 | REGISTERED_ID_TAG => Unsupported(tag & !(CONTEXT_SPECIFIC | CONSTRUCTED)),
313
314 _ => return Err(Error::BadDer),
315 })
316 }
317
318 const TYPE_ID: DerTypeId = DerTypeId::GeneralName;
319}
320
321#[cfg(feature = "alloc")]
322impl fmt::Debug for GeneralName<'_> {
323 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
324 match self {
325 GeneralName::DnsName(name) => write!(
326 f,
327 "DnsName(\"{}\")",
328 String::from_utf8_lossy(name.as_slice_less_safe())
329 ),
330 GeneralName::DirectoryName => write!(f, "DirectoryName"),
331 GeneralName::IpAddress(ip) => {
332 write!(f, "IpAddress({:?})", IpAddrSlice(ip.as_slice_less_safe()))
333 }
334 GeneralName::UniformResourceIdentifier(uri) => write!(
335 f,
336 "UniformResourceIdentifier(\"{}\")",
337 String::from_utf8_lossy(uri.as_slice_less_safe())
338 ),
339 GeneralName::Unsupported(tag) => write!(f, "Unsupported(0x{tag:02x})"),
340 }
341 }
342}
343
344#[cfg(feature = "alloc")]
345struct IpAddrSlice<'a>(&'a [u8]);
346
347#[cfg(feature = "alloc")]
348impl fmt::Debug for IpAddrSlice<'_> {
349 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
350 match self.0.len() {
351 4 => {
352 let mut first = true;
353 for byte in self.0 {
354 match first {
355 true => first = false,
356 false => f.write_str(".")?,
357 }
358
359 write!(f, "{byte}")?;
360 }
361
362 Ok(())
363 }
364 16 => {
365 let (mut first, mut skipping) = (true, false);
366 for group in self.0.chunks_exact(2) {
367 match (first, group == [0, 0], skipping) {
368 (true, _, _) => first = false,
369 (false, false, false) => f.write_str(":")?,
370 (false, true, _) => {
371 skipping = true;
372 continue;
373 }
374 (false, false, true) => {
375 skipping = false;
376 f.write_str("::")?;
377 }
378 }
379
380 if group[0] != 0 {
381 write!(f, "{:x}", group[0])?;
382 }
383
384 match group[0] {
385 0 => write!(f, "{:x}", group[1])?,
386 _ => write!(f, "{:02x}", group[1])?,
387 }
388 }
389 Ok(())
390 }
391 _ => {
392 f.write_str("[invalid: ")?;
393 let mut first = true;
394 for byte in self.0 {
395 match first {
396 true => first = false,
397 false => f.write_str(", ")?,
398 }
399 write!(f, "{byte:02x}")?;
400 }
401 f.write_str("]")
402 }
403 }
404 }
405}
406
407#[cfg(all(test, feature = "alloc"))]
408mod tests {
409 use super::*;
410
411 #[test]
412 fn debug_names() {
413 assert_eq!(
414 format!(
415 "{:?}",
416 GeneralName::DnsName(untrusted::Input::from(b"example.com"))
417 ),
418 "DnsName(\"example.com\")"
419 );
420
421 assert_eq!(format!("{:?}", GeneralName::DirectoryName), "DirectoryName");
422
423 assert_eq!(
424 format!(
425 "{:?}",
426 GeneralName::IpAddress(untrusted::Input::from(&[192, 0, 2, 1][..]))
427 ),
428 "IpAddress(192.0.2.1)"
429 );
430
431 assert_eq!(
432 format!(
433 "{:?}",
434 GeneralName::IpAddress(untrusted::Input::from(
435 &[0x20, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x0d, 0xb8][..]
436 ))
437 ),
438 "IpAddress(2001::db8)"
439 );
440
441 assert_eq!(
442 format!(
443 "{:?}",
444 GeneralName::IpAddress(untrusted::Input::from(&[1, 2, 3, 4, 5, 6][..]))
445 ),
446 "IpAddress([invalid: 01, 02, 03, 04, 05, 06])"
447 );
448
449 assert_eq!(
450 format!(
451 "{:?}",
452 GeneralName::UniformResourceIdentifier(untrusted::Input::from(
453 b"https://example.com"
454 ))
455 ),
456 "UniformResourceIdentifier(\"https://example.com\")"
457 );
458
459 assert_eq!(
460 format!("{:?}", GeneralName::Unsupported(0x66)),
461 "Unsupported(0x66)"
462 );
463 }
464
465 #[test]
466 fn name_iter_end_after_error() {
467 let input = untrusted::Input::from(&[0x30]);
468 let mut iter = NameIterator::new(Some(input));
469 assert_eq!(iter.next().unwrap().unwrap_err(), Error::BadDer);
470 assert!(iter.next().is_none());
471 }
472}