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::dns_name::{self, IdRole}; |
16 | use super::ip_address; |
17 | use crate::der::{self, FromDer}; |
18 | use crate::error::{DerTypeId, Error}; |
19 | use crate::verify_cert::{Budget, PathNode}; |
20 | |
21 | // https://tools.ietf.org/html/rfc5280#section-4.2.1.10 |
22 | pub(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 | |
69 | fn 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)] |
189 | enum Subtrees { |
190 | PermittedSubtrees, |
191 | ExcludedSubtrees, |
192 | } |
193 | |
194 | pub(crate) struct NameIterator<'a> { |
195 | subject_alt_name: Option<untrusted::Reader<'a>>, |
196 | subject_directory_name: Option<untrusted::Input<'a>>, |
197 | } |
198 | |
199 | impl<'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 | |
213 | impl<'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)] |
254 | pub(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 | |
266 | impl<'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 | |