OLD | NEW |
---|---|
(Empty) | |
1 // Copyright 2015 The Chromium Authors. All rights reserved. | |
2 // Use of this source code is governed by a BSD-style license that can be | |
3 // found in the LICENSE file. | |
4 | |
5 #include "net/cert/internal/name_constraints.h" | |
6 | |
7 #include "base/strings/string_util.h" | |
8 #include "net/cert/internal/verify_name_match.h" | |
9 #include "net/der/input.h" | |
10 #include "net/der/parser.h" | |
11 #include "net/der/tag.h" | |
12 | |
13 namespace net { | |
14 | |
15 namespace { | |
16 | |
17 // Return true if |name| falls in the subtree defined by |name_space|. | |
18 // RFC 5280 section 4.2.1.10: | |
19 // DNS name restrictions are expressed as host.example.com. Any DNS | |
20 // name that can be constructed by simply adding zero or more labels | |
21 // to the left-hand side of the name satisfies the name constraint. For | |
22 // example, www.host.example.com would satisfy the constraint but | |
23 // host1.example.com would not. | |
24 // | |
25 // Also handles wildcard names (|name| starts with "*."). | |
26 // If |wildcard_matching| is WILDCARD_PARTIAL_MATCH "*.bar.com" is considered to | |
27 // match the constraint "foo.bar.com". If it is WILDCARD_FULL_MATCH, "*.bar.com" | |
28 // will match "bar.com" but not "foo.bar.com". | |
29 // Wildcard handling is not specified by RFC 5280, but since certificate | |
30 // verification allows it, name constraints must check it similarly. | |
31 enum WildcardMatchType { WILDCARD_PARTIAL_MATCH, WILDCARD_FULL_MATCH }; | |
eroman
2015/08/26 19:56:43
nit: newline after this
mattm
2015/08/29 01:37:18
Done.
| |
32 bool DNSNameMatches(const std::string& raw_name, | |
33 const std::string& raw_name_space, | |
34 WildcardMatchType wildcard_matching) { | |
35 base::StringPiece name(raw_name); | |
36 base::StringPiece name_space(raw_name_space); | |
37 // Normalize absolute DNS names by removing the trailing dot. | |
38 if (!name.empty() && *name.rbegin() == '.') | |
39 name.remove_suffix(1); | |
40 if (!name_space.empty() && *name_space.rbegin() == '.') | |
41 name_space.remove_suffix(1); | |
42 | |
43 // Everything matches the empty name space. | |
44 if (name_space.empty()) | |
45 return true; | |
46 | |
47 // Wildcard partial-match handling ("*.bar.com" matching name space | |
48 // "foo.bar.com"). | |
49 if (wildcard_matching == WILDCARD_PARTIAL_MATCH && name.size() > 2 && | |
50 name[0] == '*' && name[1] == '.') { | |
51 size_t name_space_dot_pos = name_space.find('.'); | |
52 if (name_space_dot_pos != std::string::npos) { | |
53 base::StringPiece name_space_domain( | |
54 name_space.begin() + name_space_dot_pos + 1, | |
55 name_space.size() - name_space_dot_pos - 1); | |
56 base::StringPiece wildcard_domain(name.begin() + 2, name.size() - 2); | |
57 if (base::EqualsCaseInsensitiveASCII(wildcard_domain, name_space_domain)) | |
58 return true; | |
59 } | |
60 } | |
61 | |
62 if (!base::EndsWith(name, name_space, base::CompareCase::INSENSITIVE_ASCII)) | |
63 return false; | |
64 // Exact match. | |
65 if (name.size() == name_space.size()) | |
66 return true; | |
67 // Subtree match. | |
68 if (name.size() >= name_space.size() + 2 && | |
69 name[name.size() - name_space.size() - 1] == '.') | |
70 return true; | |
71 // Trailing text matches, but not in a subtree (e.g., "foobar.com" is not a | |
72 // match for "bar.com"). | |
73 return false; | |
74 } | |
75 | |
76 // Return true if |ip| matches the ip/netmask pair |ip_constraint|. | |
77 // RFC 5280 section 4.2.1.10: | |
78 // The syntax of iPAddress MUST be as described in Section 4.2.1.6 with | |
79 // the following additions specifically for name constraints. For IPv4 | |
80 // addresses, the iPAddress field of GeneralName MUST contain eight (8) | |
81 // octets, encoded in the style of RFC 4632 (CIDR) to represent an | |
82 // address range [RFC4632]. For IPv6 addresses, the iPAddress field | |
83 // MUST contain 32 octets similarly encoded. For example, a name | |
84 // constraint for "class C" subnet 192.0.2.0 is represented as the | |
85 // octets C0 00 02 00 FF FF FF 00, representing the CIDR notation | |
86 // 192.0.2.0/24 (mask 255.255.255.0). | |
87 bool VerifyIPMatchesConstraint(const IPAddressNumber& ip, | |
88 const std::vector<uint8_t>& ip_constraint) { | |
89 if (ip.size() != kIPv4AddressSize && ip.size() != kIPv6AddressSize) | |
90 return false; | |
91 if (ip_constraint.size() != ip.size() * 2) | |
92 return false; | |
93 | |
94 std::vector<uint8_t>::const_iterator prefix_iter = ip_constraint.begin(); | |
95 std::vector<uint8_t>::const_iterator netmask_iter = | |
96 ip_constraint.begin() + ip_constraint.size() / 2; | |
97 IPAddressNumber::const_iterator ip_iter = ip.begin(); | |
98 for (; ip_iter != ip.end(); ++ip_iter, ++prefix_iter, ++netmask_iter) { | |
99 // This assumes that any non-masked bits of the prefix are 0, as required by | |
100 // RFC 4632 section 3.1. | |
101 if ((*ip_iter & *netmask_iter) != *prefix_iter) | |
102 return false; | |
103 } | |
104 return true; | |
105 } | |
106 | |
107 enum ParseGeneralNameUnsupportedTypeBehavior { | |
108 RECORD_UNSUPPORTED, | |
109 IGNORE_UNSUPPORTED, | |
110 }; | |
eroman
2015/08/26 19:56:43
nit: newline
mattm
2015/08/29 01:37:17
Done.
| |
111 // Parse a GeneralName value and add it to |subtrees|. | |
eroman
2015/08/26 19:56:43
nit: Parse --> Parses ?
mattm
2015/08/29 01:37:18
Done.
| |
112 // The GeneralName values are not validated here, since failing on invalid names | |
113 // here could cause an unnecessary failure if a name of that type does not | |
114 // actually appear in the cert chain. | |
115 bool ParseGeneralName( | |
116 const der::Input& input, | |
117 NameConstraints::GeneralNames* subtrees, | |
118 ParseGeneralNameUnsupportedTypeBehavior on_unsupported_types) { | |
119 der::Parser parser(input); | |
120 der::Tag tag; | |
121 der::Input value; | |
122 if (!parser.ReadTagAndValue(&tag, &value)) | |
123 return false; | |
124 if ((tag & der::kTagClassMask) != der::kTagContextSpecific) | |
125 return false; | |
126 int klass = tag & ~der::kTagClassMask; | |
eroman
2015/08/26 19:56:43
klass ?
mattm
2015/08/29 01:37:18
Since class is a reserved word. But I've changed i
| |
127 // GeneralName ::= CHOICE { | |
128 switch (klass) { | |
129 // otherName [0] OtherName, | |
130 case 0 + der::kTagConstructed: | |
131 if (on_unsupported_types != IGNORE_UNSUPPORTED) | |
eroman
2015/08/26 19:56:43
Rather than check this in every branch, can you ge
mattm
2015/08/29 01:37:18
It's not checked in every branch, only the branche
| |
132 subtrees->has_other_names = true; | |
133 break; | |
134 // rfc822Name [1] IA5String, | |
135 case 1: | |
136 if (on_unsupported_types != IGNORE_UNSUPPORTED) | |
137 subtrees->has_rfc822_names = true; | |
138 break; | |
139 // dNSName [2] IA5String, | |
140 case 2: | |
141 subtrees->dns_names.push_back(value.AsString()); | |
142 break; | |
143 // x400Address [3] ORAddress, | |
144 case 3 + der::kTagConstructed: | |
145 if (on_unsupported_types != IGNORE_UNSUPPORTED) | |
146 subtrees->has_x400_addresses = true; | |
147 break; | |
148 // directoryName [4] Name, | |
149 case 4 + der::kTagConstructed: | |
150 subtrees->directory_names.push_back(std::vector<uint8_t>( | |
151 value.UnsafeData(), value.UnsafeData() + value.Length())); | |
152 break; | |
153 // ediPartyName [5] EDIPartyName, | |
154 case 5 + der::kTagConstructed: | |
155 if (on_unsupported_types != IGNORE_UNSUPPORTED) | |
156 subtrees->has_edi_party_names = true; | |
157 break; | |
158 // uniformResourceIdentifier [6] IA5String, | |
159 case 6: | |
160 if (on_unsupported_types != IGNORE_UNSUPPORTED) | |
161 subtrees->has_uniform_resource_identifiers = true; | |
162 break; | |
163 // iPAddress [7] OCTET STRING, | |
164 case 7: | |
165 subtrees->ip_addresses.push_back(std::vector<uint8_t>( | |
eroman
2015/08/26 19:56:44
Should the ip be validated?
mattm
2015/08/29 01:37:18
Validating the ip would require a bit of extra wor
| |
166 value.UnsafeData(), value.UnsafeData() + value.Length())); | |
eroman
2015/08/26 19:56:43
der::Input() has a ToString() method. Maybe this g
| |
167 break; | |
168 // registeredID [8] OBJECT IDENTIFIER } | |
169 case 8: | |
170 if (on_unsupported_types != IGNORE_UNSUPPORTED) | |
171 subtrees->has_registered_ids = true; | |
172 break; | |
173 default: | |
174 return false; | |
175 } | |
176 return true; | |
177 } | |
178 | |
179 // Parse a GeneralSubtrees |value| and store the contents in |subtrees|. | |
180 // NOTE: |subtrees| will be modified regardless of the return. | |
181 WARN_UNUSED_RESULT bool ParseGeneralSubtrees( | |
eroman
2015/08/26 19:56:43
nit: Why WARN_UNUSED_RESULT here but not for the o
mattm
2015/08/29 01:37:18
Done.
| |
182 const der::Input& value, | |
183 NameConstraints::GeneralNames* subtrees, | |
eroman
2015/08/26 19:56:43
nit: Should the output parameter be last?
mattm
2015/08/29 01:37:18
Done.
| |
184 bool is_critical) { | |
185 // GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree | |
186 // | |
187 // GeneralSubtree ::= SEQUENCE { | |
188 // base GeneralName, | |
189 // minimum [0] BaseDistance DEFAULT 0, | |
190 // maximum [1] BaseDistance OPTIONAL } | |
191 // | |
192 // BaseDistance ::= INTEGER (0..MAX) | |
193 der::Parser sequence_parser(value); | |
194 while (sequence_parser.HasMore()) { | |
195 der::Parser subtree_sequence; | |
196 if (!sequence_parser.ReadSequence(&subtree_sequence)) | |
197 return false; | |
198 | |
199 der::Input raw_general_name; | |
200 if (!subtree_sequence.ReadRawTLV(&raw_general_name)) | |
201 return false; | |
202 | |
203 if (!ParseGeneralName(raw_general_name, subtrees, is_critical | |
204 ? RECORD_UNSUPPORTED | |
205 : IGNORE_UNSUPPORTED)) | |
206 return false; | |
207 | |
208 // RFC 5280 section 4.2.1.10: | |
209 // Within this profile, the minimum and maximum fields are not used with any | |
210 // name forms, thus, the minimum MUST be zero, and maximum MUST be absent. | |
211 // However, if an application encounters a critical name constraints | |
212 // extension that specifies other values for minimum or maximum for a name | |
213 // form that appears in a subsequent certificate, the application MUST | |
214 // either process these fields or reject the certificate. | |
215 | |
216 // TODO(mattm): Technically we don't need to fail here: rather we only need | |
217 // to fail if a name of this type actually appears in a subsequent cert and | |
218 // this extension was marked critical. | |
219 // TODO(mattm): should this allow for the case that minimum is present but | |
220 // zero? (0 is the default, so it should not be present in DER encoding..) | |
221 if (subtree_sequence.HasMore()) | |
222 return false; | |
223 } | |
224 return true; | |
225 } | |
226 | |
227 } // namespace | |
228 | |
229 NameConstraints::GeneralNames::GeneralNames() | |
230 : has_other_names(false), | |
eroman
2015/08/26 19:56:44
nit: These might be easier to write as member init
mattm
2015/08/29 01:37:17
Done.
| |
231 has_rfc822_names(false), | |
232 has_x400_addresses(false), | |
233 has_edi_party_names(false), | |
234 has_uniform_resource_identifiers(false), | |
235 has_registered_ids(false) {} | |
236 | |
237 NameConstraints::GeneralNames::~GeneralNames() {} | |
238 | |
239 NameConstraints::~NameConstraints() {} | |
240 | |
241 bool NameConstraints::Parse(const der::Input& extension_value, | |
242 bool is_critical) { | |
243 der::Parser extension_parser(extension_value); | |
244 der::Parser sequence_parser; | |
245 | |
246 // NameConstraints ::= SEQUENCE { | |
247 // permittedSubtrees [0] GeneralSubtrees OPTIONAL, | |
248 // excludedSubtrees [1] GeneralSubtrees OPTIONAL } | |
249 if (!extension_parser.ReadSequence(&sequence_parser)) | |
250 return false; | |
251 if (extension_parser.HasMore()) | |
252 return false; | |
253 | |
254 bool had_permitted_subtrees = false; | |
255 der::Input permitted_subtrees_value; | |
256 if (!sequence_parser.ReadOptionalTag(der::ContextSpecificConstructed(0), | |
257 &permitted_subtrees_value, | |
258 &had_permitted_subtrees)) | |
259 return false; | |
260 if (had_permitted_subtrees) { | |
261 if (!ParseGeneralSubtrees(permitted_subtrees_value, &permitted_subtrees_, | |
262 is_critical)) | |
263 return false; | |
264 } | |
265 | |
266 bool had_excluded_subtrees = false; | |
267 der::Input excluded_subtrees_value; | |
268 if (!sequence_parser.ReadOptionalTag(der::ContextSpecificConstructed(1), | |
269 &excluded_subtrees_value, | |
270 &had_excluded_subtrees)) | |
271 return false; | |
272 if (had_excluded_subtrees) { | |
273 if (!ParseGeneralSubtrees(excluded_subtrees_value, &excluded_subtrees_, | |
eroman
2015/08/26 19:56:43
note: The assumption is that Parse() is only calle
mattm
2015/08/29 01:37:18
noted in Parse method comment.
| |
274 is_critical)) | |
275 return false; | |
276 } | |
277 | |
278 // RFC 5280 section 4.2.1.10: | |
279 // Conforming CAs MUST NOT issue certificates where name constraints is an | |
280 // empty sequence. That is, either the permittedSubtrees field or the | |
281 // excludedSubtrees MUST be present. | |
282 if (!had_permitted_subtrees && !had_excluded_subtrees) | |
283 return false; | |
284 | |
285 if (sequence_parser.HasMore()) | |
286 return false; | |
287 | |
288 return true; | |
289 } | |
290 | |
291 bool NameConstraints::IsPermittedCert(const der::Input& subject_rdn_sequence, | |
292 const der::Input& subject_alt_name, | |
293 bool is_leaf_cert) const { | |
294 // Subject Alternative Name handling: | |
295 // | |
296 // RFC 5280 section 4.2.1.6: | |
297 // id-ce-subjectAltName OBJECT IDENTIFIER ::= { id-ce 17 } | |
298 // | |
299 // SubjectAltName ::= GeneralNames | |
300 // | |
301 // GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName | |
302 | |
303 GeneralNames san_names; | |
304 if (subject_alt_name.Length()) { | |
305 der::Parser subject_alt_name_parser(subject_alt_name); | |
306 der::Parser san_sequence_parser; | |
307 if (!subject_alt_name_parser.ReadSequence(&san_sequence_parser)) | |
308 return false; | |
309 if (subject_alt_name_parser.HasMore()) | |
310 return false; | |
311 | |
312 while (san_sequence_parser.HasMore()) { | |
313 der::Input raw_general_name; | |
314 if (!san_sequence_parser.ReadRawTLV(&raw_general_name)) | |
315 return false; | |
316 | |
317 if (!ParseGeneralName(raw_general_name, &san_names, RECORD_UNSUPPORTED)) | |
318 return false; | |
319 } | |
320 | |
321 if (san_names.has_other_names && !IsPermittedOtherName()) | |
eroman
2015/08/26 19:56:43
What is IsPermittedOtherName() testing here, that
mattm
2015/08/29 01:37:18
This is testing that if a SubjectAlternativeName c
| |
322 return false; | |
323 if (san_names.has_rfc822_names && !IsPermittedRFC822Name()) | |
324 return false; | |
325 if (san_names.has_x400_addresses && !IsPermittedX400Address()) | |
326 return false; | |
327 if (san_names.has_edi_party_names && !IsPermittedEdiPartyName()) | |
328 return false; | |
329 if (san_names.has_uniform_resource_identifiers && !IsPermittedURI()) | |
330 return false; | |
331 if (san_names.has_registered_ids && !IsPermittedRegisteredId()) | |
332 return false; | |
333 | |
334 for (const auto& dns_name : san_names.dns_names) { | |
335 if (!IsPermittedDNSName(dns_name)) | |
336 return false; | |
337 } | |
338 | |
339 for (const auto& directory_name : san_names.directory_names) { | |
340 if (!IsPermittedDirectoryName( | |
341 der::Input(directory_name.data(), directory_name.size()))) | |
342 return false; | |
343 } | |
344 | |
345 for (const auto& ip_address : san_names.ip_addresses) { | |
346 if (!IsPermittedIP(ip_address)) | |
347 return false; | |
348 } | |
349 } | |
350 | |
351 // Subject handling: | |
352 | |
353 // RFC 5280 section 4.2.1.10: | |
354 // Legacy implementations exist where an electronic mail address is embedded | |
355 // in the subject distinguished name in an attribute of type emailAddress | |
356 // (Section 4.1.2.6). When constraints are imposed on the rfc822Name name | |
357 // form, but the certificate does not include a subject alternative name, the | |
358 // rfc822Name constraint MUST be applied to the attribute of type emailAddress | |
359 // in the subject distinguished name. | |
360 if (!subject_alt_name.Length() && !IsPermittedRFC822Name() && | |
361 NameContainsEmailAddress(subject_rdn_sequence)) | |
362 return false; | |
363 | |
364 // RFC 5280 does not specify checking name constraints against subject | |
365 // CommonName, but since certificate verification allows it, name constraints | |
366 // must check it similarly. | |
367 if (is_leaf_cert && | |
368 (san_names.dns_names.empty() && san_names.ip_addresses.empty()) && | |
369 (!permitted_subtrees_.dns_names.empty() || | |
370 !excluded_subtrees_.dns_names.empty() || | |
371 !permitted_subtrees_.ip_addresses.empty() || | |
372 !excluded_subtrees_.ip_addresses.empty())) { | |
373 // Note that while the commonName is transcoded to UTF-8, no special | |
374 // handling is done of internationalized domain names. (If an | |
375 // internationalized hostname is specified in commonName, it must be in | |
376 // punycode form.) | |
377 std::string common_name = | |
378 GetNormalizedCommonNameFromName(subject_rdn_sequence); | |
379 // If commonName is not present, or is an unsupported type, or contains | |
380 // invalid data, fail out. | |
381 if (common_name.empty()) | |
382 return false; | |
383 IPAddressNumber ip_number; | |
384 bool was_ip = ParseIPLiteralToNumber(common_name, &ip_number); | |
385 // For IP addresses, Chrome only allows IPv4 in commonName (see comment in | |
386 // X509Certificate::VerifyHostname), otherwise interpret as a dNSName. | |
387 if (was_ip && ip_number.size() == kIPv4AddressSize) { | |
388 if (!IsPermittedIP(ip_number)) | |
389 return false; | |
390 } else { | |
391 if (!IsPermittedDNSName(common_name)) | |
392 return false; | |
393 } | |
394 } | |
395 | |
396 // RFC 5280 4.1.2.6: | |
397 // If subject naming information is present only in the subjectAltName | |
398 // extension (e.g., a key bound only to an email address or URI), then the | |
399 // subject name MUST be an empty sequence and the subjectAltName extension | |
400 // MUST be critical. | |
401 if (subject_alt_name.Length() && subject_rdn_sequence.Length() == 0) | |
402 return true; | |
403 | |
404 return IsPermittedDirectoryName(subject_rdn_sequence); | |
405 } | |
406 | |
407 bool NameConstraints::IsPermittedDNSName(const std::string& name) const { | |
408 if (permitted_subtrees_.dns_names.empty() && | |
409 excluded_subtrees_.dns_names.empty()) | |
410 return true; | |
411 | |
412 for (const std::string& excluded_name : excluded_subtrees_.dns_names) { | |
413 if (DNSNameMatches(name, excluded_name, WILDCARD_PARTIAL_MATCH)) | |
414 return false; | |
415 } | |
416 for (const std::string& permitted_name : permitted_subtrees_.dns_names) { | |
417 if (DNSNameMatches(name, permitted_name, WILDCARD_FULL_MATCH)) | |
418 return true; | |
419 } | |
420 | |
421 return false; | |
422 } | |
423 | |
424 bool NameConstraints::IsPermittedDirectoryName( | |
425 const der::Input& name_rdn_sequence) const { | |
426 if (permitted_subtrees_.directory_names.empty() && | |
427 excluded_subtrees_.directory_names.empty()) | |
428 return true; | |
429 | |
430 for (const auto& excluded_name : excluded_subtrees_.directory_names) { | |
431 if (VerifyNameInSubtree( | |
432 name_rdn_sequence, | |
433 der::Input(excluded_name.data(), excluded_name.size()))) { | |
434 return false; | |
435 } | |
436 } | |
437 for (const auto& permitted_name : permitted_subtrees_.directory_names) { | |
438 if (VerifyNameInSubtree( | |
439 name_rdn_sequence, | |
440 der::Input(permitted_name.data(), permitted_name.size()))) { | |
441 return true; | |
442 } | |
443 } | |
444 | |
445 return false; | |
446 } | |
447 | |
448 bool NameConstraints::IsPermittedIP(const IPAddressNumber& ip) const { | |
449 if (permitted_subtrees_.ip_addresses.empty() && | |
450 excluded_subtrees_.ip_addresses.empty()) | |
451 return true; | |
452 | |
453 for (const auto& excluded_ip : excluded_subtrees_.ip_addresses) { | |
454 if (VerifyIPMatchesConstraint(ip, excluded_ip)) | |
455 return false; | |
456 } | |
457 for (const auto& permitted_ip : permitted_subtrees_.ip_addresses) { | |
458 if (VerifyIPMatchesConstraint(ip, permitted_ip)) | |
459 return true; | |
460 } | |
461 | |
462 return false; | |
463 } | |
464 | |
465 bool NameConstraints::IsPermittedOtherName() const { | |
466 return (!permitted_subtrees_.has_other_names && | |
467 !excluded_subtrees_.has_other_names); | |
468 } | |
469 | |
470 bool NameConstraints::IsPermittedRFC822Name() const { | |
471 return (!permitted_subtrees_.has_rfc822_names && | |
472 !excluded_subtrees_.has_rfc822_names); | |
473 } | |
474 | |
475 bool NameConstraints::IsPermittedX400Address() const { | |
476 return (!permitted_subtrees_.has_x400_addresses && | |
477 !excluded_subtrees_.has_x400_addresses); | |
478 } | |
479 | |
480 bool NameConstraints::IsPermittedEdiPartyName() const { | |
481 return (!permitted_subtrees_.has_edi_party_names && | |
482 !excluded_subtrees_.has_edi_party_names); | |
483 } | |
484 | |
485 bool NameConstraints::IsPermittedURI() const { | |
486 return (!permitted_subtrees_.has_uniform_resource_identifiers && | |
487 !excluded_subtrees_.has_uniform_resource_identifiers); | |
488 } | |
489 | |
490 bool NameConstraints::IsPermittedRegisteredId() const { | |
491 return (!permitted_subtrees_.has_registered_ids && | |
492 !excluded_subtrees_.has_registered_ids); | |
493 } | |
494 | |
495 } // namespace net | |
OLD | NEW |