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 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 | |
OLD | NEW |