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