Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/security/pkix/lib/pkixnames.cpp
Line
Count
Source (jump to first uncovered line)
1
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
2
/* vim: set ts=8 sts=2 et sw=2 tw=80: */
3
/* This code is made available to you under your choice of the following sets
4
 * of licensing terms:
5
 */
6
/* This Source Code Form is subject to the terms of the Mozilla Public
7
 * License, v. 2.0. If a copy of the MPL was not distributed with this
8
 * file, You can obtain one at http://mozilla.org/MPL/2.0/.
9
 */
10
/* Copyright 2014 Mozilla Contributors
11
 *
12
 * Licensed under the Apache License, Version 2.0 (the "License");
13
 * you may not use this file except in compliance with the License.
14
 * You may obtain a copy of the License at
15
 *
16
 *     http://www.apache.org/licenses/LICENSE-2.0
17
 *
18
 * Unless required by applicable law or agreed to in writing, software
19
 * distributed under the License is distributed on an "AS IS" BASIS,
20
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
21
 * See the License for the specific language governing permissions and
22
 * limitations under the License.
23
 */
24
25
// This code implements RFC6125-ish name matching, RFC5280-ish name constraint
26
// checking, and related things.
27
//
28
// In this code, identifiers are classified as either "presented" or
29
// "reference" identifiers are defined in
30
// http://tools.ietf.org/html/rfc6125#section-1.8. A "presented identifier" is
31
// one in the subjectAltName of the certificate, or sometimes within a CN of
32
// the certificate's subject. The "reference identifier" is the one we are
33
// being asked to match the certificate against. When checking name
34
// constraints, the reference identifier is the entire encoded name constraint
35
// extension value.
36
37
#include <algorithm>
38
39
#include "pkixcheck.h"
40
#include "pkixutil.h"
41
42
namespace mozilla { 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
76
ReadGeneralName(Reader& reader,
77
                /*out*/ GeneralNameType& generalNameType,
78
                /*out*/ Input& value)
79
0
{
80
0
  uint8_t tag;
81
0
  Result rv = der::ReadTagAndGetValue(reader, tag, value);
82
0
  if (rv != Success) {
83
0
    return rv;
84
0
  }
85
0
  switch (tag) {
86
0
    case static_cast<uint8_t>(GeneralNameType::otherName):
87
0
      generalNameType = GeneralNameType::otherName;
88
0
      break;
89
0
    case static_cast<uint8_t>(GeneralNameType::rfc822Name):
90
0
      generalNameType = GeneralNameType::rfc822Name;
91
0
      break;
92
0
    case static_cast<uint8_t>(GeneralNameType::dNSName):
93
0
      generalNameType = GeneralNameType::dNSName;
94
0
      break;
95
0
    case static_cast<uint8_t>(GeneralNameType::x400Address):
96
0
      generalNameType = GeneralNameType::x400Address;
97
0
      break;
98
0
    case static_cast<uint8_t>(GeneralNameType::directoryName):
99
0
      generalNameType = GeneralNameType::directoryName;
100
0
      break;
101
0
    case static_cast<uint8_t>(GeneralNameType::ediPartyName):
102
0
      generalNameType = GeneralNameType::ediPartyName;
103
0
      break;
104
0
    case static_cast<uint8_t>(GeneralNameType::uniformResourceIdentifier):
105
0
      generalNameType = GeneralNameType::uniformResourceIdentifier;
106
0
      break;
107
0
    case static_cast<uint8_t>(GeneralNameType::iPAddress):
108
0
      generalNameType = GeneralNameType::iPAddress;
109
0
      break;
110
0
    case static_cast<uint8_t>(GeneralNameType::registeredID):
111
0
      generalNameType = GeneralNameType::registeredID;
112
0
      break;
113
0
    default:
114
0
      return Result::ERROR_BAD_DER;
115
0
  }
116
0
  return Success;
117
0
}
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
207
MatchPresentedDNSIDWithReferenceDNSID(Input presentedDNSID,
208
                                      Input referenceDNSID,
209
                                      /*out*/ bool& matches)
210
0
{
211
0
  return MatchPresentedDNSIDWithReferenceDNSID(
212
0
           presentedDNSID, AllowWildcards::Yes,
213
0
           AllowDotlessSubdomainMatches::Yes, IDRole::ReferenceID,
214
0
           referenceDNSID, matches);
215
0
}
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
222
CheckCertHostname(Input endEntityCertDER, Input hostname,
223
                  NameMatchingPolicy& nameMatchingPolicy)
224
0
{
225
0
  BackCert cert(endEntityCertDER, EndEntityOrCA::MustBeEndEntity, nullptr);
226
0
  Result rv = cert.Init();
227
0
  if (rv != Success) {
228
0
    return rv;
229
0
  }
230
0
231
0
  Time notBefore(Time::uninitialized);
232
0
  rv = ParseValidity(cert.GetValidity(), &notBefore);
233
0
  if (rv != Success) {
234
0
    return rv;
235
0
  }
236
0
  FallBackToSearchWithinSubject fallBackToSearchWithinSubject;
237
0
  rv = nameMatchingPolicy.FallBackToCommonName(notBefore,
238
0
                                               fallBackToSearchWithinSubject);
239
0
  if (rv != Success) {
240
0
    return rv;
241
0
  }
242
0
243
0
  const Input* subjectAltName(cert.GetSubjectAltName());
244
0
  Input subject(cert.GetSubject());
245
0
246
0
  // For backward compatibility with legacy certificates, we may fall back to
247
0
  // searching for a name match in the subject common name for DNS names and
248
0
  // IPv4 addresses. We don't do so for IPv6 addresses because we do not think
249
0
  // there are many certificates that would need such fallback, and because
250
0
  // comparisons of string representations of IPv6 addresses are particularly
251
0
  // error prone due to the syntactic flexibility that IPv6 addresses have.
252
0
  //
253
0
  // IPv4 and IPv6 addresses are represented using the same type of GeneralName
254
0
  // (iPAddress); they are differentiated by the lengths of the values.
255
0
  MatchResult match;
256
0
  uint8_t ipv6[16];
257
0
  uint8_t ipv4[4];
258
0
  if (IsValidReferenceDNSID(hostname)) {
259
0
    rv = SearchNames(subjectAltName, subject, GeneralNameType::dNSName,
260
0
                     hostname, fallBackToSearchWithinSubject, match);
261
0
  } else if (ParseIPv6Address(hostname, ipv6)) {
262
0
    rv = SearchNames(subjectAltName, subject, GeneralNameType::iPAddress,
263
0
                     Input(ipv6), FallBackToSearchWithinSubject::No, match);
264
0
  } else if (ParseIPv4Address(hostname, ipv4)) {
265
0
    rv = SearchNames(subjectAltName, subject, GeneralNameType::iPAddress,
266
0
                     Input(ipv4), fallBackToSearchWithinSubject, match);
267
0
  } else {
268
0
    return Result::ERROR_BAD_CERT_DOMAIN;
269
0
  }
270
0
  if (rv != Success) {
271
0
    return rv;
272
0
  }
273
0
  switch (match) {
274
0
    case MatchResult::NoNamesOfGivenType: // fall through
275
0
    case MatchResult::Mismatch:
276
0
      return Result::ERROR_BAD_CERT_DOMAIN;
277
0
    case MatchResult::Match:
278
0
      return Success;
279
0
    MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
280
0
  }
281
0
}
282
283
// 4.2.1.10. Name Constraints
284
Result
285
CheckNameConstraints(Input encodedNameConstraints,
286
                     const BackCert& firstChild,
287
                     KeyPurposeId requiredEKUIfPresent)
288
0
{
289
0
  for (const BackCert* child = &firstChild; child; child = child->childCert) {
290
0
    FallBackToSearchWithinSubject fallBackToCommonName
291
0
      = (child->endEntityOrCA == EndEntityOrCA::MustBeEndEntity &&
292
0
         requiredEKUIfPresent == KeyPurposeId::id_kp_serverAuth)
293
0
      ? FallBackToSearchWithinSubject::Yes
294
0
      : FallBackToSearchWithinSubject::No;
295
0
296
0
    MatchResult match;
297
0
    Result rv = SearchNames(child->GetSubjectAltName(), child->GetSubject(),
298
0
                            GeneralNameType::nameConstraints,
299
0
                            encodedNameConstraints, fallBackToCommonName,
300
0
                            match);
301
0
    if (rv != Success) {
302
0
      return rv;
303
0
    }
304
0
    switch (match) {
305
0
      case MatchResult::Match: // fall through
306
0
      case MatchResult::NoNamesOfGivenType:
307
0
        break;
308
0
      case MatchResult::Mismatch:
309
0
        return Result::ERROR_CERT_NOT_IN_NAME_SPACE;
310
0
    }
311
0
  }
312
0
313
0
  return Success;
314
0
}
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
334
SearchNames(/*optional*/ const Input* subjectAltName,
335
            Input subject,
336
            GeneralNameType referenceIDType,
337
            Input referenceID,
338
            FallBackToSearchWithinSubject fallBackToCommonName,
339
            /*out*/ MatchResult& match)
340
0
{
341
0
  Result rv;
342
0
343
0
  match = MatchResult::NoNamesOfGivenType;
344
0
345
0
  // RFC 6125 says "A client MUST NOT seek a match for a reference identifier
346
0
  // of CN-ID if the presented identifiers include a DNS-ID, SRV-ID, URI-ID, or
347
0
  // any application-specific identifier types supported by the client."
348
0
  // Accordingly, we only consider CN-IDs if there are no DNS-IDs in the
349
0
  // subjectAltName.
350
0
  //
351
0
  // RFC 6125 says that IP addresses are out of scope, but for backward
352
0
  // compatibility we accept them, by considering IP addresses to be an
353
0
  // "application-specific identifier type supported by the client."
354
0
  //
355
0
  // TODO(bug XXXXXXX): Consider strengthening this check to "A client MUST NOT
356
0
  // seek a match for a reference identifier of CN-ID if the certificate
357
0
  // contains a subjectAltName extension."
358
0
  //
359
0
  // TODO(bug XXXXXXX): Consider dropping support for IP addresses as
360
0
  // identifiers completely.
361
0
362
0
  if (subjectAltName) {
363
0
    Reader altNames;
364
0
    rv = der::ExpectTagAndGetValueAtEnd(*subjectAltName, der::SEQUENCE,
365
0
                                        altNames);
366
0
    if (rv != Success) {
367
0
      return rv;
368
0
    }
369
0
370
0
    // According to RFC 5280, "If the subjectAltName extension is present, the
371
0
    // sequence MUST contain at least one entry." For compatibility reasons, we
372
0
    // do not enforce this. See bug 1143085.
373
0
    while (!altNames.AtEnd()) {
374
0
      GeneralNameType presentedIDType;
375
0
      Input presentedID;
376
0
      rv = ReadGeneralName(altNames, presentedIDType, presentedID);
377
0
      if (rv != Success) {
378
0
        return rv;
379
0
      }
380
0
381
0
      rv = MatchPresentedIDWithReferenceID(presentedIDType, presentedID,
382
0
                                           referenceIDType, referenceID,
383
0
                                           match);
384
0
      if (rv != Success) {
385
0
        return rv;
386
0
      }
387
0
      if (referenceIDType != GeneralNameType::nameConstraints &&
388
0
          match == MatchResult::Match) {
389
0
        return Success;
390
0
      }
391
0
      if (presentedIDType == GeneralNameType::dNSName ||
392
0
          presentedIDType == GeneralNameType::iPAddress) {
393
0
        fallBackToCommonName = FallBackToSearchWithinSubject::No;
394
0
      }
395
0
    }
396
0
  }
397
0
398
0
  if (referenceIDType == GeneralNameType::nameConstraints) {
399
0
    rv = CheckPresentedIDConformsToConstraints(GeneralNameType::directoryName,
400
0
                                               subject, referenceID);
401
0
    if (rv != Success) {
402
0
      return rv;
403
0
    }
404
0
  }
405
0
406
0
  FallBackToSearchWithinSubject fallBackToEmailAddress;
407
0
  if (!subjectAltName &&
408
0
      (referenceIDType == GeneralNameType::rfc822Name ||
409
0
       referenceIDType == GeneralNameType::nameConstraints)) {
410
0
    fallBackToEmailAddress = FallBackToSearchWithinSubject::Yes;
411
0
  } else {
412
0
    fallBackToEmailAddress = FallBackToSearchWithinSubject::No;
413
0
  }
414
0
415
0
  // Short-circuit the parsing of the subject name if we're not going to match
416
0
  // any names in it
417
0
  if (fallBackToEmailAddress == FallBackToSearchWithinSubject::No &&
418
0
      fallBackToCommonName == FallBackToSearchWithinSubject::No) {
419
0
    return Success;
420
0
  }
421
0
422
0
  // Attempt to match the reference ID against the CN-ID, which we consider to
423
0
  // be the most-specific CN AVA in the subject field.
424
0
  //
425
0
  // https://tools.ietf.org/html/rfc6125#section-2.3.1 says:
426
0
  //
427
0
  //   To reduce confusion, in this specification we avoid such terms and
428
0
  //   instead use the terms provided under Section 1.8; in particular, we
429
0
  //   do not use the term "(most specific) Common Name field in the subject
430
0
  //   field" from [HTTP-TLS] and instead state that a CN-ID is a Relative
431
0
  //   Distinguished Name (RDN) in the certificate subject containing one
432
0
  //   and only one attribute-type-and-value pair of type Common Name (thus
433
0
  //   removing the possibility that an RDN might contain multiple AVAs
434
0
  //   (Attribute Value Assertions) of type CN, one of which could be
435
0
  //   considered "most specific").
436
0
  //
437
0
  // https://tools.ietf.org/html/rfc6125#section-7.4 says:
438
0
  //
439
0
  //   [...] Although it would be preferable to
440
0
  //   forbid multiple CN-IDs entirely, there are several reasons at this
441
0
  //   time why this specification states that they SHOULD NOT (instead of
442
0
  //   MUST NOT) be included [...]
443
0
  //
444
0
  // Consequently, it is unclear what to do when there are multiple CNs in the
445
0
  // subject, regardless of whether there "SHOULD NOT" be.
446
0
  //
447
0
  // NSS's CERT_VerifyCertName mostly follows RFC2818 in this instance, which
448
0
  // says:
449
0
  //
450
0
  //   If a subjectAltName extension of type dNSName is present, that MUST
451
0
  //   be used as the identity. Otherwise, the (most specific) Common Name
452
0
  //   field in the Subject field of the certificate MUST be used.
453
0
  //
454
0
  //   [...]
455
0
  //
456
0
  //   In some cases, the URI is specified as an IP address rather than a
457
0
  //   hostname. In this case, the iPAddress subjectAltName must be present
458
0
  //   in the certificate and must exactly match the IP in the URI.
459
0
  //
460
0
  // (The main difference from RFC2818 is that NSS's CERT_VerifyCertName also
461
0
  // matches IP addresses in the most-specific CN.)
462
0
  //
463
0
  // NSS's CERT_VerifyCertName finds the most specific CN via
464
0
  // CERT_GetCommoName, which uses CERT_GetLastNameElement. Note that many
465
0
  // NSS-based applications, including Gecko, also use CERT_GetCommonName. It
466
0
  // is likely that other, non-NSS-based, applications also expect only the
467
0
  // most specific CN to be matched against the reference ID.
468
0
  //
469
0
  // "A Layman's Guide to a Subset of ASN.1, BER, and DER" and other sources
470
0
  // agree that an RDNSequence is ordered from most significant (least
471
0
  // specific) to least significant (most specific), as do other references.
472
0
  //
473
0
  // However, Chromium appears to use the least-specific (first) CN instead of
474
0
  // the most-specific; see https://crbug.com/366957. Also, MSIE and some other
475
0
  // popular implementations apparently attempt to match the reference ID
476
0
  // against any/all CNs in the subject. Since we're trying to phase out the
477
0
  // use of CN-IDs, we intentionally avoid trying to match MSIE's more liberal
478
0
  // behavior.
479
0
480
0
  // Name ::= CHOICE { -- only one possibility for now --
481
0
  //   rdnSequence  RDNSequence }
482
0
  //
483
0
  // RDNSequence ::= SEQUENCE OF RelativeDistinguishedName
484
0
  //
485
0
  // RelativeDistinguishedName ::=
486
0
  //   SET SIZE (1..MAX) OF AttributeTypeAndValue
487
0
  Reader subjectReader(subject);
488
0
  return der::NestedOf(subjectReader, der::SEQUENCE, der::SET,
489
0
                       der::EmptyAllowed::Yes, [&](Reader& r) {
490
0
    return SearchWithinRDN(r, referenceIDType, referenceID,
491
0
                          fallBackToEmailAddress, fallBackToCommonName, match);
492
0
  });
493
0
}
494
495
// RelativeDistinguishedName ::=
496
//   SET SIZE (1..MAX) OF AttributeTypeAndValue
497
//
498
// AttributeTypeAndValue ::= SEQUENCE {
499
//   type     AttributeType,
500
//   value    AttributeValue }
501
Result
502
SearchWithinRDN(Reader& rdn,
503
                GeneralNameType referenceIDType,
504
                Input referenceID,
505
                FallBackToSearchWithinSubject fallBackToEmailAddress,
506
                FallBackToSearchWithinSubject fallBackToCommonName,
507
                /*in/out*/ MatchResult& match)
508
0
{
509
0
  do {
510
0
    Input type;
511
0
    uint8_t valueTag;
512
0
    Input value;
513
0
    Result rv = ReadAVA(rdn, type, valueTag, value);
514
0
    if (rv != Success) {
515
0
      return rv;
516
0
    }
517
0
    rv = MatchAVA(type, valueTag, value, referenceIDType, referenceID,
518
0
                  fallBackToEmailAddress, fallBackToCommonName, match);
519
0
    if (rv != Success) {
520
0
      return rv;
521
0
    }
522
0
  } while (!rdn.AtEnd());
523
0
524
0
  return Success;
525
0
}
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
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
0
{
549
0
  // Try to match the  CN as a DNSName or an IPAddress.
550
0
  //
551
0
  // id-at-commonName        AttributeType ::= { id-at 3 }
552
0
  //
553
0
  // -- Naming attributes of type X520CommonName:
554
0
  // --   X520CommonName ::= DirectoryName (SIZE (1..ub-common-name))
555
0
  // --
556
0
  // -- Expanded to avoid parameterized type:
557
0
  // X520CommonName ::= CHOICE {
558
0
  //       teletexString     TeletexString   (SIZE (1..ub-common-name)),
559
0
  //       printableString   PrintableString (SIZE (1..ub-common-name)),
560
0
  //       universalString   UniversalString (SIZE (1..ub-common-name)),
561
0
  //       utf8String        UTF8String      (SIZE (1..ub-common-name)),
562
0
  //       bmpString         BMPString       (SIZE (1..ub-common-name)) }
563
0
  //
564
0
  // python DottedOIDToCode.py id-at-commonName 2.5.4.3
565
0
  static const uint8_t id_at_commonName[] = {
566
0
    0x55, 0x04, 0x03
567
0
  };
568
0
  if (fallBackToCommonName == FallBackToSearchWithinSubject::Yes &&
569
0
      InputsAreEqual(type, Input(id_at_commonName))) {
570
0
    // We might have previously found a match. Now that we've found another CN,
571
0
    // we no longer consider that previous match to be a match, so "forget" about
572
0
    // it.
573
0
    match = MatchResult::NoNamesOfGivenType;
574
0
575
0
    // PrintableString is a subset of ASCII that contains all the characters
576
0
    // allowed in CN-IDs except '*'. Although '*' is illegal, there are many
577
0
    // real-world certificates that are encoded this way, so we accept it.
578
0
    //
579
0
    // In the case of UTF8String, we rely on the fact that in UTF-8 the octets in
580
0
    // a multi-byte encoding of a code point are always distinct from ASCII. Any
581
0
    // non-ASCII byte in a UTF-8 string causes us to fail to match. We make no
582
0
    // attempt to detect or report malformed UTF-8 (e.g. incomplete or overlong
583
0
    // encodings of code points, or encodings of invalid code points).
584
0
    //
585
0
    // TeletexString is supported as long as it does not contain any escape
586
0
    // sequences, which are not supported. We'll reject escape sequences as
587
0
    // invalid characters in names, which means we only accept strings that are
588
0
    // in the default character set, which is a superset of ASCII. Note that NSS
589
0
    // actually treats TeletexString as ISO-8859-1. Many certificates that have
590
0
    // wildcard CN-IDs (e.g. "*.example.com") use TeletexString because
591
0
    // PrintableString is defined to not allow '*' and because, at one point in
592
0
    // history, UTF8String was too new to use for compatibility reasons.
593
0
    //
594
0
    // UniversalString and BMPString are also deprecated, and they are a little
595
0
    // harder to support because they are not single-byte ASCII superset
596
0
    // encodings, so we don't bother.
597
0
    if (valueEncodingTag != der::PrintableString &&
598
0
        valueEncodingTag != der::UTF8String &&
599
0
        valueEncodingTag != der::TeletexString) {
600
0
      return Success;
601
0
    }
602
0
603
0
    if (IsValidPresentedDNSID(presentedID)) {
604
0
      MatchSubjectPresentedIDWithReferenceID(GeneralNameType::dNSName,
605
0
                                             presentedID, referenceIDType,
606
0
                                             referenceID, match);
607
0
    } else {
608
0
      // We don't match CN-IDs for IPv6 addresses.
609
0
      // MatchSubjectPresentedIDWithReferenceID ensures that it won't match an
610
0
      // IPv4 address with an IPv6 address, so we don't need to check that
611
0
      // referenceID is an IPv4 address here.
612
0
      uint8_t ipv4[4];
613
0
      if (ParseIPv4Address(presentedID, ipv4)) {
614
0
        MatchSubjectPresentedIDWithReferenceID(GeneralNameType::iPAddress,
615
0
                                               Input(ipv4), referenceIDType,
616
0
                                               referenceID, match);
617
0
      }
618
0
    }
619
0
620
0
    // Regardless of whether there was a match, we keep going in case we find
621
0
    // another CN later. If we do find another one, then this match/mismatch
622
0
    // will be ignored, because we only care about the most specific CN.
623
0
624
0
    return Success;
625
0
  }
626
0
627
0
  // Match an email address against an emailAddress attribute in the
628
0
  // subject.
629
0
  //
630
0
  // id-emailAddress      AttributeType ::= { pkcs-9 1 }
631
0
  //
632
0
  // EmailAddress ::=     IA5String (SIZE (1..ub-emailaddress-length))
633
0
  //
634
0
  // python DottedOIDToCode.py id-emailAddress 1.2.840.113549.1.9.1
635
0
  static const uint8_t id_emailAddress[] = {
636
0
    0x2a, 0x86, 0x48, 0x86, 0xf7, 0x0d, 0x01, 0x09, 0x01
637
0
  };
638
0
  if (fallBackToEmailAddress == FallBackToSearchWithinSubject::Yes &&
639
0
      InputsAreEqual(type, Input(id_emailAddress))) {
640
0
    if (referenceIDType == GeneralNameType::rfc822Name &&
641
0
        match == MatchResult::Match) {
642
0
      // We already found a match; we don't need to match another one
643
0
      return Success;
644
0
    }
645
0
    if (valueEncodingTag != der::IA5String) {
646
0
      return Result::ERROR_BAD_DER;
647
0
    }
648
0
    return MatchPresentedIDWithReferenceID(GeneralNameType::rfc822Name,
649
0
                                           presentedID, referenceIDType,
650
0
                                           referenceID, match);
651
0
  }
652
0
653
0
  return Success;
654
0
}
655
656
void
657
MatchSubjectPresentedIDWithReferenceID(GeneralNameType presentedIDType,
658
                                       Input presentedID,
659
                                       GeneralNameType referenceIDType,
660
                                       Input referenceID,
661
                                       /*in/out*/ MatchResult& match)
662
0
{
663
0
  Result rv = MatchPresentedIDWithReferenceID(presentedIDType, presentedID,
664
0
                                              referenceIDType, referenceID,
665
0
                                              match);
666
0
  if (rv != Success) {
667
0
    match = MatchResult::Mismatch;
668
0
  }
669
0
}
670
671
Result
672
MatchPresentedIDWithReferenceID(GeneralNameType presentedIDType,
673
                                Input presentedID,
674
                                GeneralNameType referenceIDType,
675
                                Input referenceID,
676
                                /*out*/ MatchResult& matchResult)
677
0
{
678
0
  if (referenceIDType == GeneralNameType::nameConstraints) {
679
0
    // matchResult is irrelevant when checking name constraints; only the
680
0
    // pass/fail result of CheckPresentedIDConformsToConstraints matters.
681
0
    return CheckPresentedIDConformsToConstraints(presentedIDType, presentedID,
682
0
                                                 referenceID);
683
0
  }
684
0
685
0
  if (presentedIDType != referenceIDType) {
686
0
    matchResult = MatchResult::Mismatch;
687
0
    return Success;
688
0
  }
689
0
690
0
  Result rv;
691
0
  bool foundMatch;
692
0
693
0
  switch (referenceIDType) {
694
0
    case GeneralNameType::dNSName:
695
0
      rv = MatchPresentedDNSIDWithReferenceDNSID(
696
0
             presentedID, AllowWildcards::Yes,
697
0
             AllowDotlessSubdomainMatches::Yes, IDRole::ReferenceID,
698
0
             referenceID, foundMatch);
699
0
      break;
700
0
701
0
    case GeneralNameType::iPAddress:
702
0
      foundMatch = InputsAreEqual(presentedID, referenceID);
703
0
      rv = Success;
704
0
      break;
705
0
706
0
    case GeneralNameType::rfc822Name:
707
0
      rv = MatchPresentedRFC822NameWithReferenceRFC822Name(
708
0
             presentedID, IDRole::ReferenceID, referenceID, foundMatch);
709
0
      break;
710
0
711
0
    case GeneralNameType::directoryName:
712
0
      // TODO: At some point, we may add APIs for matching DirectoryNames.
713
0
      // fall through
714
0
715
0
    case GeneralNameType::otherName: // fall through
716
0
    case GeneralNameType::x400Address: // fall through
717
0
    case GeneralNameType::ediPartyName: // fall through
718
0
    case GeneralNameType::uniformResourceIdentifier: // fall through
719
0
    case GeneralNameType::registeredID: // fall through
720
0
    case GeneralNameType::nameConstraints:
721
0
      return NotReached("unexpected nameType for SearchType::Match",
722
0
                        Result::FATAL_ERROR_INVALID_ARGS);
723
0
724
0
    MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
725
0
 }
726
0
727
0
  if (rv != Success) {
728
0
    return rv;
729
0
  }
730
0
  matchResult = foundMatch ? MatchResult::Match : MatchResult::Mismatch;
731
0
  return Success;
732
0
}
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
753
CheckPresentedIDConformsToConstraints(
754
  GeneralNameType presentedIDType,
755
  Input presentedID,
756
  Input encodedNameConstraints)
757
0
{
758
0
  // NameConstraints ::= SEQUENCE {
759
0
  //      permittedSubtrees       [0]     GeneralSubtrees OPTIONAL,
760
0
  //      excludedSubtrees        [1]     GeneralSubtrees OPTIONAL }
761
0
  Reader nameConstraints;
762
0
  Result rv = der::ExpectTagAndGetValueAtEnd(encodedNameConstraints,
763
0
                                             der::SEQUENCE, nameConstraints);
764
0
  if (rv != Success) {
765
0
    return rv;
766
0
  }
767
0
768
0
  // RFC 5280 says "Conforming CAs MUST NOT issue certificates where name
769
0
  // constraints is an empty sequence. That is, either the permittedSubtrees
770
0
  // field or the excludedSubtrees MUST be present."
771
0
  if (nameConstraints.AtEnd()) {
772
0
    return Result::ERROR_BAD_DER;
773
0
  }
774
0
775
0
  rv = CheckPresentedIDConformsToNameConstraintsSubtrees(
776
0
         presentedIDType, presentedID, nameConstraints,
777
0
         NameConstraintsSubtrees::permittedSubtrees);
778
0
  if (rv != Success) {
779
0
    return rv;
780
0
  }
781
0
782
0
  rv = CheckPresentedIDConformsToNameConstraintsSubtrees(
783
0
         presentedIDType, presentedID, nameConstraints,
784
0
         NameConstraintsSubtrees::excludedSubtrees);
785
0
  if (rv != Success) {
786
0
    return rv;
787
0
  }
788
0
789
0
  return der::End(nameConstraints);
790
0
}
791
792
Result
793
CheckPresentedIDConformsToNameConstraintsSubtrees(
794
  GeneralNameType presentedIDType,
795
  Input presentedID,
796
  Reader& nameConstraints,
797
  NameConstraintsSubtrees subtreesType)
798
0
{
799
0
  if (!nameConstraints.Peek(static_cast<uint8_t>(subtreesType))) {
800
0
    return Success;
801
0
  }
802
0
803
0
  Reader subtrees;
804
0
  Result rv = der::ExpectTagAndGetValue(nameConstraints,
805
0
                                        static_cast<uint8_t>(subtreesType),
806
0
                                        subtrees);
807
0
  if (rv != Success) {
808
0
    return rv;
809
0
  }
810
0
811
0
  bool hasPermittedSubtreesMatch = false;
812
0
  bool hasPermittedSubtreesMismatch = false;
813
0
814
0
  // GeneralSubtrees ::= SEQUENCE SIZE (1..MAX) OF GeneralSubtree
815
0
  //
816
0
  // do { ... } while(...) because subtrees isn't allowed to be empty.
817
0
  do {
818
0
    // GeneralSubtree ::= SEQUENCE {
819
0
    //      base                    GeneralName,
820
0
    //      minimum         [0]     BaseDistance DEFAULT 0,
821
0
    //      maximum         [1]     BaseDistance OPTIONAL }
822
0
    Reader subtree;
823
0
    rv = ExpectTagAndGetValue(subtrees, der::SEQUENCE, subtree);
824
0
    if (rv != Success) {
825
0
      return rv;
826
0
    }
827
0
    GeneralNameType nameConstraintType;
828
0
    Input base;
829
0
    rv = ReadGeneralName(subtree, nameConstraintType, base);
830
0
    if (rv != Success) {
831
0
      return rv;
832
0
    }
833
0
    // http://tools.ietf.org/html/rfc5280#section-4.2.1.10: "Within this
834
0
    // profile, the minimum and maximum fields are not used with any name
835
0
    // forms, thus, the minimum MUST be zero, and maximum MUST be absent."
836
0
    //
837
0
    // Since the default value isn't allowed to be encoded according to the DER
838
0
    // encoding rules for DEFAULT, this is equivalent to saying that neither
839
0
    // minimum or maximum must be encoded.
840
0
    rv = der::End(subtree);
841
0
    if (rv != Success) {
842
0
      return rv;
843
0
    }
844
0
845
0
    if (presentedIDType == nameConstraintType) {
846
0
      bool matches;
847
0
848
0
      switch (presentedIDType) {
849
0
        case GeneralNameType::dNSName:
850
0
          rv = MatchPresentedDNSIDWithReferenceDNSID(
851
0
                 presentedID, AllowWildcards::Yes,
852
0
                 AllowDotlessSubdomainMatches::Yes, IDRole::NameConstraint,
853
0
                 base, matches);
854
0
          if (rv != Success) {
855
0
            return rv;
856
0
          }
857
0
          break;
858
0
859
0
        case GeneralNameType::iPAddress:
860
0
          rv = MatchPresentedIPAddressWithConstraint(presentedID, base,
861
0
                                                     matches);
862
0
          if (rv != Success) {
863
0
            return rv;
864
0
          }
865
0
          break;
866
0
867
0
        case GeneralNameType::directoryName:
868
0
          rv = MatchPresentedDirectoryNameWithConstraint(subtreesType,
869
0
                                                         presentedID, base,
870
0
                                                         matches);
871
0
          if (rv != Success) {
872
0
            return rv;
873
0
          }
874
0
          break;
875
0
876
0
        case GeneralNameType::rfc822Name:
877
0
          rv = MatchPresentedRFC822NameWithReferenceRFC822Name(
878
0
                 presentedID, IDRole::NameConstraint, base, matches);
879
0
          if (rv != Success) {
880
0
            return rv;
881
0
          }
882
0
          break;
883
0
884
0
        // RFC 5280 says "Conforming CAs [...] SHOULD NOT impose name
885
0
        // constraints on the x400Address, ediPartyName, or registeredID
886
0
        // name forms. It also says "Applications conforming to this profile
887
0
        // [...] SHOULD be able to process name constraints that are imposed
888
0
        // on [...] uniformResourceIdentifier [...]", but we don't bother.
889
0
        //
890
0
        // TODO: Ask to have spec updated to say ""Conforming CAs [...] SHOULD
891
0
        // NOT impose name constraints on the otherName, x400Address,
892
0
        // ediPartyName, uniformResourceIdentifier, or registeredID name
893
0
        // forms."
894
0
        case GeneralNameType::otherName: // fall through
895
0
        case GeneralNameType::x400Address: // fall through
896
0
        case GeneralNameType::ediPartyName: // fall through
897
0
        case GeneralNameType::uniformResourceIdentifier: // fall through
898
0
        case GeneralNameType::registeredID: // fall through
899
0
          return Result::ERROR_CERT_NOT_IN_NAME_SPACE;
900
0
901
0
        case GeneralNameType::nameConstraints:
902
0
          return NotReached("invalid presentedIDType",
903
0
                            Result::FATAL_ERROR_LIBRARY_FAILURE);
904
0
905
0
        MOZILLA_PKIX_UNREACHABLE_DEFAULT_ENUM
906
0
      }
907
0
908
0
      switch (subtreesType) {
909
0
        case NameConstraintsSubtrees::permittedSubtrees:
910
0
          if (matches) {
911
0
            hasPermittedSubtreesMatch = true;
912
0
          } else {
913
0
            hasPermittedSubtreesMismatch = true;
914
0
          }
915
0
          break;
916
0
        case NameConstraintsSubtrees::excludedSubtrees:
917
0
          if (matches) {
918
0
            return Result::ERROR_CERT_NOT_IN_NAME_SPACE;
919
0
          }
920
0
          break;
921
0
      }
922
0
    }
923
0
  } while (!subtrees.AtEnd());
924
0
925
0
  if (hasPermittedSubtreesMismatch && !hasPermittedSubtreesMatch) {
926
0
    // If there was any entry of the given type in permittedSubtrees, then it
927
0
    // required that at least one of them must match. Since none of them did,
928
0
    // we have a failure.
929
0
    return Result::ERROR_CERT_NOT_IN_NAME_SPACE;
930
0
  }
931
0
932
0
  return Success;
933
0
}
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
1056
MatchPresentedDNSIDWithReferenceDNSID(
1057
  Input presentedDNSID,
1058
  AllowWildcards allowWildcards,
1059
  AllowDotlessSubdomainMatches allowDotlessSubdomainMatches,
1060
  IDRole referenceDNSIDRole,
1061
  Input referenceDNSID,
1062
  /*out*/ bool& matches)
1063
0
{
1064
0
  if (!IsValidDNSID(presentedDNSID, IDRole::PresentedID, allowWildcards)) {
1065
0
    return Result::ERROR_BAD_DER;
1066
0
  }
1067
0
1068
0
  if (!IsValidDNSID(referenceDNSID, referenceDNSIDRole, AllowWildcards::No)) {
1069
0
    return Result::ERROR_BAD_DER;
1070
0
  }
1071
0
1072
0
  Reader presented(presentedDNSID);
1073
0
  Reader reference(referenceDNSID);
1074
0
1075
0
  switch (referenceDNSIDRole)
1076
0
  {
1077
0
    case IDRole::ReferenceID:
1078
0
      break;
1079
0
1080
0
    case IDRole::NameConstraint:
1081
0
    {
1082
0
      if (presentedDNSID.GetLength() > referenceDNSID.GetLength()) {
1083
0
        if (referenceDNSID.GetLength() == 0) {
1084
0
          // An empty constraint matches everything.
1085
0
          matches = true;
1086
0
          return Success;
1087
0
        }
1088
0
        // If the reference ID starts with a dot then skip the prefix of
1089
0
        // of the presented ID and start the comparison at the position of that
1090
0
        // dot. Examples:
1091
0
        //
1092
0
        //                                       Matches     Doesn't Match
1093
0
        //     -----------------------------------------------------------
1094
0
        //       original presented ID:  www.example.com    badexample.com
1095
0
        //                     skipped:  www                ba
1096
0
        //     presented ID w/o prefix:     .example.com      dexample.com
1097
0
        //                reference ID:     .example.com      .example.com
1098
0
        //
1099
0
        // If the reference ID does not start with a dot then we skip the
1100
0
        // prefix of the presented ID but also verify that the prefix ends with
1101
0
        // a dot. Examples:
1102
0
        //
1103
0
        //                                       Matches     Doesn't Match
1104
0
        //     -----------------------------------------------------------
1105
0
        //       original presented ID:  www.example.com    badexample.com
1106
0
        //                     skipped:  www                ba
1107
0
        //                 must be '.':     .                 d
1108
0
        //     presented ID w/o prefix:      example.com       example.com
1109
0
        //                reference ID:      example.com       example.com
1110
0
        //
1111
0
        if (reference.Peek('.')) {
1112
0
          if (presented.Skip(static_cast<Input::size_type>(
1113
0
                               presentedDNSID.GetLength() -
1114
0
                                 referenceDNSID.GetLength())) != Success) {
1115
0
            return NotReached("skipping subdomain failed",
1116
0
                              Result::FATAL_ERROR_LIBRARY_FAILURE);
1117
0
          }
1118
0
        } else if (allowDotlessSubdomainMatches ==
1119
0
                   AllowDotlessSubdomainMatches::Yes) {
1120
0
          if (presented.Skip(static_cast<Input::size_type>(
1121
0
                               presentedDNSID.GetLength() -
1122
0
                                 referenceDNSID.GetLength() - 1)) != Success) {
1123
0
            return NotReached("skipping subdomains failed",
1124
0
                              Result::FATAL_ERROR_LIBRARY_FAILURE);
1125
0
          }
1126
0
          uint8_t b;
1127
0
          if (presented.Read(b) != Success) {
1128
0
            return NotReached("reading from presentedDNSID failed",
1129
0
                              Result::FATAL_ERROR_LIBRARY_FAILURE);
1130
0
          }
1131
0
          if (b != '.') {
1132
0
            matches = false;
1133
0
            return Success;
1134
0
          }
1135
0
        }
1136
0
      }
1137
0
      break;
1138
0
    }
1139
0
1140
0
    case IDRole::PresentedID: // fall through
1141
0
      return NotReached("IDRole::PresentedID is not a valid referenceDNSIDRole",
1142
0
                        Result::FATAL_ERROR_INVALID_ARGS);
1143
0
  }
1144
0
1145
0
  // We only allow wildcard labels that consist only of '*'.
1146
0
  if (presented.Peek('*')) {
1147
0
    if (presented.Skip(1) != Success) {
1148
0
      return NotReached("Skipping '*' failed",
1149
0
                        Result::FATAL_ERROR_LIBRARY_FAILURE);
1150
0
    }
1151
0
    do {
1152
0
      // This will happen if reference is a single, relative label
1153
0
      if (reference.AtEnd()) {
1154
0
        matches = false;
1155
0
        return Success;
1156
0
      }
1157
0
      uint8_t referenceByte;
1158
0
      if (reference.Read(referenceByte) != Success) {
1159
0
        return NotReached("invalid reference ID",
1160
0
                          Result::FATAL_ERROR_INVALID_ARGS);
1161
0
      }
1162
0
    } while (!reference.Peek('.'));
1163
0
  }
1164
0
1165
0
  for (;;) {
1166
0
    uint8_t presentedByte;
1167
0
    if (presented.Read(presentedByte) != Success) {
1168
0
      matches = false;
1169
0
      return Success;
1170
0
    }
1171
0
    uint8_t referenceByte;
1172
0
    if (reference.Read(referenceByte) != Success) {
1173
0
      matches = false;
1174
0
      return Success;
1175
0
    }
1176
0
    if (LocaleInsensitveToLower(presentedByte) !=
1177
0
        LocaleInsensitveToLower(referenceByte)) {
1178
0
      matches = false;
1179
0
      return Success;
1180
0
    }
1181
0
    if (presented.AtEnd()) {
1182
0
      // Don't allow presented IDs to be absolute.
1183
0
      if (presentedByte == '.') {
1184
0
        return Result::ERROR_BAD_DER;
1185
0
      }
1186
0
      break;
1187
0
    }
1188
0
  }
1189
0
1190
0
  // Allow a relative presented DNS ID to match an absolute reference DNS ID,
1191
0
  // unless we're matching a name constraint.
1192
0
  if (!reference.AtEnd()) {
1193
0
    if (referenceDNSIDRole != IDRole::NameConstraint) {
1194
0
      uint8_t referenceByte;
1195
0
      if (reference.Read(referenceByte) != Success) {
1196
0
        return NotReached("read failed but not at end",
1197
0
                          Result::FATAL_ERROR_LIBRARY_FAILURE);
1198
0
      }
1199
0
      if (referenceByte != '.') {
1200
0
        matches = false;
1201
0
        return Success;
1202
0
      }
1203
0
    }
1204
0
    if (!reference.AtEnd()) {
1205
0
      matches = false;
1206
0
      return Success;
1207
0
    }
1208
0
  }
1209
0
1210
0
  matches = true;
1211
0
  return Success;
1212
0
}
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
1224
MatchPresentedIPAddressWithConstraint(Input presentedID,
1225
                                      Input iPAddressConstraint,
1226
                                      /*out*/ bool& foundMatch)
1227
0
{
1228
0
  if (presentedID.GetLength() != 4 && presentedID.GetLength() != 16) {
1229
0
    return Result::ERROR_BAD_DER;
1230
0
  }
1231
0
  if (iPAddressConstraint.GetLength() != 8 &&
1232
0
      iPAddressConstraint.GetLength() != 32) {
1233
0
    return Result::ERROR_BAD_DER;
1234
0
  }
1235
0
1236
0
  // an IPv4 address never matches an IPv6 constraint, and vice versa.
1237
0
  if (presentedID.GetLength() * 2 != iPAddressConstraint.GetLength()) {
1238
0
    foundMatch = false;
1239
0
    return Success;
1240
0
  }
1241
0
1242
0
  Reader constraint(iPAddressConstraint);
1243
0
  Reader constraintAddress;
1244
0
  Result rv = constraint.Skip(iPAddressConstraint.GetLength() / 2u,
1245
0
                              constraintAddress);
1246
0
  if (rv != Success) {
1247
0
    return rv;
1248
0
  }
1249
0
  Reader constraintMask;
1250
0
  rv = constraint.Skip(iPAddressConstraint.GetLength() / 2u, constraintMask);
1251
0
  if (rv != Success) {
1252
0
    return rv;
1253
0
  }
1254
0
  rv = der::End(constraint);
1255
0
  if (rv != Success) {
1256
0
    return rv;
1257
0
  }
1258
0
1259
0
  Reader presented(presentedID);
1260
0
  do {
1261
0
    uint8_t presentedByte;
1262
0
    rv = presented.Read(presentedByte);
1263
0
    if (rv != Success) {
1264
0
      return rv;
1265
0
    }
1266
0
    uint8_t constraintAddressByte;
1267
0
    rv = constraintAddress.Read(constraintAddressByte);
1268
0
    if (rv != Success) {
1269
0
      return rv;
1270
0
    }
1271
0
    uint8_t constraintMaskByte;
1272
0
    rv = constraintMask.Read(constraintMaskByte);
1273
0
    if (rv != Success) {
1274
0
      return rv;
1275
0
    }
1276
0
    foundMatch =
1277
0
      ((presentedByte ^ constraintAddressByte) & constraintMaskByte) == 0;
1278
0
  } while (foundMatch && !presented.AtEnd());
1279
0
1280
0
  return Success;
1281
0
}
1282
1283
// AttributeTypeAndValue ::= SEQUENCE {
1284
//   type     AttributeType,
1285
//   value    AttributeValue }
1286
//
1287
// AttributeType ::= OBJECT IDENTIFIER
1288
//
1289
// AttributeValue ::= ANY -- DEFINED BY AttributeType
1290
Result
1291
ReadAVA(Reader& rdn,
1292
        /*out*/ Input& type,
1293
        /*out*/ uint8_t& valueTag,
1294
        /*out*/ Input& value)
1295
0
{
1296
0
  return der::Nested(rdn, der::SEQUENCE, [&](Reader& ava) -> Result {
1297
0
    Result rv = der::ExpectTagAndGetValue(ava, der::OIDTag, type);
1298
0
    if (rv != Success) {
1299
0
      return rv;
1300
0
    }
1301
0
    rv = der::ReadTagAndGetValue(ava, valueTag, value);
1302
0
    if (rv != Success) {
1303
0
      return rv;
1304
0
    }
1305
0
    return Success;
1306
0
  });
1307
0
}
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
1350
MatchPresentedDirectoryNameWithConstraint(NameConstraintsSubtrees subtreesType,
1351
                                          Input presentedID,
1352
                                          Input directoryNameConstraint,
1353
                                          /*out*/ bool& matches)
1354
0
{
1355
0
  Reader constraintRDNs;
1356
0
  Result rv = der::ExpectTagAndGetValueAtEnd(directoryNameConstraint,
1357
0
                                             der::SEQUENCE, constraintRDNs);
1358
0
  if (rv != Success) {
1359
0
    return rv;
1360
0
  }
1361
0
  Reader presentedRDNs;
1362
0
  rv = der::ExpectTagAndGetValueAtEnd(presentedID, der::SEQUENCE,
1363
0
                                      presentedRDNs);
1364
0
  if (rv != Success) {
1365
0
    return rv;
1366
0
  }
1367
0
1368
0
  switch (subtreesType) {
1369
0
    case NameConstraintsSubtrees::permittedSubtrees:
1370
0
      break; // dealt with below
1371
0
    case NameConstraintsSubtrees::excludedSubtrees:
1372
0
      if (!constraintRDNs.AtEnd() || !presentedRDNs.AtEnd()) {
1373
0
        return Result::ERROR_CERT_NOT_IN_NAME_SPACE;
1374
0
      }
1375
0
      matches = true;
1376
0
      return Success;
1377
0
  }
1378
0
1379
0
  for (;;) {
1380
0
    // The AVAs have to be fully equal, but the constraint RDNs just need to be
1381
0
    // a prefix of the presented RDNs.
1382
0
    if (constraintRDNs.AtEnd()) {
1383
0
      matches = true;
1384
0
      return Success;
1385
0
    }
1386
0
    if (presentedRDNs.AtEnd()) {
1387
0
      matches = false;
1388
0
      return Success;
1389
0
    }
1390
0
    Reader constraintRDN;
1391
0
    rv = der::ExpectTagAndGetValue(constraintRDNs, der::SET, constraintRDN);
1392
0
    if (rv != Success) {
1393
0
      return rv;
1394
0
    }
1395
0
    Reader presentedRDN;
1396
0
    rv = der::ExpectTagAndGetValue(presentedRDNs, der::SET, presentedRDN);
1397
0
    if (rv != Success) {
1398
0
      return rv;
1399
0
    }
1400
0
    while (!constraintRDN.AtEnd() && !presentedRDN.AtEnd()) {
1401
0
      Input constraintType;
1402
0
      uint8_t constraintValueTag;
1403
0
      Input constraintValue;
1404
0
      rv = ReadAVA(constraintRDN, constraintType, constraintValueTag,
1405
0
                   constraintValue);
1406
0
      if (rv != Success) {
1407
0
        return rv;
1408
0
      }
1409
0
      Input presentedType;
1410
0
      uint8_t presentedValueTag;
1411
0
      Input presentedValue;
1412
0
      rv = ReadAVA(presentedRDN, presentedType, presentedValueTag,
1413
0
                   presentedValue);
1414
0
      if (rv != Success) {
1415
0
        return rv;
1416
0
      }
1417
0
      // TODO (bug 1155767): verify that if an AVA is a PrintableString it
1418
0
      // consists only of characters valid for PrintableStrings.
1419
0
      bool avasMatch =
1420
0
        InputsAreEqual(constraintType, presentedType) &&
1421
0
        InputsAreEqual(constraintValue, presentedValue) &&
1422
0
        (constraintValueTag == presentedValueTag ||
1423
0
         (constraintValueTag == der::Tag::UTF8String &&
1424
0
          presentedValueTag == der::Tag::PrintableString) ||
1425
0
         (constraintValueTag == der::Tag::PrintableString &&
1426
0
          presentedValueTag == der::Tag::UTF8String));
1427
0
      if (!avasMatch) {
1428
0
        matches = false;
1429
0
        return Success;
1430
0
      }
1431
0
    }
1432
0
    if (!constraintRDN.AtEnd() || !presentedRDN.AtEnd()) {
1433
0
      matches = false;
1434
0
      return Success;
1435
0
    }
1436
0
  }
1437
0
}
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
1465
IsValidRFC822Name(Input input)
1466
0
{
1467
0
  Reader reader(input);
1468
0
1469
0
  // Local-part@.
1470
0
  bool startOfAtom = true;
1471
0
  for (;;) {
1472
0
    uint8_t presentedByte;
1473
0
    if (reader.Read(presentedByte) != Success) {
1474
0
      return false;
1475
0
    }
1476
0
    switch (presentedByte) {
1477
0
      // atext is defined in https://tools.ietf.org/html/rfc2822#section-3.2.4
1478
0
      case 'A': case 'a': case 'N': case 'n': case '0': case '!': case '#':
1479
0
      case 'B': case 'b': case 'O': case 'o': case '1': case '$': case '%':
1480
0
      case 'C': case 'c': case 'P': case 'p': case '2': case '&': case '\'':
1481
0
      case 'D': case 'd': case 'Q': case 'q': case '3': case '*': case '+':
1482
0
      case 'E': case 'e': case 'R': case 'r': case '4': case '-': case '/':
1483
0
      case 'F': case 'f': case 'S': case 's': case '5': case '=': case '?':
1484
0
      case 'G': case 'g': case 'T': case 't': case '6': case '^': case '_':
1485
0
      case 'H': case 'h': case 'U': case 'u': case '7': case '`': case '{':
1486
0
      case 'I': case 'i': case 'V': case 'v': case '8': case '|': case '}':
1487
0
      case 'J': case 'j': case 'W': case 'w': case '9': case '~':
1488
0
      case 'K': case 'k': case 'X': case 'x':
1489
0
      case 'L': case 'l': case 'Y': case 'y':
1490
0
      case 'M': case 'm': case 'Z': case 'z':
1491
0
        startOfAtom = false;
1492
0
        break;
1493
0
1494
0
      case '.':
1495
0
        if (startOfAtom) {
1496
0
          return false;
1497
0
        }
1498
0
        startOfAtom = true;
1499
0
        break;
1500
0
1501
0
      case '@':
1502
0
      {
1503
0
        if (startOfAtom) {
1504
0
          return false;
1505
0
        }
1506
0
        Input domain;
1507
0
        if (reader.SkipToEnd(domain) != Success) {
1508
0
          return false;
1509
0
        }
1510
0
        return IsValidDNSID(domain, IDRole::PresentedID, AllowWildcards::No);
1511
0
      }
1512
0
1513
0
      default:
1514
0
        return false;
1515
0
    }
1516
0
  }
1517
0
}
1518
1519
Result
1520
MatchPresentedRFC822NameWithReferenceRFC822Name(Input presentedRFC822Name,
1521
                                                IDRole referenceRFC822NameRole,
1522
                                                Input referenceRFC822Name,
1523
                                                /*out*/ bool& matches)
1524
0
{
1525
0
  if (!IsValidRFC822Name(presentedRFC822Name)) {
1526
0
    return Result::ERROR_BAD_DER;
1527
0
  }
1528
0
  Reader presented(presentedRFC822Name);
1529
0
1530
0
  switch (referenceRFC822NameRole)
1531
0
  {
1532
0
    case IDRole::PresentedID:
1533
0
      return Result::FATAL_ERROR_INVALID_ARGS;
1534
0
1535
0
    case IDRole::ReferenceID:
1536
0
      break;
1537
0
1538
0
    case IDRole::NameConstraint:
1539
0
    {
1540
0
      if (InputContains(referenceRFC822Name, '@')) {
1541
0
        // The constraint is of the form "Local-part@Domain".
1542
0
        break;
1543
0
      }
1544
0
1545
0
      // The constraint is of the form "example.com" or ".example.com".
1546
0
1547
0
      // Skip past the '@' in the presented ID.
1548
0
      for (;;) {
1549
0
        uint8_t presentedByte;
1550
0
        if (presented.Read(presentedByte) != Success) {
1551
0
          return Result::FATAL_ERROR_LIBRARY_FAILURE;
1552
0
        }
1553
0
        if (presentedByte == '@') {
1554
0
          break;
1555
0
        }
1556
0
      }
1557
0
1558
0
      Input presentedDNSID;
1559
0
      if (presented.SkipToEnd(presentedDNSID) != Success) {
1560
0
        return Result::FATAL_ERROR_LIBRARY_FAILURE;
1561
0
      }
1562
0
1563
0
      return MatchPresentedDNSIDWithReferenceDNSID(
1564
0
               presentedDNSID, AllowWildcards::No,
1565
0
               AllowDotlessSubdomainMatches::No, IDRole::NameConstraint,
1566
0
               referenceRFC822Name, matches);
1567
0
    }
1568
0
  }
1569
0
1570
0
  if (!IsValidRFC822Name(referenceRFC822Name)) {
1571
0
    return Result::ERROR_BAD_DER;
1572
0
  }
1573
0
1574
0
  Reader reference(referenceRFC822Name);
1575
0
1576
0
  for (;;) {
1577
0
    uint8_t presentedByte;
1578
0
    if (presented.Read(presentedByte) != Success) {
1579
0
      matches = reference.AtEnd();
1580
0
      return Success;
1581
0
    }
1582
0
    uint8_t referenceByte;
1583
0
    if (reference.Read(referenceByte) != Success) {
1584
0
      matches = false;
1585
0
      return Success;
1586
0
    }
1587
0
    if (LocaleInsensitveToLower(presentedByte) !=
1588
0
        LocaleInsensitveToLower(referenceByte)) {
1589
0
      matches = false;
1590
0
      return Success;
1591
0
    }
1592
0
  }
1593
0
}
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
1598
LocaleInsensitveToLower(uint8_t a)
1599
0
{
1600
0
  if (a >= 'A' && a <= 'Z') { // unlikely
1601
0
    return static_cast<uint8_t>(
1602
0
             static_cast<uint8_t>(a - static_cast<uint8_t>('A')) +
1603
0
             static_cast<uint8_t>('a'));
1604
0
  }
1605
0
  return a;
1606
0
}
1607
1608
bool
1609
StartsWithIDNALabel(Input id)
1610
0
{
1611
0
  static const uint8_t IDN_ALABEL_PREFIX[4] = { 'x', 'n', '-', '-' };
1612
0
  Reader input(id);
1613
0
  for (const uint8_t prefixByte : IDN_ALABEL_PREFIX) {
1614
0
    uint8_t b;
1615
0
    if (input.Read(b) != Success) {
1616
0
      return false;
1617
0
    }
1618
0
    if (b != prefixByte) {
1619
0
      return false;
1620
0
    }
1621
0
  }
1622
0
  return true;
1623
0
}
1624
1625
bool
1626
ReadIPv4AddressComponent(Reader& input, bool lastComponent,
1627
                         /*out*/ uint8_t& valueOut)
1628
0
{
1629
0
  size_t length = 0;
1630
0
  unsigned int value = 0; // Must be larger than uint8_t.
1631
0
1632
0
  for (;;) {
1633
0
    if (input.AtEnd() && lastComponent) {
1634
0
      break;
1635
0
    }
1636
0
1637
0
    uint8_t b;
1638
0
    if (input.Read(b) != Success) {
1639
0
      return false;
1640
0
    }
1641
0
1642
0
    if (b >= '0' && b <= '9') {
1643
0
      if (value == 0 && length > 0) {
1644
0
        return false; // Leading zeros are not allowed.
1645
0
      }
1646
0
      value = (value * 10) + (b - '0');
1647
0
      if (value > 255) {
1648
0
        return false; // Component's value is too large.
1649
0
      }
1650
0
      ++length;
1651
0
    } else if (!lastComponent && b == '.') {
1652
0
      break;
1653
0
    } else {
1654
0
      return false; // Invalid character.
1655
0
    }
1656
0
  }
1657
0
1658
0
  if (length == 0) {
1659
0
    return false; // empty components not allowed
1660
0
  }
1661
0
1662
0
  valueOut = static_cast<uint8_t>(value);
1663
0
  return true;
1664
0
}
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
1672
ParseIPv4Address(Input hostname, /*out*/ uint8_t (&out)[4])
1673
0
{
1674
0
  Reader input(hostname);
1675
0
  return ReadIPv4AddressComponent(input, false, out[0]) &&
1676
0
         ReadIPv4AddressComponent(input, false, out[1]) &&
1677
0
         ReadIPv4AddressComponent(input, false, out[2]) &&
1678
0
         ReadIPv4AddressComponent(input, true, out[3]);
1679
0
}
1680
1681
namespace {
1682
1683
bool
1684
FinishIPv6Address(/*in/out*/ uint8_t (&address)[16], int numComponents,
1685
                  int contractionIndex)
1686
0
{
1687
0
  assert(numComponents >= 0);
1688
0
  assert(numComponents <= 8);
1689
0
  assert(contractionIndex >= -1);
1690
0
  assert(contractionIndex <= 8);
1691
0
  assert(contractionIndex <= numComponents);
1692
0
  if (!(numComponents >= 0 &&
1693
0
        numComponents <= 8 &&
1694
0
        contractionIndex >= -1 &&
1695
0
        contractionIndex <= 8 &&
1696
0
        contractionIndex <= numComponents)) {
1697
0
    return false;
1698
0
  }
1699
0
1700
0
  if (contractionIndex == -1) {
1701
0
    // no contraction
1702
0
    return numComponents == 8;
1703
0
  }
1704
0
1705
0
  if (numComponents >= 8) {
1706
0
    return false; // no room left to expand the contraction.
1707
0
  }
1708
0
1709
0
  // Shift components that occur after the contraction over.
1710
0
  std::copy_backward(address + (2u * static_cast<size_t>(contractionIndex)),
1711
0
                     address + (2u * static_cast<size_t>(numComponents)),
1712
0
                     address + (2u * 8u));
1713
0
  // 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
0
1717
0
  return true;
1718
0
}
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
ParseIPv6Address(Input hostname, /*out*/ uint8_t (&out)[16])
1727
0
{
1728
0
  Reader input(hostname);
1729
0
1730
0
  int currentComponentIndex = 0;
1731
0
  int contractionIndex = -1;
1732
0
1733
0
  if (input.Peek(':')) {
1734
0
    // A valid input can only start with ':' if there is a contraction at the
1735
0
    // beginning.
1736
0
    uint8_t b;
1737
0
    if (input.Read(b) != Success || b != ':') {
1738
0
      assert(false);
1739
0
      return false;
1740
0
    }
1741
0
    if (input.Read(b) != Success) {
1742
0
      return false;
1743
0
    }
1744
0
    if (b != ':') {
1745
0
      return false;
1746
0
    }
1747
0
    contractionIndex = 0;
1748
0
  }
1749
0
1750
0
  for (;;) {
1751
0
    // If we encounter a '.' then we'll have to backtrack to parse the input
1752
0
    // 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
0
      uint8_t value;
1758
0
      uint8_t b;
1759
0
      if (input.Read(b) != Success) {
1760
0
        assert(false);
1761
0
        return false;
1762
0
      }
1763
0
      switch (b) {
1764
0
        case '0': case '1': case '2': case '3': case '4':
1765
0
        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
0
        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
0
                                       UINT8_C(10));
1771
0
          break;
1772
0
        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
0
                                       UINT8_C(10));
1775
0
          break;
1776
0
        case '.':
1777
0
        {
1778
0
          // A dot indicates we hit a IPv4-syntax component. Backtrack, parsing
1779
0
          // the input from startOfComponent to the end of the input as an IPv4
1780
0
          // address, and then combine it with the other components.
1781
0
1782
0
          if (currentComponentIndex > 6) {
1783
0
            return false; // Too many components before the IPv4 component
1784
0
          }
1785
0
1786
0
          input.SkipToEnd();
1787
0
          Input ipv4Component;
1788
0
          if (input.GetInput(startOfComponent, ipv4Component) != Success) {
1789
0
            return false;
1790
0
          }
1791
0
          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
0
          }
1796
0
          assert(input.AtEnd());
1797
0
          currentComponentIndex += 2;
1798
0
1799
0
          return FinishIPv6Address(out, currentComponentIndex,
1800
0
                                   contractionIndex);
1801
0
        }
1802
0
        default:
1803
0
          return false;
1804
0
      }
1805
0
      if (componentLength >= 4) {
1806
0
        // component too long
1807
0
        return false;
1808
0
      }
1809
0
      ++componentLength;
1810
0
      componentValue = (componentValue * 0x10u) + value;
1811
0
    }
