// The code that executes in the inner loop of BuildForward static Result BuildForwardInner(TrustDomain& trustDomain, BackCert& subject, PRTime time, KeyPurposeId requiredEKUIfPresent, const CertPolicyId& requiredPolicy, const SECItem& potentialIssuerDER, unsigned int subCACount, ScopedCERTCertList& results) { BackCert potentialIssuer(&subject, BackCert::IncludeCN::No); Result rv = potentialIssuer.Init(potentialIssuerDER); if (rv != Success) { return rv; } // RFC5280 4.2.1.1. Authority Key Identifier // RFC5280 4.2.1.2. Subject Key Identifier // 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 (BackCert* prev = potentialIssuer.childCert; !loopDetected && prev != nullptr; prev = prev->childCert) { if (SECITEM_ItemsAreEqual(&potentialIssuer.GetSubjectPublicKeyInfo(), &prev->GetSubjectPublicKeyInfo()) && SECITEM_ItemsAreEqual(&potentialIssuer.GetSubject(), &prev->GetSubject())) { return Fail(RecoverableError, SEC_ERROR_UNKNOWN_ISSUER); // XXX: error code } } rv = CheckNameConstraints(potentialIssuer); if (rv != Success) { return rv; } rv = BuildForward(trustDomain, potentialIssuer, time, EndEntityOrCA::MustBeCA, KU_KEY_CERT_SIGN, requiredEKUIfPresent, requiredPolicy, nullptr, subCACount, results); if (rv != Success) { return rv; } return subject.VerifyOwnSignatureWithKey( trustDomain, potentialIssuer.GetSubjectPublicKeyInfo()); }
// 4.2.1.10. Name Constraints Result CheckNameConstraints(BackCert& cert) { if (!cert.encodedNameConstraints) { return Success; } PLArenaPool* arena = cert.GetArena(); if (!arena) { return FatalError; } // Owned by arena const CERTNameConstraints* constraints = CERT_DecodeNameConstraintsExtension(arena, cert.encodedNameConstraints); if (!constraints) { return MapSECStatus(SECFailure); } for (BackCert* prev = cert.childCert; prev; prev = prev->childCert) { const CERTGeneralName* names = nullptr; Result rv = prev->GetConstrainedNames(&names); if (rv != Success) { return rv; } PORT_Assert(names); CERTGeneralName* currentName = const_cast<CERTGeneralName*>(names); do { if (CERT_CheckNameSpace(arena, constraints, currentName) != SECSuccess) { // XXX: It seems like CERT_CheckNameSpace doesn't always call // PR_SetError when it fails. We set the error code here, though this // may be papering over some fatal errors. NSS's // cert_VerifyCertChainOld does something similar. PR_SetError(SEC_ERROR_CERT_NOT_IN_NAME_SPACE, 0); return RecoverableError; } currentName = CERT_GetNextGeneralName(currentName); } while (currentName != names); } return Success; }
Result CheckIssuerIndependentProperties(TrustDomain& trustDomain, const BackCert& cert, PRTime time, KeyUsage requiredKeyUsageIfPresent, KeyPurposeId requiredEKUIfPresent, const CertPolicyId& requiredPolicy, unsigned int subCACount, /*out*/ TrustLevel& trustLevel) { Result rv; const EndEntityOrCA endEntityOrCA = cert.endEntityOrCA; rv = trustDomain.GetCertTrust(endEntityOrCA, requiredPolicy, cert.GetDER(), trustLevel); if (rv != Success) { return rv; } if (trustLevel == TrustLevel::ActivelyDistrusted) { return Result::ERROR_UNTRUSTED_CERT; } if (trustLevel != TrustLevel::TrustAnchor && trustLevel != TrustLevel::InheritsTrust) { // The TrustDomain returned a trust level that we weren't expecting. return Result::FATAL_ERROR_INVALID_STATE; } // 4.2.1.1. Authority Key Identifier is ignored (see bug 965136). // 4.2.1.2. Subject Key Identifier is ignored (see bug 965136). // 4.2.1.3. Key Usage rv = CheckKeyUsage(endEntityOrCA, cert.GetKeyUsage(), requiredKeyUsageIfPresent); if (rv != Success) { return rv; } // 4.2.1.4. Certificate Policies rv = CheckCertificatePolicies(endEntityOrCA, cert.GetCertificatePolicies(), cert.GetInhibitAnyPolicy(), trustLevel, requiredPolicy); if (rv != Success) { return rv; } // 4.2.1.5. Policy Mappings are not supported; see the documentation about // policy enforcement in pkix.h. // 4.2.1.6. Subject Alternative Name dealt with during name constraint // checking and during name verification (CERT_VerifyCertName). // 4.2.1.7. Issuer Alternative Name is not something that needs checking. // 4.2.1.8. Subject Directory Attributes is not something that needs // checking. // 4.2.1.9. Basic Constraints. rv = CheckBasicConstraints(endEntityOrCA, cert.GetBasicConstraints(), cert.GetVersion(), trustLevel, subCACount); if (rv != Success) { return rv; } // 4.2.1.10. Name Constraints is dealt with in during path building. // 4.2.1.11. Policy Constraints are implicitly supported; see the // documentation about policy enforcement in pkix.h. // 4.2.1.12. Extended Key Usage rv = CheckExtendedKeyUsage(endEntityOrCA, cert.GetExtKeyUsage(), requiredEKUIfPresent); if (rv != Success) { return rv; } // 4.2.1.13. CRL Distribution Points is not supported, though the // TrustDomain's CheckRevocation method may parse it and process it // on its own. // 4.2.1.14. Inhibit anyPolicy is implicitly supported; see the documentation // about policy enforcement in pkix.h. // IMPORTANT: This check must come after the other checks in order for error // ranking to work correctly. rv = CheckValidity(cert.GetValidity(), time); if (rv != Success) { return rv; } 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, 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 // 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); }
// The code that executes in the inner loop of BuildForward static Result BuildForwardInner(TrustDomain& trustDomain, BackCert& subject, PRTime time, EndEntityOrCA endEntityOrCA, SECOidTag requiredEKUIfPresent, SECOidTag requiredPolicy, CERTCertificate* potentialIssuerCertToDup, /*optional*/ const SECItem* stapledOCSPResponse, unsigned int subCACount, ScopedCERTCertList& results) { PORT_Assert(potentialIssuerCertToDup); BackCert potentialIssuer(potentialIssuerCertToDup, &subject, BackCert::ExcludeCN); Result rv = potentialIssuer.Init(); if (rv != Success) { return rv; } // RFC5280 4.2.1.1. Authority Key Identifier // RFC5280 4.2.1.2. Subject Key Identifier // 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 (BackCert* prev = potentialIssuer.childCert; !loopDetected && prev != nullptr; prev = prev->childCert) { if (SECITEM_ItemsAreEqual(&potentialIssuer.GetNSSCert()->derPublicKey, &prev->GetNSSCert()->derPublicKey) && SECITEM_ItemsAreEqual(&potentialIssuer.GetNSSCert()->derSubject, &prev->GetNSSCert()->derSubject)) { return Fail(RecoverableError, SEC_ERROR_UNKNOWN_ISSUER); // XXX: error code } } rv = CheckNameConstraints(potentialIssuer); if (rv != Success) { return rv; } unsigned int newSubCACount = subCACount; if (endEntityOrCA == MustBeCA) { newSubCACount = subCACount + 1; } else { PR_ASSERT(newSubCACount == 0); } rv = BuildForward(trustDomain, potentialIssuer, time, MustBeCA, KU_KEY_CERT_SIGN, requiredEKUIfPresent, requiredPolicy, nullptr, newSubCACount, results); if (rv != Success) { return rv; } if (trustDomain.VerifySignedData(&subject.GetNSSCert()->signatureWrap, potentialIssuer.GetNSSCert()) != SECSuccess) { return MapSECStatus(SECFailure); } 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); }
Result CheckIssuerIndependentProperties(TrustDomain& trustDomain, const BackCert& cert, Time time, KeyUsage requiredKeyUsageIfPresent, KeyPurposeId requiredEKUIfPresent, const CertPolicyId& requiredPolicy, unsigned int subCACount, /*out*/ TrustLevel& trustLevel) { Result rv; const EndEntityOrCA endEntityOrCA = cert.endEntityOrCA; // Check the cert's trust first, because we want to minimize the amount of // processing we do on a distrusted cert, in case it is trying to exploit // some bug in our processing. rv = trustDomain.GetCertTrust(endEntityOrCA, requiredPolicy, cert.GetDER(), trustLevel); if (rv != Success) { return rv; } if (trustLevel == TrustLevel::TrustAnchor && endEntityOrCA == EndEntityOrCA::MustBeEndEntity && requiredEKUIfPresent == KeyPurposeId::id_kp_OCSPSigning) { // OCSP signer certificates can never be trust anchors, especially // since we don't support designated OCSP responders. All of the checks // below that are dependent on trustLevel rely on this overriding of the // trust level for OCSP signers. trustLevel = TrustLevel::InheritsTrust; } switch (trustLevel) { case TrustLevel::InheritsTrust: rv = CheckSignatureAlgorithm(trustDomain, endEntityOrCA, cert.GetSignedData(), cert.GetSignature()); if (rv != Success) { return rv; } break; case TrustLevel::TrustAnchor: // We don't even bother checking signatureAlgorithm or signature for // syntactic validity for trust anchors, because we don't use those // fields for anything, and because the trust anchor might be signed // with a signature algorithm we don't actually support. break; case TrustLevel::ActivelyDistrusted: return Result::ERROR_UNTRUSTED_CERT; } // Check the SPKI early, because it is one of the most selective properties // of the certificate due to SHA-1 deprecation and the deprecation of // certificates with keys weaker than RSA 2048. Reader spki(cert.GetSubjectPublicKeyInfo()); rv = der::Nested(spki, der::SEQUENCE, [&](Reader& r) { return CheckSubjectPublicKeyInfo(r, trustDomain, endEntityOrCA); }); if (rv != Success) { return rv; } rv = der::End(spki); if (rv != Success) { return rv; } // 4.2.1.1. Authority Key Identifier is ignored (see bug 965136). // 4.2.1.2. Subject Key Identifier is ignored (see bug 965136). // 4.2.1.3. Key Usage rv = CheckKeyUsage(endEntityOrCA, cert.GetKeyUsage(), requiredKeyUsageIfPresent); if (rv != Success) { return rv; } // 4.2.1.4. Certificate Policies rv = CheckCertificatePolicies(endEntityOrCA, cert.GetCertificatePolicies(), cert.GetInhibitAnyPolicy(), trustLevel, requiredPolicy); if (rv != Success) { return rv; } // 4.2.1.5. Policy Mappings are not supported; see the documentation about // policy enforcement in pkix.h. // 4.2.1.6. Subject Alternative Name dealt with during name constraint // checking and during name verification (CERT_VerifyCertName). // 4.2.1.7. Issuer Alternative Name is not something that needs checking. // 4.2.1.8. Subject Directory Attributes is not something that needs // checking. // 4.2.1.9. Basic Constraints. rv = CheckBasicConstraints(endEntityOrCA, cert.GetBasicConstraints(), cert.GetVersion(), trustLevel, subCACount); if (rv != Success) { return rv; } // 4.2.1.10. Name Constraints is dealt with in during path building. // 4.2.1.11. Policy Constraints are implicitly supported; see the // documentation about policy enforcement in pkix.h. // 4.2.1.12. Extended Key Usage rv = CheckExtendedKeyUsage(endEntityOrCA, cert.GetExtKeyUsage(), requiredEKUIfPresent); if (rv != Success) { return rv; } // 4.2.1.13. CRL Distribution Points is not supported, though the // TrustDomain's CheckRevocation method may parse it and process it // on its own. // 4.2.1.14. Inhibit anyPolicy is implicitly supported; see the documentation // about policy enforcement in pkix.h. // IMPORTANT: This check must come after the other checks in order for error // ranking to work correctly. Time notBefore(Time::uninitialized); Time notAfter(Time::uninitialized); rv = CheckValidity(cert.GetValidity(), time, ¬Before, ¬After); if (rv != Success) { return rv; } rv = trustDomain.CheckValidityIsAcceptable(notBefore, notAfter, endEntityOrCA, requiredEKUIfPresent); if (rv != Success) { return rv; } 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, 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); }
Result CheckIssuerIndependentProperties(TrustDomain& trustDomain, BackCert& cert, PRTime time, EndEntityOrCA endEntityOrCA, KeyUsages requiredKeyUsagesIfPresent, SECOidTag requiredEKUIfPresent, SECOidTag requiredPolicy, unsigned int subCACount, /*optional out*/ TrustDomain::TrustLevel* trustLevelOut) { Result rv; TrustDomain::TrustLevel trustLevel; rv = MapSECStatus(trustDomain.GetCertTrust(endEntityOrCA, requiredPolicy, cert.GetNSSCert(), &trustLevel)); if (rv != Success) { return rv; } if (trustLevel == TrustDomain::ActivelyDistrusted) { PORT_SetError(SEC_ERROR_UNTRUSTED_CERT); return RecoverableError; } if (trustLevel != TrustDomain::TrustAnchor && trustLevel != TrustDomain::InheritsTrust) { // The TrustDomain returned a trust level that we weren't expecting. PORT_SetError(PR_INVALID_STATE_ERROR); return FatalError; } if (trustLevelOut) { *trustLevelOut = trustLevel; } bool isTrustAnchor = endEntityOrCA == MustBeCA && trustLevel == TrustDomain::TrustAnchor; PLArenaPool* arena = cert.GetArena(); if (!arena) { return FatalError; } // 4.2.1.1. Authority Key Identifier is ignored (see bug 965136). // 4.2.1.2. Subject Key Identifier is ignored (see bug 965136). // 4.2.1.3. Key Usage rv = CheckKeyUsage(endEntityOrCA, isTrustAnchor, cert.encodedKeyUsage, requiredKeyUsagesIfPresent, arena); if (rv != Success) { return rv; } // 4.2.1.4. Certificate Policies rv = CheckCertificatePolicies(cert, endEntityOrCA, isTrustAnchor, requiredPolicy); if (rv != Success) { return rv; } // 4.2.1.5. Policy Mappings are not supported; see the documentation about // policy enforcement in pkix.h. // 4.2.1.6. Subject Alternative Name dealt with during name constraint // checking and during name verification (CERT_VerifyCertName). // 4.2.1.7. Issuer Alternative Name is not something that needs checking. // 4.2.1.8. Subject Directory Attributes is not something that needs // checking. // 4.2.1.9. Basic Constraints. rv = CheckBasicConstraints(cert, endEntityOrCA, isTrustAnchor, subCACount); if (rv != Success) { return rv; } // 4.2.1.10. Name Constraints is dealt with in during path building. // 4.2.1.11. Policy Constraints are implicitly supported; see the // documentation about policy enforcement in pkix.h. // 4.2.1.12. Extended Key Usage rv = CheckExtendedKeyUsage(endEntityOrCA, cert.encodedExtendedKeyUsage, requiredEKUIfPresent); if (rv != Success) { return rv; } // 4.2.1.13. CRL Distribution Points is not supported, though the // TrustDomain's CheckRevocation method may parse it and process it // on its own. // 4.2.1.14. Inhibit anyPolicy is implicitly supported; see the documentation // about policy enforcement in pkix.h. // IMPORTANT: This check must come after the other checks in order for error // ranking to work correctly. rv = CheckTimes(cert.GetNSSCert(), time); if (rv != Success) { return rv; } return Success; }
// RFC5280 4.2.1.9. Basic Constraints (id-ce-basicConstraints) Result CheckBasicConstraints(const BackCert& cert, EndEntityOrCA endEntityOrCA, bool isTrustAnchor, unsigned int subCACount) { CERTBasicConstraints basicConstraints; if (cert.encodedBasicConstraints) { SECStatus rv = CERT_DecodeBasicConstraintValue(&basicConstraints, cert.encodedBasicConstraints); if (rv != SECSuccess) { return MapSECStatus(rv); } } else { // Synthesize a non-CA basic constraints by default basicConstraints.isCA = false; basicConstraints.pathLenConstraint = 0; // "If the basic constraints extension is not present in a version 3 // certificate, or the extension is present but the cA boolean is not // asserted, then the certified public key MUST NOT be used to verify // certificate signatures." // // For compatibility, we must accept v1 trust anchors without basic // constraints as CAs. // // TODO: add check for self-signedness? if (endEntityOrCA == MustBeCA && isTrustAnchor) { const CERTCertificate* nssCert = cert.GetNSSCert(); der::Input versionDer; if (versionDer.Init(nssCert->version.data, nssCert->version.len) != der::Success) { return RecoverableError; } uint8_t version; if (der::OptionalVersion(versionDer, version) || der::End(versionDer) != der::Success) { return RecoverableError; } if (version == 1) { basicConstraints.isCA = true; basicConstraints.pathLenConstraint = CERT_UNLIMITED_PATH_CONSTRAINT; } } } if (endEntityOrCA == MustBeEndEntity) { // CA certificates are not trusted as EE certs. if (basicConstraints.isCA) { // XXX: We use SEC_ERROR_CA_CERT_INVALID here so we can distinguish // this error from other errors, given that NSS does not have a "CA cert // used as end-entity" error code since it doesn't have such a // prohibition. We should add such an error code and stop abusing // SEC_ERROR_CA_CERT_INVALID this way. // // Note, in particular, that this check prevents a delegated OCSP // response signing certificate with the CA bit from successfully // validating when we check it from pkixocsp.cpp, which is a good thing. // return Fail(RecoverableError, SEC_ERROR_CA_CERT_INVALID); } return Success; } PORT_Assert(endEntityOrCA == MustBeCA); // End-entity certificates are not allowed to act as CA certs. if (!basicConstraints.isCA) { return Fail(RecoverableError, SEC_ERROR_CA_CERT_INVALID); } if (basicConstraints.pathLenConstraint >= 0) { if (subCACount > static_cast<unsigned int>(basicConstraints.pathLenConstraint)) { return Fail(RecoverableError, SEC_ERROR_PATH_LEN_CONSTRAINT_INVALID); } } return Success; }
Result CheckIssuerIndependentProperties(TrustDomain& trustDomain, BackCert& cert, PRTime time, EndEntityOrCA endEntityOrCA, KeyUsages requiredKeyUsagesIfPresent, KeyPurposeId requiredEKUIfPresent, const CertPolicyId& requiredPolicy, unsigned int subCACount, /*optional out*/ TrustLevel* trustLevelOut) { Result rv; TrustLevel trustLevel; rv = MapSECStatus(trustDomain.GetCertTrust(endEntityOrCA, requiredPolicy, cert.GetNSSCert(), &trustLevel)); if (rv != Success) { return rv; } if (trustLevel == TrustLevel::ActivelyDistrusted) { return Fail(RecoverableError, SEC_ERROR_UNTRUSTED_CERT); } if (trustLevel != TrustLevel::TrustAnchor && trustLevel != TrustLevel::InheritsTrust) { // The TrustDomain returned a trust level that we weren't expecting. PORT_SetError(PR_INVALID_STATE_ERROR); return FatalError; } if (trustLevelOut) { *trustLevelOut = trustLevel; } // XXX: Good enough for now. There could be an illegal explicit version // number or one we don't support, but we can safely treat those all as v3 // for now since processing of v3 certificates is strictly more strict than // processing of v1 certificates. der::Version version = (!cert.GetNSSCert()->version.data && !cert.GetNSSCert()->version.len) ? der::Version::v1 : der::Version::v3; PLArenaPool* arena = cert.GetArena(); if (!arena) { return FatalError; } // 4.2.1.1. Authority Key Identifier is ignored (see bug 965136). // 4.2.1.2. Subject Key Identifier is ignored (see bug 965136). // 4.2.1.3. Key Usage rv = CheckKeyUsage(endEntityOrCA, cert.encodedKeyUsage, requiredKeyUsagesIfPresent, arena); if (rv != Success) { return rv; } // 4.2.1.4. Certificate Policies rv = CheckCertificatePolicies(endEntityOrCA, cert.encodedCertificatePolicies, cert.encodedInhibitAnyPolicy, trustLevel, requiredPolicy); if (rv != Success) { return rv; } // 4.2.1.5. Policy Mappings are not supported; see the documentation about // policy enforcement in pkix.h. // 4.2.1.6. Subject Alternative Name dealt with during name constraint // checking and during name verification (CERT_VerifyCertName). // 4.2.1.7. Issuer Alternative Name is not something that needs checking. // 4.2.1.8. Subject Directory Attributes is not something that needs // checking. // 4.2.1.9. Basic Constraints. rv = CheckBasicConstraints(endEntityOrCA, cert.encodedBasicConstraints, version, trustLevel, subCACount); if (rv != Success) { return rv; } // 4.2.1.10. Name Constraints is dealt with in during path building. // 4.2.1.11. Policy Constraints are implicitly supported; see the // documentation about policy enforcement in pkix.h. // 4.2.1.12. Extended Key Usage rv = CheckExtendedKeyUsage(endEntityOrCA, cert.encodedExtendedKeyUsage, requiredEKUIfPresent); if (rv != Success) { return rv; } // 4.2.1.13. CRL Distribution Points is not supported, though the // TrustDomain's CheckRevocation method may parse it and process it // on its own. // 4.2.1.14. Inhibit anyPolicy is implicitly supported; see the documentation // about policy enforcement in pkix.h. // IMPORTANT: This check must come after the other checks in order for error // ranking to work correctly. rv = CheckTimes(cert.GetNSSCert(), time); if (rv != Success) { return rv; } return Success; }