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