Coverage Report

Created: 2018-09-25 14:53

/src/mozilla-central/security/pkix/lib/pkixbuild.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 2013 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
#include "pkix/pkix.h"
26
27
#include "pkixcheck.h"
28
#include "pkixutil.h"
29
30
namespace mozilla { namespace pkix {
31
32
static Result BuildForward(TrustDomain& trustDomain,
33
                           const BackCert& subject,
34
                           Time time,
35
                           KeyUsage requiredKeyUsageIfPresent,
36
                           KeyPurposeId requiredEKUIfPresent,
37
                           const CertPolicyId& requiredPolicy,
38
                           /*optional*/ const Input* stapledOCSPResponse,
39
                           unsigned int subCACount,
40
                           unsigned int& buildForwardCallBudget);
41
42
0
TrustDomain::IssuerChecker::IssuerChecker() { }
43
0
TrustDomain::IssuerChecker::~IssuerChecker() { }
44
45
// The implementation of TrustDomain::IssuerTracker is in a subclass only to
46
// hide the implementation from external users.
47
class PathBuildingStep final : public TrustDomain::IssuerChecker
48
{
49
public:
50
  PathBuildingStep(TrustDomain& aTrustDomain, const BackCert& aSubject,
51
                   Time aTime, KeyPurposeId aRequiredEKUIfPresent,
52
                   const CertPolicyId& aRequiredPolicy,
53
                   /*optional*/ const Input* aStapledOCSPResponse,
54
                   unsigned int aSubCACount, Result aDeferredSubjectError,
55
                   unsigned int& aBuildForwardCallBudget)
56
    : trustDomain(aTrustDomain)
57
    , subject(aSubject)
58
    , time(aTime)
59
    , requiredEKUIfPresent(aRequiredEKUIfPresent)
60
    , requiredPolicy(aRequiredPolicy)
61
    , stapledOCSPResponse(aStapledOCSPResponse)
62
    , subCACount(aSubCACount)
63
    , deferredSubjectError(aDeferredSubjectError)
64
    , subjectSignaturePublicKeyAlg(der::PublicKeyAlgorithm::Uninitialized)
65
    , result(Result::FATAL_ERROR_LIBRARY_FAILURE)
66
    , resultWasSet(false)
67
    , buildForwardCallBudget(aBuildForwardCallBudget)
68
0
  {
69
0
  }
70
71
  Result Check(Input potentialIssuerDER,
72
               /*optional*/ const Input* additionalNameConstraints,
73
               /*out*/ bool& keepGoing) override;
74
75
  Result CheckResult() const;
76
77
private:
78
  TrustDomain& trustDomain;
79
  const BackCert& subject;
80
  const Time time;
81
  const KeyPurposeId requiredEKUIfPresent;
82
  const CertPolicyId& requiredPolicy;
83
  /*optional*/ Input const* const stapledOCSPResponse;
84
  const unsigned int subCACount;
85
  const Result deferredSubjectError;
86
87
  // Initialized lazily.
88
  uint8_t subjectSignatureDigestBuf[MAX_DIGEST_SIZE_IN_BYTES];
89
  der::PublicKeyAlgorithm subjectSignaturePublicKeyAlg;
90
  SignedDigest subjectSignature;
91
92
  Result RecordResult(Result currentResult, /*out*/ bool& keepGoing);
93
  Result result;
94
  bool resultWasSet;
95
  unsigned int& buildForwardCallBudget;
96
97
  PathBuildingStep(const PathBuildingStep&) = delete;
98
  void operator=(const PathBuildingStep&) = delete;
99
};
100
101
Result
102
PathBuildingStep::RecordResult(Result newResult, /*out*/ bool& keepGoing)
103
0
{
104
0
  if (newResult == Result::ERROR_UNTRUSTED_CERT) {
105
0
    newResult = Result::ERROR_UNTRUSTED_ISSUER;
106
0
  } else if (newResult == Result::ERROR_EXPIRED_CERTIFICATE) {
107
0
    newResult = Result::ERROR_EXPIRED_ISSUER_CERTIFICATE;
108
0
  } else if (newResult == Result::ERROR_NOT_YET_VALID_CERTIFICATE) {
109
0
    newResult = Result::ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE;
110
0
  }
111
0
112
0
  if (resultWasSet) {
113
0
    if (result == Success) {
114
0
      return NotReached("RecordResult called after finding a chain",
115
0
                        Result::FATAL_ERROR_INVALID_STATE);
116
0
    }
117
0
    // If every potential issuer has the same problem (e.g. expired) and/or if
118
0
    // there is only one bad potential issuer, then return a more specific
119
0
    // error. Otherwise, punt on trying to decide which error should be
120
0
    // returned by returning the generic Result::ERROR_UNKNOWN_ISSUER error.
121
0
    if (newResult != Success && newResult != result) {
122
0
      newResult = Result::ERROR_UNKNOWN_ISSUER;
123
0
    }
124
0
  }
125
0
126
0
  result = newResult;
127
0
  resultWasSet = true;
128
0
  keepGoing = result != Success;
129
0
  return Success;
130
0
}
131
132
Result
133
PathBuildingStep::CheckResult() const
134
0
{
135
0
  if (!resultWasSet) {
136
0
    return Result::ERROR_UNKNOWN_ISSUER;
137
0
  }
138
0
  return result;
139
0
}
140
141
// The code that executes in the inner loop of BuildForward
142
Result
143
PathBuildingStep::Check(Input potentialIssuerDER,
144
           /*optional*/ const Input* additionalNameConstraints,
145
                /*out*/ bool& keepGoing)
146
0
{
147
0
  BackCert potentialIssuer(potentialIssuerDER, EndEntityOrCA::MustBeCA,
148
0
                           &subject);
149
0
  Result rv = potentialIssuer.Init();
150
0
  if (rv != Success) {
151
0
    return RecordResult(rv, keepGoing);
152
0
  }
153
0
154
0
  // Simple TrustDomain::FindIssuers implementations may pass in all possible
155
0
  // CA certificates without any filtering. Because of this, we don't consider
156
0
  // a mismatched name to be an error. Instead, we just pretend that any
157
0
  // certificate without a matching name was never passed to us. In particular,
158
0
  // we treat the case where the TrustDomain only asks us to check CA
159
0
  // certificates with mismatched names as equivalent to the case where the
160
0
  // TrustDomain never called Check() at all.
161
0
  if (!InputsAreEqual(potentialIssuer.GetSubject(), subject.GetIssuer())) {
162
0
    keepGoing = true;
163
0
    return Success;
164
0
  }
165
0
166
0
  // Loop prevention, done as recommended by RFC4158 Section 5.2
167
0
  // TODO: this doesn't account for subjectAltNames!
168
0
  // TODO(perf): This probably can and should be optimized in some way.
169
0
  for (const BackCert* prev = potentialIssuer.childCert; prev;
170
0
       prev = prev->childCert) {
171
0
    if (InputsAreEqual(potentialIssuer.GetSubjectPublicKeyInfo(),
172
0
                       prev->GetSubjectPublicKeyInfo()) &&
173
0
        InputsAreEqual(potentialIssuer.GetSubject(), prev->GetSubject())) {
174
0
      // XXX: error code
175
0
      return RecordResult(Result::ERROR_UNKNOWN_ISSUER, keepGoing);
176
0
    }
177
0
  }
178
0
179
0
  if (potentialIssuer.GetNameConstraints()) {
180
0
    rv = CheckNameConstraints(*potentialIssuer.GetNameConstraints(),
181
0
                              subject, requiredEKUIfPresent);
182
0
    if (rv != Success) {
183
0
       return RecordResult(rv, keepGoing);
184
0
    }
185
0
  }
186
0
187
0
  if (additionalNameConstraints) {
188
0
    rv = CheckNameConstraints(*additionalNameConstraints, subject,
189
0
                              requiredEKUIfPresent);
190
0
    if (rv != Success) {
191
0
       return RecordResult(rv, keepGoing);
192
0
    }
193
0
  }
194
0
195
0
  rv = CheckTLSFeatures(subject, potentialIssuer);
196
0
  if (rv != Success) {
197
0
    return RecordResult(rv, keepGoing);
198
0
  }
199
0
200
0
  // If we've ran out of budget, stop searching.
201
0
  if (buildForwardCallBudget == 0) {
202
0
    Result savedRv = RecordResult(Result::ERROR_UNKNOWN_ISSUER, keepGoing);
203
0
    keepGoing = false;
204
0
    return savedRv;
205
0
  }
206
0
  buildForwardCallBudget--;
207
0
208
0
  // RFC 5280, Section 4.2.1.3: "If the keyUsage extension is present, then the
209
0
  // subject public key MUST NOT be used to verify signatures on certificates
210
0
  // or CRLs unless the corresponding keyCertSign or cRLSign bit is set."
211
0
  rv = BuildForward(trustDomain, potentialIssuer, time, KeyUsage::keyCertSign,
212
0
                    requiredEKUIfPresent, requiredPolicy, nullptr, subCACount,
213
0
                    buildForwardCallBudget);
214
0
  if (rv != Success) {
215
0
    return RecordResult(rv, keepGoing);
216
0
  }
217
0
218
0
  // Calculate the digest of the subject's signed data if we haven't already
219
0
  // done so. We do this lazily to avoid doing it at all if we backtrack before
220
0
  // getting to this point. We cache the result to avoid recalculating it if we
221
0
  // backtrack after getting to this point.
222
0
  if (subjectSignature.digest.GetLength() == 0) {
223
0
    rv = DigestSignedData(trustDomain, subject.GetSignedData(),
224
0
                          subjectSignatureDigestBuf,
225
0
                          subjectSignaturePublicKeyAlg, subjectSignature);
226
0
    if (rv != Success) {
227
0
      return rv;
228
0
    }
229
0
  }
230
0
231
0
  rv = VerifySignedDigest(trustDomain, subjectSignaturePublicKeyAlg,
232
0
                          subjectSignature,
233
0
                          potentialIssuer.GetSubjectPublicKeyInfo());
234
0
  if (rv != Success) {
235
0
    return RecordResult(rv, keepGoing);
236
0
  }
237
0
238
0
  // We avoid doing revocation checking for expired certificates because OCSP
239
0
  // responders are allowed to forget about expired certificates, and many OCSP
240
0
  // responders return an error when asked for the status of an expired
241
0
  // certificate.
242
0
  if (deferredSubjectError != Result::ERROR_EXPIRED_CERTIFICATE) {
243
0
    CertID certID(subject.GetIssuer(), potentialIssuer.GetSubjectPublicKeyInfo(),
244
0
                  subject.GetSerialNumber());
245
0
    Time notBefore(Time::uninitialized);
246
0
    Time notAfter(Time::uninitialized);
247
0
    // This should never fail. If we're here, we've already parsed the validity
248
0
    // and checked that the given time is in the certificate's validity period.
249
0
    rv = ParseValidity(subject.GetValidity(), &notBefore, &notAfter);
250
0
    if (rv != Success) {
251
0
      return rv;
252
0
    }
253
0
    Duration validityDuration(notAfter, notBefore);
254
0
    rv = trustDomain.CheckRevocation(subject.endEntityOrCA, certID, time,
255
0
                                     validityDuration, stapledOCSPResponse,
256
0
                                     subject.GetAuthorityInfoAccess());
257
0
    if (rv != Success) {
258
0
      // Since this is actually a problem with the current subject certificate
259
0
      // (rather than the issuer), it doesn't make sense to keep going; all
260
0
      // paths through this certificate will fail.
261
0
      Result savedRv = RecordResult(rv, keepGoing);
262
0
      keepGoing = false;
263
0
      return savedRv;
264
0
    }
265
0
266
0
    if (subject.endEntityOrCA == EndEntityOrCA::MustBeEndEntity) {
267
0
      const Input* sctExtension = subject.GetSignedCertificateTimestamps();
268
0
      if (sctExtension) {
269
0
        Input sctList;
270
0
        rv = ExtractSignedCertificateTimestampListFromExtension(*sctExtension,
271
0
                                                                sctList);
272
0
        if (rv != Success) {
273
0
          // Again, the problem is with this certificate, and all paths through
274
0
          // it will fail.
275
0
          Result savedRv = RecordResult(rv, keepGoing);
276
0
          keepGoing = false;
277
0
          return savedRv;
278
0
        }
279
0
        trustDomain.NoteAuxiliaryExtension(AuxiliaryExtension::EmbeddedSCTList,
280
0
                                           sctList);
281
0
      }
282
0
    }
283
0
  }
284
0
285
0
  return RecordResult(Success, keepGoing);
286
0
}
287
288
// Recursively build the path from the given subject certificate to the root.
289
//
290
// Be very careful about changing the order of checks. The order is significant
291
// because it affects which error we return when a certificate or certificate
292
// chain has multiple problems. See the error ranking documentation in
293
// pkix/pkix.h.
294
static Result
295
BuildForward(TrustDomain& trustDomain,
296
             const BackCert& subject,
297
             Time time,
298
             KeyUsage requiredKeyUsageIfPresent,
299
             KeyPurposeId requiredEKUIfPresent,
300
             const CertPolicyId& requiredPolicy,
301
             /*optional*/ const Input* stapledOCSPResponse,
302
             unsigned int subCACount,
303
             unsigned int& buildForwardCallBudget)
304
0
{
305
0
  Result rv;
306
0
307
0
  TrustLevel trustLevel;
308
0
  // If this is an end-entity and not a trust anchor, we defer reporting
309
0
  // any error found here until after attempting to find a valid chain.
310
0
  // See the explanation of error prioritization in pkix.h.
311
0
  rv = CheckIssuerIndependentProperties(trustDomain, subject, time,
312
0
                                        requiredKeyUsageIfPresent,
313
0
                                        requiredEKUIfPresent, requiredPolicy,
314
0
                                        subCACount, trustLevel);
315
0
  Result deferredEndEntityError = Success;
316
0
  if (rv != Success) {
317
0
    if (subject.endEntityOrCA == EndEntityOrCA::MustBeEndEntity &&
318
0
        trustLevel != TrustLevel::TrustAnchor) {
319
0
      deferredEndEntityError = rv;
320
0
    } else {
321
0
      return rv;
322
0
    }
323
0
  }
324
0
325
0
  if (trustLevel == TrustLevel::TrustAnchor) {
326
0
    // End of the recursion.
327
0
328
0
    NonOwningDERArray chain;
329
0
    for (const BackCert* cert = &subject; cert; cert = cert->childCert) {
330
0
      rv = chain.Append(cert->GetDER());
331
0
      if (rv != Success) {
332
0
        return NotReached("NonOwningDERArray::SetItem failed.", rv);
333
0
      }
334
0
    }
335
0
336
0
    // This must be done here, after the chain is built but before any
337
0
    // revocation checks have been done.
338
0
    return trustDomain.IsChainValid(chain, time, requiredPolicy);
339
0
  }
340
0
341
0
  if (subject.endEntityOrCA == EndEntityOrCA::MustBeCA) {
342
0
    // Avoid stack overflows and poor performance by limiting cert chain
343
0
    // length.
344
0
    static const unsigned int MAX_SUBCA_COUNT = 6;
345
0
    static_assert(1/*end-entity*/ + MAX_SUBCA_COUNT + 1/*root*/ ==
346
0
                  NonOwningDERArray::MAX_LENGTH,
347
0
                  "MAX_SUBCA_COUNT and NonOwningDERArray::MAX_LENGTH mismatch.");
348
0
    if (subCACount >= MAX_SUBCA_COUNT) {
349
0
      return Result::ERROR_UNKNOWN_ISSUER;
350
0
    }
351
0
    ++subCACount;
352
0
  } else {
353
0
    assert(subCACount == 0);
354
0
  }
355
0
356
0
  // Find a trusted issuer.
357
0
358
0
  PathBuildingStep pathBuilder(trustDomain, subject, time,
359
0
                               requiredEKUIfPresent, requiredPolicy,
360
0
                               stapledOCSPResponse, subCACount,
361
0
                               deferredEndEntityError, buildForwardCallBudget);
362
0
363
0
  // TODO(bug 965136): Add SKI/AKI matching optimizations
364
0
  rv = trustDomain.FindIssuer(subject.GetIssuer(), pathBuilder, time);
365
0
  if (rv != Success) {
366
0
    return rv;
367
0
  }
368
0
369
0
  rv = pathBuilder.CheckResult();
370
0
  if (rv != Success) {
371
0
    return rv;
372
0
  }
373
0
374
0
  // If we found a valid chain but deferred reporting an error with the
375
0
  // end-entity certificate, report it now.
376
0
  if (deferredEndEntityError != Success) {
377
0
    return deferredEndEntityError;
378
0
  }
379
0
380
0
  // We've built a valid chain from the subject cert up to a trusted root.
381
0
  return Success;
382
0
}
383
384
Result
385
BuildCertChain(TrustDomain& trustDomain, Input certDER,
386
               Time time, EndEntityOrCA endEntityOrCA,
387
               KeyUsage requiredKeyUsageIfPresent,
388
               KeyPurposeId requiredEKUIfPresent,
389
               const CertPolicyId& requiredPolicy,
390
               /*optional*/ const Input* stapledOCSPResponse)
391
0
{
392
0
  // XXX: Support the legacy use of the subject CN field for indicating the
393
0
  // domain name the certificate is valid for.
394
0
  BackCert cert(certDER, endEntityOrCA, nullptr);
395
0
  Result rv = cert.Init();
396
0
  if (rv != Success) {
397
0
    return rv;
398
0
  }
399
0
400
0
  // See bug 1056341 for context. If mozilla::pkix is being used in an
401
0
  // environment where there are many certificates that all have the same
402
0
  // distinguished name as their subject and issuer (but different SPKIs - see
403
0
  // the loop prevention as per RFC4158 Section 5.2 in PathBuildingStep::Check),
404
0
  // the space to search becomes exponential. Because it would be prohibitively
405
0
  // expensive to explore the entire space, we introduce a budget here that,
406
0
  // when exhausted, terminates the search with the result
407
0
  // Result::ERROR_UNKNOWN_ISSUER. Essentially, we limit the total number of
408
0
  // times `BuildForward` can be called. The current value appears to be a good
409
0
  // balance between finding a path when one exists (when the space isn't too
410
0
  // large) and timing out quickly enough when the space is too large or there
411
0
  // is no valid path to a trust anchor.
412
0
  unsigned int buildForwardCallBudget = 200000;
413
0
  return BuildForward(trustDomain, cert, time, requiredKeyUsageIfPresent,
414
0
                      requiredEKUIfPresent, requiredPolicy, stapledOCSPResponse,
415
0
                      0/*subCACount*/, buildForwardCallBudget);
416
0
}
417
418
} } // namespace mozilla::pkix