// 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) { 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); } 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); // 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; }
// 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, BackCert& subject, PRTime time, EndEntityOrCA endEntityOrCA, KeyUsages requiredKeyUsagesIfPresent, KeyPurposeId requiredEKUIfPresent, const CertPolicyId& requiredPolicy, /*optional*/ const SECItem* stapledOCSPResponse, unsigned int subCACount, /*out*/ ScopedCERTCertList& results) { 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, endEntityOrCA, requiredKeyUsagesIfPresent, requiredEKUIfPresent, requiredPolicy, subCACount, &trustLevel); PRErrorCode deferredEndEntityError = 0; if (rv != Success) { if (endEntityOrCA == EndEntityOrCA::MustBeEndEntity && trustLevel != TrustLevel::TrustAnchor) { deferredEndEntityError = PR_GetError(); } else { return rv; } } if (trustLevel == TrustLevel::TrustAnchor) { // End of the recursion. // Construct the results cert chain. results = CERT_NewCertList(); if (!results) { return MapSECStatus(SECFailure); } for (BackCert* cert = &subject; cert; cert = cert->childCert) { CERTCertificate* dup = CERT_DupCertificate(cert->GetNSSCert()); if (CERT_AddCertToListHead(results.get(), dup) != SECSuccess) { CERT_DestroyCertificate(dup); return MapSECStatus(SECFailure); } // dup is now owned by results. } // This must be done here, after the chain is built but before any // revocation checks have been done. SECStatus srv = trustDomain.IsChainValid(results.get()); if (srv != SECSuccess) { return MapSECStatus(srv); } return Success; } if (endEntityOrCA == EndEntityOrCA::MustBeCA) { // Avoid stack overflows and poor performance by limiting cert chain // length. static const unsigned int MAX_SUBCA_COUNT = 6; if (subCACount >= MAX_SUBCA_COUNT) { return Fail(RecoverableError, SEC_ERROR_UNKNOWN_ISSUER); } ++subCACount; } else { PR_ASSERT(subCACount == 0); } // Find a trusted issuer. // TODO(bug 965136): Add SKI/AKI matching optimizations ScopedCERTCertList candidates; if (trustDomain.FindPotentialIssuers(&subject.GetNSSCert()->derIssuer, time, candidates) != SECSuccess) { return MapSECStatus(SECFailure); } if (!candidates) { return Fail(RecoverableError, SEC_ERROR_UNKNOWN_ISSUER); } PRErrorCode errorToReturn = 0; for (CERTCertListNode* n = CERT_LIST_HEAD(candidates); !CERT_LIST_END(n, candidates); n = CERT_LIST_NEXT(n)) { rv = BuildForwardInner(trustDomain, subject, time, requiredEKUIfPresent, requiredPolicy, n->cert->derCert, subCACount, results); if (rv == Success) { // If we found a valid chain but deferred reporting an error with the // end-entity certificate, report it now. if (deferredEndEntityError != 0) { return Fail(FatalError, deferredEndEntityError); } SECStatus srv = trustDomain.CheckRevocation(endEntityOrCA, subject.GetNSSCert(), n->cert, time, stapledOCSPResponse); if (srv != SECSuccess) { return MapSECStatus(SECFailure); } // We found a trusted issuer. At this point, we know the cert is valid // and results contains the complete cert chain. return Success; } if (rv != RecoverableError) { return rv; } PRErrorCode currentError = PR_GetError(); switch (currentError) { case 0: PR_NOT_REACHED("Error code not set!"); return Fail(FatalError, PR_INVALID_STATE_ERROR); case SEC_ERROR_UNTRUSTED_CERT: currentError = SEC_ERROR_UNTRUSTED_ISSUER; break; default: break; } if (errorToReturn == 0) { errorToReturn = currentError; } else if (errorToReturn != currentError) { errorToReturn = SEC_ERROR_UNKNOWN_ISSUER; } } if (errorToReturn == 0) { errorToReturn = SEC_ERROR_UNKNOWN_ISSUER; } return Fail(RecoverableError, errorToReturn); }
// 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 // insanity/pkix.h. static Result BuildForward(TrustDomain& trustDomain, BackCert& subject, PRTime time, EndEntityOrCA endEntityOrCA, KeyUsages requiredKeyUsagesIfPresent, SECOidTag requiredEKUIfPresent, SECOidTag requiredPolicy, /*optional*/ const SECItem* stapledOCSPResponse, unsigned int subCACount, /*out*/ ScopedCERTCertList& results) { // Avoid stack overflows and poor performance by limiting cert length. // XXX: 6 is not enough for chains.sh anypolicywithlevel.cfg tests static const size_t MAX_DEPTH = 8; if (subCACount >= MAX_DEPTH - 1) { return RecoverableError; } Result rv; TrustDomain::TrustLevel trustLevel; bool expiredEndEntity = false; rv = CheckIssuerIndependentProperties(trustDomain, subject, time, endEntityOrCA, requiredKeyUsagesIfPresent, requiredEKUIfPresent, requiredPolicy, subCACount, &trustLevel); if (rv != Success) { // CheckIssuerIndependentProperties checks for expiration last, so if // it returned SEC_ERROR_EXPIRED_CERTIFICATE we know that is the only // problem with the cert found so far. Keep going to see if we can build // a path; if not, it's better to return the path building failure. expiredEndEntity = endEntityOrCA == MustBeEndEntity && trustLevel != TrustDomain::TrustAnchor && PR_GetError() == SEC_ERROR_EXPIRED_CERTIFICATE; if (!expiredEndEntity) { return rv; } } if (trustLevel == TrustDomain::TrustAnchor) { // End of the recursion. Create the result list and add the trust anchor to // it. results = CERT_NewCertList(); if (!results) { return FatalError; } rv = subject.PrependNSSCertToList(results.get()); return rv; } // Find a trusted issuer. // TODO(bug 965136): Add SKI/AKI matching optimizations ScopedCERTCertList candidates; if (trustDomain.FindPotentialIssuers(&subject.GetNSSCert()->derIssuer, time, candidates) != SECSuccess) { return MapSECStatus(SECFailure); } PORT_Assert(candidates.get()); if (!candidates) { return Fail(RecoverableError, SEC_ERROR_UNKNOWN_ISSUER); } PRErrorCode errorToReturn = 0; for (CERTCertListNode* n = CERT_LIST_HEAD(candidates); !CERT_LIST_END(n, candidates); n = CERT_LIST_NEXT(n)) { rv = BuildForwardInner(trustDomain, subject, time, endEntityOrCA, requiredEKUIfPresent, requiredPolicy, n->cert, stapledOCSPResponse, subCACount, results); if (rv == Success) { if (expiredEndEntity) { // We deferred returning this error to see if we should return // "unknown issuer" instead. Since we found a valid issuer, it's // time to return "expired." PR_SetError(SEC_ERROR_EXPIRED_CERTIFICATE, 0); return RecoverableError; } SECStatus srv = trustDomain.CheckRevocation(endEntityOrCA, subject.GetNSSCert(), n->cert, time, stapledOCSPResponse); if (srv != SECSuccess) { return MapSECStatus(SECFailure); } // We found a trusted issuer. At this point, we know the cert is valid return subject.PrependNSSCertToList(results.get()); } if (rv != RecoverableError) { return rv; } PRErrorCode currentError = PR_GetError(); switch (currentError) { case 0: PR_NOT_REACHED("Error code not set!"); PR_SetError(PR_INVALID_STATE_ERROR, 0); return FatalError; case SEC_ERROR_UNTRUSTED_CERT: currentError = SEC_ERROR_UNTRUSTED_ISSUER; break; default: break; } if (errorToReturn == 0) { errorToReturn = currentError; } else if (errorToReturn != currentError) { errorToReturn = SEC_ERROR_UNKNOWN_ISSUER; } } if (errorToReturn == 0) { errorToReturn = SEC_ERROR_UNKNOWN_ISSUER; } return Fail(RecoverableError, errorToReturn); }
// 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, BackCert& subject, PRTime time, EndEntityOrCA endEntityOrCA, KeyUsage requiredKeyUsageIfPresent, SECOidTag requiredEKUIfPresent, SECOidTag requiredPolicy, /*optional*/ const SECItem* stapledOCSPResponse, unsigned int subCACount, /*out*/ ScopedCERTCertList& results) { // Avoid stack overflows and poor performance by limiting cert length. // XXX: 6 is not enough for chains.sh anypolicywithlevel.cfg tests static const size_t MAX_DEPTH = 8; if (subCACount >= MAX_DEPTH - 1) { return Fail(RecoverableError, SEC_ERROR_UNKNOWN_ISSUER); } Result rv; TrustDomain::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, endEntityOrCA, requiredKeyUsageIfPresent, requiredEKUIfPresent, requiredPolicy, subCACount, &trustLevel); PRErrorCode deferredEndEntityError = 0; if (rv != Success) { if (endEntityOrCA == MustBeEndEntity && trustLevel != TrustDomain::TrustAnchor) { deferredEndEntityError = PR_GetError(); } else { return rv; } } if (trustLevel == TrustDomain::TrustAnchor) { // End of the recursion. Create the result list and add the trust anchor to // it. results = CERT_NewCertList(); if (!results) { return FatalError; } rv = subject.PrependNSSCertToList(results.get()); return rv; } // Find a trusted issuer. // TODO(bug 965136): Add SKI/AKI matching optimizations ScopedCERTCertList candidates; if (trustDomain.FindPotentialIssuers(&subject.GetNSSCert()->derIssuer, time, candidates) != SECSuccess) { return MapSECStatus(SECFailure); } if (!candidates) { return Fail(RecoverableError, SEC_ERROR_UNKNOWN_ISSUER); } PRErrorCode errorToReturn = 0; for (CERTCertListNode* n = CERT_LIST_HEAD(candidates); !CERT_LIST_END(n, candidates); n = CERT_LIST_NEXT(n)) { rv = BuildForwardInner(trustDomain, subject, time, endEntityOrCA, requiredEKUIfPresent, requiredPolicy, n->cert, stapledOCSPResponse, subCACount, results); if (rv == Success) { // If we found a valid chain but deferred reporting an error with the // end-entity certificate, report it now. if (deferredEndEntityError != 0) { PR_SetError(deferredEndEntityError, 0); return FatalError; } SECStatus srv = trustDomain.CheckRevocation(endEntityOrCA, subject.GetNSSCert(), n->cert, time, stapledOCSPResponse); if (srv != SECSuccess) { return MapSECStatus(SECFailure); } // We found a trusted issuer. At this point, we know the cert is valid return subject.PrependNSSCertToList(results.get()); } if (rv != RecoverableError) { return rv; } PRErrorCode currentError = PR_GetError(); switch (currentError) { case 0: PR_NOT_REACHED("Error code not set!"); PR_SetError(PR_INVALID_STATE_ERROR, 0); return FatalError; case SEC_ERROR_UNTRUSTED_CERT: currentError = SEC_ERROR_UNTRUSTED_ISSUER; break; default: break; } if (errorToReturn == 0) { errorToReturn = currentError; } else if (errorToReturn != currentError) { errorToReturn = SEC_ERROR_UNKNOWN_ISSUER; } } if (errorToReturn == 0) { errorToReturn = SEC_ERROR_UNKNOWN_ISSUER; } return Fail(RecoverableError, errorToReturn); }