1812
0
1813
0
    if (currentComponentIndex >= 8) {
1814
0
      return false; // too many components
1815
0
    }
1816
0
1817
0
    if (componentLength == 0) {
1818
0
      if (input.AtEnd() && currentComponentIndex == contractionIndex) {
1819
0
        if (contractionIndex == 0) {
1820
0
          // don't accept "::"
1821
0
          return false;
1822
0
        }
1823
0
        return FinishIPv6Address(out, currentComponentIndex,
1824
0
                                 contractionIndex);
1825
0
      }
1826
0
      return false;
1827
0
    }
1828
0
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
0
1834
0
    ++currentComponentIndex;
1835
0
1836
0
    if (input.AtEnd()) {
1837
0
      return FinishIPv6Address(out, currentComponentIndex,
1838
0
                               contractionIndex);
1839
0
    }
1840
0
1841
0
    uint8_t b;
1842
0
    if (input.Read(b) != Success || b != ':') {
1843
0
      assert(false);
1844
0
      return false;
1845
0
    }
1846
0
1847
0
    if (input.Peek(':')) {
1848
0
      // Contraction
1849
0
      if (contractionIndex != -1) {
1850
0
        return false; // multiple contractions are not allowed.
1851
0
      }
1852
0
      if (input.Read(b) != Success || b != ':') {
1853
0
        assert(false);
1854
0
        return false;
1855
0
      }
1856
0
      contractionIndex = currentComponentIndex;
1857
0
      if (input.AtEnd()) {
1858
0
        // "::" at the end of the input.
1859
0
        return FinishIPv6Address(out, currentComponentIndex,
1860
0
                                 contractionIndex);
1861
0
      }
1862
0
    }
