summaryrefslogtreecommitdiff
path: root/security/pkix/lib/pkixbuild.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'security/pkix/lib/pkixbuild.cpp')
-rw-r--r--security/pkix/lib/pkixbuild.cpp108
1 files changed, 64 insertions, 44 deletions
diff --git a/security/pkix/lib/pkixbuild.cpp b/security/pkix/lib/pkixbuild.cpp
index d6754554ce..02de1ff705 100644
--- a/security/pkix/lib/pkixbuild.cpp
+++ b/security/pkix/lib/pkixbuild.cpp
@@ -1,30 +1,13 @@
/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
-/* This code is made available to you under your choice of the following sets
- * of licensing terms:
- */
/* This Source Code Form is subject to the terms of the Mozilla Public
* License, v. 2.0. If a copy of the MPL was not distributed with this
* file, You can obtain one at http://mozilla.org/MPL/2.0/.
*/
-/* Copyright 2013 Mozilla Contributors
- *
- * Licensed under the Apache License, Version 2.0 (the "License");
- * you may not use this file except in compliance with the License.
- * You may obtain a copy of the License at
- *
- * http://www.apache.org/licenses/LICENSE-2.0
- *
- * Unless required by applicable law or agreed to in writing, software
- * distributed under the License is distributed on an "AS IS" BASIS,
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
- * See the License for the specific language governing permissions and
- * limitations under the License.
- */
#include "pkix/pkix.h"
-#include "pkixcheck.h"
-#include "pkixutil.h"
+#include "pkix/pkixcheck.h"
+#include "pkix/pkixutil.h"
namespace mozilla { namespace pkix {
@@ -35,7 +18,8 @@ static Result BuildForward(TrustDomain& trustDomain,
KeyPurposeId requiredEKUIfPresent,
const CertPolicyId& requiredPolicy,
/*optional*/ const Input* stapledOCSPResponse,
- unsigned int subCACount);
+ unsigned int subCACount,
+ unsigned int& buildForwardCallBudget);
TrustDomain::IssuerChecker::IssuerChecker() { }
TrustDomain::IssuerChecker::~IssuerChecker() { }
@@ -45,21 +29,24 @@ TrustDomain::IssuerChecker::~IssuerChecker() { }
class PathBuildingStep final : public TrustDomain::IssuerChecker
{
public:
- PathBuildingStep(TrustDomain& trustDomain, const BackCert& subject,
- Time time, KeyPurposeId requiredEKUIfPresent,
- const CertPolicyId& requiredPolicy,
- /*optional*/ const Input* stapledOCSPResponse,
- unsigned int subCACount, Result deferredSubjectError)
- : trustDomain(trustDomain)
- , subject(subject)
- , time(time)
- , requiredEKUIfPresent(requiredEKUIfPresent)
- , requiredPolicy(requiredPolicy)
- , stapledOCSPResponse(stapledOCSPResponse)
- , subCACount(subCACount)
- , deferredSubjectError(deferredSubjectError)
+ PathBuildingStep(TrustDomain& aTrustDomain, const BackCert& aSubject,
+ Time aTime, KeyPurposeId aRequiredEKUIfPresent,
+ const CertPolicyId& aRequiredPolicy,
+ /*optional*/ const Input* aStapledOCSPResponse,
+ unsigned int aSubCACount, Result aDeferredSubjectError,
+ unsigned int& aBuildForwardCallBudget)
+ : trustDomain(aTrustDomain)
+ , subject(aSubject)
+ , time(aTime)
+ , requiredEKUIfPresent(aRequiredEKUIfPresent)
+ , requiredPolicy(aRequiredPolicy)
+ , stapledOCSPResponse(aStapledOCSPResponse)
+ , subCACount(aSubCACount)
+ , deferredSubjectError(aDeferredSubjectError)
+ , subjectSignaturePublicKeyAlg(der::PublicKeyAlgorithm::Uninitialized)
, result(Result::FATAL_ERROR_LIBRARY_FAILURE)
, resultWasSet(false)
+ , buildForwardCallBudget(aBuildForwardCallBudget)
{
}
@@ -87,6 +74,7 @@ private:
Result RecordResult(Result currentResult, /*out*/ bool& keepGoing);
Result result;
bool resultWasSet;
+ unsigned int& buildForwardCallBudget;
PathBuildingStep(const PathBuildingStep&) = delete;
void operator=(const PathBuildingStep&) = delete;
@@ -160,9 +148,8 @@ PathBuildingStep::Check(Input potentialIssuerDER,
// Loop prevention, done as recommended by RFC4158 Section 5.2
// TODO: this doesn't account for subjectAltNames!
// TODO(perf): This probably can and should be optimized in some way.
- bool loopDetected = false;
- for (const BackCert* prev = potentialIssuer.childCert;
- !loopDetected && prev != nullptr; prev = prev->childCert) {
+ for (const BackCert* prev = potentialIssuer.childCert; prev;
+ prev = prev->childCert) {
if (InputsAreEqual(potentialIssuer.GetSubjectPublicKeyInfo(),
prev->GetSubjectPublicKeyInfo()) &&
InputsAreEqual(potentialIssuer.GetSubject(), prev->GetSubject())) {
@@ -192,11 +179,20 @@ PathBuildingStep::Check(Input potentialIssuerDER,
return RecordResult(rv, keepGoing);
}
+ // If we've ran out of budget, stop searching.
+ if (buildForwardCallBudget == 0) {
+ Result savedRv = RecordResult(Result::ERROR_UNKNOWN_ISSUER, keepGoing);
+ keepGoing = false;
+ return savedRv;
+ }
+ buildForwardCallBudget--;
+
// RFC 5280, Section 4.2.1.3: "If the keyUsage extension is present, then the
// subject public key MUST NOT be used to verify signatures on certificates
// or CRLs unless the corresponding keyCertSign or cRLSign bit is set."
rv = BuildForward(trustDomain, potentialIssuer, time, KeyUsage::keyCertSign,
- requiredEKUIfPresent, requiredPolicy, nullptr, subCACount);
+ requiredEKUIfPresent, requiredPolicy, nullptr, subCACount,
+ buildForwardCallBudget);
if (rv != Success) {
return RecordResult(rv, keepGoing);
}
@@ -239,9 +235,15 @@ PathBuildingStep::Check(Input potentialIssuerDER,
Duration validityDuration(notAfter, notBefore);
rv = trustDomain.CheckRevocation(subject.endEntityOrCA, certID, time,
validityDuration, stapledOCSPResponse,
- subject.GetAuthorityInfoAccess());
+ subject.GetAuthorityInfoAccess(),
+ subject.GetSignedCertificateTimestamps());
if (rv != Success) {
- return RecordResult(rv, keepGoing);
+ // Since this is actually a problem with the current subject certificate
+ // (rather than the issuer), it doesn't make sense to keep going; all
+ // paths through this certificate will fail.
+ Result savedRv = RecordResult(rv, keepGoing);
+ keepGoing = false;
+ return savedRv;
}
if (subject.endEntityOrCA == EndEntityOrCA::MustBeEndEntity) {
@@ -251,7 +253,11 @@ PathBuildingStep::Check(Input potentialIssuerDER,
rv = ExtractSignedCertificateTimestampListFromExtension(*sctExtension,
sctList);
if (rv != Success) {
- return RecordResult(rv, keepGoing);
+ // Again, the problem is with this certificate, and all paths through
+ // it will fail.
+ Result savedRv = RecordResult(rv, keepGoing);
+ keepGoing = false;
+ return savedRv;
}
trustDomain.NoteAuxiliaryExtension(AuxiliaryExtension::EmbeddedSCTList,
sctList);
@@ -276,7 +282,8 @@ BuildForward(TrustDomain& trustDomain,
KeyPurposeId requiredEKUIfPresent,
const CertPolicyId& requiredPolicy,
/*optional*/ const Input* stapledOCSPResponse,
- unsigned int subCACount)
+ unsigned int subCACount,
+ unsigned int& buildForwardCallBudget)
{
Result rv;
@@ -311,7 +318,7 @@ BuildForward(TrustDomain& trustDomain,
// This must be done here, after the chain is built but before any
// revocation checks have been done.
- return trustDomain.IsChainValid(chain, time);
+ return trustDomain.IsChainValid(chain, time, requiredPolicy);
}
if (subject.endEntityOrCA == EndEntityOrCA::MustBeCA) {
@@ -334,7 +341,7 @@ BuildForward(TrustDomain& trustDomain,
PathBuildingStep pathBuilder(trustDomain, subject, time,
requiredEKUIfPresent, requiredPolicy,
stapledOCSPResponse, subCACount,
- deferredEndEntityError);
+ deferredEndEntityError, buildForwardCallBudget);
// TODO(bug 965136): Add SKI/AKI matching optimizations
rv = trustDomain.FindIssuer(subject.GetIssuer(), pathBuilder, time);
@@ -373,9 +380,22 @@ BuildCertChain(TrustDomain& trustDomain, Input certDER,
return rv;
}
+ // See bug 1056341 for context. If mozilla::pkix is being used in an
+ // environment where there are many certificates that all have the same
+ // distinguished name as their subject and issuer (but different SPKIs - see
+ // the loop prevention as per RFC4158 Section 5.2 in PathBuildingStep::Check),
+ // the space to search becomes exponential. Because it would be prohibitively
+ // expensive to explore the entire space, we introduce a budget here that,
+ // when exhausted, terminates the search with the result
+ // Result::ERROR_UNKNOWN_ISSUER. Essentially, we limit the total number of
+ // times `BuildForward` can be called. The current value appears to be a good
+ // balance between finding a path when one exists (when the space isn't too
+ // large) and timing out quickly enough when the space is too large or there
+ // is no valid path to a trust anchor.
+ unsigned int buildForwardCallBudget = 200000;
return BuildForward(trustDomain, cert, time, requiredKeyUsageIfPresent,
requiredEKUIfPresent, requiredPolicy, stapledOCSPResponse,
- 0/*subCACount*/);
+ 0/*subCACount*/, buildForwardCallBudget);
}
} } // namespace mozilla::pkix