123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402 |
- /* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
- /* 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/.
- */
- #include "pkix/pkix.h"
- #include "pkix/pkixcheck.h"
- #include "pkix/pkixutil.h"
- namespace mozilla { namespace pkix {
- static Result BuildForward(TrustDomain& trustDomain,
- const BackCert& subject,
- Time time,
- KeyUsage requiredKeyUsageIfPresent,
- KeyPurposeId requiredEKUIfPresent,
- const CertPolicyId& requiredPolicy,
- /*optional*/ const Input* stapledOCSPResponse,
- unsigned int subCACount,
- unsigned int& buildForwardCallBudget);
- TrustDomain::IssuerChecker::IssuerChecker() { }
- TrustDomain::IssuerChecker::~IssuerChecker() { }
- // The implementation of TrustDomain::IssuerTracker is in a subclass only to
- // hide the implementation from external users.
- class PathBuildingStep final : public TrustDomain::IssuerChecker
- {
- public:
- 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)
- {
- }
- Result Check(Input potentialIssuerDER,
- /*optional*/ const Input* additionalNameConstraints,
- /*out*/ bool& keepGoing) override;
- Result CheckResult() const;
- private:
- TrustDomain& trustDomain;
- const BackCert& subject;
- const Time time;
- const KeyPurposeId requiredEKUIfPresent;
- const CertPolicyId& requiredPolicy;
- /*optional*/ Input const* const stapledOCSPResponse;
- const unsigned int subCACount;
- const Result deferredSubjectError;
- // Initialized lazily.
- uint8_t subjectSignatureDigestBuf[MAX_DIGEST_SIZE_IN_BYTES];
- der::PublicKeyAlgorithm subjectSignaturePublicKeyAlg;
- SignedDigest subjectSignature;
- Result RecordResult(Result currentResult, /*out*/ bool& keepGoing);
- Result result;
- bool resultWasSet;
- unsigned int& buildForwardCallBudget;
- PathBuildingStep(const PathBuildingStep&) = delete;
- void operator=(const PathBuildingStep&) = delete;
- };
- Result
- PathBuildingStep::RecordResult(Result newResult, /*out*/ bool& keepGoing)
- {
- if (newResult == Result::ERROR_UNTRUSTED_CERT) {
- newResult = Result::ERROR_UNTRUSTED_ISSUER;
- } else if (newResult == Result::ERROR_EXPIRED_CERTIFICATE) {
- newResult = Result::ERROR_EXPIRED_ISSUER_CERTIFICATE;
- } else if (newResult == Result::ERROR_NOT_YET_VALID_CERTIFICATE) {
- newResult = Result::ERROR_NOT_YET_VALID_ISSUER_CERTIFICATE;
- }
- if (resultWasSet) {
- if (result == Success) {
- return NotReached("RecordResult called after finding a chain",
- Result::FATAL_ERROR_INVALID_STATE);
- }
- // If every potential issuer has the same problem (e.g. expired) and/or if
- // there is only one bad potential issuer, then return a more specific
- // error. Otherwise, punt on trying to decide which error should be
- // returned by returning the generic Result::ERROR_UNKNOWN_ISSUER error.
- if (newResult != Success && newResult != result) {
- newResult = Result::ERROR_UNKNOWN_ISSUER;
- }
- }
- result = newResult;
- resultWasSet = true;
- keepGoing = result != Success;
- return Success;
- }
- Result
- PathBuildingStep::CheckResult() const
- {
- if (!resultWasSet) {
- return Result::ERROR_UNKNOWN_ISSUER;
- }
- return result;
- }
- // The code that executes in the inner loop of BuildForward
- Result
- PathBuildingStep::Check(Input potentialIssuerDER,
- /*optional*/ const Input* additionalNameConstraints,
- /*out*/ bool& keepGoing)
- {
- BackCert potentialIssuer(potentialIssuerDER, EndEntityOrCA::MustBeCA,
- &subject);
- Result rv = potentialIssuer.Init();
- if (rv != Success) {
- return RecordResult(rv, keepGoing);
- }
- // Simple TrustDomain::FindIssuers implementations may pass in all possible
- // CA certificates without any filtering. Because of this, we don't consider
- // a mismatched name to be an error. Instead, we just pretend that any
- // certificate without a matching name was never passed to us. In particular,
- // we treat the case where the TrustDomain only asks us to check CA
- // certificates with mismatched names as equivalent to the case where the
- // TrustDomain never called Check() at all.
- if (!InputsAreEqual(potentialIssuer.GetSubject(), subject.GetIssuer())) {
- keepGoing = true;
- return Success;
- }
- // 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.
- for (const BackCert* prev = potentialIssuer.childCert; prev;
- prev = prev->childCert) {
- if (InputsAreEqual(potentialIssuer.GetSubjectPublicKeyInfo(),
- prev->GetSubjectPublicKeyInfo()) &&
- InputsAreEqual(potentialIssuer.GetSubject(), prev->GetSubject())) {
- // XXX: error code
- return RecordResult(Result::ERROR_UNKNOWN_ISSUER, keepGoing);
- }
- }
- if (potentialIssuer.GetNameConstraints()) {
- rv = CheckNameConstraints(*potentialIssuer.GetNameConstraints(),
- subject, requiredEKUIfPresent);
- if (rv != Success) {
- return RecordResult(rv, keepGoing);
- }
- }
- if (additionalNameConstraints) {
- rv = CheckNameConstraints(*additionalNameConstraints, subject,
- requiredEKUIfPresent);
- if (rv != Success) {
- return RecordResult(rv, keepGoing);
- }
- }
- rv = CheckTLSFeatures(subject, potentialIssuer);
- if (rv != Success) {
- 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,
- buildForwardCallBudget);
- if (rv != Success) {
- return RecordResult(rv, keepGoing);
- }
- // Calculate the digest of the subject's signed data if we haven't already
- // done so. We do this lazily to avoid doing it at all if we backtrack before
- // getting to this point. We cache the result to avoid recalculating it if we
- // backtrack after getting to this point.
- if (subjectSignature.digest.GetLength() == 0) {
- rv = DigestSignedData(trustDomain, subject.GetSignedData(),
- subjectSignatureDigestBuf,
- subjectSignaturePublicKeyAlg, subjectSignature);
- if (rv != Success) {
- return rv;
- }
- }
- rv = VerifySignedDigest(trustDomain, subjectSignaturePublicKeyAlg,
- subjectSignature,
- potentialIssuer.GetSubjectPublicKeyInfo());
- if (rv != Success) {
- return RecordResult(rv, keepGoing);
- }
- // We avoid doing revocation checking for expired certificates because OCSP
- // responders are allowed to forget about expired certificates, and many OCSP
- // responders return an error when asked for the status of an expired
- // certificate.
- if (deferredSubjectError != Result::ERROR_EXPIRED_CERTIFICATE) {
- CertID certID(subject.GetIssuer(), potentialIssuer.GetSubjectPublicKeyInfo(),
- subject.GetSerialNumber());
- Time notBefore(Time::uninitialized);
- Time notAfter(Time::uninitialized);
- // This should never fail. If we're here, we've already parsed the validity
- // and checked that the given time is in the certificate's validity period.
- rv = ParseValidity(subject.GetValidity(), ¬Before, ¬After);
- if (rv != Success) {
- return rv;
- }
- Duration validityDuration(notAfter, notBefore);
- rv = trustDomain.CheckRevocation(subject.endEntityOrCA, certID, time,
- validityDuration, stapledOCSPResponse,
- subject.GetAuthorityInfoAccess(),
- subject.GetSignedCertificateTimestamps());
- if (rv != Success) {
- // 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) {
- const Input* sctExtension = subject.GetSignedCertificateTimestamps();
- if (sctExtension) {
- Input sctList;
- rv = ExtractSignedCertificateTimestampListFromExtension(*sctExtension,
- sctList);
- if (rv != Success) {
- // 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);
- }
- }
- }
- return RecordResult(Success, keepGoing);
- }
- // Recursively build the path from the given subject certificate to the root.
- //
- // Be very careful about changing the order of checks. The order is significant
- // because it affects which error we return when a certificate or certificate
- // chain has multiple problems. See the error ranking documentation in
- // pkix/pkix.h.
- static Result
- BuildForward(TrustDomain& trustDomain,
- const BackCert& subject,
- Time time,
- KeyUsage requiredKeyUsageIfPresent,
- KeyPurposeId requiredEKUIfPresent,
- const CertPolicyId& requiredPolicy,
- /*optional*/ const Input* stapledOCSPResponse,
- unsigned int subCACount,
- unsigned int& buildForwardCallBudget)
- {
- Result rv;
- TrustLevel trustLevel;
- // If this is an end-entity and not a trust anchor, we defer reporting
- // any error found here until after attempting to find a valid chain.
- // See the explanation of error prioritization in pkix.h.
- rv = CheckIssuerIndependentProperties(trustDomain, subject, time,
- requiredKeyUsageIfPresent,
- requiredEKUIfPresent, requiredPolicy,
- subCACount, trustLevel);
- Result deferredEndEntityError = Success;
- if (rv != Success) {
- if (subject.endEntityOrCA == EndEntityOrCA::MustBeEndEntity &&
- trustLevel != TrustLevel::TrustAnchor) {
- deferredEndEntityError = rv;
- } else {
- return rv;
- }
- }
- if (trustLevel == TrustLevel::TrustAnchor) {
- // End of the recursion.
- NonOwningDERArray chain;
- for (const BackCert* cert = &subject; cert; cert = cert->childCert) {
- rv = chain.Append(cert->GetDER());
- if (rv != Success) {
- return NotReached("NonOwningDERArray::SetItem failed.", rv);
- }
- }
- // This must be done here, after the chain is built but before any
- // revocation checks have been done.
- return trustDomain.IsChainValid(chain, time, requiredPolicy);
- }
- if (subject.endEntityOrCA == EndEntityOrCA::MustBeCA) {
- // Avoid stack overflows and poor performance by limiting cert chain
- // length.
- static const unsigned int MAX_SUBCA_COUNT = 6;
- static_assert(1/*end-entity*/ + MAX_SUBCA_COUNT + 1/*root*/ ==
- NonOwningDERArray::MAX_LENGTH,
- "MAX_SUBCA_COUNT and NonOwningDERArray::MAX_LENGTH mismatch.");
- if (subCACount >= MAX_SUBCA_COUNT) {
- return Result::ERROR_UNKNOWN_ISSUER;
- }
- ++subCACount;
- } else {
- assert(subCACount == 0);
- }
- // Find a trusted issuer.
- PathBuildingStep pathBuilder(trustDomain, subject, time,
- requiredEKUIfPresent, requiredPolicy,
- stapledOCSPResponse, subCACount,
- deferredEndEntityError, buildForwardCallBudget);
- // TODO(bug 965136): Add SKI/AKI matching optimizations
- rv = trustDomain.FindIssuer(subject.GetIssuer(), pathBuilder, time);
- if (rv != Success) {
- return rv;
- }
- rv = pathBuilder.CheckResult();
- if (rv != Success) {
- return rv;
- }
- // If we found a valid chain but deferred reporting an error with the
- // end-entity certificate, report it now.
- if (deferredEndEntityError != Success) {
- return deferredEndEntityError;
- }
- // We've built a valid chain from the subject cert up to a trusted root.
- return Success;
- }
- Result
- BuildCertChain(TrustDomain& trustDomain, Input certDER,
- Time time, EndEntityOrCA endEntityOrCA,
- KeyUsage requiredKeyUsageIfPresent,
- KeyPurposeId requiredEKUIfPresent,
- const CertPolicyId& requiredPolicy,
- /*optional*/ const Input* stapledOCSPResponse)
- {
- // XXX: Support the legacy use of the subject CN field for indicating the
- // domain name the certificate is valid for.
- BackCert cert(certDER, endEntityOrCA, nullptr);
- Result rv = cert.Init();
- if (rv != Success) {
- 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*/, buildForwardCallBudget);
- }
- } } // namespace mozilla::pkix
|