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

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 #12 Created 5 years, 2 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
« no previous file with comments | « net/cert/internal/name_constraints.h ('k') | net/cert/internal/name_constraints_unittest.cc » ('j') | no next file with comments »
Toggle Intra-line Diffs ('i') | Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
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 // The name types of GeneralName that are fully supported in name constraints.
18 //
19 // (The other types will have the minimal checking described by RFC 5280
20 // section 4.2.1.10: If a name constraints extension that is marked as critical
21 // imposes constraints on a particular name form, and an instance of
22 // that name form appears in the subject field or subjectAltName
23 // extension of a subsequent certificate, then the application MUST
24 // either process the constraint or reject the certificate.)
25 const int kSupportedNameTypes = GENERAL_NAME_DNS_NAME |
26 GENERAL_NAME_DIRECTORY_NAME |
27 GENERAL_NAME_IP_ADDRESS;
28
29 // Controls wildcard handling of DNSNameMatches.
30 // If WildcardMatchType is WILDCARD_PARTIAL_MATCH "*.bar.com" is considered to
31 // match the constraint "foo.bar.com". If it is WILDCARD_FULL_MATCH, "*.bar.com"
32 // will match "bar.com" but not "foo.bar.com".
33 enum WildcardMatchType { WILDCARD_PARTIAL_MATCH, WILDCARD_FULL_MATCH };
34
35 // Returns true if |name| falls in the subtree defined by |dns_constraint|.
36 // RFC 5280 section 4.2.1.10:
37 // DNS name restrictions are expressed as host.example.com. Any DNS
38 // name that can be constructed by simply adding zero or more labels
39 // to the left-hand side of the name satisfies the name constraint. For
40 // example, www.host.example.com would satisfy the constraint but
41 // host1.example.com would not.
42 //
43 // |wildcard_matching| controls handling of wildcard names (|name| starts with
44 // "*."). Wildcard handling is not specified by RFC 5280, but certificate
45 // verification allows it, name constraints must check it similarly.
46 bool DNSNameMatches(base::StringPiece name,
47 base::StringPiece dns_constraint,
48 WildcardMatchType wildcard_matching) {
49 // Everything matches the empty DNS name constraint.
50 if (dns_constraint.empty())
51 return true;
52
53 // Normalize absolute DNS names by removing the trailing dot, if any.
54 if (!name.empty() && *name.rbegin() == '.')
55 name.remove_suffix(1);
56 if (!dns_constraint.empty() && *dns_constraint.rbegin() == '.')
57 dns_constraint.remove_suffix(1);
58
59 // Wildcard partial-match handling ("*.bar.com" matching name constraint
60 // "foo.bar.com"). This only handles the case where the the dnsname and the
61 // constraint match after removing the leftmost label, otherwise it is handled
62 // by falling through to the check of whether the dnsname is fully within or
63 // fully outside of the constraint.
64 if (wildcard_matching == WILDCARD_PARTIAL_MATCH && name.size() > 2 &&
65 name[0] == '*' && name[1] == '.') {
66 size_t dns_constraint_dot_pos = dns_constraint.find('.');
67 if (dns_constraint_dot_pos != std::string::npos) {
68 base::StringPiece dns_constraint_domain(
69 dns_constraint.begin() + dns_constraint_dot_pos + 1,
70 dns_constraint.size() - dns_constraint_dot_pos - 1);
71 base::StringPiece wildcard_domain(name.begin() + 2, name.size() - 2);
72 if (base::EqualsCaseInsensitiveASCII(wildcard_domain,
73 dns_constraint_domain)) {
74 return true;
75 }
76 }
77 }
78
79 if (!base::EndsWith(name, dns_constraint,
80 base::CompareCase::INSENSITIVE_ASCII)) {
81 return false;
82 }
83 // Exact match.
84 if (name.size() == dns_constraint.size())
85 return true;
86 // Subtree match.
87 if (name.size() > dns_constraint.size() &&
88 name[name.size() - dns_constraint.size() - 1] == '.') {
89 return true;
90 }
91 // Trailing text matches, but not in a subtree (e.g., "foobar.com" is not a
92 // match for "bar.com").
93 return false;
94 }
95
96 // Return true if the bitmask |mask| contains only zeros after the first
97 // |prefix_length| bits.
98 bool IsSuffixZero(std::vector<uint8_t> mask, unsigned prefix_length) {
Ryan Sleevi 2015/10/29 01:50:47 Shouldn't mask be const-ref?
mattm 2015/10/29 04:20:38 Indeed. Done.
99 size_t zero_bits = mask.size() * CHAR_BIT - prefix_length;
100 size_t zero_bytes = zero_bits / CHAR_BIT;
101 std::vector<uint8_t> zeros(zero_bytes, 0);
102 if (memcmp(zeros.data(), mask.data() + mask.size() - zero_bytes, zero_bytes))
103 return false;
104 size_t leftover_bits = zero_bits % CHAR_BIT;
105 if (leftover_bits) {
106 uint8_t b = mask[mask.size() - zero_bytes - 1];
107 for (size_t i = 0; i < leftover_bits; ++i) {
108 if (b & (1 << i))
109 return false;
110 }
111 }
112 return true;
113 }
114
115 // Controls handling of unsupported name types in ParseGeneralName. (Unsupported
116 // types are those not in kSupportedNameTypes.)
117 // RECORD_UNSUPPORTED causes unsupported types to be recorded in
118 // |present_name_types|.
119 // IGNORE_UNSUPPORTED causes unsupported types to not be recorded.
120 enum ParseGeneralNameUnsupportedTypeBehavior {
121 RECORD_UNSUPPORTED,
122 IGNORE_UNSUPPORTED,
123 };
124
125 // Controls parsing of iPAddress names in ParseGeneralName.
126 // IP_ADDRESS_ONLY parses the iPAddress names as a 4 or 16 byte IP address.
127 // IP_ADDRESS_AND_NETMASK parses the iPAddress names as 8 or 32 bytes containing
128 // an IP address followed by a netmask.
129 enum ParseGeneralNameIPAddressType {
130 IP_ADDRESS_ONLY,
131 IP_ADDRESS_AND_NETMASK,
132 };
133
134 // Parses a GeneralName value and adds it to |subtrees|.
135 WARN_UNUSED_RESULT bool ParseGeneralName(
136 const der::Input& input,
137 ParseGeneralNameUnsupportedTypeBehavior unsupported_type_behavior,
138 ParseGeneralNameIPAddressType ip_address_type,
139 NameConstraints::GeneralNames* subtrees) {
140 der::Parser parser(input);
141 der::Tag tag;
142 der::Input value;
143 if (!parser.ReadTagAndValue(&tag, &value))
144 return false;
145 if (!der::IsContextSpecific(tag))
146 return false;
147 GeneralNameTypes name_type = GENERAL_NAME_NONE;
148 // GeneralName ::= CHOICE {
149 switch (der::GetTagNumber(tag)) {
150 // otherName [0] OtherName,
151 case 0:
152 if (!der::IsConstructed(tag))
153 return false;
154 name_type = GENERAL_NAME_OTHER_NAME;
155 break;
156 // rfc822Name [1] IA5String,
157 case 1:
158 if (der::IsConstructed(tag))
159 return false;
160 name_type = GENERAL_NAME_RFC822_NAME;
161 break;
162 // dNSName [2] IA5String,
163 case 2: {
164 if (der::IsConstructed(tag))
165 return false;
166 name_type = GENERAL_NAME_DNS_NAME;
167 const std::string s = value.AsString();
168 if (!base::IsStringASCII(s))
169 return false;
170 subtrees->dns_names.push_back(s);
171 break;
172 }
173 // x400Address [3] ORAddress,
174 case 3:
175 if (!der::IsConstructed(tag))
176 return false;
177 name_type = GENERAL_NAME_X400_ADDRESS;
178 break;
179 // directoryName [4] Name,
180 case 4:
181 if (!der::IsConstructed(tag))
182 return false;
183 name_type = GENERAL_NAME_DIRECTORY_NAME;
184 subtrees->directory_names.push_back(std::vector<uint8_t>(
185 value.UnsafeData(), value.UnsafeData() + value.Length()));
186 break;
187 // ediPartyName [5] EDIPartyName,
188 case 5:
189 if (!der::IsConstructed(tag))
190 return false;
191 name_type = GENERAL_NAME_EDI_PARTY_NAME;
192 break;
193 // uniformResourceIdentifier [6] IA5String,
194 case 6:
195 if (der::IsConstructed(tag))
196 return false;
197 name_type = GENERAL_NAME_UNIFORM_RESOURCE_IDENTIFIER;
198 break;
199 // iPAddress [7] OCTET STRING,
200 case 7:
201 if (der::IsConstructed(tag))
202 return false;
203 name_type = GENERAL_NAME_IP_ADDRESS;
204 if (ip_address_type == IP_ADDRESS_ONLY) {
205 // RFC 5280 section 4.2.1.6:
206 // When the subjectAltName extension contains an iPAddress, the address
207 // MUST be stored in the octet string in "network byte order", as
208 // specified in [RFC791]. The least significant bit (LSB) of each octet
209 // is the LSB of the corresponding byte in the network address. For IP
210 // version 4, as specified in [RFC791], the octet string MUST contain
211 // exactly four octets. For IP version 6, as specified in [RFC2460],
212 // the octet string MUST contain exactly sixteen octets.
213 if ((value.Length() != kIPv4AddressSize &&
214 value.Length() != kIPv6AddressSize)) {
215 return false;
216 }
217 subtrees->ip_addresses.push_back(std::vector<uint8_t>(
218 value.UnsafeData(), value.UnsafeData() + value.Length()));
219 } else {
220 DCHECK_EQ(ip_address_type, IP_ADDRESS_AND_NETMASK);
221 // RFC 5280 section 4.2.1.10:
222 // The syntax of iPAddress MUST be as described in Section 4.2.1.6 with
223 // the following additions specifically for name constraints. For IPv4
224 // addresses, the iPAddress field of GeneralName MUST contain eight (8)
225 // octets, encoded in the style of RFC 4632 (CIDR) to represent an
226 // address range [RFC4632]. For IPv6 addresses, the iPAddress field
227 // MUST contain 32 octets similarly encoded. For example, a name
228 // constraint for "class C" subnet 192.0.2.0 is represented as the
229 // octets C0 00 02 00 FF FF FF 00, representing the CIDR notation
230 // 192.0.2.0/24 (mask 255.255.255.0).
231 if (value.Length() != kIPv4AddressSize * 2 &&
232 value.Length() != kIPv6AddressSize * 2) {
233 return false;
234 }
235 const std::vector<uint8_t> mask(value.UnsafeData() + value.Length() / 2,
236 value.UnsafeData() + value.Length());
237 const unsigned mask_prefix_length = MaskPrefixLength(mask);
238 if (!IsSuffixZero(mask, mask_prefix_length))
239 return false;
240 subtrees->ip_address_ranges.push_back(std::make_pair(
241 std::vector<uint8_t>(value.UnsafeData(),
242 value.UnsafeData() + value.Length() / 2),
243 mask_prefix_length));
244 }
245 break;
246 // registeredID [8] OBJECT IDENTIFIER }
247 case 8:
248 if (der::IsConstructed(tag))
249 return false;
250 name_type = GENERAL_NAME_REGISTERED_ID;
251 break;
252 default:
253 return false;
254 }
255 DCHECK_NE(GENERAL_NAME_NONE, name_type);
256 if ((name_type & kSupportedNameTypes) ||
257 unsupported_type_behavior == RECORD_UNSUPPORTED) {
258 subtrees->present_name_types |= name_type;
259 }
260 return true;
261 }
262
263 // Parses a GeneralSubtrees |value| and store the contents in |subtrees|.
264 // The individual values stored into |subtrees| are not validated by this
265 // function.
266 // NOTE: |subtrees| will be modified regardless of the return.
267 WARN_UNUSED_RESULT bool ParseGeneralSubtrees(
268 const der::Input& value,
269 bool is_critical,
270 NameConstraints::GeneralNames* subtrees) {
271 // GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
272 //
273 // GeneralSubtree ::= SEQUENCE {
274 // base GeneralName,
275 // minimum [0] BaseDistance DEFAULT 0,
276 // maximum [1] BaseDistance OPTIONAL }
277 //
278 // BaseDistance ::= INTEGER (0..MAX)
279 der::Parser sequence_parser(value);
280 // The GeneralSubtrees sequence should have at least 1 element.
281 if (!sequence_parser.HasMore())
282 return false;
283 while (sequence_parser.HasMore()) {
284 der::Parser subtree_sequence;
285 if (!sequence_parser.ReadSequence(&subtree_sequence))
286 return false;
287
288 der::Input raw_general_name;
289 if (!subtree_sequence.ReadRawTLV(&raw_general_name))
290 return false;
291
292 if (!ParseGeneralName(raw_general_name,
293 is_critical ? RECORD_UNSUPPORTED : IGNORE_UNSUPPORTED,
294 IP_ADDRESS_AND_NETMASK, subtrees)) {
295 return false;
296 }
297
298 // RFC 5280 section 4.2.1.10:
299 // Within this profile, the minimum and maximum fields are not used with any
300 // name forms, thus, the minimum MUST be zero, and maximum MUST be absent.
301 // However, if an application encounters a critical name constraints
302 // extension that specifies other values for minimum or maximum for a name
303 // form that appears in a subsequent certificate, the application MUST
304 // either process these fields or reject the certificate.
305
306 // Note that technically failing here isn't required: rather only need to
307 // fail if a name of this type actually appears in a subsequent cert and
308 // this extension was marked critical. However the minimum and maximum
309 // fields appear uncommon enough that implementing that isn't useful.
310 if (subtree_sequence.HasMore())
311 return false;
312 }
313 return true;
314 }
315
316 } // namespace
317
318 NameConstraints::GeneralNames::GeneralNames() {}
319
320 NameConstraints::GeneralNames::~GeneralNames() {}
321
322 NameConstraints::~NameConstraints() {}
323
324 // static
325 scoped_ptr<NameConstraints> NameConstraints::CreateFromDer(
326 const der::Input& extension_value,
327 bool is_critical) {
328 scoped_ptr<NameConstraints> name_constraints(new NameConstraints());
329 if (!name_constraints->Parse(extension_value, is_critical))
330 return nullptr;
331 return name_constraints;
332 }
333
334 bool NameConstraints::Parse(const der::Input& extension_value,
335 bool is_critical) {
336 der::Parser extension_parser(extension_value);
337 der::Parser sequence_parser;
338
339 // NameConstraints ::= SEQUENCE {
340 // permittedSubtrees [0] GeneralSubtrees OPTIONAL,
341 // excludedSubtrees [1] GeneralSubtrees OPTIONAL }
342 if (!extension_parser.ReadSequence(&sequence_parser))
343 return false;
344 if (extension_parser.HasMore())
345 return false;
346
347 bool had_permitted_subtrees = false;
348 der::Input permitted_subtrees_value;
349 if (!sequence_parser.ReadOptionalTag(der::ContextSpecificConstructed(0),
350 &permitted_subtrees_value,
351 &had_permitted_subtrees)) {
352 return false;
353 }
354 if (had_permitted_subtrees &&
355 !ParseGeneralSubtrees(permitted_subtrees_value, is_critical,
356 &permitted_subtrees_)) {
357 return false;
358 }
359
360 bool had_excluded_subtrees = false;
361 der::Input excluded_subtrees_value;
362 if (!sequence_parser.ReadOptionalTag(der::ContextSpecificConstructed(1),
363 &excluded_subtrees_value,
364 &had_excluded_subtrees)) {
365 return false;
366 }
367 if (had_excluded_subtrees &&
368 !ParseGeneralSubtrees(excluded_subtrees_value, is_critical,
369 &excluded_subtrees_)) {
370 return false;
371 }
372
373 // RFC 5280 section 4.2.1.10:
374 // Conforming CAs MUST NOT issue certificates where name constraints is an
375 // empty sequence. That is, either the permittedSubtrees field or the
376 // excludedSubtrees MUST be present.
377 if (!had_permitted_subtrees && !had_excluded_subtrees)
378 return false;
379
380 if (sequence_parser.HasMore())
381 return false;
382
383 return true;
384 }
385
386 bool NameConstraints::IsPermittedCert(
387 const der::Input& subject_rdn_sequence,
388 const der::Input& subject_alt_name_extnvalue_tlv) const {
389 // Subject Alternative Name handling:
390 //
391 // RFC 5280 section 4.2.1.6:
392 // id-ce-subjectAltName OBJECT IDENTIFIER ::= { id-ce 17 }
393 //
394 // SubjectAltName ::= GeneralNames
395 //
396 // GeneralNames ::= SEQUENCE SIZE (1..MAX) OF GeneralName
397
398 GeneralNames san_names;
399 if (subject_alt_name_extnvalue_tlv.Length()) {
400 der::Parser extnvalue_parser(subject_alt_name_extnvalue_tlv);
401 der::Input subject_alt_name_tlv;
402 if (!extnvalue_parser.ReadTag(der::kOctetString, &subject_alt_name_tlv))
403 return false;
404
405 der::Parser subject_alt_name_parser(subject_alt_name_tlv);
406 der::Parser san_sequence_parser;
407 if (!subject_alt_name_parser.ReadSequence(&san_sequence_parser))
408 return false;
409 // Should not have trailing data after subjectAltName sequence.
410 if (subject_alt_name_parser.HasMore())
411 return false;
412 // The subjectAltName sequence should have at least 1 element.
413 if (!san_sequence_parser.HasMore())
414 return false;
415
416 while (san_sequence_parser.HasMore()) {
417 der::Input raw_general_name;
418 if (!san_sequence_parser.ReadRawTLV(&raw_general_name))
419 return false;
420
421 if (!ParseGeneralName(raw_general_name, RECORD_UNSUPPORTED,
422 IP_ADDRESS_ONLY, &san_names))
423 return false;
424 }
425
426 // Check unsupported name types:
427 // ConstrainedNameTypes for the unsupported types will only be true if that
428 // type of name was present in a name constraint that was marked critical.
429 //
430 // RFC 5280 section 4.2.1.10:
431 // If a name constraints extension that is marked as critical
432 // imposes constraints on a particular name form, and an instance of
433 // that name form appears in the subject field or subjectAltName
434 // extension of a subsequent certificate, then the application MUST
435 // either process the constraint or reject the certificate.
436 if (ConstrainedNameTypes() & san_names.present_name_types &
437 ~kSupportedNameTypes) {
438 return false;
439 }
440
441 // Check supported name types:
442 for (const auto& dns_name : san_names.dns_names) {
443 if (!IsPermittedDNSName(dns_name))
444 return false;
445 }
446
447 for (const auto& directory_name : san_names.directory_names) {
448 if (!IsPermittedDirectoryName(
449 der::Input(directory_name.data(), directory_name.size()))) {
450 return false;
451 }
452 }
453
454 for (const auto& ip_address : san_names.ip_addresses) {
455 if (!IsPermittedIP(ip_address))
456 return false;
457 }
458 }
459
460 // Subject handling:
461
462 // RFC 5280 section 4.2.1.10:
463 // Legacy implementations exist where an electronic mail address is embedded
464 // in the subject distinguished name in an attribute of type emailAddress
465 // (Section 4.1.2.6). When constraints are imposed on the rfc822Name name
466 // form, but the certificate does not include a subject alternative name, the
467 // rfc822Name constraint MUST be applied to the attribute of type emailAddress
468 // in the subject distinguished name.
469 if (!subject_alt_name_extnvalue_tlv.Length() &&
470 (ConstrainedNameTypes() & GENERAL_NAME_RFC822_NAME)) {
471 bool contained_email_address = false;
472 if (!NameContainsEmailAddress(subject_rdn_sequence,
473 &contained_email_address)) {
474 return false;
475 }
476 if (contained_email_address)
477 return false;
478 }
479
480 // RFC 5280 4.1.2.6:
481 // If subject naming information is present only in the subjectAltName
482 // extension (e.g., a key bound only to an email address or URI), then the
483 // subject name MUST be an empty sequence and the subjectAltName extension
484 // MUST be critical.
Ryan Sleevi 2015/10/29 01:50:47 This code doesn't check the criticality; is the ex
mattm 2015/10/29 04:20:38 That was my thought (for example, if there are no
mattm 2015/10/29 04:38:25 Also I imagine this code will end up changed to sp
485 if (subject_alt_name_extnvalue_tlv.Length() &&
486 subject_rdn_sequence.Length() == 0) {
487 return true;
488 }
489
490 return IsPermittedDirectoryName(subject_rdn_sequence);
491 }
492
493 bool NameConstraints::IsPermittedDNSName(const std::string& name) const {
494 // If there are no name constraints for DNS names, all names are accepted.
495 if (!(ConstrainedNameTypes() & GENERAL_NAME_DNS_NAME))
496 return true;
497
498 for (const std::string& excluded_name : excluded_subtrees_.dns_names) {
499 // When matching wildcard hosts against excluded subtrees, consider it a
500 // match if the constraint would match any expansion of the wildcard. Eg,
501 // CN=*.bar.com should match a constraint of foo.bar.com.
Ryan Sleevi 2015/10/29 01:50:47 remove the CN= ?
mattm 2015/10/29 04:20:38 Done.
502 if (DNSNameMatches(name, excluded_name, WILDCARD_PARTIAL_MATCH))
503 return false;
504 }
505 for (const std::string& permitted_name : permitted_subtrees_.dns_names) {
506 // When matching wildcard hosts against permitted subtrees, consider it a
507 // match only if the constraint would match all expansions of the wildcard.
508 // Eg, CN=*.bar.com should match a constraint of bar.com, but not
Ryan Sleevi 2015/10/29 01:50:47 remove CN= ?
mattm 2015/10/29 04:20:38 Done.
509 // foo.bar.com.
510 if (DNSNameMatches(name, permitted_name, WILDCARD_FULL_MATCH))
511 return true;
512 }
513
514 return false;
515 }
516
517 bool NameConstraints::IsPermittedDirectoryName(
518 const der::Input& name_rdn_sequence) const {
519 // If there are no name constraints for directory names, all names are
520 // accepted.
521 if (!(ConstrainedNameTypes() & GENERAL_NAME_DIRECTORY_NAME))
522 return true;
523
524 for (const auto& excluded_name : excluded_subtrees_.directory_names) {
525 if (VerifyNameInSubtree(
526 name_rdn_sequence,
527 der::Input(excluded_name.data(), excluded_name.size()))) {
528 return false;
529 }
530 }
531 for (const auto& permitted_name : permitted_subtrees_.directory_names) {
532 if (VerifyNameInSubtree(
533 name_rdn_sequence,
534 der::Input(permitted_name.data(), permitted_name.size()))) {
535 return true;
536 }
537 }
538
539 return false;
540 }
541
542 bool NameConstraints::IsPermittedIP(const IPAddressNumber& ip) const {
543 // If there are no name constraints for IP Address names, all names are
544 // accepted.
545 if (!(ConstrainedNameTypes() & GENERAL_NAME_IP_ADDRESS))
546 return true;
547
548 for (const auto& excluded_ip : excluded_subtrees_.ip_address_ranges) {
549 if (IPNumberMatchesPrefix(ip, excluded_ip.first, excluded_ip.second))
550 return false;
551 }
552 for (const auto& permitted_ip : permitted_subtrees_.ip_address_ranges) {
553 if (IPNumberMatchesPrefix(ip, permitted_ip.first, permitted_ip.second))
554 return true;
555 }
556
557 return false;
558 }
559
560 int NameConstraints::ConstrainedNameTypes() const {
561 return (permitted_subtrees_.present_name_types |
562 excluded_subtrees_.present_name_types);
563 }
564
565 } // namespace net
OLDNEW
« no previous file with comments | « net/cert/internal/name_constraints.h ('k') | net/cert/internal/name_constraints_unittest.cc » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698