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

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

Powered by Google App Engine
This is Rietveld 408576698