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