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::PermittedSubtrees, permitted_subtrees),
97 (Subtrees::ExcludedSubtrees, 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) {
130 (GeneralName::DnsName(name), GeneralName::DnsName(base)) => {
131 dns_name::presented_id_matches_reference_id(name, IdRole::NameConstraint, base)
132 }
133
134 (GeneralName::DirectoryName, GeneralName::DirectoryName) => Ok(
135 match subtrees {
149 Subtrees::PermittedSubtrees => false,
150 Subtrees::ExcludedSubtrees => true,
151 },
152 ),
153
154 (GeneralName::IpAddress(name), GeneralName::IpAddress(base)) => {
155 ip_address::presented_id_matches_constraint(name, base)
156 }
157
158 (GeneralName::Unsupported(name_tag), GeneralName::Unsupported(base_tag))
167 if name_tag == base_tag =>
168 {
169 Err(Error::NameConstraintViolation)
170 }
171
172 _ => {
173 continue;
176 }
177 };
178
179 match (subtrees, matches) {
180 (Subtrees::PermittedSubtrees, Ok(true)) => {
181 has_permitted_subtrees_match = true;
182 }
183
184 (Subtrees::PermittedSubtrees, Ok(false)) => {
185 has_permitted_subtrees_mismatch = true;
186 }
187
188 (Subtrees::ExcludedSubtrees, Ok(true)) => {
189 return Some(Err(Error::NameConstraintViolation));
190 }
191
192 (Subtrees::ExcludedSubtrees, Ok(false)) => (),
193 (_, Err(err)) => return Some(Err(err)),
194 }
195 }
196
197 if has_permitted_subtrees_mismatch && !has_permitted_subtrees_match {
198 return Some(Err(Error::NameConstraintViolation));
202 }
203 }
204
205 None
206}
207
208#[derive(Clone, Copy)]
209enum Subtrees {
210 PermittedSubtrees,
211 ExcludedSubtrees,
212}
213
214pub(crate) struct NameIterator<'a> {
215 subject_alt_name: Option<untrusted::Reader<'a>>,
216}
217
218impl<'a> NameIterator<'a> {
219 pub(crate) fn new(subject_alt_name: Option<untrusted::Input<'a>>) -> Self {
220 Self {
221 subject_alt_name: subject_alt_name.map(untrusted::Reader::new),
222 }
223 }
224}
225
226impl<'a> Iterator for NameIterator<'a> {
227 type Item = Result<GeneralName<'a>, Error>;
228
229 fn next(&mut self) -> Option<Self::Item> {
230 let subject_alt_name = self.subject_alt_name.as_mut()?;
231 if subject_alt_name.at_end() {
239 self.subject_alt_name = None;
240 return None;
241 }
242
243 let err = match GeneralName::from_der(subject_alt_name) {
244 Ok(name) => return Some(Ok(name)),
245 Err(err) => err,
246 };
247
248 self.subject_alt_name = None;
250 Some(Err(err))
251 }
252}
253
254#[derive(Clone, Copy)]
260pub(crate) enum GeneralName<'a> {
261 DnsName(untrusted::Input<'a>),
262 DirectoryName,
263 IpAddress(untrusted::Input<'a>),
264 UniformResourceIdentifier(untrusted::Input<'a>),
265
266 Unsupported(u8),
270}
271
272impl<'a> FromDer<'a> for GeneralName<'a> {
273 fn from_der(reader: &mut untrusted::Reader<'a>) -> Result<Self, Error> {
274 use GeneralName::*;
275 use der::{CONSTRUCTED, CONTEXT_SPECIFIC};
276
277 #[allow(clippy::identity_op)]
278 const OTHER_NAME_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 0;
279 const RFC822_NAME_TAG: u8 = CONTEXT_SPECIFIC | 1;
280 const DNS_NAME_TAG: u8 = CONTEXT_SPECIFIC | 2;
281 const X400_ADDRESS_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 3;
282 const DIRECTORY_NAME_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 4;
283 const EDI_PARTY_NAME_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 5;
284 const UNIFORM_RESOURCE_IDENTIFIER_TAG: u8 = CONTEXT_SPECIFIC | 6;
285 const IP_ADDRESS_TAG: u8 = CONTEXT_SPECIFIC | 7;
286 const REGISTERED_ID_TAG: u8 = CONTEXT_SPECIFIC | 8;
287
288 let (tag, value) = der::read_tag_and_get_value(reader)?;
289 Ok(match tag {
290 DNS_NAME_TAG => DnsName(value),
291 DIRECTORY_NAME_TAG => DirectoryName,
292 IP_ADDRESS_TAG => IpAddress(value),
293 UNIFORM_RESOURCE_IDENTIFIER_TAG => UniformResourceIdentifier(value),
294
295 OTHER_NAME_TAG | RFC822_NAME_TAG | X400_ADDRESS_TAG | EDI_PARTY_NAME_TAG
296 | REGISTERED_ID_TAG => Unsupported(tag & !(CONTEXT_SPECIFIC | CONSTRUCTED)),
297
298 _ => return Err(Error::BadDer),
299 })
300 }
301
302 const TYPE_ID: DerTypeId = DerTypeId::GeneralName;
303}
304
305#[cfg(feature = "alloc")]
306impl fmt::Debug for GeneralName<'_> {
307 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
308 match self {
309 GeneralName::DnsName(name) => write!(
310 f,
311 "DnsName(\"{}\")",
312 String::from_utf8_lossy(name.as_slice_less_safe())
313 ),
314 GeneralName::DirectoryName => write!(f, "DirectoryName"),
315 GeneralName::IpAddress(ip) => {
316 write!(f, "IpAddress({:?})", IpAddrSlice(ip.as_slice_less_safe()))
317 }
318 GeneralName::UniformResourceIdentifier(uri) => write!(
319 f,
320 "UniformResourceIdentifier(\"{}\")",
321 String::from_utf8_lossy(uri.as_slice_less_safe())
322 ),
323 GeneralName::Unsupported(tag) => write!(f, "Unsupported(0x{tag:02x})"),
324 }
325 }
326}
327
328#[cfg(feature = "alloc")]
329struct IpAddrSlice<'a>(&'a [u8]);
330
331#[cfg(feature = "alloc")]
332impl fmt::Debug for IpAddrSlice<'_> {
333 fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result {
334 match self.0.len() {
335 4 => {
336 let mut first = true;
337 for byte in self.0 {
338 match first {
339 true => first = false,
340 false => f.write_str(".")?,
341 }
342
343 write!(f, "{}", byte)?;
344 }
345
346 Ok(())
347 }
348 16 => {
349 let (mut first, mut skipping) = (true, false);
350 for group in self.0.chunks_exact(2) {
351 match (first, group == [0, 0], skipping) {
352 (true, _, _) => first = false,
353 (false, false, false) => f.write_str(":")?,
354 (false, true, _) => {
355 skipping = true;
356 continue;
357 }
358 (false, false, true) => {
359 skipping = false;
360 f.write_str("::")?;
361 }
362 }
363
364 if group[0] != 0 {
365 write!(f, "{:x}", group[0])?;
366 }
367
368 match group[0] {
369 0 => write!(f, "{:x}", group[1])?,
370 _ => write!(f, "{:02x}", group[1])?,
371 }
372 }
373 Ok(())
374 }
375 _ => {
376 f.write_str("[invalid: ")?;
377 let mut first = true;
378 for byte in self.0 {
379 match first {
380 true => first = false,
381 false => f.write_str(", ")?,
382 }
383 write!(f, "{:02x}", byte)?;
384 }
385 f.write_str("]")
386 }
387 }
388 }
389}
390
391#[cfg(all(test, feature = "alloc"))]
392mod tests {
393 use super::*;
394
395 #[test]
396 fn debug_names() {
397 assert_eq!(
398 format!(
399 "{:?}",
400 GeneralName::DnsName(untrusted::Input::from(b"example.com"))
401 ),
402 "DnsName(\"example.com\")"
403 );
404
405 assert_eq!(format!("{:?}", GeneralName::DirectoryName), "DirectoryName");
406
407 assert_eq!(
408 format!(
409 "{:?}",
410 GeneralName::IpAddress(untrusted::Input::from(&[192, 0, 2, 1][..]))
411 ),
412 "IpAddress(192.0.2.1)"
413 );
414
415 assert_eq!(
416 format!(
417 "{:?}",
418 GeneralName::IpAddress(untrusted::Input::from(
419 &[0x20, 0x01, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0x0d, 0xb8][..]
420 ))
421 ),
422 "IpAddress(2001::db8)"
423 );
424
425 assert_eq!(
426 format!(
427 "{:?}",
428 GeneralName::IpAddress(untrusted::Input::from(&[1, 2, 3, 4, 5, 6][..]))
429 ),
430 "IpAddress([invalid: 01, 02, 03, 04, 05, 06])"
431 );
432
433 assert_eq!(
434 format!(
435 "{:?}",
436 GeneralName::UniformResourceIdentifier(untrusted::Input::from(
437 b"https://example.com"
438 ))
439 ),
440 "UniformResourceIdentifier(\"https://example.com\")"
441 );
442
443 assert_eq!(
444 format!("{:?}", GeneralName::Unsupported(0x66)),
445 "Unsupported(0x66)"
446 );
447 }
448
449 #[test]
450 fn name_iter_end_after_error() {
451 let input = untrusted::Input::from(&[0x30]);
452 let mut iter = NameIterator::new(Some(input));
453 assert_eq!(iter.next().unwrap().unwrap_err(), Error::BadDer);
454 assert!(iter.next().is_none());
455 }
456}