1863
0
  }
1864
0
}
1865
1866
bool
1867
IsValidReferenceDNSID(Input hostname)
1868
0
{
1869
0
  return IsValidDNSID(hostname, IDRole::ReferenceID, AllowWildcards::No);
1870
0
}
1871
1872
bool
1873
IsValidPresentedDNSID(Input hostname)
1874
0
{
1875
0
  return IsValidDNSID(hostname, IDRole::PresentedID, AllowWildcards::Yes);
1876
0
}
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
IsValidDNSID(Input hostname, IDRole idRole, AllowWildcards allowWildcards)
1886
0
{
1887
0
  if (hostname.GetLength() > 253) {
1888
0
    return false;
1889
0
  }
1890
0
1891
0
  Reader input(hostname);
1892
0
1893
0
  if (idRole == IDRole::NameConstraint && input.AtEnd()) {
1894
0
    return true;
1895
0
  }
1896
0
1897
0
  size_t dotCount = 0;
1898
0
  size_t labelLength = 0;
1899
0
  bool labelIsAllNumeric = false;
1900
0
  bool labelEndsWithHyphen = false;
1901
0
1902
0
  // Only presented IDs are allowed to have wildcard labels. And, like
1903
0
  // Chromium, be stricter than RFC 6125 requires by insisting that a
1904
0
  // 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
0
    }
