webpki/subject_name/
verify.rs

1// Copyright 2015 Brian Smith.
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
15use super::dns_name::{self, IdRole};
16use super::ip_address;
17use crate::der::{self, FromDer};
18use crate::error::{DerTypeId, Error};
19use crate::verify_cert::{Budget, PathNode};
20
21// https://tools.ietf.org/html/rfc5280#section-4.2.1.10
22pub(crate) fn check_name_constraints(
23    constraints: Option<&mut untrusted::Reader<'_>>,
24    path: &PathNode<'_>,
25    budget: &mut Budget,
26) -> Result<(), Error> {
27    let constraints = match constraints {
28        Some(input) => input,
29        None => return Ok(()),
30    };
31
32    fn parse_subtrees<'b>(
33        inner: &mut untrusted::Reader<'b>,
34        subtrees_tag: der::Tag,
35    ) -> Result<Option<untrusted::Input<'b>>, Error> {
36        if !inner.peek(subtrees_tag.into()) {
37            return Ok(None);
38        }
39        der::expect_tag(inner, subtrees_tag).map(Some)
40    }
41
42    let permitted_subtrees = parse_subtrees(constraints, der::Tag::ContextSpecificConstructed0)?;
43    let excluded_subtrees = parse_subtrees(constraints, der::Tag::ContextSpecificConstructed1)?;
44
45    for path in path.iter() {
46        let result = NameIterator::new(Some(path.cert.subject), path.cert.subject_alt_name)
47            .find_map(|result| {
48                let name = match result {
49                    Ok(name) => name,
50                    Err(err) => return Some(Err(err)),
51                };
52
53                check_presented_id_conforms_to_constraints(
54                    name,
55                    permitted_subtrees,
56                    excluded_subtrees,
57                    budget,
58                )
59            });
60
61        if let Some(Err(err)) = result {
62            return Err(err);
63        }
64    }
65
66    Ok(())
67}
68
69fn check_presented_id_conforms_to_constraints(
70    name: GeneralName<'_>,
71    permitted_subtrees: Option<untrusted::Input<'_>>,
72    excluded_subtrees: Option<untrusted::Input<'_>>,
73    budget: &mut Budget,
74) -> Option<Result<(), Error>> {
75    let subtrees = [
76        (Subtrees::PermittedSubtrees, permitted_subtrees),
77        (Subtrees::ExcludedSubtrees, excluded_subtrees),
78    ];
79
80    fn general_subtree<'b>(input: &mut untrusted::Reader<'b>) -> Result<GeneralName<'b>, Error> {
81        der::read_all(der::expect_tag(input, der::Tag::Sequence)?)
82    }
83
84    for (subtrees, constraints) in subtrees {
85        let mut constraints = match constraints {
86            Some(constraints) => untrusted::Reader::new(constraints),
87            None => continue,
88        };
89
90        let mut has_permitted_subtrees_match = false;
91        let mut has_permitted_subtrees_mismatch = false;
92        while !constraints.at_end() {
93            if let Err(e) = budget.consume_name_constraint_comparison() {
94                return Some(Err(e));
95            }
96
97            // http://tools.ietf.org/html/rfc5280#section-4.2.1.10: "Within this
98            // profile, the minimum and maximum fields are not used with any name
99            // forms, thus, the minimum MUST be zero, and maximum MUST be absent."
100            //
101            // Since the default value isn't allowed to be encoded according to the
102            // DER encoding rules for DEFAULT, this is equivalent to saying that
103            // neither minimum or maximum must be encoded.
104            let base = match general_subtree(&mut constraints) {
105                Ok(base) => base,
106                Err(err) => return Some(Err(err)),
107            };
108
109            let matches = match (name, base) {
110                (GeneralName::DnsName(name), GeneralName::DnsName(base)) => {
111                    dns_name::presented_id_matches_reference_id(name, IdRole::NameConstraint, base)
112                }
113
114                (GeneralName::DirectoryName, GeneralName::DirectoryName) => Ok(
115                    // Reject any uses of directory name constraints; we don't implement this.
116                    //
117                    // Rejecting everything technically confirms to RFC5280:
118                    //
119                    //   "If a name constraints extension that is marked as critical imposes constraints
120                    //    on a particular name form, and an instance of that name form appears in the
121                    //    subject field or subjectAltName extension of a subsequent certificate, then
122                    //    the application MUST either process the constraint or _reject the certificate_."
123                    //
124                    // TODO: rustls/webpki#19
125                    //
126                    // Rejection is achieved by not matching any PermittedSubtrees, and matching all
127                    // ExcludedSubtrees.
128                    match subtrees {
129                        Subtrees::PermittedSubtrees => false,
130                        Subtrees::ExcludedSubtrees => true,
131                    },
132                ),
133
134                (GeneralName::IpAddress(name), GeneralName::IpAddress(base)) => {
135                    ip_address::presented_id_matches_constraint(name, base)
136                }
137
138                // RFC 4280 says "If a name constraints extension that is marked as
139                // critical imposes constraints on a particular name form, and an
140                // instance of that name form appears in the subject field or
141                // subjectAltName extension of a subsequent certificate, then the
142                // application MUST either process the constraint or reject the
143                // certificate." Later, the CABForum agreed to support non-critical
144                // constraints, so it is important to reject the cert without
145                // considering whether the name constraint it critical.
146                (GeneralName::Unsupported(name_tag), GeneralName::Unsupported(base_tag))
147                    if name_tag == base_tag =>
148                {
149                    Err(Error::NameConstraintViolation)
150                }
151
152                _ => {
153                    // mismatch between constraint and name types; continue with current
154                    // name and next constraint
155                    continue;
156                }
157            };
158
159            match (subtrees, matches) {
160                (Subtrees::PermittedSubtrees, Ok(true)) => {
161                    has_permitted_subtrees_match = true;
162                }
163
164                (Subtrees::PermittedSubtrees, Ok(false)) => {
165                    has_permitted_subtrees_mismatch = true;
166                }
167
168                (Subtrees::ExcludedSubtrees, Ok(true)) => {
169                    return Some(Err(Error::NameConstraintViolation));
170                }
171
172                (Subtrees::ExcludedSubtrees, Ok(false)) => (),
173                (_, Err(err)) => return Some(Err(err)),
174            }
175        }
176
177        if has_permitted_subtrees_mismatch && !has_permitted_subtrees_match {
178            // If there was any entry of the given type in permittedSubtrees, then
179            // it required that at least one of them must match. Since none of them
180            // did, we have a failure.
181            return Some(Err(Error::NameConstraintViolation));
182        }
183    }
184
185    None
186}
187
188#[derive(Clone, Copy)]
189enum Subtrees {
190    PermittedSubtrees,
191    ExcludedSubtrees,
192}
193
194pub(crate) struct NameIterator<'a> {
195    subject_alt_name: Option<untrusted::Reader<'a>>,
196    subject_directory_name: Option<untrusted::Input<'a>>,
197}
198
199impl<'a> NameIterator<'a> {
200    pub(crate) fn new(
201        subject: Option<untrusted::Input<'a>>,
202        subject_alt_name: Option<untrusted::Input<'a>>,
203    ) -> Self {
204        NameIterator {
205            subject_alt_name: subject_alt_name.map(untrusted::Reader::new),
206
207            // If `subject` is present, we always consider it as a `DirectoryName`.
208            subject_directory_name: subject,
209        }
210    }
211}
212
213impl<'a> Iterator for NameIterator<'a> {
214    type Item = Result<GeneralName<'a>, Error>;
215
216    fn next(&mut self) -> Option<Self::Item> {
217        if let Some(subject_alt_name) = &mut self.subject_alt_name {
218            // https://bugzilla.mozilla.org/show_bug.cgi?id=1143085: An empty
219            // subjectAltName is not legal, but some certificates have an empty
220            // subjectAltName. Since we don't support CN-IDs, the certificate
221            // will be rejected either way, but checking `at_end` before
222            // attempting to parse the first entry allows us to return a better
223            // error code.
224
225            if !subject_alt_name.at_end() {
226                let err = match GeneralName::from_der(subject_alt_name) {
227                    Ok(name) => return Some(Ok(name)),
228                    Err(err) => err,
229                };
230
231                // Make sure we don't yield any items after this error.
232                self.subject_alt_name = None;
233                self.subject_directory_name = None;
234                return Some(Err(err));
235            } else {
236                self.subject_alt_name = None;
237            }
238        }
239
240        if self.subject_directory_name.take().is_some() {
241            return Some(Ok(GeneralName::DirectoryName));
242        }
243
244        None
245    }
246}
247
248// It is *not* valid to derive `Eq`, `PartialEq, etc. for this type. In
249// particular, for the types of `GeneralName`s that we don't understand, we
250// don't even store the value. Also, the meaning of a `GeneralName` in a name
251// constraint is different than the meaning of the identically-represented
252// `GeneralName` in other contexts.
253#[derive(Clone, Copy)]
254pub(crate) enum GeneralName<'a> {
255    DnsName(untrusted::Input<'a>),
256    DirectoryName,
257    IpAddress(untrusted::Input<'a>),
258    UniformResourceIdentifier(untrusted::Input<'a>),
259
260    // The value is the `tag & ~(der::CONTEXT_SPECIFIC | der::CONSTRUCTED)` so
261    // that the name constraint checking matches tags regardless of whether
262    // those bits are set.
263    Unsupported(u8),
264}
265
266impl<'a> FromDer<'a> for GeneralName<'a> {
267    fn from_der(reader: &mut untrusted::Reader<'a>) -> Result<Self, Error> {
268        use der::{CONSTRUCTED, CONTEXT_SPECIFIC};
269        use GeneralName::*;
270
271        #[allow(clippy::identity_op)]
272        const OTHER_NAME_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 0;
273        const RFC822_NAME_TAG: u8 = CONTEXT_SPECIFIC | 1;
274        const DNS_NAME_TAG: u8 = CONTEXT_SPECIFIC | 2;
275        const X400_ADDRESS_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 3;
276        const DIRECTORY_NAME_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 4;
277        const EDI_PARTY_NAME_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 5;
278        const UNIFORM_RESOURCE_IDENTIFIER_TAG: u8 = CONTEXT_SPECIFIC | 6;
279        const IP_ADDRESS_TAG: u8 = CONTEXT_SPECIFIC | 7;
280        const REGISTERED_ID_TAG: u8 = CONTEXT_SPECIFIC | 8;
281
282        let (tag, value) = der::read_tag_and_get_value(reader)?;
283        Ok(match tag {
284            DNS_NAME_TAG => DnsName(value),
285            DIRECTORY_NAME_TAG => DirectoryName,
286            IP_ADDRESS_TAG => IpAddress(value),
287            UNIFORM_RESOURCE_IDENTIFIER_TAG => UniformResourceIdentifier(value),
288
289            OTHER_NAME_TAG | RFC822_NAME_TAG | X400_ADDRESS_TAG | EDI_PARTY_NAME_TAG
290            | REGISTERED_ID_TAG => Unsupported(tag & !(CONTEXT_SPECIFIC | CONSTRUCTED)),
291
292            _ => return Err(Error::BadDer),
293        })
294    }
295
296    const TYPE_ID: DerTypeId = DerTypeId::GeneralName;
297}