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::{
16    dns_name::{self, DnsNameRef},
17    ip_address::{self, IpAddrRef},
18    name::SubjectNameRef,
19};
20use crate::{
21    cert::{Cert, EndEntityOrCa},
22    der,
23    verify_cert::Budget,
24    Error,
25};
26#[cfg(feature = "alloc")]
27use {
28    alloc::vec::Vec,
29    dns_name::{GeneralDnsNameRef, WildcardDnsNameRef},
30};
31
32pub(crate) fn verify_cert_dns_name(
33    cert: &crate::EndEntityCert,
34    dns_name: DnsNameRef,
35) -> Result<(), Error> {
36    let cert = cert.inner();
37    let dns_name = untrusted::Input::from(dns_name.as_ref().as_bytes());
38    iterate_names(
39        Some(cert.subject),
40        cert.subject_alt_name,
41        Err(Error::CertNotValidForName),
42        &mut |name| {
43            if let GeneralName::DnsName(presented_id) = name {
44                match dns_name::presented_id_matches_reference_id(presented_id, dns_name) {
45                    Ok(true) => return NameIteration::Stop(Ok(())),
46                    Ok(false) | Err(Error::MalformedDnsIdentifier) => (),
47                    Err(e) => return NameIteration::Stop(Err(e)),
48                }
49            }
50            NameIteration::KeepGoing
51        },
52    )
53}
54
55pub(crate) fn verify_cert_subject_name(
56    cert: &crate::EndEntityCert,
57    subject_name: SubjectNameRef,
58) -> Result<(), Error> {
59    let ip_address = match subject_name {
60        SubjectNameRef::DnsName(dns_name) => return verify_cert_dns_name(cert, dns_name),
61        SubjectNameRef::IpAddress(IpAddrRef::V4(_, ref ip_address_octets)) => {
62            untrusted::Input::from(ip_address_octets)
63        }
64        SubjectNameRef::IpAddress(IpAddrRef::V6(_, ref ip_address_octets)) => {
65            untrusted::Input::from(ip_address_octets)
66        }
67    };
68
69    iterate_names(
70        // IP addresses are not compared against the subject field;
71        // only against Subject Alternative Names.
72        None,
73        cert.inner().subject_alt_name,
74        Err(Error::CertNotValidForName),
75        &mut |name| {
76            if let GeneralName::IpAddress(presented_id) = name {
77                match ip_address::presented_id_matches_reference_id(presented_id, ip_address) {
78                    true => return NameIteration::Stop(Ok(())),
79                    false => (),
80                }
81            }
82            NameIteration::KeepGoing
83        },
84    )
85}
86
87// https://tools.ietf.org/html/rfc5280#section-4.2.1.10
88pub(crate) fn check_name_constraints(
89    input: Option<&mut untrusted::Reader>,
90    subordinate_certs: &Cert,
91    budget: &mut Budget,
92) -> Result<(), Error> {
93    let input = match input {
94        Some(input) => input,
95        None => {
96            return Ok(());
97        }
98    };
99
100    fn parse_subtrees<'b>(
101        inner: &mut untrusted::Reader<'b>,
102        subtrees_tag: der::Tag,
103    ) -> Result<Option<untrusted::Input<'b>>, Error> {
104        if !inner.peek(subtrees_tag.into()) {
105            return Ok(None);
106        }
107        der::expect_tag_and_get_value(inner, subtrees_tag).map(Some)
108    }
109
110    let permitted_subtrees = parse_subtrees(input, der::Tag::ContextSpecificConstructed0)?;
111    let excluded_subtrees = parse_subtrees(input, der::Tag::ContextSpecificConstructed1)?;
112
113    let mut child = subordinate_certs;
114    loop {
115        iterate_names(
116            Some(child.subject),
117            child.subject_alt_name,
118            Ok(()),
119            &mut |name| {
120                check_presented_id_conforms_to_constraints(
121                    name,
122                    permitted_subtrees,
123                    excluded_subtrees,
124                    budget,
125                )
126            },
127        )?;
128
129        child = match child.ee_or_ca {
130            EndEntityOrCa::Ca(child_cert) => child_cert,
131            EndEntityOrCa::EndEntity => {
132                break;
133            }
134        };
135    }
136
137    Ok(())
138}
139
140fn check_presented_id_conforms_to_constraints(
141    name: GeneralName,
142    permitted_subtrees: Option<untrusted::Input>,
143    excluded_subtrees: Option<untrusted::Input>,
144    budget: &mut Budget,
145) -> NameIteration {
146    match check_presented_id_conforms_to_constraints_in_subtree(
147        name,
148        Subtrees::PermittedSubtrees,
149        permitted_subtrees,
150        budget,
151    ) {
152        stop @ NameIteration::Stop(..) => {
153            return stop;
154        }
155        NameIteration::KeepGoing => (),
156    };
157
158    check_presented_id_conforms_to_constraints_in_subtree(
159        name,
160        Subtrees::ExcludedSubtrees,
161        excluded_subtrees,
162        budget,
163    )
164}
165
166#[derive(Clone, Copy)]
167enum Subtrees {
168    PermittedSubtrees,
169    ExcludedSubtrees,
170}
171
172fn check_presented_id_conforms_to_constraints_in_subtree(
173    name: GeneralName,
174    subtrees: Subtrees,
175    constraints: Option<untrusted::Input>,
176    budget: &mut Budget,
177) -> NameIteration {
178    let mut constraints = match constraints {
179        Some(constraints) => untrusted::Reader::new(constraints),
180        None => {
181            return NameIteration::KeepGoing;
182        }
183    };
184
185    let mut has_permitted_subtrees_match = false;
186    let mut has_permitted_subtrees_mismatch = false;
187
188    while !constraints.at_end() {
189        if let Err(e) = budget.consume_name_constraint_comparison() {
190            return NameIteration::Stop(Err(e));
191        }
192
193        // http://tools.ietf.org/html/rfc5280#section-4.2.1.10: "Within this
194        // profile, the minimum and maximum fields are not used with any name
195        // forms, thus, the minimum MUST be zero, and maximum MUST be absent."
196        //
197        // Since the default value isn't allowed to be encoded according to the
198        // DER encoding rules for DEFAULT, this is equivalent to saying that
199        // neither minimum or maximum must be encoded.
200        fn general_subtree<'b>(
201            input: &mut untrusted::Reader<'b>,
202        ) -> Result<GeneralName<'b>, Error> {
203            let general_subtree = der::expect_tag_and_get_value(input, der::Tag::Sequence)?;
204            general_subtree.read_all(Error::BadDer, GeneralName::from_der)
205        }
206
207        let base = match general_subtree(&mut constraints) {
208            Ok(base) => base,
209            Err(err) => {
210                return NameIteration::Stop(Err(err));
211            }
212        };
213
214        let matches = match (name, base) {
215            (GeneralName::DnsName(name), GeneralName::DnsName(base)) => {
216                dns_name::presented_id_matches_constraint(name, base)
217            }
218
219            (GeneralName::DirectoryName(name), GeneralName::DirectoryName(base)) => Ok(
220                presented_directory_name_matches_constraint(name, base, subtrees),
221            ),
222
223            (GeneralName::IpAddress(name), GeneralName::IpAddress(base)) => {
224                ip_address::presented_id_matches_constraint(name, base)
225            }
226
227            // RFC 4280 says "If a name constraints extension that is marked as
228            // critical imposes constraints on a particular name form, and an
229            // instance of that name form appears in the subject field or
230            // subjectAltName extension of a subsequent certificate, then the
231            // application MUST either process the constraint or reject the
232            // certificate." Later, the CABForum agreed to support non-critical
233            // constraints, so it is important to reject the cert without
234            // considering whether the name constraint it critical.
235            (GeneralName::Unsupported(name_tag), GeneralName::Unsupported(base_tag))
236                if name_tag == base_tag =>
237            {
238                Err(Error::NameConstraintViolation)
239            }
240
241            _ => {
242                // mismatch between constraint and name types; continue with current
243                // name and next constraint
244                continue;
245            }
246        };
247
248        match (subtrees, matches) {
249            (Subtrees::PermittedSubtrees, Ok(true)) => {
250                has_permitted_subtrees_match = true;
251            }
252
253            (Subtrees::PermittedSubtrees, Ok(false)) => {
254                has_permitted_subtrees_mismatch = true;
255            }
256
257            (Subtrees::ExcludedSubtrees, Ok(true)) => {
258                return NameIteration::Stop(Err(Error::NameConstraintViolation));
259            }
260
261            (Subtrees::ExcludedSubtrees, Ok(false)) => (),
262
263            (_, Err(err)) => {
264                return NameIteration::Stop(Err(err));
265            }
266        }
267    }
268
269    if has_permitted_subtrees_mismatch && !has_permitted_subtrees_match {
270        // If there was any entry of the given type in permittedSubtrees, then
271        // it required that at least one of them must match. Since none of them
272        // did, we have a failure.
273        NameIteration::Stop(Err(Error::NameConstraintViolation))
274    } else {
275        NameIteration::KeepGoing
276    }
277}
278
279fn presented_directory_name_matches_constraint(
280    _name: untrusted::Input,
281    _constraint: untrusted::Input,
282    subtrees: Subtrees,
283) -> bool {
284    // Reject any uses of directory name constraints; we don't implement this.
285    //
286    // Rejecting everything technically confirms to RFC5280:
287    //
288    //   "If a name constraints extension that is marked as critical imposes constraints
289    //    on a particular name form, and an instance of that name form appears in the
290    //    subject field or subjectAltName extension of a subsequent certificate, then
291    //    the application MUST either process the constraint or _reject the certificate_."
292    //
293    // TODO: rustls/webpki#19
294    //
295    // Rejection is achieved by not matching any PermittedSubtrees, and matching all
296    // ExcludedSubtrees.
297    match subtrees {
298        Subtrees::PermittedSubtrees => false,
299        Subtrees::ExcludedSubtrees => true,
300    }
301}
302
303#[derive(Clone, Copy)]
304enum NameIteration {
305    KeepGoing,
306    Stop(Result<(), Error>),
307}
308
309fn iterate_names<'names>(
310    subject: Option<untrusted::Input<'names>>,
311    subject_alt_name: Option<untrusted::Input<'names>>,
312    result_if_never_stopped_early: Result<(), Error>,
313    f: &mut impl FnMut(GeneralName<'names>) -> NameIteration,
314) -> Result<(), Error> {
315    if let Some(subject_alt_name) = subject_alt_name {
316        let mut subject_alt_name = untrusted::Reader::new(subject_alt_name);
317        // https://bugzilla.mozilla.org/show_bug.cgi?id=1143085: An empty
318        // subjectAltName is not legal, but some certificates have an empty
319        // subjectAltName. Since we don't support CN-IDs, the certificate
320        // will be rejected either way, but checking `at_end` before
321        // attempting to parse the first entry allows us to return a better
322        // error code.
323        while !subject_alt_name.at_end() {
324            let name = GeneralName::from_der(&mut subject_alt_name)?;
325            match f(name) {
326                NameIteration::Stop(result) => {
327                    return result;
328                }
329                NameIteration::KeepGoing => (),
330            }
331        }
332    }
333
334    if let Some(subject) = subject {
335        match f(GeneralName::DirectoryName(subject)) {
336            NameIteration::Stop(result) => return result,
337            NameIteration::KeepGoing => (),
338        };
339    }
340
341    result_if_never_stopped_early
342}
343
344#[cfg(feature = "alloc")]
345pub(crate) fn list_cert_dns_names<'names>(
346    cert: &'names crate::EndEntityCert<'names>,
347) -> Result<impl Iterator<Item = GeneralDnsNameRef<'names>>, Error> {
348    let cert = &cert.inner();
349    let mut names = Vec::new();
350
351    iterate_names(
352        Some(cert.subject),
353        cert.subject_alt_name,
354        Ok(()),
355        &mut |name| {
356            if let GeneralName::DnsName(presented_id) = name {
357                let dns_name = DnsNameRef::try_from_ascii(presented_id.as_slice_less_safe())
358                    .map(GeneralDnsNameRef::DnsName)
359                    .or_else(|_| {
360                        WildcardDnsNameRef::try_from_ascii(presented_id.as_slice_less_safe())
361                            .map(GeneralDnsNameRef::Wildcard)
362                    });
363
364                // if the name could be converted to a DNS name, add it; otherwise,
365                // keep going.
366                if let Ok(name) = dns_name {
367                    names.push(name)
368                }
369            }
370            NameIteration::KeepGoing
371        },
372    )
373    .map(|_| names.into_iter())
374}
375
376// It is *not* valid to derive `Eq`, `PartialEq, etc. for this type. In
377// particular, for the types of `GeneralName`s that we don't understand, we
378// don't even store the value. Also, the meaning of a `GeneralName` in a name
379// constraint is different than the meaning of the identically-represented
380// `GeneralName` in other contexts.
381#[derive(Clone, Copy)]
382enum GeneralName<'a> {
383    DnsName(untrusted::Input<'a>),
384    DirectoryName(untrusted::Input<'a>),
385    IpAddress(untrusted::Input<'a>),
386
387    // The value is the `tag & ~(der::CONTEXT_SPECIFIC | der::CONSTRUCTED)` so
388    // that the name constraint checking matches tags regardless of whether
389    // those bits are set.
390    Unsupported(u8),
391}
392
393impl<'a> GeneralName<'a> {
394    fn from_der(input: &mut untrusted::Reader<'a>) -> Result<Self, Error> {
395        use ring::io::der::{CONSTRUCTED, CONTEXT_SPECIFIC};
396        #[allow(clippy::identity_op)]
397        const OTHER_NAME_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 0;
398        const RFC822_NAME_TAG: u8 = CONTEXT_SPECIFIC | 1;
399        const DNS_NAME_TAG: u8 = CONTEXT_SPECIFIC | 2;
400        const X400_ADDRESS_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 3;
401        const DIRECTORY_NAME_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 4;
402        const EDI_PARTY_NAME_TAG: u8 = CONTEXT_SPECIFIC | CONSTRUCTED | 5;
403        const UNIFORM_RESOURCE_IDENTIFIER_TAG: u8 = CONTEXT_SPECIFIC | 6;
404        const IP_ADDRESS_TAG: u8 = CONTEXT_SPECIFIC | 7;
405        const REGISTERED_ID_TAG: u8 = CONTEXT_SPECIFIC | 8;
406
407        let (tag, value) = der::read_tag_and_get_value(input)?;
408        Ok(match tag {
409            DNS_NAME_TAG => GeneralName::DnsName(value),
410            DIRECTORY_NAME_TAG => GeneralName::DirectoryName(value),
411            IP_ADDRESS_TAG => GeneralName::IpAddress(value),
412
413            OTHER_NAME_TAG
414            | RFC822_NAME_TAG
415            | X400_ADDRESS_TAG
416            | EDI_PARTY_NAME_TAG
417            | UNIFORM_RESOURCE_IDENTIFIER_TAG
418            | REGISTERED_ID_TAG => {
419                GeneralName::Unsupported(tag & !(CONTEXT_SPECIFIC | CONSTRUCTED))
420            }
421
422            _ => return Err(Error::BadDer),
423        })
424    }
425}