1913
0
1914
0
    uint8_t b;
1915
0
    rv = input.Read(b);
1916
0
    if (rv != Success) {
1917
0
      return false;
1918
0
    }
1919
0
    if (b != '.') {
1920
0
      return false;
1921
0
    }
1922
0
    ++dotCount;
1923
0
  }
1924
0
1925
0
  do {
1926
0
    static const size_t MAX_LABEL_LENGTH = 63;
1927
0
1928
0
    uint8_t b;
1929
0
    if (input.Read(b) != Success) {
1930
0
      return false;
1931
0
    }
1932
0
    switch (b) {
1933
0
      case '-':
1934
0
        if (labelLength == 0) {
1935
0
          return false; // Labels must not start with a hyphen.
1936
0
        }
1937
0
        labelIsAllNumeric = false;
1938
0
        labelEndsWithHyphen = true;
1939
0
        ++labelLength;
1940
0
        if (labelLength > MAX_LABEL_LENGTH) {
1941
0
          return false;
1942
0
        }
1943
0
        break;
1944
0
1945
0
      // We avoid isdigit because it is locale-sensitive. See
1946
0
      // http://pubs.opengroup.org/onlinepubs/009695399/functions/isdigit.html
1947
0
      case '0': case '5':
1948
0
      case '1': case '6':
1949
0
      case '2': case '7':
1950
0
      case '3': case '8':
1951
0
      case '4': case '9':
1952
0
        if (labelLength == 0) {
1953
0
          labelIsAllNumeric = true;
1954
0
        }
1955
0
        labelEndsWithHyphen = false;
1956
0
        ++labelLength;
1957
0
        if (labelLength > MAX_LABEL_LENGTH) {
1958
0
          return false;
1959
0
        }
1960
0
        break;
1961
0
1962
0
      // We avoid using islower/isupper/tolower/toupper or similar things, to
1963
0
      // avoid any possibility of this code being locale-sensitive. See
1964
0
      // http://pubs.opengroup.org/onlinepubs/009695399/functions/isupper.html
1965
0
      case 'a': case 'A': case 'n': case 'N':
1966
0
      case 'b': case 'B': case 'o': case 'O':
1967
0
      case 'c': case 'C': case 'p': case 'P':
1968
0
      case 'd': case 'D': case 'q': case 'Q':
1969
0
      case 'e': case 'E': case 'r': case 'R':
1970
0
      case 'f': case 'F': case 's': case 'S':
1971
0
      case 'g': case 'G': case 't': case 'T':
1972
0
      case 'h': case 'H': case 'u': case 'U':
1973
0
      case 'i': case 'I': case 'v': case 'V':
1974
0
      case 'j': case 'J': case 'w': case 'W':
1975
0
      case 'k': case 'K': case 'x': case 'X':
1976
0
      case 'l': case 'L': case 'y': case 'Y':
1977
0
      case 'm': case 'M': case 'z': case 'Z':
1978
0
      // We allow underscores for compatibility with existing practices.
1979
0
      // See bug 1136616.
1980
0
      case '_':
1981
0
        labelIsAllNumeric = false;
1982
0
        labelEndsWithHyphen = false;
1983
0
        ++labelLength;
1984
0
        if (labelLength > MAX_LABEL_LENGTH) {
1985
0
          return false;
1986
0
        }
1987
0
        break;
1988
0
1989
0
      case '.':
1990
0
        ++dotCount;
1991
0
        if (labelLength == 0 &&
1992
0
            (idRole != IDRole::NameConstraint || !isFirstByte)) {
1993
0
          return false;
1994
0
        }
1995
0
        if (labelEndsWithHyphen) {
1996
0
          return false; // Labels must not end with a hyphen.
1997
0
        }
1998
0
        labelLength = 0;
1999
0
        break;
2000
0
2001
0
      default:
2002
0
        return false; // Invalid character.
2003
0
    }
2004
0
    isFirstByte = false;
2005
0
  } while (!input.AtEnd());
