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::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            // 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            // Avoid having a catch-all branch here which might fail open on new variants
130            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                    // Reject any uses of directory name constraints; we don't implement this.
142                    //
143                    // Rejecting everything technically confirms to RFC5280:
144                    //
145                    //   "If a name constraints extension that is marked as critical imposes constraints
146                    //    on a particular name form, and an instance of that name form appears in the
147                    //    subject field or subjectAltName extension of a subsequent certificate, then
148                    //    the application MUST either process the constraint or _reject the certificate_."
149                    //
150                    // TODO: rustls/webpki#19
151                    //
152                    // Rejection is achieved by not matching any PermittedSubtrees, and matching all
153                    // ExcludedSubtrees.
154                    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                // We currently don't support URI constraints -- fail closed for now.
167                //
168                // Rejection is achieved by not matching any PermittedSubtrees, and matching all
169                // ExcludedSubtrees.
170                (
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                // RFC 4280 says "If a name constraints extension that is marked as
180                // critical imposes constraints on a particular name form, and an
181                // instance of that name form appears in the subject field or
182                // subjectAltName extension of a subsequent certificate, then the
183                // application MUST either process the constraint or reject the
184                // certificate." Later, the CABForum agreed to support non-critical
185                // constraints, so it is important to reject the cert without
186                // considering whether the name constraint it critical.
187                (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            // If there was any entry of the given type in permittedSubtrees, then
215            // it required that at least one of them must match. Since none of them
216            // did, we have a failure.
217            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        // https://bugzilla.mozilla.org/show_bug.cgi?id=1143085: An empty
248        // subjectAltName is not legal, but some certificates have an empty
249        // subjectAltName. Since we don't support CN-IDs, the certificate
250        // will be rejected either way, but checking `at_end` before
251        // attempting to parse the first entry allows us to return a better
252        // error code.
253
254        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        // Make sure we don't yield any items after this error.
265        self.subject_alt_name = None;
266        Some(Err(err))
267    }
268}
269
270// It is *not* valid to derive `Eq`, `PartialEq, etc. for this type. In
271// particular, for the types of `GeneralName`s that we don't understand, we
272// don't even store the value. Also, the meaning of a `GeneralName` in a name
273// constraint is different than the meaning of the identically-represented
274// `GeneralName` in other contexts.
275#[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    // The value is the `tag & ~(der::CONTEXT_SPECIFIC | der::CONSTRUCTED)` so
283    // that the name constraint checking matches tags regardless of whether
284    // those bits are set.
285    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}