1 /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2 /* vim: set ts=8 sts=2 et sw=2 tw=80: */
3 /* This code is made available to you under your choice of the following sets
4 * of licensing terms:
5 */
6 /* This Source Code Form is subject to the terms of the Mozilla Public
7 * License, v. 2.0. If a copy of the MPL was not distributed with this
8 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9 */
10 /* Copyright 2014 Mozilla Contributors
11 *
12 * Licensed under the Apache License, Version 2.0 (the "License");
13 * you may not use this file except in compliance with the License.
14 * You may obtain a copy of the License at
15 *
16 * http://www.apache.org/licenses/LICENSE-2.0
17 *
18 * Unless required by applicable law or agreed to in writing, software
19 * distributed under the License is distributed on an "AS IS" BASIS,
20 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21 * See the License for the specific language governing permissions and
22 * limitations under the License.
23 */
24
25 // This code implements RFC6125-ish name matching, RFC5280-ish name constraint
26 // checking, and related things.
27 //
28 // In this code, identifiers are classified as either "presented" or
29 // "reference" identifiers are defined in
30 // http://tools.ietf.org/html/rfc6125#section-1.8. A "presented identifier" is
31 // one in the subjectAltName of the certificate, or sometimes within a CN of
32 // the certificate's subject. The "reference identifier" is the one we are
33 // being asked to match the certificate against. When checking name
34 // constraints, the reference identifier is the entire encoded name constraint
35 // extension value.
36
37 #include <algorithm>
38
39 #include "pkixcheck.h"
40 #include "pkixutil.h"
41
42 namespace mozilla {
43 namespace pkix {
44
45 namespace {
46
47 // GeneralName ::= CHOICE {
48 // otherName [0] OtherName,
49 // rfc822Name [1] IA5String,
50 // dNSName [2] IA5String,
51 // x400Address [3] ORAddress,
52 // directoryName [4] Name,
53 // ediPartyName [5] EDIPartyName,
54 // uniformResourceIdentifier [6] IA5String,
55 // iPAddress [7] OCTET STRING,
56 // registeredID [8] OBJECT IDENTIFIER }
57 enum class GeneralNameType : uint8_t {
58 // Note that these values are NOT contiguous. Some values have the
59 // der::CONSTRUCTED bit set while others do not.
60 // (The der::CONSTRUCTED bit is for types where the value is a SEQUENCE.)
61 otherName = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 0,
62 rfc822Name = der::CONTEXT_SPECIFIC | 1,
63 dNSName = der::CONTEXT_SPECIFIC | 2,
64 x400Address = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 3,
65 directoryName = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 4,
66 ediPartyName = der::CONTEXT_SPECIFIC | der::CONSTRUCTED | 5,
67 uniformResourceIdentifier = der::CONTEXT_SPECIFIC | 6,
68 iPAddress = der::CONTEXT_SPECIFIC | 7,
69 registeredID = der::CONTEXT_SPECIFIC | 8,
70 // nameConstraints is a pseudo-GeneralName used to signify that a
71 // reference ID is actually the entire name constraint extension.
72 nameConstraints = 0xff
73 };
74
ReadGeneralName(Reader & reader,GeneralNameType & generalNameType,Input & value)75 inline Result ReadGeneralName(Reader& reader,
76 /*out*/ GeneralNameType& generalNameType,
77 /*out*/ Input& value) {
78 uint8_t tag;
79 Result rv = der::ReadTagAndGetValue(reader, tag, value);
80 if (rv != Success) {
81 return rv;
82 }
83 switch (tag) {
84 case static_cast<uint8_t>(GeneralNameType::otherName):
85 generalNameType = GeneralNameType::otherName;
86 break;
87 case static_cast<uint8_t>(GeneralNameType::rfc822Name):
88 generalNameType = GeneralNameType::rfc822Name;
89 break;
90 case static_cast<uint8_t>(GeneralNameType::dNSName):
91 generalNameType = GeneralNameType::dNSName;
92 break;
93 case static_cast<uint8_t>(GeneralNameType::x400Address):
94 generalNameType = GeneralNameType::x400Address;
95 break;
96 case static_cast<uint8_t>(GeneralNameType::directoryName):
97 generalNameType = GeneralNameType::directoryName;
98 break;
99 case static_cast<uint8_t>(GeneralNameType::ediPartyName):
100 generalNameType = GeneralNameType::ediPartyName;
101 break;
102 case static_cast<uint8_t>(GeneralNameType::uniformResourceIdentifier):
103 generalNameType = GeneralNameType::uniformResourceIdentifier;
104 break;
105 case static_cast<uint8_t>(GeneralNameType::iPAddress):
106 generalNameType = GeneralNameType::iPAddress;
107 break;
108 case static_cast<uint8_t>(GeneralNameType::registeredID):
109 generalNameType = GeneralNameType::registeredID;
110 break;
111 default:
112 return Result::ERROR_BAD_DER;
113 }
114 return Success;
115 }
116
117 enum class MatchResult { NoNamesOfGivenType = 0, Mismatch = 1, Match = 2 };
118
119 Result SearchNames(const Input* subjectAltName, Input subject,
120 GeneralNameType referenceIDType, Input referenceID,
121 FallBackToSearchWithinSubject fallBackToCommonName,
122 /*out*/ MatchResult& match);
123 Result SearchWithinRDN(Reader& rdn, GeneralNameType referenceIDType,
124 Input referenceID,
125 FallBackToSearchWithinSubject fallBackToEmailAddress,
126 FallBackToSearchWithinSubject fallBackToCommonName,
127 /*in/out*/ MatchResult& match);
128 Result MatchAVA(Input type, uint8_t valueEncodingTag, Input presentedID,
129 GeneralNameType referenceIDType, Input referenceID,
130 FallBackToSearchWithinSubject fallBackToEmailAddress,
131 FallBackToSearchWithinSubject fallBackToCommonName,
132 /*in/out*/ MatchResult& match);
133 Result ReadAVA(Reader& rdn,
134 /*out*/ Input& type,
135 /*out*/ uint8_t& valueTag,
136 /*out*/ Input& value);
137 void MatchSubjectPresentedIDWithReferenceID(GeneralNameType presentedIDType,
138 Input presentedID,
139 GeneralNameType referenceIDType,
140 Input referenceID,
141 /*in/out*/ MatchResult& match);
142
143 Result MatchPresentedIDWithReferenceID(GeneralNameType presentedIDType,
144 Input presentedID,
145 GeneralNameType referenceIDType,
146 Input referenceID,
147 /*in/out*/ MatchResult& matchResult);
148 Result CheckPresentedIDConformsToConstraints(GeneralNameType referenceIDType,
149 Input presentedID,
150 Input nameConstraints);
151
152 uint8_t LocaleInsensitveToLower(uint8_t a);
153 bool StartsWithIDNALabel(Input id);
154
155 enum class IDRole {
156 ReferenceID = 0,
157 PresentedID = 1,
158 NameConstraint = 2,
159 };
160
161 enum class AllowWildcards { No = 0, Yes = 1 };
162
163 // DNSName constraints implicitly allow subdomain matching when there is no
164 // leading dot ("foo.example.com" matches a constraint of "example.com"), but
165 // RFC822Name constraints only allow subdomain matching when there is a leading
166 // dot ("foo.example.com" does not match "example.com" but does match
167 // ".example.com").
168 enum class AllowDotlessSubdomainMatches { No = 0, Yes = 1 };
169
170 bool IsValidDNSID(Input hostname, IDRole idRole, AllowWildcards allowWildcards);
171
172 Result MatchPresentedDNSIDWithReferenceDNSID(
173 Input presentedDNSID, AllowWildcards allowWildcards,
174 AllowDotlessSubdomainMatches allowDotlessSubdomainMatches,
175 IDRole referenceDNSIDRole, Input referenceDNSID,
176 /*out*/ bool& matches);
177
178 Result MatchPresentedRFC822NameWithReferenceRFC822Name(
179 Input presentedRFC822Name, IDRole referenceRFC822NameRole,
180 Input referenceRFC822Name, /*out*/ bool& matches);
181
182 } // namespace
183
184 bool IsValidReferenceDNSID(Input hostname);
185 bool IsValidPresentedDNSID(Input hostname);
186 bool ParseIPv4Address(Input hostname, /*out*/ uint8_t (&out)[4]);
187 bool ParseIPv6Address(Input hostname, /*out*/ uint8_t (&out)[16]);
188
189 // This is used by the pkixnames_tests.cpp tests.
MatchPresentedDNSIDWithReferenceDNSID(Input presentedDNSID,Input referenceDNSID,bool & matches)190 Result MatchPresentedDNSIDWithReferenceDNSID(Input presentedDNSID,
191 Input referenceDNSID,
192 /*out*/ bool& matches) {
193 return MatchPresentedDNSIDWithReferenceDNSID(
194 presentedDNSID, AllowWildcards::Yes, AllowDotlessSubdomainMatches::Yes,
195 IDRole::ReferenceID, referenceDNSID, matches);
196 }
197
198 // Verify that the given end-entity cert, which is assumed to have been already
199 // validated with BuildCertChain, is valid for the given hostname. hostname is
200 // assumed to be a string representation of an IPv4 address, an IPv6 addresss,
201 // or a normalized ASCII (possibly punycode) DNS name.
CheckCertHostname(Input endEntityCertDER,Input hostname,NameMatchingPolicy & nameMatchingPolicy)202 Result CheckCertHostname(Input endEntityCertDER, Input hostname,
203 NameMatchingPolicy& nameMatchingPolicy) {
204 BackCert cert(endEntityCertDER, EndEntityOrCA::MustBeEndEntity, nullptr);
205 Result rv = cert.Init();
206 if (rv != Success) {
207 return rv;
208 }
209
210 Time notBefore(Time::uninitialized);
211 rv = ParseValidity(cert.GetValidity(), ¬Before);
212 if (rv != Success) {
213 return rv;
214 }
215 FallBackToSearchWithinSubject fallBackToSearchWithinSubject;
216 rv = nameMatchingPolicy.FallBackToCommonName(notBefore,
217 fallBackToSearchWithinSubject);
218 if (rv != Success) {
219 return rv;
220 }
221
222 const Input* subjectAltName(cert.GetSubjectAltName());
223 Input subject(cert.GetSubject());
224
225 // For backward compatibility with legacy certificates, we may fall back to
226 // searching for a name match in the subject common name for DNS names and
227 // IPv4 addresses. We don't do so for IPv6 addresses because we do not think
228 // there are many certificates that would need such fallback, and because
229 // comparisons of string representations of IPv6 addresses are particularly
230 // error prone due to the syntactic flexibility that IPv6 addresses have.
231 //
232 // IPv4 and IPv6 addresses are represented using the same type of GeneralName
233 // (iPAddress); they are differentiated by the lengths of the values.
234 MatchResult match;
235 uint8_t ipv6[16];
236 uint8_t ipv4[4];
237 if (IsValidReferenceDNSID(hostname)) {
238 rv = SearchNames(subjectAltName, subject, GeneralNameType::dNSName,
239 hostname, fallBackToSearchWithinSubject, match);
240 } else if (ParseIPv6Address(hostname, ipv6)) {
241 rv = SearchNames(subjectAltName, subject, GeneralNameType::iPAddress,
242 Input(ipv6), FallBackToSearchWithinSubject::No, match);
243 } else if (ParseIPv4Address(hostname, ipv4)) {
244 rv = SearchNames(subjectAltName, subject, GeneralNameType::iPAddress,
245 Input(ipv4), fallBackToSearchWithinSubject, match);
246 } else {
247 return Result::ERROR_BAD_CERT_DOMAIN;
248 }
249 if (rv != Success) {
250 return rv;
251 }
252 switch (match) {
253 case MatchResult::NoNamesOfGivenType: // fall through
254 case MatchResult::Mismatch:
255 return Result::ERROR_BAD_CERT_DOMAIN;
256 case MatchResult::Match:
257 return Success;
258 MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
259 }
260 }
261
262 // 4.2.1.10. Name Constraints
CheckNameConstraints(Input encodedNameConstraints,const BackCert & firstChild,KeyPurposeId requiredEKUIfPresent)263 Result CheckNameConstraints(Input encodedNameConstraints,
264 const BackCert& firstChild,
265 KeyPurposeId requiredEKUIfPresent) {
266 for (const BackCert* child = &firstChild; child; child = child->childCert) {
267 FallBackToSearchWithinSubject fallBackToCommonName =
268 (child->endEntityOrCA == EndEntityOrCA::MustBeEndEntity &&
269 requiredEKUIfPresent == KeyPurposeId::id_kp_serverAuth)
270 ? FallBackToSearchWithinSubject::Yes
271 : FallBackToSearchWithinSubject::No;
272
273 MatchResult match;
274 Result rv =
275 SearchNames(child->GetSubjectAltName(), child->GetSubject(),
276 GeneralNameType::nameConstraints, encodedNameConstraints,
277 fallBackToCommonName, match);
278 if (rv != Success) {
279 return rv;
280 }
281 switch (match) {
282 case MatchResult::Match: // fall through
283 case MatchResult::NoNamesOfGivenType:
284 break;
285 case MatchResult::Mismatch:
286 return Result::ERROR_CERT_NOT_IN_NAME_SPACE;
287 }
288 }
289
290 return Success;
291 }
292
293 namespace {
294
295 // SearchNames is used by CheckCertHostname and CheckNameConstraints.
296 //
297 // When called during name constraint checking, referenceIDType is
298 // GeneralNameType::nameConstraints and referenceID is the entire encoded name
299 // constraints extension value.
300 //
301 // The main benefit of using the exact same code paths for both is that we
302 // ensure consistency between name validation and name constraint enforcement
303 // regarding thing like "Which CN attributes should be considered as potential
304 // CN-IDs" and "Which character sets are acceptable for CN-IDs?" If the name
305 // matching and the name constraint enforcement logic were out of sync on these
306 // issues (e.g. if name matching were to consider all subject CN attributes,
307 // but name constraints were only enforced on the most specific subject CN),
308 // trivial name constraint bypasses could result.
309
SearchNames(const Input * subjectAltName,Input subject,GeneralNameType referenceIDType,Input referenceID,FallBackToSearchWithinSubject fallBackToCommonName,MatchResult & match)310 Result SearchNames(/*optional*/ const Input* subjectAltName, Input subject,
311 GeneralNameType referenceIDType, Input referenceID,
312 FallBackToSearchWithinSubject fallBackToCommonName,
313 /*out*/ MatchResult& match) {
314 Result rv;
315
316 match = MatchResult::NoNamesOfGivenType;
317
318 // RFC 6125 says "A client MUST NOT seek a match for a reference identifier
319 // of CN-ID if the presented identifiers include a DNS-ID, SRV-ID, URI-ID, or
320 // any application-specific identifier types supported by the client."
321 // Accordingly, we only consider CN-IDs if there are no DNS-IDs in the
322 // subjectAltName.
323 //
324 // RFC 6125 says that IP addresses are out of scope, but for backward
325 // compatibility we accept them, by considering IP addresses to be an
326 // "application-specific identifier type supported by the client."
327 //
328 // TODO(bug XXXXXXX): Consider strengthening this check to "A client MUST NOT
329 // seek a match for a reference identifier of CN-ID if the certificate
330 // contains a subjectAltName extension."
331 //
332 // TODO(bug XXXXXXX): Consider dropping support for IP addresses as
333 // identifiers completely.
334
335 if (subjectAltName) {
336 Reader altNames;
337 rv = der::ExpectTagAndGetValueAtEnd(*subjectAltName, der::SEQUENCE,
338 altNames);
339 if (rv != Success) {
340 return rv;
341 }
342
343 // According to RFC 5280, "If the subjectAltName extension is present, the
344 // sequence MUST contain at least one entry." For compatibility reasons, we
345 // do not enforce this. See bug 1143085.
346 while (!altNames.AtEnd()) {
347 GeneralNameType presentedIDType;
348 Input presentedID;
349 rv = ReadGeneralName(altNames, presentedIDType, presentedID);
350 if (rv != Success) {
351 return rv;
352 }
353
354 rv = MatchPresentedIDWithReferenceID(presentedIDType, presentedID,
355 referenceIDType, referenceID, match);
356 if (rv != Success) {
357 return rv;
358 }
359 if (referenceIDType != GeneralNameType::nameConstraints &&
360 match == MatchResult::Match) {
361 return Success;
362 }
363 if (presentedIDType == GeneralNameType::dNSName ||
364 presentedIDType == GeneralNameType::iPAddress) {
365 fallBackToCommonName = FallBackToSearchWithinSubject::No;
366 }
367 }
368 }
369
370 if (referenceIDType == GeneralNameType::nameConstraints) {
371 rv = CheckPresentedIDConformsToConstraints(GeneralNameType::directoryName,
372 subject, referenceID);
373 if (rv != Success) {
374 return rv;
375 }
376 }
377
378 FallBackToSearchWithinSubject fallBackToEmailAddress;
379 if (!subjectAltName &&
380 (referenceIDType == GeneralNameType::rfc822Name ||
381 referenceIDType == GeneralNameType::nameConstraints)) {
382 fallBackToEmailAddress = FallBackToSearchWithinSubject::Yes;
383 } else {
384 fallBackToEmailAddress = FallBackToSearchWithinSubject::No;
385 }
386
387 // Short-circuit the parsing of the subject name if we're not going to match
388 // any names in it
389 if (fallBackToEmailAddress == FallBackToSearchWithinSubject::No &&
390 fallBackToCommonName == FallBackToSearchWithinSubject::No) {
391 return Success;
392 }
393
394 // Attempt to match the reference ID against the CN-ID, which we consider to
395 // be the most-specific CN AVA in the subject field.
396 //
397 // https://tools.ietf.org/html/rfc6125#section-2.3.1 says:
398 //
399 // To reduce confusion, in this specification we avoid such terms and
400 // instead use the terms provided under Section 1.8; in particular, we
401 // do not use the term "(most specific) Common Name field in the subject
402 // field" from [HTTP-TLS] and instead state that a CN-ID is a Relative
403 // Distinguished Name (RDN) in the certificate subject containing one
404 // and only one attribute-type-and-value pair of type Common Name (thus
405 // removing the possibility that an RDN might contain multiple AVAs
406 // (Attribute Value Assertions) of type CN, one of which could be
407 // considered "most specific").
408 //
409 // https://tools.ietf.org/html/rfc6125#section-7.4 says:
410 //
411 // [...] Although it would be preferable to
412 // forbid multiple CN-IDs entirely, there are several reasons at this
413 // time why this specification states that they SHOULD NOT (instead of
414 // MUST NOT) be included [...]
415 //
416 // Consequently, it is unclear what to do when there are multiple CNs in the
417 // subject, regardless of whether there "SHOULD NOT" be.
418 //
419 // NSS's CERT_VerifyCertName mostly follows RFC2818 in this instance, which
420 // says:
421 //
422 // If a subjectAltName extension of type dNSName is present, that MUST
423 // be used as the identity. Otherwise, the (most specific) Common Name
424 // field in the Subject field of the certificate MUST be used.
425 //
426 // [...]
427 //
428 // In some cases, the URI is specified as an IP address rather than a
429 // hostname. In this case, the iPAddress subjectAltName must be present
430 // in the certificate and must exactly match the IP in the URI.
431 //
432 // (The main difference from RFC2818 is that NSS's CERT_VerifyCertName also
433 // matches IP addresses in the most-specific CN.)
434 //
435 // NSS's CERT_VerifyCertName finds the most specific CN via
436 // CERT_GetCommoName, which uses CERT_GetLastNameElement. Note that many
437 // NSS-based applications, including Gecko, also use CERT_GetCommonName. It
438 // is likely that other, non-NSS-based, applications also expect only the
439 // most specific CN to be matched against the reference ID.
440 //
441 // "A Layman's Guide to a Subset of ASN.1, BER, and DER" and other sources
442 // agree that an RDNSequence is ordered from most significant (least
443 // specific) to least significant (most specific), as do other references.
444 //
445 // However, Chromium appears to use the least-specific (first) CN instead of
446 // the most-specific; see https://crbug.com/366957. Also, MSIE and some other
447 // popular implementations apparently attempt to match the reference ID
448 // against any/all CNs in the subject. Since we're trying to phase out the
449 // use of CN-IDs, we intentionally avoid trying to match MSIE's more liberal
450 // behavior.
451
452 // Name ::= CHOICE { -- only one possibility for now --
453 // rdnSequence RDNSequence }
454 //
455 // RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
456 //
457 // RelativeDistinguishedName ::=
458 // SET SIZE (1..MAX) OF AttributeTypeAndValue
459 Reader subjectReader(subject);
460 return der::NestedOf(subjectReader, der::SEQUENCE, der::SET,
461 der::EmptyAllowed::Yes, [&](Reader& r) {
462 return SearchWithinRDN(r, referenceIDType, referenceID,
463 fallBackToEmailAddress,
464 fallBackToCommonName, match);
465 });
466 }
467
468 // RelativeDistinguishedName ::=
469 // SET SIZE (1..MAX) OF AttributeTypeAndValue
470 //
471 // AttributeTypeAndValue ::= SEQUENCE {
472 // type AttributeType,
473 // value AttributeValue }
SearchWithinRDN(Reader & rdn,GeneralNameType referenceIDType,Input referenceID,FallBackToSearchWithinSubject fallBackToEmailAddress,FallBackToSearchWithinSubject fallBackToCommonName,MatchResult & match)474 Result SearchWithinRDN(Reader& rdn, GeneralNameType referenceIDType,
475 Input referenceID,
476 FallBackToSearchWithinSubject fallBackToEmailAddress,
477 FallBackToSearchWithinSubject fallBackToCommonName,
478 /*in/out*/ MatchResult& match) {
479 do {
480 Input type;
481 uint8_t valueTag;
482 Input value;
483 Result rv = ReadAVA(rdn, type, valueTag, value);
484 if (rv != Success) {
485 return rv;
486 }
487 rv = MatchAVA(type, valueTag, value, referenceIDType, referenceID,
488 fallBackToEmailAddress, fallBackToCommonName, match);
489 if (rv != Success) {
490 return rv;
491 }
492 } while (!rdn.AtEnd());
493
494 return Success;
495 }
496
497 // AttributeTypeAndValue ::= SEQUENCE {
498 // type AttributeType,
499 // value AttributeValue }
500 //
501 // AttributeType ::= OBJECT IDENTIFIER
502 //
503 // AttributeValue ::= ANY -- DEFINED BY AttributeType
504 //
505 // DirectoryString ::= CHOICE {
506 // teletexString TeletexString (SIZE (1..MAX)),
507 // printableString PrintableString (SIZE (1..MAX)),
508 // universalString UniversalString (SIZE (1..MAX)),
509 // utf8String UTF8String (SIZE (1..MAX)),
510 // bmpString BMPString (SIZE (1..MAX)) }
MatchAVA(Input type,uint8_t valueEncodingTag,Input presentedID,GeneralNameType referenceIDType,Input referenceID,FallBackToSearchWithinSubject fallBackToEmailAddress,FallBackToSearchWithinSubject fallBackToCommonName,MatchResult & match)511 Result MatchAVA(Input type, uint8_t valueEncodingTag, Input presentedID,
512 GeneralNameType referenceIDType, Input referenceID,
513 FallBackToSearchWithinSubject fallBackToEmailAddress,
514 FallBackToSearchWithinSubject fallBackToCommonName,
515 /*in/out*/ MatchResult& match) {
516 // Try to match the CN as a DNSName or an IPAddress.
517 //
518 // id-at-commonName AttributeType ::= { id-at 3 }
519 //
520 // -- Naming attributes of type X520CommonName:
521 // -- X520CommonName ::= DirectoryName (SIZE (1..ub-common-name))
522 // --
523 // -- Expanded to avoid parameterized type:
524 // X520CommonName ::= CHOICE {
525 // teletexString TeletexString (SIZE (1..ub-common-name)),
526 // printableString PrintableString (SIZE (1..ub-common-name)),
527 // universalString UniversalString (SIZE (1..ub-common-name)),
528 // utf8String UTF8String (SIZE (1..ub-common-name)),
529 // bmpString BMPString (SIZE (1..ub-common-name)) }
530 //
531 // python DottedOIDToCode.py id-at-commonName 2.5.4.3
532 static const uint8_t id_at_commonName[] = {0x55, 0x04, 0x03};
533 if (fallBackToCommonName == FallBackToSearchWithinSubject::Yes &&
534 InputsAreEqual(type, Input(id_at_commonName))) {
535 // We might have previously found a match. Now that we've found another CN,
536 // we no longer consider that previous match to be a match, so "forget"
537 // about it.
538 match = MatchResult::NoNamesOfGivenType;
539
540 // PrintableString is a subset of ASCII that contains all the characters
541 // allowed in CN-IDs except '*'. Although '*' is illegal, there are many
542 // real-world certificates that are encoded this way, so we accept it.
543 //
544 // In the case of UTF8String, we rely on the fact that in UTF-8 the octets
545 // in a multi-byte encoding of a code point are always distinct from ASCII.
546 // Any non-ASCII byte in a UTF-8 string causes us to fail to match. We make
547 // no attempt to detect or report malformed UTF-8 (e.g. incomplete or
548 // overlong encodings of code points, or encodings of invalid code points).
549 //
550 // TeletexString is supported as long as it does not contain any escape
551 // sequences, which are not supported. We'll reject escape sequences as
552 // invalid characters in names, which means we only accept strings that are
553 // in the default character set, which is a superset of ASCII. Note that NSS
554 // actually treats TeletexString as ISO-8859-1. Many certificates that have
555 // wildcard CN-IDs (e.g. "*.example.com") use TeletexString because
556 // PrintableString is defined to not allow '*' and because, at one point in
557 // history, UTF8String was too new to use for compatibility reasons.
558 //
559 // UniversalString and BMPString are also deprecated, and they are a little
560 // harder to support because they are not single-byte ASCII superset
561 // encodings, so we don't bother.
562 if (valueEncodingTag != der::PrintableString &&
563 valueEncodingTag != der::UTF8String &&
564 valueEncodingTag != der::TeletexString) {
565 return Success;
566 }
567
568 if (IsValidPresentedDNSID(presentedID)) {
569 MatchSubjectPresentedIDWithReferenceID(GeneralNameType::dNSName,
570 presentedID, referenceIDType,
571 referenceID, match);
572 } else {
573 // We don't match CN-IDs for IPv6 addresses.
574 // MatchSubjectPresentedIDWithReferenceID ensures that it won't match an
575 // IPv4 address with an IPv6 address, so we don't need to check that
576 // referenceID is an IPv4 address here.
577 uint8_t ipv4[4];
578 if (ParseIPv4Address(presentedID, ipv4)) {
579 MatchSubjectPresentedIDWithReferenceID(GeneralNameType::iPAddress,
580 Input(ipv4), referenceIDType,
581 referenceID, match);
582 }
583 }
584
585 // Regardless of whether there was a match, we keep going in case we find
586 // another CN later. If we do find another one, then this match/mismatch
587 // will be ignored, because we only care about the most specific CN.
588
589 return Success;
590 }
591
592 // Match an email address against an emailAddress attribute in the
593 // subject.
594 //
595 // id-emailAddress AttributeType ::= { pkcs-9 1 }
596 //
597 // EmailAddress ::= IA5String (SIZE (1..ub-emailaddress-length))
598 //
599 // python DottedOIDToCode.py id-emailAddress 1.2.840.113549.1.9.1
600 static const uint8_t id_emailAddress[] = {0x2a, 0x86, 0x48, 0x86, 0xf7,
601 0x0d, 0x01, 0x09, 0x01};
602 if (fallBackToEmailAddress == FallBackToSearchWithinSubject::Yes &&
603 InputsAreEqual(type, Input(id_emailAddress))) {
604 if (referenceIDType == GeneralNameType::rfc822Name &&
605 match == MatchResult::Match) {
606 // We already found a match; we don't need to match another one
607 return Success;
608 }
609 if (valueEncodingTag != der::IA5String) {
610 return Result::ERROR_BAD_DER;
611 }
612 return MatchPresentedIDWithReferenceID(GeneralNameType::rfc822Name,
613 presentedID, referenceIDType,
614 referenceID, match);
615 }
616
617 return Success;
618 }
619
MatchSubjectPresentedIDWithReferenceID(GeneralNameType presentedIDType,Input presentedID,GeneralNameType referenceIDType,Input referenceID,MatchResult & match)620 void MatchSubjectPresentedIDWithReferenceID(GeneralNameType presentedIDType,
621 Input presentedID,
622 GeneralNameType referenceIDType,
623 Input referenceID,
624 /*in/out*/ MatchResult& match) {
625 Result rv = MatchPresentedIDWithReferenceID(
626 presentedIDType, presentedID, referenceIDType, referenceID, match);
627 if (rv != Success) {
628 match = MatchResult::Mismatch;
629 }
630 }
631
MatchPresentedIDWithReferenceID(GeneralNameType presentedIDType,Input presentedID,GeneralNameType referenceIDType,Input referenceID,MatchResult & matchResult)632 Result MatchPresentedIDWithReferenceID(GeneralNameType presentedIDType,
633 Input presentedID,
634 GeneralNameType referenceIDType,
635 Input referenceID,
636 /*out*/ MatchResult& matchResult) {
637 if (referenceIDType == GeneralNameType::nameConstraints) {
638 // matchResult is irrelevant when checking name constraints; only the
639 // pass/fail result of CheckPresentedIDConformsToConstraints matters.
640 return CheckPresentedIDConformsToConstraints(presentedIDType, presentedID,
641 referenceID);
642 }
643
644 if (presentedIDType != referenceIDType) {
645 matchResult = MatchResult::Mismatch;
646 return Success;
647 }
648
649 Result rv;
650 bool foundMatch;
651
652 switch (referenceIDType) {
653 case GeneralNameType::dNSName:
654 rv = MatchPresentedDNSIDWithReferenceDNSID(
655 presentedID, AllowWildcards::Yes, AllowDotlessSubdomainMatches::Yes,
656 IDRole::ReferenceID, referenceID, foundMatch);
657 break;
658
659 case GeneralNameType::iPAddress:
660 foundMatch = InputsAreEqual(presentedID, referenceID);
661 rv = Success;
662 break;
663
664 case GeneralNameType::rfc822Name:
665 rv = MatchPresentedRFC822NameWithReferenceRFC822Name(
666 presentedID, IDRole::ReferenceID, referenceID, foundMatch);
667 break;
668
669 case GeneralNameType::directoryName:
670 // TODO: At some point, we may add APIs for matching DirectoryNames.
671 // fall through
672
673 case GeneralNameType::otherName: // fall through
674 case GeneralNameType::x400Address: // fall through
675 case GeneralNameType::ediPartyName: // fall through
676 case GeneralNameType::uniformResourceIdentifier: // fall through
677 case GeneralNameType::registeredID: // fall through
678 case GeneralNameType::nameConstraints:
679 return NotReached("unexpected nameType for SearchType::Match",
680 Result::FATAL_ERROR_INVALID_ARGS);
681
682 MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
683 }
684
685 if (rv != Success) {
686 return rv;
687 }
688 matchResult = foundMatch ? MatchResult::Match : MatchResult::Mismatch;
689 return Success;
690 }
691
692 enum class NameConstraintsSubtrees : uint8_t {
693 permittedSubtrees = der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 0,
694 excludedSubtrees = der::CONSTRUCTED | der::CONTEXT_SPECIFIC | 1
695 };
696
697 Result CheckPresentedIDConformsToNameConstraintsSubtrees(
698 GeneralNameType presentedIDType, Input presentedID, Reader& nameConstraints,
699 NameConstraintsSubtrees subtreesType);
700 Result MatchPresentedIPAddressWithConstraint(Input presentedID,
701 Input iPAddressConstraint,
702 /*out*/ bool& foundMatch);
703 Result MatchPresentedDirectoryNameWithConstraint(
704 NameConstraintsSubtrees subtreesType, Input presentedID,
705 Input directoryNameConstraint, /*out*/ bool& matches);
706
CheckPresentedIDConformsToConstraints(GeneralNameType presentedIDType,Input presentedID,Input encodedNameConstraints)707 Result CheckPresentedIDConformsToConstraints(GeneralNameType presentedIDType,
708 Input presentedID,
709 Input encodedNameConstraints) {
710 // NameConstraints ::= SEQUENCE {
711 // permittedSubtrees [0] GeneralSubtrees OPTIONAL,
712 // excludedSubtrees [1] GeneralSubtrees OPTIONAL }
713 Reader nameConstraints;
714 Result rv = der::ExpectTagAndGetValueAtEnd(encodedNameConstraints,
715 der::SEQUENCE, nameConstraints);
716 if (rv != Success) {
717 return rv;
718 }
719
720 // RFC 5280 says "Conforming CAs MUST NOT issue certificates where name
721 // constraints is an empty sequence. That is, either the permittedSubtrees
722 // field or the excludedSubtrees MUST be present."
723 if (nameConstraints.AtEnd()) {
724 return Result::ERROR_BAD_DER;
725 }
726
727 rv = CheckPresentedIDConformsToNameConstraintsSubtrees(
728 presentedIDType, presentedID, nameConstraints,
729 NameConstraintsSubtrees::permittedSubtrees);
730 if (rv != Success) {
731 return rv;
732 }
733
734 rv = CheckPresentedIDConformsToNameConstraintsSubtrees(
735 presentedIDType, presentedID, nameConstraints,
736 NameConstraintsSubtrees::excludedSubtrees);
737 if (rv != Success) {
738 return rv;
739 }
740
741 return der::End(nameConstraints);
742 }
743
CheckPresentedIDConformsToNameConstraintsSubtrees(GeneralNameType presentedIDType,Input presentedID,Reader & nameConstraints,NameConstraintsSubtrees subtreesType)744 Result CheckPresentedIDConformsToNameConstraintsSubtrees(
745 GeneralNameType presentedIDType, Input presentedID, Reader& nameConstraints,
746 NameConstraintsSubtrees subtreesType) {
747 if (!nameConstraints.Peek(static_cast<uint8_t>(subtreesType))) {
748 return Success;
749 }
750
751 Reader subtrees;
752 Result rv = der::ExpectTagAndGetValue(
753 nameConstraints, static_cast<uint8_t>(subtreesType), subtrees);
754 if (rv != Success) {
755 return rv;
756 }
757
758 bool hasPermittedSubtreesMatch = false;
759 bool hasPermittedSubtreesMismatch = false;
760
761 // GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
762 //
763 // do { ... } while(...) because subtrees isn't allowed to be empty.
764 do {
765 // GeneralSubtree ::= SEQUENCE {
766 // base GeneralName,
767 // minimum [0] BaseDistance DEFAULT 0,
768 // maximum [1] BaseDistance OPTIONAL }
769 Reader subtree;
770 rv = ExpectTagAndGetValue(subtrees, der::SEQUENCE, subtree);
771 if (rv != Success) {
772 return rv;
773 }
774 GeneralNameType nameConstraintType;
775 Input base;
776 rv = ReadGeneralName(subtree, nameConstraintType, base);
777 if (rv != Success) {
778 return rv;
779 }
780 // http://tools.ietf.org/html/rfc5280#section-4.2.1.10: "Within this
781 // profile, the minimum and maximum fields are not used with any name
782 // forms, thus, the minimum MUST be zero, and maximum MUST be absent."
783 //
784 // Since the default value isn't allowed to be encoded according to the DER
785 // encoding rules for DEFAULT, this is equivalent to saying that neither
786 // minimum or maximum must be encoded.
787 rv = der::End(subtree);
788 if (rv != Success) {
789 return rv;
790 }
791
792 if (presentedIDType == nameConstraintType) {
793 bool matches;
794
795 switch (presentedIDType) {
796 case GeneralNameType::dNSName:
797 rv = MatchPresentedDNSIDWithReferenceDNSID(
798 presentedID, AllowWildcards::Yes,
799 AllowDotlessSubdomainMatches::Yes, IDRole::NameConstraint, base,
800 matches);
801 if (rv != Success) {
802 return rv;
803 }
804 break;
805
806 case GeneralNameType::iPAddress:
807 rv =
808 MatchPresentedIPAddressWithConstraint(presentedID, base, matches);
809 if (rv != Success) {
810 return rv;
811 }
812 break;
813
814 case GeneralNameType::directoryName:
815 rv = MatchPresentedDirectoryNameWithConstraint(
816 subtreesType, presentedID, base, matches);
817 if (rv != Success) {
818 return rv;
819 }
820 break;
821
822 case GeneralNameType::rfc822Name:
823 rv = MatchPresentedRFC822NameWithReferenceRFC822Name(
824 presentedID, IDRole::NameConstraint, base, matches);
825 if (rv != Success) {
826 return rv;
827 }
828 break;
829
830 // RFC 5280 says "Conforming CAs [...] SHOULD NOT impose name
831 // constraints on the x400Address, ediPartyName, or registeredID
832 // name forms. It also says "Applications conforming to this profile
833 // [...] SHOULD be able to process name constraints that are imposed
834 // on [...] uniformResourceIdentifier [...]", but we don't bother.
835 //
836 // TODO: Ask to have spec updated to say ""Conforming CAs [...] SHOULD
837 // NOT impose name constraints on the otherName, x400Address,
838 // ediPartyName, uniformResourceIdentifier, or registeredID name
839 // forms."
840 case GeneralNameType::otherName: // fall through
841 case GeneralNameType::x400Address: // fall through
842 case GeneralNameType::ediPartyName: // fall through
843 case GeneralNameType::uniformResourceIdentifier: // fall through
844 case GeneralNameType::registeredID: // fall through
845 return Result::ERROR_CERT_NOT_IN_NAME_SPACE;
846
847 case GeneralNameType::nameConstraints:
848 return NotReached("invalid presentedIDType",
849 Result::FATAL_ERROR_LIBRARY_FAILURE);
850
851 MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
852 }
853
854 switch (subtreesType) {
855 case NameConstraintsSubtrees::permittedSubtrees:
856 if (matches) {
857 hasPermittedSubtreesMatch = true;
858 } else {
859 hasPermittedSubtreesMismatch = true;
860 }
861 break;
862 case NameConstraintsSubtrees::excludedSubtrees:
863 if (matches) {
864 return Result::ERROR_CERT_NOT_IN_NAME_SPACE;
865 }
866 break;
867 }
868 }
869 } while (!subtrees.AtEnd());
870
871 if (hasPermittedSubtreesMismatch && !hasPermittedSubtreesMatch) {
872 // If there was any entry of the given type in permittedSubtrees, then it
873 // required that at least one of them must match. Since none of them did,
874 // we have a failure.
875 return Result::ERROR_CERT_NOT_IN_NAME_SPACE;
876 }
877
878 return Success;
879 }
880
881 // We do not distinguish between a syntactically-invalid presentedDNSID and one
882 // that is syntactically valid but does not match referenceDNSID; in both
883 // cases, the result is false.
884 //
885 // We assume that both presentedDNSID and referenceDNSID are encoded in such a
886 // way that US-ASCII (7-bit) characters are encoded in one byte and no encoding
887 // of a non-US-ASCII character contains a code point in the range 0-127. For
888 // example, UTF-8 is OK but UTF-16 is not.
889 //
890 // RFC6125 says that a wildcard label may be of the form <x>*<y>.<DNSID>, where
891 // <x> and/or <y> may be empty. However, NSS requires <y> to be empty, and we
892 // follow NSS's stricter policy by accepting wildcards only of the form
893 // <x>*.<DNSID>, where <x> may be empty.
894 //
895 // An relative presented DNS ID matches both an absolute reference ID and a
896 // relative reference ID. Absolute presented DNS IDs are not supported:
897 //
898 // Presented ID Reference ID Result
899 // -------------------------------------
900 // example.com example.com Match
901 // example.com. example.com Mismatch
902 // example.com example.com. Match
903 // example.com. example.com. Mismatch
904 //
905 // There are more subtleties documented inline in the code.
906 //
907 // Name constraints ///////////////////////////////////////////////////////////
908 //
909 // This is all RFC 5280 has to say about DNSName constraints:
910 //
911 // DNS name restrictions are expressed as host.example.com. Any DNS
912 // name that can be constructed by simply adding zero or more labels to
913 // the left-hand side of the name satisfies the name constraint. For
914 // example, www.host.example.com would satisfy the constraint but
915 // host1.example.com would not.
916 //
917 // This lack of specificity has lead to a lot of uncertainty regarding
918 // subdomain matching. In particular, the following questions have been
919 // raised and answered:
920 //
921 // Q: Does a presented identifier equal (case insensitive) to the name
922 // constraint match the constraint? For example, does the presented
923 // ID "host.example.com" match a "host.example.com" constraint?
924 // A: Yes. RFC5280 says "by simply adding zero or more labels" and this
925 // is the case of adding zero labels.
926 //
927 // Q: When the name constraint does not start with ".", do subdomain
928 // presented identifiers match it? For example, does the presented
929 // ID "www.host.example.com" match a "host.example.com" constraint?
930 // A: Yes. RFC5280 says "by simply adding zero or more labels" and this
931 // is the case of adding more than zero labels. The example is the
932 // one from RFC 5280.
933 //
934 // Q: When the name constraint does not start with ".", does a
935 // non-subdomain prefix match it? For example, does "bigfoo.bar.com"
936 // match "foo.bar.com"? [4]
937 // A: No. We interpret RFC 5280's language of "adding zero or more labels"
938 // to mean that whole labels must be prefixed.
939 //
940 // (Note that the above three scenarios are the same as the RFC 6265
941 // domain matching rules [0].)
942 //
943 // Q: Is a name constraint that starts with "." valid, and if so, what
944 // semantics does it have? For example, does a presented ID of
945 // "www.example.com" match a constraint of ".example.com"? Does a
946 // presented ID of "example.com" match a constraint of ".example.com"?
947 // A: This implementation, NSS[1], and SChannel[2] all support a
948 // leading ".", but OpenSSL[3] does not yet. Amongst the
949 // implementations that support it, a leading "." is legal and means
950 // the same thing as when the "." is omitted, EXCEPT that a
951 // presented identifier equal (case insensitive) to the name
952 // constraint is not matched; i.e. presented DNSName identifiers
953 // must be subdomains. Some CAs in Mozilla's CA program (e.g. HARICA)
954 // have name constraints with the leading "." in their root
955 // certificates. The name constraints imposed on DCISS by Mozilla also
956 // have the it, so supporting this is a requirement for backward
957 // compatibility, even if it is not yet standardized. So, for example, a
958 // presented ID of "www.example.com" matches a constraint of
959 // ".example.com" but a presented ID of "example.com" does not.
960 //
961 // Q: Is there a way to prevent subdomain matches?
962 // A: Yes.
963 //
964 // Some people have proposed that dNSName constraints that do not
965 // start with a "." should be restricted to exact (case insensitive)
966 // matches. However, such a change of semantics from what RFC5280
967 // specifies would be a non-backward-compatible change in the case of
968 // permittedSubtrees constraints, and it would be a security issue for
969 // excludedSubtrees constraints.
970 //
971 // However, it can be done with a combination of permittedSubtrees and
972 // excludedSubtrees, e.g. "example.com" in permittedSubtrees and
973 // ".example.com" in excudedSubtrees.
974 //
975 // Q: Are name constraints allowed to be specified as absolute names?
976 // For example, does a presented ID of "example.com" match a name
977 // constraint of "example.com." and vice versa.
978 // A: Absolute names are not supported as presented IDs or name
979 // constraints. Only reference IDs may be absolute.
980 //
981 // Q: Is "" a valid DNSName constraints? If so, what does it mean?
982 // A: Yes. Any valid presented DNSName can be formed "by simply adding zero
983 // or more labels to the left-hand side" of "". In particular, an
984 // excludedSubtrees DNSName constraint of "" forbids all DNSNames.
985 //
986 // Q: Is "." a valid DNSName constraints? If so, what does it mean?
987 // A: No, because absolute names are not allowed (see above).
988 //
989 // [0] RFC 6265 (Cookies) Domain Matching rules:
990 // http://tools.ietf.org/html/rfc6265#section-5.1.3
991 // [1] NSS source code:
992 // https://mxr.mozilla.org/nss/source/lib/certdb/genname.c?rev=2a7348f013cb#1209
993 // [2] Description of SChannel's behavior from Microsoft:
994 // http://www.imc.org/ietf-pkix/mail-archive/msg04668.html
995 // [3] Proposal to add such support to OpenSSL:
996 // http://www.mail-archive.com/openssl-dev%40openssl.org/msg36204.html
997 // https://rt.openssl.org/Ticket/Display.html?id=3562
998 // [4] Feedback on the lack of clarify in the definition that never got
999 // incorporated into the spec:
1000 // https://www.ietf.org/mail-archive/web/pkix/current/msg21192.html
MatchPresentedDNSIDWithReferenceDNSID(Input presentedDNSID,AllowWildcards allowWildcards,AllowDotlessSubdomainMatches allowDotlessSubdomainMatches,IDRole referenceDNSIDRole,Input referenceDNSID,bool & matches)1001 Result MatchPresentedDNSIDWithReferenceDNSID(
1002 Input presentedDNSID, AllowWildcards allowWildcards,
1003 AllowDotlessSubdomainMatches allowDotlessSubdomainMatches,
1004 IDRole referenceDNSIDRole, Input referenceDNSID,
1005 /*out*/ bool& matches) {
1006 if (!IsValidDNSID(presentedDNSID, IDRole::PresentedID, allowWildcards)) {
1007 return Result::ERROR_BAD_DER;
1008 }
1009
1010 if (!IsValidDNSID(referenceDNSID, referenceDNSIDRole, AllowWildcards::No)) {
1011 return Result::ERROR_BAD_DER;
1012 }
1013
1014 Reader presented(presentedDNSID);
1015 Reader reference(referenceDNSID);
1016
1017 switch (referenceDNSIDRole) {
1018 case IDRole::ReferenceID:
1019 break;
1020
1021 case IDRole::NameConstraint: {
1022 if (presentedDNSID.GetLength() > referenceDNSID.GetLength()) {
1023 if (referenceDNSID.GetLength() == 0) {
1024 // An empty constraint matches everything.
1025 matches = true;
1026 return Success;
1027 }
1028 // If the reference ID starts with a dot then skip the prefix of
1029 // of the presented ID and start the comparison at the position of that
1030 // dot. Examples:
1031 //
1032 // Matches Doesn't Match
1033 // -----------------------------------------------------------
1034 // original presented ID: www.example.com badexample.com
1035 // skipped: www ba
1036 // presented ID w/o prefix: .example.com dexample.com
1037 // reference ID: .example.com .example.com
1038 //
1039 // If the reference ID does not start with a dot then we skip the
1040 // prefix of the presented ID but also verify that the prefix ends with
1041 // a dot. Examples:
1042 //
1043 // Matches Doesn't Match
1044 // -----------------------------------------------------------
1045 // original presented ID: www.example.com badexample.com
1046 // skipped: www ba
1047 // must be '.': . d
1048 // presented ID w/o prefix: example.com example.com
1049 // reference ID: example.com example.com
1050 //
1051 if (reference.Peek('.')) {
1052 if (presented.Skip(static_cast<Input::size_type>(
1053 presentedDNSID.GetLength() - referenceDNSID.GetLength())) !=
1054 Success) {
1055 return NotReached("skipping subdomain failed",
1056 Result::FATAL_ERROR_LIBRARY_FAILURE);
1057 }
1058 } else if (allowDotlessSubdomainMatches ==
1059 AllowDotlessSubdomainMatches::Yes) {
1060 if (presented.Skip(static_cast<Input::size_type>(
1061 presentedDNSID.GetLength() - referenceDNSID.GetLength() -
1062 1)) != Success) {
1063 return NotReached("skipping subdomains failed",
1064 Result::FATAL_ERROR_LIBRARY_FAILURE);
1065 }
1066 uint8_t b;
1067 if (presented.Read(b) != Success) {
1068 return NotReached("reading from presentedDNSID failed",
1069 Result::FATAL_ERROR_LIBRARY_FAILURE);
1070 }
1071 if (b != '.') {
1072 matches = false;
1073 return Success;
1074 }
1075 }
1076 }
1077 break;
1078 }
1079
1080 case IDRole::PresentedID: // fall through
1081 return NotReached("IDRole::PresentedID is not a valid referenceDNSIDRole",
1082 Result::FATAL_ERROR_INVALID_ARGS);
1083 }
1084
1085 // We only allow wildcard labels that consist only of '*'.
1086 if (presented.Peek('*')) {
1087 if (presented.Skip(1) != Success) {
1088 return NotReached("Skipping '*' failed",
1089 Result::FATAL_ERROR_LIBRARY_FAILURE);
1090 }
1091 do {
1092 // This will happen if reference is a single, relative label
1093 if (reference.AtEnd()) {
1094 matches = false;
1095 return Success;
1096 }
1097 uint8_t referenceByte;
1098 if (reference.Read(referenceByte) != Success) {
1099 return NotReached("invalid reference ID",
1100 Result::FATAL_ERROR_INVALID_ARGS);
1101 }
1102 } while (!reference.Peek('.'));
1103 }
1104
1105 for (;;) {
1106 uint8_t presentedByte;
1107 if (presented.Read(presentedByte) != Success) {
1108 matches = false;
1109 return Success;
1110 }
1111 uint8_t referenceByte;
1112 if (reference.Read(referenceByte) != Success) {
1113 matches = false;
1114 return Success;
1115 }
1116 if (LocaleInsensitveToLower(presentedByte) !=
1117 LocaleInsensitveToLower(referenceByte)) {
1118 matches = false;
1119 return Success;
1120 }
1121 if (presented.AtEnd()) {
1122 // Don't allow presented IDs to be absolute.
1123 if (presentedByte == '.') {
1124 return Result::ERROR_BAD_DER;
1125 }
1126 break;
1127 }
1128 }
1129
1130 // Allow a relative presented DNS ID to match an absolute reference DNS ID,
1131 // unless we're matching a name constraint.
1132 if (!reference.AtEnd()) {
1133 if (referenceDNSIDRole != IDRole::NameConstraint) {
1134 uint8_t referenceByte;
1135 if (reference.Read(referenceByte) != Success) {
1136 return NotReached("read failed but not at end",
1137 Result::FATAL_ERROR_LIBRARY_FAILURE);
1138 }
1139 if (referenceByte != '.') {
1140 matches = false;
1141 return Success;
1142 }
1143 }
1144 if (!reference.AtEnd()) {
1145 matches = false;
1146 return Success;
1147 }
1148 }
1149
1150 matches = true;
1151 return Success;
1152 }
1153
1154 // https://tools.ietf.org/html/rfc5280#section-4.2.1.10 says:
1155 //
1156 // For IPv4 addresses, the iPAddress field of GeneralName MUST contain
1157 // eight (8) octets, encoded in the style of RFC 4632 (CIDR) to represent
1158 // an address range [RFC4632]. For IPv6 addresses, the iPAddress field
1159 // MUST contain 32 octets similarly encoded. For example, a name
1160 // constraint for "class C" subnet 192.0.2.0 is represented as the
1161 // octets C0 00 02 00 FF FF FF 00, representing the CIDR notation
1162 // 192.0.2.0/24 (mask 255.255.255.0).
MatchPresentedIPAddressWithConstraint(Input presentedID,Input iPAddressConstraint,bool & foundMatch)1163 Result MatchPresentedIPAddressWithConstraint(Input presentedID,
1164 Input iPAddressConstraint,
1165 /*out*/ bool& foundMatch) {
1166 if (presentedID.GetLength() != 4 && presentedID.GetLength() != 16) {
1167 return Result::ERROR_BAD_DER;
1168 }
1169 if (iPAddressConstraint.GetLength() != 8 &&
1170 iPAddressConstraint.GetLength() != 32) {
1171 return Result::ERROR_BAD_DER;
1172 }
1173
1174 // an IPv4 address never matches an IPv6 constraint, and vice versa.
1175 if (presentedID.GetLength() * 2 != iPAddressConstraint.GetLength()) {
1176 foundMatch = false;
1177 return Success;
1178 }
1179
1180 Reader constraint(iPAddressConstraint);
1181 Reader constraintAddress;
1182 Result rv =
1183 constraint.Skip(iPAddressConstraint.GetLength() / 2u, constraintAddress);
1184 if (rv != Success) {
1185 return rv;
1186 }
1187 Reader constraintMask;
1188 rv = constraint.Skip(iPAddressConstraint.GetLength() / 2u, constraintMask);
1189 if (rv != Success) {
1190 return rv;
1191 }
1192 rv = der::End(constraint);
1193 if (rv != Success) {
1194 return rv;
1195 }
1196
1197 Reader presented(presentedID);
1198 do {
1199 uint8_t presentedByte;
1200 rv = presented.Read(presentedByte);
1201 if (rv != Success) {
1202 return rv;
1203 }
1204 uint8_t constraintAddressByte;
1205 rv = constraintAddress.Read(constraintAddressByte);
1206 if (rv != Success) {
1207 return rv;
1208 }
1209 uint8_t constraintMaskByte;
1210 rv = constraintMask.Read(constraintMaskByte);
1211 if (rv != Success) {
1212 return rv;
1213 }
1214 foundMatch =
1215 ((presentedByte ^ constraintAddressByte) & constraintMaskByte) == 0;
1216 } while (foundMatch && !presented.AtEnd());
1217
1218 return Success;
1219 }
1220
1221 // AttributeTypeAndValue ::= SEQUENCE {
1222 // type AttributeType,
1223 // value AttributeValue }
1224 //
1225 // AttributeType ::= OBJECT IDENTIFIER
1226 //
1227 // AttributeValue ::= ANY -- DEFINED BY AttributeType
ReadAVA(Reader & rdn,Input & type,uint8_t & valueTag,Input & value)1228 Result ReadAVA(Reader& rdn,
1229 /*out*/ Input& type,
1230 /*out*/ uint8_t& valueTag,
1231 /*out*/ Input& value) {
1232 return der::Nested(rdn, der::SEQUENCE, [&](Reader& ava) -> Result {
1233 Result rv = der::ExpectTagAndGetValue(ava, der::OIDTag, type);
1234 if (rv != Success) {
1235 return rv;
1236 }
1237 rv = der::ReadTagAndGetValue(ava, valueTag, value);
1238 if (rv != Success) {
1239 return rv;
1240 }
1241 return Success;
1242 });
1243 }
1244
1245 // Names are sequences of RDNs. RDNS are sets of AVAs. That means that RDNs are
1246 // unordered, so in theory we should match RDNs with equivalent AVAs that are
1247 // in different orders. Within the AVAs are DirectoryNames that are supposed to
1248 // be compared according to LDAP stringprep normalization rules (e.g.
1249 // normalizing whitespace), consideration of different character encodings,
1250 // etc. Indeed, RFC 5280 says we MUST deal with all of that.
1251 //
1252 // In practice, many implementations, including NSS, only match Names in a way
1253 // that only meets a subset of the requirements of RFC 5280. Those
1254 // normalization and character encoding conversion steps appear to be
1255 // unnecessary for processing real-world certificates, based on experience from
1256 // having used NSS in Firefox for many years.
1257 //
1258 // RFC 5280 also says "CAs issuing certificates with a restriction of the form
1259 // directoryName SHOULD NOT rely on implementation of the full
1260 // ISO DN name comparison algorithm. This implies name restrictions MUST
1261 // be stated identically to the encoding used in the subject field or
1262 // subjectAltName extension." It goes on to say, in the security
1263 // considerations:
1264 //
1265 // In addition, name constraints for distinguished names MUST be stated
1266 // identically to the encoding used in the subject field or
1267 // subjectAltName extension. If not, then name constraints stated as
1268 // excludedSubtrees will not match and invalid paths will be accepted
1269 // and name constraints expressed as permittedSubtrees will not match
1270 // and valid paths will be rejected. To avoid acceptance of invalid
1271 // paths, CAs SHOULD state name constraints for distinguished names as
1272 // permittedSubtrees wherever possible.
1273 //
1274 // For permittedSubtrees, the MUST-level requirement is relaxed for
1275 // compatibility in the case of PrintableString and UTF8String. That is, if a
1276 // name constraint has been encoded using UTF8String and the presented ID has
1277 // been encoded with a PrintableString (or vice-versa), they are considered to
1278 // match if they are equal everywhere except for the tag identifying the
1279 // encoding. See bug 1150114.
1280 //
1281 // For excludedSubtrees, we simply prohibit any non-empty directoryName
1282 // constraint to ensure we are not being too lenient. We support empty
1283 // DirectoryName constraints in excludedSubtrees so that a CA can say "Do not
1284 // allow any DirectoryNames in issued certificates."
MatchPresentedDirectoryNameWithConstraint(NameConstraintsSubtrees subtreesType,Input presentedID,Input directoryNameConstraint,bool & matches)1285 Result MatchPresentedDirectoryNameWithConstraint(
1286 NameConstraintsSubtrees subtreesType, Input presentedID,
1287 Input directoryNameConstraint,
1288 /*out*/ bool& matches) {
1289 Reader constraintRDNs;
1290 Result rv = der::ExpectTagAndGetValueAtEnd(directoryNameConstraint,
1291 der::SEQUENCE, constraintRDNs);
1292 if (rv != Success) {
1293 return rv;
1294 }
1295 Reader presentedRDNs;
1296 rv =
1297 der::ExpectTagAndGetValueAtEnd(presentedID, der::SEQUENCE, presentedRDNs);
1298 if (rv != Success) {
1299 return rv;
1300 }
1301
1302 switch (subtreesType) {
1303 case NameConstraintsSubtrees::permittedSubtrees:
1304 break; // dealt with below
1305 case NameConstraintsSubtrees::excludedSubtrees:
1306 if (!constraintRDNs.AtEnd() || !presentedRDNs.AtEnd()) {
1307 return Result::ERROR_CERT_NOT_IN_NAME_SPACE;
1308 }
1309 matches = true;
1310 return Success;
1311 }
1312
1313 for (;;) {
1314 // The AVAs have to be fully equal, but the constraint RDNs just need to be
1315 // a prefix of the presented RDNs.
1316 if (constraintRDNs.AtEnd()) {
1317 matches = true;
1318 return Success;
1319 }
1320 if (presentedRDNs.AtEnd()) {
1321 matches = false;
1322 return Success;
1323 }
1324 Reader constraintRDN;
1325 rv = der::ExpectTagAndGetValue(constraintRDNs, der::SET, constraintRDN);
1326 if (rv != Success) {
1327 return rv;
1328 }
1329 Reader presentedRDN;
1330 rv = der::ExpectTagAndGetValue(presentedRDNs, der::SET, presentedRDN);
1331 if (rv != Success) {
1332 return rv;
1333 }
1334 while (!constraintRDN.AtEnd() && !presentedRDN.AtEnd()) {
1335 Input constraintType;
1336 uint8_t constraintValueTag;
1337 Input constraintValue;
1338 rv = ReadAVA(constraintRDN, constraintType, constraintValueTag,
1339 constraintValue);
1340 if (rv != Success) {
1341 return rv;
1342 }
1343 Input presentedType;
1344 uint8_t presentedValueTag;
1345 Input presentedValue;
1346 rv = ReadAVA(presentedRDN, presentedType, presentedValueTag,
1347 presentedValue);
1348 if (rv != Success) {
1349 return rv;
1350 }
1351 // TODO (bug 1155767): verify that if an AVA is a PrintableString it
1352 // consists only of characters valid for PrintableStrings.
1353 bool avasMatch = InputsAreEqual(constraintType, presentedType) &&
1354 InputsAreEqual(constraintValue, presentedValue) &&
1355 (constraintValueTag == presentedValueTag ||
1356 (constraintValueTag == der::Tag::UTF8String &&
1357 presentedValueTag == der::Tag::PrintableString) ||
1358 (constraintValueTag == der::Tag::PrintableString &&
1359 presentedValueTag == der::Tag::UTF8String));
1360 if (!avasMatch) {
1361 matches = false;
1362 return Success;
1363 }
1364 }
1365 if (!constraintRDN.AtEnd() || !presentedRDN.AtEnd()) {
1366 matches = false;
1367 return Success;
1368 }
1369 }
1370 }
1371
1372 // RFC 5280 says:
1373 //
1374 // The format of an rfc822Name is a "Mailbox" as defined in Section 4.1.2
1375 // of [RFC2821]. A Mailbox has the form "Local-part@Domain". Note that a
1376 // Mailbox has no phrase (such as a common name) before it, has no comment
1377 // (text surrounded in parentheses) after it, and is not surrounded by "<"
1378 // and ">". Rules for encoding Internet mail addresses that include
1379 // internationalized domain names are specified in Section 7.5.
1380 //
1381 // and:
1382 //
1383 // A name constraint for Internet mail addresses MAY specify a
1384 // particular mailbox, all addresses at a particular host, or all
1385 // mailboxes in a domain. To indicate a particular mailbox, the
1386 // constraint is the complete mail address. For example,
1387 // "root@example.com" indicates the root mailbox on the host
1388 // "example.com". To indicate all Internet mail addresses on a
1389 // particular host, the constraint is specified as the host name. For
1390 // example, the constraint "example.com" is satisfied by any mail
1391 // address at the host "example.com". To specify any address within a
1392 // domain, the constraint is specified with a leading period (as with
1393 // URIs). For example, ".example.com" indicates all the Internet mail
1394 // addresses in the domain "example.com", but not Internet mail
1395 // addresses on the host "example.com".
1396
IsValidRFC822Name(Input input)1397 bool IsValidRFC822Name(Input input) {
1398 Reader reader(input);
1399
1400 // Local-part@.
1401 bool startOfAtom = true;
1402 for (;;) {
1403 uint8_t presentedByte;
1404 if (reader.Read(presentedByte) != Success) {
1405 return false;
1406 }
1407 switch (presentedByte) {
1408 // atext is defined in https://tools.ietf.org/html/rfc2822#section-3.2.4
1409 case 'A':
1410 case 'a':
1411 case 'N':
1412 case 'n':
1413 case '0':
1414 case '!':
1415 case '#':
1416 case 'B':
1417 case 'b':
1418 case 'O':
1419 case 'o':
1420 case '1':
1421 case '$':
1422 case '%':
1423 case 'C':
1424 case 'c':
1425 case 'P':
1426 case 'p':
1427 case '2':
1428 case '&':
1429 case '\'':
1430 case 'D':
1431 case 'd':
1432 case 'Q':
1433 case 'q':
1434 case '3':
1435 case '*':
1436 case '+':
1437 case 'E':
1438 case 'e':
1439 case 'R':
1440 case 'r':
1441 case '4':
1442 case '-':
1443 case '/':
1444 case 'F':
1445 case 'f':
1446 case 'S':
1447 case 's':
1448 case '5':
1449 case '=':
1450 case '?':
1451 case 'G':
1452 case 'g':
1453 case 'T':
1454 case 't':
1455 case '6':
1456 case '^':
1457 case '_':
1458 case 'H':
1459 case 'h':
1460 case 'U':
1461 case 'u':
1462 case '7':
1463 case '`':
1464 case '{':
1465 case 'I':
1466 case 'i':
1467 case 'V':
1468 case 'v':
1469 case '8':
1470 case '|':
1471 case '}':
1472 case 'J':
1473 case 'j':
1474 case 'W':
1475 case 'w':
1476 case '9':
1477 case '~':
1478 case 'K':
1479 case 'k':
1480 case 'X':
1481 case 'x':
1482 case 'L':
1483 case 'l':
1484 case 'Y':
1485 case 'y':
1486 case 'M':
1487 case 'm':
1488 case 'Z':
1489 case 'z':
1490 startOfAtom = false;
1491 break;
1492
1493 case '.':
1494 if (startOfAtom) {
1495 return false;
1496 }
1497 startOfAtom = true;
1498 break;
1499
1500 case '@': {
1501 if (startOfAtom) {
1502 return false;
1503 }
1504 Input domain;
1505 if (reader.SkipToEnd(domain) != Success) {
1506 return false;
1507 }
1508 return IsValidDNSID(domain, IDRole::PresentedID, AllowWildcards::No);
1509 }
1510
1511 default:
1512 return false;
1513 }
1514 }
1515 }
1516
MatchPresentedRFC822NameWithReferenceRFC822Name(Input presentedRFC822Name,IDRole referenceRFC822NameRole,Input referenceRFC822Name,bool & matches)1517 Result MatchPresentedRFC822NameWithReferenceRFC822Name(
1518 Input presentedRFC822Name, IDRole referenceRFC822NameRole,
1519 Input referenceRFC822Name,
1520 /*out*/ bool& matches) {
1521 if (!IsValidRFC822Name(presentedRFC822Name)) {
1522 return Result::ERROR_BAD_DER;
1523 }
1524 Reader presented(presentedRFC822Name);
1525
1526 switch (referenceRFC822NameRole) {
1527 case IDRole::PresentedID:
1528 return Result::FATAL_ERROR_INVALID_ARGS;
1529
1530 case IDRole::ReferenceID:
1531 break;
1532
1533 case IDRole::NameConstraint: {
1534 if (InputContains(referenceRFC822Name, '@')) {
1535 // The constraint is of the form "Local-part@Domain".
1536 break;
1537 }
1538
1539 // The constraint is of the form "example.com" or ".example.com".
1540
1541 // Skip past the '@' in the presented ID.
1542 for (;;) {
1543 uint8_t presentedByte;
1544 if (presented.Read(presentedByte) != Success) {
1545 return Result::FATAL_ERROR_LIBRARY_FAILURE;
1546 }
1547 if (presentedByte == '@') {
1548 break;
1549 }
1550 }
1551
1552 Input presentedDNSID;
1553 if (presented.SkipToEnd(presentedDNSID) != Success) {
1554 return Result::FATAL_ERROR_LIBRARY_FAILURE;
1555 }
1556
1557 return MatchPresentedDNSIDWithReferenceDNSID(
1558 presentedDNSID, AllowWildcards::No, AllowDotlessSubdomainMatches::No,
1559 IDRole::NameConstraint, referenceRFC822Name, matches);
1560 }
1561 }
1562
1563 if (!IsValidRFC822Name(referenceRFC822Name)) {
1564 return Result::ERROR_BAD_DER;
1565 }
1566
1567 Reader reference(referenceRFC822Name);
1568
1569 for (;;) {
1570 uint8_t presentedByte;
1571 if (presented.Read(presentedByte) != Success) {
1572 matches = reference.AtEnd();
1573 return Success;
1574 }
1575 uint8_t referenceByte;
1576 if (reference.Read(referenceByte) != Success) {
1577 matches = false;
1578 return Success;
1579 }
1580 if (LocaleInsensitveToLower(presentedByte) !=
1581 LocaleInsensitveToLower(referenceByte)) {
1582 matches = false;
1583 return Success;
1584 }
1585 }
1586 }
1587
1588 // We avoid isdigit because it is locale-sensitive. See
1589 // http://pubs.opengroup.org/onlinepubs/009695399/functions/tolower.html.
LocaleInsensitveToLower(uint8_t a)1590 inline uint8_t LocaleInsensitveToLower(uint8_t a) {
1591 if (a >= 'A' && a <= 'Z') { // unlikely
1592 return static_cast<uint8_t>(
1593 static_cast<uint8_t>(a - static_cast<uint8_t>('A')) +
1594 static_cast<uint8_t>('a'));
1595 }
1596 return a;
1597 }
1598
StartsWithIDNALabel(Input id)1599 bool StartsWithIDNALabel(Input id) {
1600 static const uint8_t IDN_ALABEL_PREFIX[4] = {'x', 'n', '-', '-'};
1601 Reader input(id);
1602 for (const uint8_t prefixByte : IDN_ALABEL_PREFIX) {
1603 uint8_t b;
1604 if (input.Read(b) != Success) {
1605 return false;
1606 }
1607 if (b != prefixByte) {
1608 return false;
1609 }
1610 }
1611 return true;
1612 }
1613
ReadIPv4AddressComponent(Reader & input,bool lastComponent,uint8_t & valueOut)1614 bool ReadIPv4AddressComponent(Reader& input, bool lastComponent,
1615 /*out*/ uint8_t& valueOut) {
1616 size_t length = 0;
1617 unsigned int value = 0; // Must be larger than uint8_t.
1618
1619 for (;;) {
1620 if (input.AtEnd() && lastComponent) {
1621 break;
1622 }
1623
1624 uint8_t b;
1625 if (input.Read(b) != Success) {
1626 return false;
1627 }
1628
1629 if (b >= '0' && b <= '9') {
1630 if (value == 0 && length > 0) {
1631 return false; // Leading zeros are not allowed.
1632 }
1633 value = (value * 10) + (b - '0');
1634 if (value > 255) {
1635 return false; // Component's value is too large.
1636 }
1637 ++length;
1638 } else if (!lastComponent && b == '.') {
1639 break;
1640 } else {
1641 return false; // Invalid character.
1642 }
1643 }
1644
1645 if (length == 0) {
1646 return false; // empty components not allowed
1647 }
1648
1649 valueOut = static_cast<uint8_t>(value);
1650 return true;
1651 }
1652
1653 } // namespace
1654
1655 // On Windows and maybe other platforms, OS-provided IP address parsing
1656 // functions might fail if the protocol (IPv4 or IPv6) has been disabled, so we
1657 // can't rely on them.
ParseIPv4Address(Input hostname,uint8_t (& out)[4])1658 bool ParseIPv4Address(Input hostname, /*out*/ uint8_t (&out)[4]) {
1659 Reader input(hostname);
1660 return ReadIPv4AddressComponent(input, false, out[0]) &&
1661 ReadIPv4AddressComponent(input, false, out[1]) &&
1662 ReadIPv4AddressComponent(input, false, out[2]) &&
1663 ReadIPv4AddressComponent(input, true, out[3]);
1664 }
1665
1666 namespace {
1667
FinishIPv6Address(uint8_t (& address)[16],int numComponents,int contractionIndex)1668 bool FinishIPv6Address(/*in/out*/ uint8_t (&address)[16], int numComponents,
1669 int contractionIndex) {
1670 assert(numComponents >= 0);
1671 assert(numComponents <= 8);
1672 assert(contractionIndex >= -1);
1673 assert(contractionIndex <= 8);
1674 assert(contractionIndex <= numComponents);
1675 if (!(numComponents >= 0 && numComponents <= 8 && contractionIndex >= -1 &&
1676 contractionIndex <= 8 && contractionIndex <= numComponents)) {
1677 return false;
1678 }
1679
1680 if (contractionIndex == -1) {
1681 // no contraction
1682 return numComponents == 8;
1683 }
1684
1685 if (numComponents >= 8) {
1686 return false; // no room left to expand the contraction.
1687 }
1688
1689 // Shift components that occur after the contraction over.
1690 std::copy_backward(address + (2u * static_cast<size_t>(contractionIndex)),
1691 address + (2u * static_cast<size_t>(numComponents)),
1692 address + (2u * 8u));
1693 // Fill in the contracted area with zeros.
1694 std::fill_n(address + 2u * static_cast<size_t>(contractionIndex),
1695 (8u - static_cast<size_t>(numComponents)) * 2u,
1696 static_cast<uint8_t>(0u));
1697
1698 return true;
1699 }
1700
1701 } // namespace
1702
1703 // On Windows and maybe other platforms, OS-provided IP address parsing
1704 // functions might fail if the protocol (IPv4 or IPv6) has been disabled, so we
1705 // can't rely on them.
ParseIPv6Address(Input hostname,uint8_t (& out)[16])1706 bool ParseIPv6Address(Input hostname, /*out*/ uint8_t (&out)[16]) {
1707 Reader input(hostname);
1708
1709 int currentComponentIndex = 0;
1710 int contractionIndex = -1;
1711
1712 if (input.Peek(':')) {
1713 // A valid input can only start with ':' if there is a contraction at the
1714 // beginning.
1715 uint8_t b;
1716 if (input.Read(b) != Success || b != ':') {
1717 assert(false);
1718 return false;
1719 }
1720 if (input.Read(b) != Success) {
1721 return false;
1722 }
1723 if (b != ':') {
1724 return false;
1725 }
1726 contractionIndex = 0;
1727 }
1728
1729 for (;;) {
1730 // If we encounter a '.' then we'll have to backtrack to parse the input
1731 // from startOfComponent to the end of the input as an IPv4 address.
1732 Reader::Mark startOfComponent(input.GetMark());
1733 uint16_t componentValue = 0;
1734 size_t componentLength = 0;
1735 while (!input.AtEnd() && !input.Peek(':')) {
1736 uint8_t value;
1737 uint8_t b;
1738 if (input.Read(b) != Success) {
1739 assert(false);
1740 return false;
1741 }
1742 switch (b) {
1743 case '0':
1744 case '1':
1745 case '2':
1746 case '3':
1747 case '4':
1748 case '5':
1749 case '6':
1750 case '7':
1751 case '8':
1752 case '9':
1753 value = static_cast<uint8_t>(b - static_cast<uint8_t>('0'));
1754 break;
1755 case 'a':
1756 case 'b':
1757 case 'c':
1758 case 'd':
1759 case 'e':
1760 case 'f':
1761 value =
1762 static_cast<uint8_t>(b - static_cast<uint8_t>('a') + UINT8_C(10));
1763 break;
1764 case 'A':
1765 case 'B':
1766 case 'C':
1767 case 'D':
1768 case 'E':
1769 case 'F':
1770 value =
1771 static_cast<uint8_t>(b - static_cast<uint8_t>('A') + UINT8_C(10));
1772 break;
1773 case '.': {
1774 // A dot indicates we hit a IPv4-syntax component. Backtrack, parsing
1775 // the input from startOfComponent to the end of the input as an IPv4
1776 // address, and then combine it with the other components.
1777
1778 if (currentComponentIndex > 6) {
1779 return false; // Too many components before the IPv4 component
1780 }
1781
1782 input.SkipToEnd();
1783 Input ipv4Component;
1784 if (input.GetInput(startOfComponent, ipv4Component) != Success) {
1785 return false;
1786 }
1787 uint8_t(*ipv4)[4] =
1788 reinterpret_cast<uint8_t(*)[4]>(&out[2 * currentComponentIndex]);
1789 if (!ParseIPv4Address(ipv4Component, *ipv4)) {
1790 return false;
1791 }
1792 assert(input.AtEnd());
1793 currentComponentIndex += 2;
1794
1795 return FinishIPv6Address(out, currentComponentIndex,
1796 contractionIndex);
1797 }
1798 default:
1799 return false;
1800 }
1801 if (componentLength >= 4) {
1802 // component too long
1803 return false;
1804 }
1805 ++componentLength;
1806 componentValue = (componentValue * 0x10u) + value;
1807 }
1808
1809 if (currentComponentIndex >= 8) {
1810 return false; // too many components
1811 }
1812
1813 if (componentLength == 0) {
1814 if (input.AtEnd() && currentComponentIndex == contractionIndex) {
1815 if (contractionIndex == 0) {
1816 // don't accept "::"
1817 return false;
1818 }
1819 return FinishIPv6Address(out, currentComponentIndex, contractionIndex);
1820 }
1821 return false;
1822 }
1823
1824 out[2 * currentComponentIndex] =
1825 static_cast<uint8_t>(componentValue / 0x100);
1826 out[(2 * currentComponentIndex) + 1] =
1827 static_cast<uint8_t>(componentValue % 0x100);
1828
1829 ++currentComponentIndex;
1830
1831 if (input.AtEnd()) {
1832 return FinishIPv6Address(out, currentComponentIndex, contractionIndex);
1833 }
1834
1835 uint8_t b;
1836 if (input.Read(b) != Success || b != ':') {
1837 assert(false);
1838 return false;
1839 }
1840
1841 if (input.Peek(':')) {
1842 // Contraction
1843 if (contractionIndex != -1) {
1844 return false; // multiple contractions are not allowed.
1845 }
1846 if (input.Read(b) != Success || b != ':') {
1847 assert(false);
1848 return false;
1849 }
1850 contractionIndex = currentComponentIndex;
1851 if (input.AtEnd()) {
1852 // "::" at the end of the input.
1853 return FinishIPv6Address(out, currentComponentIndex, contractionIndex);
1854 }
1855 }
1856 }
1857 }
1858
IsValidReferenceDNSID(Input hostname)1859 bool IsValidReferenceDNSID(Input hostname) {
1860 return IsValidDNSID(hostname, IDRole::ReferenceID, AllowWildcards::No);
1861 }
1862
IsValidPresentedDNSID(Input hostname)1863 bool IsValidPresentedDNSID(Input hostname) {
1864 return IsValidDNSID(hostname, IDRole::PresentedID, AllowWildcards::Yes);
1865 }
1866
1867 namespace {
1868
1869 // RFC 5280 Section 4.2.1.6 says that a dNSName "MUST be in the 'preferred name
1870 // syntax', as specified by Section 3.5 of [RFC1034] and as modified by Section
1871 // 2.1 of [RFC1123]" except "a dNSName of ' ' MUST NOT be used." Additionally,
1872 // we allow underscores for compatibility with existing practice.
IsValidDNSID(Input hostname,IDRole idRole,AllowWildcards allowWildcards)1873 bool IsValidDNSID(Input hostname, IDRole idRole,
1874 AllowWildcards allowWildcards) {
1875 if (hostname.GetLength() > 253) {
1876 return false;
1877 }
1878
1879 Reader input(hostname);
1880
1881 if (idRole == IDRole::NameConstraint && input.AtEnd()) {
1882 return true;
1883 }
1884
1885 size_t dotCount = 0;
1886 size_t labelLength = 0;
1887 bool labelIsAllNumeric = false;
1888 bool labelEndsWithHyphen = false;
1889
1890 // Only presented IDs are allowed to have wildcard labels. And, like
1891 // Chromium, be stricter than RFC 6125 requires by insisting that a
1892 // wildcard label consist only of '*'.
1893 bool isWildcard = allowWildcards == AllowWildcards::Yes && input.Peek('*');
1894 bool isFirstByte = !isWildcard;
1895 if (isWildcard) {
1896 Result rv = input.Skip(1);
1897 if (rv != Success) {
1898 assert(false);
1899 return false;
1900 }
1901
1902 uint8_t b;
1903 rv = input.Read(b);
1904 if (rv != Success) {
1905 return false;
1906 }
1907 if (b != '.') {
1908 return false;
1909 }
1910 ++dotCount;
1911 }
1912
1913 do {
1914 static const size_t MAX_LABEL_LENGTH = 63;
1915
1916 uint8_t b;
1917 if (input.Read(b) != Success) {
1918 return false;
1919 }
1920 switch (b) {
1921 case '-':
1922 if (labelLength == 0) {
1923 return false; // Labels must not start with a hyphen.
1924 }
1925 labelIsAllNumeric = false;
1926 labelEndsWithHyphen = true;
1927 ++labelLength;
1928 if (labelLength > MAX_LABEL_LENGTH) {
1929 return false;
1930 }
1931 break;
1932
1933 // We avoid isdigit because it is locale-sensitive. See
1934 // http://pubs.opengroup.org/onlinepubs/009695399/functions/isdigit.html
1935 case '0':
1936 case '5':
1937 case '1':
1938 case '6':
1939 case '2':
1940 case '7':
1941 case '3':
1942 case '8':
1943 case '4':
1944 case '9':
1945 if (labelLength == 0) {
1946 labelIsAllNumeric = true;
1947 }
1948 labelEndsWithHyphen = false;
1949 ++labelLength;
1950 if (labelLength > MAX_LABEL_LENGTH) {
1951 return false;
1952 }
1953 break;
1954
1955 // We avoid using islower/isupper/tolower/toupper or similar things, to
1956 // avoid any possibility of this code being locale-sensitive. See
1957 // http://pubs.opengroup.org/onlinepubs/009695399/functions/isupper.html
1958 case 'a':
1959 case 'A':
1960 case 'n':
1961 case 'N':
1962 case 'b':
1963 case 'B':
1964 case 'o':
1965 case 'O':
1966 case 'c':
1967 case 'C':
1968 case 'p':
1969 case 'P':
1970 case 'd':
1971 case 'D':
1972 case 'q':
1973 case 'Q':
1974 case 'e':
1975 case 'E':
1976 case 'r':
1977 case 'R':
1978 case 'f':
1979 case 'F':
1980 case 's':
1981 case 'S':
1982 case 'g':
1983 case 'G':
1984 case 't':
1985 case 'T':
1986 case 'h':
1987 case 'H':
1988 case 'u':
1989 case 'U':
1990 case 'i':
1991 case 'I':
1992 case 'v':
1993 case 'V':
1994 case 'j':
1995 case 'J':
1996 case 'w':
1997 case 'W':
1998 case 'k':
1999 case 'K':
2000 case 'x':
2001 case 'X':
2002 case 'l':
2003 case 'L':
2004 case 'y':
2005 case 'Y':
2006 case 'm':
2007 case 'M':
2008 case 'z':
2009 case 'Z':
2010 // We allow underscores for compatibility with existing practices.
2011 // See bug 1136616.
2012 case '_':
2013 labelIsAllNumeric = false;
2014 labelEndsWithHyphen = false;
2015 ++labelLength;
2016 if (labelLength > MAX_LABEL_LENGTH) {
2017 return false;
2018 }
2019 break;
2020
2021 case '.':
2022 ++dotCount;
2023 if (labelLength == 0 &&
2024 (idRole != IDRole::NameConstraint || !isFirstByte)) {
2025 return false;
2026 }
2027 if (labelEndsWithHyphen) {
2028 return false; // Labels must not end with a hyphen.
2029 }
2030 labelLength = 0;
2031 break;
2032
2033 default:
2034 return false; // Invalid character.
2035 }
2036 isFirstByte = false;
2037 } while (!input.AtEnd());
2038
2039 // Only reference IDs, not presented IDs or name constraints, may be
2040 // absolute.
2041 if (labelLength == 0 && idRole != IDRole::ReferenceID) {
2042 return false;
2043 }
2044
2045 if (labelEndsWithHyphen) {
2046 return false; // Labels must not end with a hyphen.
2047 }
2048
2049 if (labelIsAllNumeric) {
2050 return false; // Last label must not be all numeric.
2051 }
2052
2053 if (isWildcard) {
2054 // If the DNS ID ends with a dot, the last dot signifies an absolute ID.
2055 size_t labelCount = (labelLength == 0) ? dotCount : (dotCount + 1);
2056
2057 // Like NSS, require at least two labels to follow the wildcard label.
2058 //
2059 // TODO(bug XXXXXXX): Allow the TrustDomain to control this on a
2060 // per-eTLD+1 basis, similar to Chromium. Even then, it might be better to
2061 // still enforce that there are at least two labels after the wildcard.
2062 if (labelCount < 3) {
2063 return false;
2064 }
2065 // XXX: RFC6125 says that we shouldn't accept wildcards within an IDN
2066 // A-Label. The consequence of this is that we effectively discriminate
2067 // against users of languages that cannot be encoded with ASCII.
2068 if (StartsWithIDNALabel(hostname)) {
2069 return false;
2070 }
2071
2072 // TODO(bug XXXXXXX): Wildcards are not allowed for EV certificates.
2073 // Provide an option to indicate whether wildcards should be matched, for
2074 // the purpose of helping the application enforce this.
2075 }
2076
2077 return true;
2078 }
2079
2080 } // namespace
2081
2082 } // namespace pkix
2083 } // namespace mozilla
2084