1// Copyright 2022 Rafael Fernández López.
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")]
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
31// https://tools.ietf.org/html/rfc5280#section-4.2.1.10
32pub(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 // http://tools.ietf.org/html/rfc5280#section-4.2.1.10: "Within this
118 // profile, the minimum and maximum fields are not used with any name
119 // forms, thus, the minimum MUST be zero, and maximum MUST be absent."
120 //
121 // Since the default value isn't allowed to be encoded according to the
122 // DER encoding rules for DEFAULT, this is equivalent to saying that
123 // neither minimum or maximum must be encoded.
124 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 // Reject any uses of directory name constraints; we don't implement this.
136 //
137 // Rejecting everything technically confirms to RFC5280:
138 //
139 // "If a name constraints extension that is marked as critical imposes constraints
140 // on a particular name form, and an instance of that name form appears in the
141 // subject field or subjectAltName extension of a subsequent certificate, then
142 // the application MUST either process the constraint or _reject the certificate_."
143 //
144 // TODO: rustls/webpki#19
145 //
146 // Rejection is achieved by not matching any PermittedSubtrees, and matching all
147 // ExcludedSubtrees.
148 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 // RFC 4280 says "If a name constraints extension that is marked as
159 // critical imposes constraints on a particular name form, and an
160 // instance of that name form appears in the subject field or
161 // subjectAltName extension of a subsequent certificate, then the
162 // application MUST either process the constraint or reject the
163 // certificate." Later, the CABForum agreed to support non-critical
164 // constraints, so it is important to reject the cert without
165 // considering whether the name constraint it critical.
166 (GeneralName::Unsupported(name_tag), GeneralName::Unsupported(base_tag))
167 if name_tag == base_tag =>
168 {
169 Err(Error::NameConstraintViolation)
170 }
171
172 _ => {
173 // mismatch between constraint and name types; continue with current
174 // name and next constraint
175 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 // If there was any entry of the given type in permittedSubtrees, then
199 // it required that at least one of them must match. Since none of them
200 // did, we have a failure.
201 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 // https://bugzilla.mozilla.org/show_bug.cgi?id=1143085: An empty
232 // subjectAltName is not legal, but some certificates have an empty
233 // subjectAltName. Since we don't support CN-IDs, the certificate
234 // will be rejected either way, but checking `at_end` before
235 // attempting to parse the first entry allows us to return a better
236 // error code.
237
238 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 // Make sure we don't yield any items after this error.
249 self.subject_alt_name = None;
250 Some(Err(err))
251 }
252}
253
254// It is *not* valid to derive `Eq`, `PartialEq, etc. for this type. In
255// particular, for the types of `GeneralName`s that we don't understand, we
256// don't even store the value. Also, the meaning of a `GeneralName` in a name
257// constraint is different than the meaning of the identically-represented
258// `GeneralName` in other contexts.
259#[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 // The value is the `tag & ~(der::CONTEXT_SPECIFIC | der::CONSTRUCTED)` so
267 // that the name constraint checking matches tags regardless of whether
268 // those bits are set.
269 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: &Input<'_>) => 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: &Input<'_>) => {
316 write!(f, "IpAddress({:?})", IpAddrSlice(ip.as_slice_less_safe()))
317 }
318 GeneralName::UniformResourceIdentifier(uri: &Input<'_>) => write!(
319 f,
320 "UniformResourceIdentifier(\"{}\")",
321 String::from_utf8_lossy(uri.as_slice_less_safe())
322 ),
323 GeneralName::Unsupported(tag: &u8) => 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}
457