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