2006
0
2007
0
  // Only reference IDs, not presented IDs or name constraints, may be
2008
0
  // absolute.
2009
0
  if (labelLength == 0 && idRole != IDRole::ReferenceID) {
2010
0
    return false;
2011
0
  }
2012
0
2013
0
  if (labelEndsWithHyphen) {
2014
0
    return false; // Labels must not end with a hyphen.
2015
0
  }
2016
0
2017
0
  if (labelIsAllNumeric) {
2018
0
    return false; // Last label must not be all numeric.
2019
0
  }
2020
0
2021
0
  if (isWildcard) {
2022
0
    // 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
0
2025
0
    // Like NSS, require at least two labels to follow the wildcard label.
2026
0
    //
2027
0
    // TODO(bug XXXXXXX): Allow the TrustDomain to control this on a
2028
0
    // per-eTLD+1 basis, similar to Chromium. Even then, it might be better to
2029
0
    // still enforce that there are at least two labels after the wildcard.
2030
0
    if (labelCount < 3) {
2031
0
      return false;
2032
0
    }
2033
0
    // XXX: RFC6125 says that we shouldn't accept wildcards within an IDN
2034
0
    // A-Label. The consequence of this is that we effectively discriminate
2035
0
    // against users of languages that cannot be encoded with ASCII.
2036
0
    if (StartsWithIDNALabel(hostname)) {
2037
0
      return false;
2038
0
    }
2039
0
2040
0
    // TODO(bug XXXXXXX): Wildcards are not allowed for EV certificates.
2041
0
    // Provide an option to indicate whether wildcards should be matched, for
2042
0
    // the purpose of helping the application enforce this.
2043
0
  }
2044
0
2045
0
  return true;
2046
0
}
2047
2048
} // namespace
2049
2050
} } // namespace mozilla::pkix