/src/boringssl/pki/name_constraints.cc
Line | Count | Source (jump to first uncovered line) |
1 | | // Copyright 2015 The Chromium Authors |
2 | | // |
3 | | // Licensed under the Apache License, Version 2.0 (the "License"); |
4 | | // you may not use this file except in compliance with the License. |
5 | | // You may obtain a copy of the License at |
6 | | // |
7 | | // https://www.apache.org/licenses/LICENSE-2.0 |
8 | | // |
9 | | // Unless required by applicable law or agreed to in writing, software |
10 | | // distributed under the License is distributed on an "AS IS" BASIS, |
11 | | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
12 | | // See the License for the specific language governing permissions and |
13 | | // limitations under the License. |
14 | | |
15 | | #include "name_constraints.h" |
16 | | |
17 | | #include <limits.h> |
18 | | |
19 | | #include <memory> |
20 | | #include <optional> |
21 | | |
22 | | #include <openssl/base.h> |
23 | | #include <openssl/bytestring.h> |
24 | | |
25 | | #include "cert_errors.h" |
26 | | #include "common_cert_errors.h" |
27 | | #include "general_names.h" |
28 | | #include "input.h" |
29 | | #include "ip_util.h" |
30 | | #include "parser.h" |
31 | | #include "string_util.h" |
32 | | #include "verify_name_match.h" |
33 | | |
34 | | BSSL_NAMESPACE_BEGIN |
35 | | |
36 | | namespace { |
37 | | |
38 | | // The name types of GeneralName that are fully supported in name constraints. |
39 | | // |
40 | | // (The other types will have the minimal checking described by RFC 5280 |
41 | | // section 4.2.1.10: If a name constraints extension that is marked as critical |
42 | | // imposes constraints on a particular name form, and an instance of |
43 | | // that name form appears in the subject field or subjectAltName |
44 | | // extension of a subsequent certificate, then the application MUST |
45 | | // either process the constraint or reject the certificate.) |
46 | | const int kSupportedNameTypes = |
47 | | GENERAL_NAME_RFC822_NAME | GENERAL_NAME_DNS_NAME | |
48 | | GENERAL_NAME_DIRECTORY_NAME | GENERAL_NAME_IP_ADDRESS; |
49 | | |
50 | | // Controls wildcard handling of DNSNameMatches. |
51 | | // If WildcardMatchType is WILDCARD_PARTIAL_MATCH "*.bar.com" is considered to |
52 | | // match the constraint "foo.bar.com". If it is WILDCARD_FULL_MATCH, "*.bar.com" |
53 | | // will match "bar.com" but not "foo.bar.com". |
54 | | enum WildcardMatchType { WILDCARD_PARTIAL_MATCH, WILDCARD_FULL_MATCH }; |
55 | | |
56 | | // Returns true if |name| falls in the subtree defined by |dns_constraint|. |
57 | | // RFC 5280 section 4.2.1.10: |
58 | | // DNS name restrictions are expressed as host.example.com. Any DNS |
59 | | // name that can be constructed by simply adding zero or more labels |
60 | | // to the left-hand side of the name satisfies the name constraint. For |
61 | | // example, www.host.example.com would satisfy the constraint but |
62 | | // host1.example.com would not. |
63 | | // |
64 | | // |wildcard_matching| controls handling of wildcard names (|name| starts with |
65 | | // "*."). Wildcard handling is not specified by RFC 5280, but certificate |
66 | | // verification allows it, name constraints must check it similarly. |
67 | | bool DNSNameMatches(std::string_view name, std::string_view dns_constraint, |
68 | 0 | WildcardMatchType wildcard_matching) { |
69 | | // Everything matches the empty DNS name constraint. |
70 | 0 | if (dns_constraint.empty()) { |
71 | 0 | return true; |
72 | 0 | } |
73 | | |
74 | | // Normalize absolute DNS names by removing the trailing dot, if any. |
75 | 0 | if (!name.empty() && *name.rbegin() == '.') { |
76 | 0 | name.remove_suffix(1); |
77 | 0 | } |
78 | 0 | if (!dns_constraint.empty() && *dns_constraint.rbegin() == '.') { |
79 | 0 | dns_constraint.remove_suffix(1); |
80 | 0 | } |
81 | | |
82 | | // Wildcard partial-match handling ("*.bar.com" matching name constraint |
83 | | // "foo.bar.com"). This only handles the case where the the dnsname and the |
84 | | // constraint match after removing the leftmost label, otherwise it is handled |
85 | | // by falling through to the check of whether the dnsname is fully within or |
86 | | // fully outside of the constraint. |
87 | 0 | if (wildcard_matching == WILDCARD_PARTIAL_MATCH && name.size() > 2 && |
88 | 0 | name[0] == '*' && name[1] == '.') { |
89 | 0 | size_t dns_constraint_dot_pos = dns_constraint.find('.'); |
90 | 0 | if (dns_constraint_dot_pos != std::string::npos) { |
91 | 0 | std::string_view dns_constraint_domain = |
92 | 0 | dns_constraint.substr(dns_constraint_dot_pos + 1); |
93 | 0 | std::string_view wildcard_domain = name.substr(2); |
94 | 0 | if (bssl::string_util::IsEqualNoCase(wildcard_domain, |
95 | 0 | dns_constraint_domain)) { |
96 | 0 | return true; |
97 | 0 | } |
98 | 0 | } |
99 | 0 | } |
100 | | |
101 | 0 | if (!bssl::string_util::EndsWithNoCase(name, dns_constraint)) { |
102 | 0 | return false; |
103 | 0 | } |
104 | | |
105 | | // Exact match. |
106 | 0 | if (name.size() == dns_constraint.size()) { |
107 | 0 | return true; |
108 | 0 | } |
109 | | // If dNSName constraint starts with a dot, only subdomains should match. |
110 | | // (e.g., "foo.bar.com" matches constraint ".bar.com", but "bar.com" doesn't.) |
111 | | // RFC 5280 is ambiguous, but this matches the behavior of other platforms. |
112 | 0 | if (!dns_constraint.empty() && dns_constraint[0] == '.') { |
113 | 0 | dns_constraint.remove_prefix(1); |
114 | 0 | } |
115 | | // Subtree match. |
116 | 0 | if (name.size() > dns_constraint.size() && |
117 | 0 | name[name.size() - dns_constraint.size() - 1] == '.') { |
118 | 0 | return true; |
119 | 0 | } |
120 | | // Trailing text matches, but not in a subtree (e.g., "foobar.com" is not a |
121 | | // match for "bar.com"). |
122 | 0 | return false; |
123 | 0 | } |
124 | | |
125 | | // Parses a GeneralSubtrees |value| and store the contents in |subtrees|. |
126 | | // The individual values stored into |subtrees| are not validated by this |
127 | | // function. |
128 | | // NOTE: |subtrees| is not pre-initialized by the function(it is expected to be |
129 | | // a default initialized object), and it will be modified regardless of the |
130 | | // return value. |
131 | | [[nodiscard]] bool ParseGeneralSubtrees(der::Input value, |
132 | | GeneralNames *subtrees, |
133 | 110 | CertErrors *errors) { |
134 | 110 | BSSL_CHECK(errors); |
135 | | |
136 | | // GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree |
137 | | // |
138 | | // GeneralSubtree ::= SEQUENCE { |
139 | | // base GeneralName, |
140 | | // minimum [0] BaseDistance DEFAULT 0, |
141 | | // maximum [1] BaseDistance OPTIONAL } |
142 | | // |
143 | | // BaseDistance ::= INTEGER (0..MAX) |
144 | 110 | der::Parser sequence_parser(value); |
145 | | // The GeneralSubtrees sequence should have at least 1 element. |
146 | 110 | if (!sequence_parser.HasMore()) { |
147 | 2 | return false; |
148 | 2 | } |
149 | 270 | while (sequence_parser.HasMore()) { |
150 | 230 | der::Parser subtree_sequence; |
151 | 230 | if (!sequence_parser.ReadSequence(&subtree_sequence)) { |
152 | 6 | return false; |
153 | 6 | } |
154 | | |
155 | 224 | der::Input raw_general_name; |
156 | 224 | if (!subtree_sequence.ReadRawTLV(&raw_general_name)) { |
157 | 3 | return false; |
158 | 3 | } |
159 | | |
160 | 221 | if (!ParseGeneralName(raw_general_name, |
161 | 221 | GeneralNames::IP_ADDRESS_AND_NETMASK, subtrees, |
162 | 221 | errors)) { |
163 | 49 | errors->AddError(kFailedParsingGeneralName); |
164 | 49 | return false; |
165 | 49 | } |
166 | | |
167 | | // RFC 5280 section 4.2.1.10: |
168 | | // Within this profile, the minimum and maximum fields are not used with any |
169 | | // name forms, thus, the minimum MUST be zero, and maximum MUST be absent. |
170 | | // However, if an application encounters a critical name constraints |
171 | | // extension that specifies other values for minimum or maximum for a name |
172 | | // form that appears in a subsequent certificate, the application MUST |
173 | | // either process these fields or reject the certificate. |
174 | | |
175 | | // Note that technically failing here isn't required: rather only need to |
176 | | // fail if a name of this type actually appears in a subsequent cert and |
177 | | // this extension was marked critical. However the minimum and maximum |
178 | | // fields appear uncommon enough that implementing that isn't useful. |
179 | 172 | if (subtree_sequence.HasMore()) { |
180 | 10 | return false; |
181 | 10 | } |
182 | 172 | } |
183 | 40 | return true; |
184 | 108 | } |
185 | | |
186 | 0 | bool IsAlphaDigit(char c) { |
187 | 0 | return (c >= '0' && c <= '9') || (c >= 'a' && c <= 'z') || |
188 | 0 | (c >= 'A' && c <= 'Z'); |
189 | 0 | } |
190 | | |
191 | | // Returns true if 'local_part' contains only characters that are valid in a |
192 | | // non-quoted mailbox local-part. Does not check any other part of the syntax |
193 | | // requirements. Does not allow whitespace. |
194 | 0 | bool IsAllowedRfc822LocalPart(std::string_view local_part) { |
195 | 0 | if (local_part.empty()) { |
196 | 0 | return false; |
197 | 0 | } |
198 | 0 | for (char c : local_part) { |
199 | 0 | if (!(IsAlphaDigit(c) || c == '!' || c == '#' || c == '$' || c == '%' || |
200 | 0 | c == '&' || c == '\'' || c == '*' || c == '+' || c == '-' || |
201 | 0 | c == '/' || c == '=' || c == '?' || c == '^' || c == '_' || |
202 | 0 | c == '`' || c == '{' || c == '|' || c == '}' || c == '~' || |
203 | 0 | c == '.')) { |
204 | 0 | return false; |
205 | 0 | } |
206 | 0 | } |
207 | 0 | return true; |
208 | 0 | } |
209 | | |
210 | | // Returns true if 'domain' contains only characters that are valid in a |
211 | | // mailbox domain. Does not check any other part of the syntax |
212 | | // requirements. Does not allow IPv6-address-literal as text IPv6 addresses are |
213 | | // non-unique. Does not allow other address literals either as how to handle |
214 | | // them with domain/subdomain matching isn't specified/possible. |
215 | 0 | bool IsAllowedRfc822Domain(std::string_view domain) { |
216 | 0 | if (domain.empty()) { |
217 | 0 | return false; |
218 | 0 | } |
219 | 0 | for (char c : domain) { |
220 | 0 | if (!(IsAlphaDigit(c) || c == '-' || c == '.')) { |
221 | 0 | return false; |
222 | 0 | } |
223 | 0 | } |
224 | 0 | return true; |
225 | 0 | } |
226 | | |
227 | | enum class Rfc822NameMatchType { kPermitted, kExcluded }; |
228 | | bool Rfc822NameMatches(std::string_view local_part, std::string_view domain, |
229 | | std::string_view rfc822_constraint, |
230 | | Rfc822NameMatchType match_type, |
231 | 0 | bool case_insensitive_local_part) { |
232 | | // In case of parsing errors, return a value that will cause the name to not |
233 | | // be permitted. |
234 | 0 | const bool error_value = |
235 | 0 | match_type == Rfc822NameMatchType::kPermitted ? false : true; |
236 | |
|
237 | 0 | std::vector<std::string_view> constraint_components = |
238 | 0 | bssl::string_util::SplitString(rfc822_constraint, '@'); |
239 | 0 | std::string_view constraint_local_part; |
240 | 0 | std::string_view constraint_domain; |
241 | 0 | if (constraint_components.size() == 1) { |
242 | 0 | constraint_domain = constraint_components[0]; |
243 | 0 | } else if (constraint_components.size() == 2) { |
244 | 0 | constraint_local_part = constraint_components[0]; |
245 | 0 | if (!IsAllowedRfc822LocalPart(constraint_local_part)) { |
246 | 0 | return error_value; |
247 | 0 | } |
248 | 0 | constraint_domain = constraint_components[1]; |
249 | 0 | } else { |
250 | | // If we did the full parsing then it is possible for a @ to be in a quoted |
251 | | // local-part of the name, but we don't do that, so just error if @ appears |
252 | | // more than once. |
253 | 0 | return error_value; |
254 | 0 | } |
255 | 0 | if (!IsAllowedRfc822Domain(constraint_domain)) { |
256 | 0 | return error_value; |
257 | 0 | } |
258 | | |
259 | | // RFC 5280 section 4.2.1.10: |
260 | | // To indicate a particular mailbox, the constraint is the complete mail |
261 | | // address. For example, "root@example.com" indicates the root mailbox on |
262 | | // the host "example.com". |
263 | 0 | if (!constraint_local_part.empty()) { |
264 | 0 | return (case_insensitive_local_part |
265 | 0 | ? string_util::IsEqualNoCase(local_part, constraint_local_part) |
266 | 0 | : local_part == constraint_local_part) && |
267 | 0 | string_util::IsEqualNoCase(domain, constraint_domain); |
268 | 0 | } |
269 | | |
270 | | // RFC 5280 section 4.2.1.10: |
271 | | // To specify any address within a domain, the constraint is specified with a |
272 | | // leading period (as with URIs). For example, ".example.com" indicates all |
273 | | // the Internet mail addresses in the domain "example.com", but not Internet |
274 | | // mail addresses on the host "example.com". |
275 | 0 | if (!constraint_domain.empty() && constraint_domain[0] == '.') { |
276 | 0 | return string_util::EndsWithNoCase(domain, constraint_domain); |
277 | 0 | } |
278 | | |
279 | | // RFC 5280 section 4.2.1.10: |
280 | | // To indicate all Internet mail addresses on a particular host, the |
281 | | // constraint is specified as the host name. For example, the constraint |
282 | | // "example.com" is satisfied by any mail address at the host "example.com". |
283 | 0 | return string_util::IsEqualNoCase(domain, constraint_domain); |
284 | 0 | } |
285 | | |
286 | | } // namespace |
287 | | |
288 | 102 | NameConstraints::~NameConstraints() = default; |
289 | | |
290 | | // static |
291 | | std::unique_ptr<NameConstraints> NameConstraints::Create( |
292 | 102 | der::Input extension_value, bool is_critical, CertErrors *errors) { |
293 | 102 | BSSL_CHECK(errors); |
294 | | |
295 | 102 | auto name_constraints = std::make_unique<NameConstraints>(); |
296 | 102 | if (!name_constraints->Parse(extension_value, is_critical, errors)) { |
297 | 99 | return nullptr; |
298 | 99 | } |
299 | 3 | return name_constraints; |
300 | 102 | } |
301 | | |
302 | | std::unique_ptr<NameConstraints> NameConstraints::CreateFromPermittedSubtrees( |
303 | 0 | GeneralNames permitted_subtrees) { |
304 | 0 | auto name_constraints = std::make_unique<NameConstraints>(); |
305 | |
|
306 | 0 | name_constraints->constrained_name_types_ = |
307 | 0 | permitted_subtrees.present_name_types; |
308 | 0 | name_constraints->permitted_subtrees_ = std::move(permitted_subtrees); |
309 | |
|
310 | 0 | return name_constraints; |
311 | 0 | } |
312 | | |
313 | | bool NameConstraints::Parse(der::Input extension_value, bool is_critical, |
314 | 102 | CertErrors *errors) { |
315 | 102 | BSSL_CHECK(errors); |
316 | | |
317 | 102 | der::Parser extension_parser(extension_value); |
318 | 102 | der::Parser sequence_parser; |
319 | | |
320 | | // NameConstraints ::= SEQUENCE { |
321 | | // permittedSubtrees [0] GeneralSubtrees OPTIONAL, |
322 | | // excludedSubtrees [1] GeneralSubtrees OPTIONAL } |
323 | 102 | if (!extension_parser.ReadSequence(&sequence_parser)) { |
324 | 5 | return false; |
325 | 5 | } |
326 | 97 | if (extension_parser.HasMore()) { |
327 | 1 | return false; |
328 | 1 | } |
329 | | |
330 | 96 | std::optional<der::Input> permitted_subtrees_value; |
331 | 96 | if (!sequence_parser.ReadOptionalTag( |
332 | 96 | CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 0, |
333 | 96 | &permitted_subtrees_value)) { |
334 | 1 | return false; |
335 | 1 | } |
336 | 95 | if (permitted_subtrees_value && |
337 | 95 | !ParseGeneralSubtrees(permitted_subtrees_value.value(), |
338 | 56 | &permitted_subtrees_, errors)) { |
339 | 32 | return false; |
340 | 32 | } |
341 | 63 | constrained_name_types_ |= |
342 | 63 | permitted_subtrees_.present_name_types & |
343 | 63 | (is_critical ? GENERAL_NAME_ALL_TYPES : kSupportedNameTypes); |
344 | | |
345 | 63 | std::optional<der::Input> excluded_subtrees_value; |
346 | 63 | if (!sequence_parser.ReadOptionalTag( |
347 | 63 | CBS_ASN1_CONTEXT_SPECIFIC | CBS_ASN1_CONSTRUCTED | 1, |
348 | 63 | &excluded_subtrees_value)) { |
349 | 3 | return false; |
350 | 3 | } |
351 | 60 | if (excluded_subtrees_value && |
352 | 60 | !ParseGeneralSubtrees(excluded_subtrees_value.value(), |
353 | 54 | &excluded_subtrees_, errors)) { |
354 | 38 | return false; |
355 | 38 | } |
356 | 22 | constrained_name_types_ |= |
357 | 22 | excluded_subtrees_.present_name_types & |
358 | 22 | (is_critical ? GENERAL_NAME_ALL_TYPES : kSupportedNameTypes); |
359 | | |
360 | | // RFC 5280 section 4.2.1.10: |
361 | | // Conforming CAs MUST NOT issue certificates where name constraints is an |
362 | | // empty sequence. That is, either the permittedSubtrees field or the |
363 | | // excludedSubtrees MUST be present. |
364 | 22 | if (!permitted_subtrees_value && !excluded_subtrees_value) { |
365 | 3 | return false; |
366 | 3 | } |
367 | | |
368 | 19 | if (sequence_parser.HasMore()) { |
369 | 16 | return false; |
370 | 16 | } |
371 | | |
372 | 3 | return true; |
373 | 19 | } |
374 | | |
375 | | void NameConstraints::IsPermittedCert(der::Input subject_rdn_sequence, |
376 | | const GeneralNames *subject_alt_names, |
377 | 0 | CertErrors *errors) const { |
378 | | // Checking NameConstraints is O(number_of_names * number_of_constraints). |
379 | | // Impose a hard limit to mitigate the use of name constraints as a DoS |
380 | | // mechanism. This mimics the similar check in BoringSSL x509/v_ncons.c |
381 | | // TODO(bbe): make both name constraint mechanisms subquadratic and remove |
382 | | // this check. |
383 | |
|
384 | 0 | const size_t kMaxChecks = 1048576; // 1 << 20 |
385 | | |
386 | | // Names all come from a certificate, which is bound by size_t, so adding them |
387 | | // up can not overflow a size_t. |
388 | 0 | size_t name_count = 0; |
389 | | // Constraints all come from a certificate, which is bound by a size_t, so |
390 | | // adding them up can not overflow a size_t. |
391 | 0 | size_t constraint_count = 0; |
392 | 0 | if (subject_alt_names) { |
393 | 0 | name_count = subject_alt_names->rfc822_names.size() + |
394 | 0 | subject_alt_names->dns_names.size() + |
395 | 0 | subject_alt_names->directory_names.size() + |
396 | 0 | subject_alt_names->ip_addresses.size(); |
397 | 0 | constraint_count = excluded_subtrees_.rfc822_names.size() + |
398 | 0 | permitted_subtrees_.rfc822_names.size() + |
399 | 0 | excluded_subtrees_.dns_names.size() + |
400 | 0 | permitted_subtrees_.dns_names.size() + |
401 | 0 | excluded_subtrees_.directory_names.size() + |
402 | 0 | permitted_subtrees_.directory_names.size() + |
403 | 0 | excluded_subtrees_.ip_address_ranges.size() + |
404 | 0 | permitted_subtrees_.ip_address_ranges.size(); |
405 | 0 | } else { |
406 | 0 | constraint_count += excluded_subtrees_.directory_names.size() + |
407 | 0 | permitted_subtrees_.directory_names.size(); |
408 | 0 | name_count = subject_rdn_sequence.size(); |
409 | 0 | } |
410 | | // Upper bound the number of possible checks, checking for overflow. |
411 | 0 | size_t check_count = constraint_count * name_count; |
412 | 0 | if ((constraint_count > 0 && check_count / constraint_count != name_count) || |
413 | 0 | check_count > kMaxChecks) { |
414 | 0 | errors->AddError(cert_errors::kTooManyNameConstraintChecks); |
415 | 0 | return; |
416 | 0 | } |
417 | | |
418 | 0 | std::vector<std::string> subject_email_addresses_to_check; |
419 | 0 | if (!subject_alt_names && |
420 | 0 | (constrained_name_types() & GENERAL_NAME_RFC822_NAME)) { |
421 | 0 | if (!FindEmailAddressesInName(subject_rdn_sequence, |
422 | 0 | &subject_email_addresses_to_check)) { |
423 | | // Error parsing |subject_rdn_sequence|. |
424 | 0 | errors->AddError(cert_errors::kNotPermittedByNameConstraints); |
425 | 0 | return; |
426 | 0 | } |
427 | 0 | } |
428 | | |
429 | | // Subject Alternative Name handling: |
430 | | // |
431 | | // RFC 5280 section 4.2.1.6: |
432 | | // id-ce-subjectAltName OBJECT IDENTIFIER ::= { id-ce 17 } |
433 | | // |
434 | | // SubjectAltName ::= GeneralNames |
435 | | // |
436 | | // GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName |
437 | | |
438 | 0 | if (subject_alt_names) { |
439 | | // Check unsupported name types: |
440 | | // constrained_name_types() for the unsupported types will only be true if |
441 | | // that type of name was present in a name constraint that was marked |
442 | | // critical. |
443 | | // |
444 | | // RFC 5280 section 4.2.1.10: |
445 | | // If a name constraints extension that is marked as critical |
446 | | // imposes constraints on a particular name form, and an instance of |
447 | | // that name form appears in the subject field or subjectAltName |
448 | | // extension of a subsequent certificate, then the application MUST |
449 | | // either process the constraint or reject the certificate. |
450 | 0 | if (constrained_name_types() & subject_alt_names->present_name_types & |
451 | 0 | ~kSupportedNameTypes) { |
452 | 0 | errors->AddError(cert_errors::kNotPermittedByNameConstraints); |
453 | 0 | return; |
454 | 0 | } |
455 | | |
456 | | // Check supported name types: |
457 | | |
458 | | // Only check rfc822 SANs if any rfc822 constraints are present, since we |
459 | | // might fail if there are email addresses we don't know how to parse but |
460 | | // are technically correct. |
461 | 0 | if (constrained_name_types() & GENERAL_NAME_RFC822_NAME) { |
462 | 0 | for (const auto &rfc822_name : subject_alt_names->rfc822_names) { |
463 | 0 | if (!IsPermittedRfc822Name( |
464 | 0 | rfc822_name, /*case_insensitive_exclude_localpart=*/false)) { |
465 | 0 | errors->AddError(cert_errors::kNotPermittedByNameConstraints); |
466 | 0 | return; |
467 | 0 | } |
468 | 0 | } |
469 | 0 | } |
470 | | |
471 | 0 | for (const auto &dns_name : subject_alt_names->dns_names) { |
472 | 0 | if (!IsPermittedDNSName(dns_name)) { |
473 | 0 | errors->AddError(cert_errors::kNotPermittedByNameConstraints); |
474 | 0 | return; |
475 | 0 | } |
476 | 0 | } |
477 | | |
478 | 0 | for (const auto &directory_name : subject_alt_names->directory_names) { |
479 | 0 | if (!IsPermittedDirectoryName(directory_name)) { |
480 | 0 | errors->AddError(cert_errors::kNotPermittedByNameConstraints); |
481 | 0 | return; |
482 | 0 | } |
483 | 0 | } |
484 | | |
485 | 0 | for (const auto &ip_address : subject_alt_names->ip_addresses) { |
486 | 0 | if (!IsPermittedIP(ip_address)) { |
487 | 0 | errors->AddError(cert_errors::kNotPermittedByNameConstraints); |
488 | 0 | return; |
489 | 0 | } |
490 | 0 | } |
491 | 0 | } |
492 | | |
493 | | // Subject handling: |
494 | | |
495 | | // RFC 5280 section 4.2.1.10: |
496 | | // Legacy implementations exist where an electronic mail address is embedded |
497 | | // in the subject distinguished name in an attribute of type emailAddress |
498 | | // (Section 4.1.2.6). When constraints are imposed on the rfc822Name name |
499 | | // form, but the certificate does not include a subject alternative name, the |
500 | | // rfc822Name constraint MUST be applied to the attribute of type emailAddress |
501 | | // in the subject distinguished name. |
502 | 0 | for (const auto &rfc822_name : subject_email_addresses_to_check) { |
503 | | // Whether local_part should be matched case-sensitive or not is somewhat |
504 | | // unclear. RFC 2821 says that it should be case-sensitive. RFC 2985 says |
505 | | // that emailAddress attributes in a Name are fully case-insensitive. |
506 | | // Some other verifier implementations always do local-part comparison |
507 | | // case-sensitive, while some always do it case-insensitive. Many but not |
508 | | // all SMTP servers interpret addresses as case-insensitive. |
509 | | // |
510 | | // Give how poorly specified this is, and the conflicting implementations |
511 | | // in the wild, this implementation will do case-insensitive match for |
512 | | // excluded names from the subject to avoid potentially allowing |
513 | | // something that wasn't expected. |
514 | 0 | if (!IsPermittedRfc822Name(rfc822_name, |
515 | 0 | /*case_insensitive_exclude_localpart=*/true)) { |
516 | 0 | errors->AddError(cert_errors::kNotPermittedByNameConstraints); |
517 | 0 | return; |
518 | 0 | } |
519 | 0 | } |
520 | | |
521 | | // RFC 5280 4.1.2.6: |
522 | | // If subject naming information is present only in the subjectAltName |
523 | | // extension (e.g., a key bound only to an email address or URI), then the |
524 | | // subject name MUST be an empty sequence and the subjectAltName extension |
525 | | // MUST be critical. |
526 | | // This code assumes that criticality condition is checked by the caller, and |
527 | | // therefore only needs to avoid the IsPermittedDirectoryName check against an |
528 | | // empty subject in such a case. |
529 | 0 | if (subject_alt_names && subject_rdn_sequence.empty()) { |
530 | 0 | return; |
531 | 0 | } |
532 | | |
533 | 0 | if (!IsPermittedDirectoryName(subject_rdn_sequence)) { |
534 | 0 | errors->AddError(cert_errors::kNotPermittedByNameConstraints); |
535 | 0 | return; |
536 | 0 | } |
537 | 0 | } |
538 | | |
539 | | bool NameConstraints::IsPermittedRfc822Name( |
540 | 0 | std::string_view name, bool case_insensitive_exclude_localpart) const { |
541 | | // RFC 5280 4.2.1.6. Subject Alternative Name |
542 | | // |
543 | | // When the subjectAltName extension contains an Internet mail address, |
544 | | // the address MUST be stored in the rfc822Name. The format of an |
545 | | // rfc822Name is a "Mailbox" as defined in Section 4.1.2 of [RFC2821]. |
546 | | // A Mailbox has the form "Local-part@Domain". Note that a Mailbox has |
547 | | // no phrase (such as a common name) before it, has no comment (text |
548 | | // surrounded in parentheses) after it, and is not surrounded by "<" and |
549 | | // ">". Rules for encoding Internet mail addresses that include |
550 | | // internationalized domain names are specified in Section 7.5. |
551 | | |
552 | | // Relevant parts from RFC 2821 & RFC 2822 |
553 | | // |
554 | | // Mailbox = Local-part "@" Domain |
555 | | // Local-part = Dot-string / Quoted-string |
556 | | // ; MAY be case-sensitive |
557 | | // |
558 | | // Dot-string = Atom *("." Atom) |
559 | | // Atom = 1*atext |
560 | | // Quoted-string = DQUOTE *qcontent DQUOTE |
561 | | // |
562 | | // |
563 | | // atext = ALPHA / DIGIT / ; Any character except controls, |
564 | | // "!" / "#" / ; SP, and specials. |
565 | | // "$" / "%" / ; Used for atoms |
566 | | // "&" / "'" / |
567 | | // "*" / "+" / |
568 | | // "-" / "/" / |
569 | | // "=" / "?" / |
570 | | // "^" / "_" / |
571 | | // "`" / "{" / |
572 | | // "|" / "}" / |
573 | | // "~" |
574 | | // |
575 | | // atom = [CFWS] 1*atext [CFWS] |
576 | | // |
577 | | // |
578 | | // qtext = NO-WS-CTL / ; Non white space controls |
579 | | // %d33 / ; The rest of the US-ASCII |
580 | | // %d35-91 / ; characters not including "\" |
581 | | // %d93-126 ; or the quote character |
582 | | // |
583 | | // quoted-pair = ("\" text) / obs-qp |
584 | | // qcontent = qtext / quoted-pair |
585 | | // |
586 | | // |
587 | | // Domain = (sub-domain 1*("." sub-domain)) / address-literal |
588 | | // sub-domain = Let-dig [Ldh-str] |
589 | | // |
590 | | // Let-dig = ALPHA / DIGIT |
591 | | // Ldh-str = *( ALPHA / DIGIT / "-" ) Let-dig |
592 | | // |
593 | | // address-literal = "[" IPv4-address-literal / |
594 | | // IPv6-address-literal / |
595 | | // General-address-literal "]" |
596 | | // ; See section 4.1.3 |
597 | | |
598 | | // However, no one actually implements all that. Known implementations just |
599 | | // do string comparisons, but that is technically incorrect. (Ex: a |
600 | | // constraint excluding |foo@example.com| should exclude a SAN of |
601 | | // |"foo"@example.com|, while a naive direct comparison will allow it.) |
602 | | // |
603 | | // We don't implement all that either, but do something a bit more fail-safe |
604 | | // by rejecting any addresses that contain characters that are not allowed in |
605 | | // the non-quoted formats. |
606 | |
|
607 | 0 | std::vector<std::string_view> name_components = |
608 | 0 | bssl::string_util::SplitString(name, '@'); |
609 | 0 | if (name_components.size() != 2) { |
610 | | // If we did the full parsing then it is possible for a @ to be in a quoted |
611 | | // local-part of the name, but we don't do that, so just fail if @ appears |
612 | | // more than once. |
613 | 0 | return false; |
614 | 0 | } |
615 | 0 | if (!IsAllowedRfc822LocalPart(name_components[0]) || |
616 | 0 | !IsAllowedRfc822Domain(name_components[1])) { |
617 | 0 | return false; |
618 | 0 | } |
619 | | |
620 | 0 | for (const auto &excluded_name : excluded_subtrees_.rfc822_names) { |
621 | 0 | if (Rfc822NameMatches(name_components[0], name_components[1], excluded_name, |
622 | 0 | Rfc822NameMatchType::kExcluded, |
623 | 0 | case_insensitive_exclude_localpart)) { |
624 | 0 | return false; |
625 | 0 | } |
626 | 0 | } |
627 | | |
628 | | // If permitted subtrees are not constrained, any name that is not excluded is |
629 | | // allowed. |
630 | 0 | if (!(permitted_subtrees_.present_name_types & GENERAL_NAME_RFC822_NAME)) { |
631 | 0 | return true; |
632 | 0 | } |
633 | | |
634 | 0 | for (const auto &permitted_name : permitted_subtrees_.rfc822_names) { |
635 | 0 | if (Rfc822NameMatches(name_components[0], name_components[1], |
636 | 0 | permitted_name, Rfc822NameMatchType::kPermitted, |
637 | 0 | /*case_insenitive_local_part=*/false)) { |
638 | 0 | return true; |
639 | 0 | } |
640 | 0 | } |
641 | | |
642 | 0 | return false; |
643 | 0 | } |
644 | | |
645 | 0 | bool NameConstraints::IsPermittedDNSName(std::string_view name) const { |
646 | 0 | for (const auto &excluded_name : excluded_subtrees_.dns_names) { |
647 | | // When matching wildcard hosts against excluded subtrees, consider it a |
648 | | // match if the constraint would match any expansion of the wildcard. Eg, |
649 | | // *.bar.com should match a constraint of foo.bar.com. |
650 | 0 | if (DNSNameMatches(name, excluded_name, WILDCARD_PARTIAL_MATCH)) { |
651 | 0 | return false; |
652 | 0 | } |
653 | 0 | } |
654 | | |
655 | | // If permitted subtrees are not constrained, any name that is not excluded is |
656 | | // allowed. |
657 | 0 | if (!(permitted_subtrees_.present_name_types & GENERAL_NAME_DNS_NAME)) { |
658 | 0 | return true; |
659 | 0 | } |
660 | | |
661 | 0 | for (const auto &permitted_name : permitted_subtrees_.dns_names) { |
662 | | // When matching wildcard hosts against permitted subtrees, consider it a |
663 | | // match only if the constraint would match all expansions of the wildcard. |
664 | | // Eg, *.bar.com should match a constraint of bar.com, but not foo.bar.com. |
665 | 0 | if (DNSNameMatches(name, permitted_name, WILDCARD_FULL_MATCH)) { |
666 | 0 | return true; |
667 | 0 | } |
668 | 0 | } |
669 | | |
670 | 0 | return false; |
671 | 0 | } |
672 | | |
673 | | bool NameConstraints::IsPermittedDirectoryName( |
674 | 0 | der::Input name_rdn_sequence) const { |
675 | 0 | for (const auto &excluded_name : excluded_subtrees_.directory_names) { |
676 | 0 | if (VerifyNameInSubtree(name_rdn_sequence, excluded_name)) { |
677 | 0 | return false; |
678 | 0 | } |
679 | 0 | } |
680 | | |
681 | | // If permitted subtrees are not constrained, any name that is not excluded is |
682 | | // allowed. |
683 | 0 | if (!(permitted_subtrees_.present_name_types & GENERAL_NAME_DIRECTORY_NAME)) { |
684 | 0 | return true; |
685 | 0 | } |
686 | | |
687 | 0 | for (const auto &permitted_name : permitted_subtrees_.directory_names) { |
688 | 0 | if (VerifyNameInSubtree(name_rdn_sequence, permitted_name)) { |
689 | 0 | return true; |
690 | 0 | } |
691 | 0 | } |
692 | | |
693 | 0 | return false; |
694 | 0 | } |
695 | | |
696 | 0 | bool NameConstraints::IsPermittedIP(der::Input ip) const { |
697 | 0 | for (const auto &excluded_ip : excluded_subtrees_.ip_address_ranges) { |
698 | 0 | if (IPAddressMatchesWithNetmask(ip, excluded_ip.first, |
699 | 0 | excluded_ip.second)) { |
700 | 0 | return false; |
701 | 0 | } |
702 | 0 | } |
703 | | |
704 | | // If permitted subtrees are not constrained, any name that is not excluded is |
705 | | // allowed. |
706 | 0 | if (!(permitted_subtrees_.present_name_types & GENERAL_NAME_IP_ADDRESS)) { |
707 | 0 | return true; |
708 | 0 | } |
709 | | |
710 | 0 | for (const auto &permitted_ip : permitted_subtrees_.ip_address_ranges) { |
711 | 0 | if (IPAddressMatchesWithNetmask(ip, permitted_ip.first, |
712 | 0 | permitted_ip.second)) { |
713 | 0 | return true; |
714 | 0 | } |
715 | 0 | } |
716 | | |
717 | 0 | return false; |
718 | 0 | } |
719 | | |
720 | | BSSL_NAMESPACE_END |