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}
298