Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(34)

Side by Side Diff: net/cert/internal/name_constraints.cc

Issue 1214933009: Class for parsing and evaluating RFC 5280 NameConstraints. (Closed) Base URL: https://chromium.googlesource.com/chromium/src.git@compare_DN2
Patch Set: use test_helpers.h Created 5 years, 4 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View unified diff | Download patch
OLDNEW
(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
OLDNEW

Powered by Google App Engine
This is Rietveld 408576698