webpki/subject_name/
mod.rs

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) => 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}