nsresult VerifyCMSDetachedSignatureIncludingCertificate( const SECItem& buffer, const SECItem& detachedDigest, nsresult (*verifyCertificate)(CERTCertificate* cert, void* context, void* pinArg), void *verifyCertificateContext, void* pinArg) { // XXX: missing pinArg is tolerated. if (NS_WARN_IF(!buffer.data && buffer.len > 0) || NS_WARN_IF(!detachedDigest.data && detachedDigest.len > 0) || (!verifyCertificate) || NS_WARN_IF(!verifyCertificateContext)) { return NS_ERROR_INVALID_ARG; } ScopedNSSCMSMessage cmsMsg(NSS_CMSMessage_CreateFromDER(const_cast<SECItem*>(&buffer), nullptr, nullptr, nullptr, nullptr, nullptr, nullptr)); if (!cmsMsg) { return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING; } if (!NSS_CMSMessage_IsSigned(cmsMsg.get())) { return NS_ERROR_CMS_VERIFY_NOT_SIGNED; } NSSCMSContentInfo* cinfo = NSS_CMSMessage_ContentLevel(cmsMsg.get(), 0); if (!cinfo) { return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO; } // signedData is non-owning NSSCMSSignedData* signedData = reinterpret_cast<NSSCMSSignedData*>(NSS_CMSContentInfo_GetContent(cinfo)); if (!signedData) { return NS_ERROR_CMS_VERIFY_NO_CONTENT_INFO; } // Set digest value. if (NSS_CMSSignedData_SetDigestValue(signedData, SEC_OID_SHA1, const_cast<SECItem*>(&detachedDigest))) { return NS_ERROR_CMS_VERIFY_BAD_DIGEST; } // Parse the certificates into CERTCertificate objects held in memory so // verifyCertificate will be able to find them during path building. ScopedCERTCertList certs(CERT_NewCertList()); if (!certs) { return NS_ERROR_OUT_OF_MEMORY; } if (signedData->rawCerts) { for (size_t i = 0; signedData->rawCerts[i]; ++i) { ScopedCERTCertificate cert(CERT_NewTempCertificate(CERT_GetDefaultCertDB(), signedData->rawCerts[i], nullptr, false, true)); // Skip certificates that fail to parse if (cert) { if (CERT_AddCertToListTail(certs.get(), cert.get()) == SECSuccess) { cert.forget(); // ownership transfered } else { return NS_ERROR_OUT_OF_MEMORY; } } } } // Get the end-entity certificate. int numSigners = NSS_CMSSignedData_SignerInfoCount(signedData); if (NS_WARN_IF(numSigners != 1)) { return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING; } // signer is non-owning. NSSCMSSignerInfo* signer = NSS_CMSSignedData_GetSignerInfo(signedData, 0); if (NS_WARN_IF(!signer)) { return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING; } CERTCertificate* signerCert = NSS_CMSSignerInfo_GetSigningCertificate(signer, CERT_GetDefaultCertDB()); if (!signerCert) { return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING; } nsresult rv = verifyCertificate(signerCert, verifyCertificateContext, pinArg); if (NS_WARN_IF(NS_FAILED(rv))) { return rv; } // See NSS_CMSContentInfo_GetContentTypeOID, which isn't exported from NSS. SECOidData* contentTypeOidData = SECOID_FindOID(&signedData->contentInfo.contentType); if (!contentTypeOidData) { return NS_ERROR_CMS_VERIFY_ERROR_PROCESSING; } return MapSECStatus(NSS_CMSSignerInfo_Verify(signer, const_cast<SECItem*>(&detachedDigest), &contentTypeOidData->oid)); }
NS_IMETHODIMP nsPK11Token::Reset() { return MapSECStatus(PK11_ResetToken(mSlot.get(), nullptr)); }
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; }
// Called on the worker thread. nsresult BackgroundFileSaver::ProcessStateChange() { nsresult rv; // We might have been notified because the operation is complete, verify. if (CheckCompletion()) { return NS_OK; } // Get a copy of the current shared state for the worker thread. nsCOMPtr<nsIFile> initialTarget; bool initialTargetKeepPartial; nsCOMPtr<nsIFile> renamedTarget; bool renamedTargetKeepPartial; bool sha256Enabled; bool append; { MutexAutoLock lock(mLock); initialTarget = mInitialTarget; initialTargetKeepPartial = mInitialTargetKeepPartial; renamedTarget = mRenamedTarget; renamedTargetKeepPartial = mRenamedTargetKeepPartial; sha256Enabled = mSha256Enabled; append = mAppend; // From now on, another attention event needs to be posted if state changes. mWorkerThreadAttentionRequested = false; } // The initial target can only be null if it has never been assigned. In this // case, there is nothing to do since we never created any output file. if (!initialTarget) { return NS_OK; } // Determine if we are processing the attention request for the first time. bool isContinuation = !!mActualTarget; if (!isContinuation) { // Assign the target file for the first time. mActualTarget = initialTarget; mActualTargetKeepPartial = initialTargetKeepPartial; } // Verify whether we have actually been instructed to use a different file. // This may happen the first time this function is executed, if SetTarget was // called two times before the worker thread processed the attention request. bool equalToCurrent = false; if (renamedTarget) { rv = mActualTarget->Equals(renamedTarget, &equalToCurrent); NS_ENSURE_SUCCESS(rv, rv); if (!equalToCurrent) { // If we were asked to rename the file but the initial file did not exist, // we simply create the file in the renamed location. We avoid this check // if we have already started writing the output file ourselves. bool exists = true; if (!isContinuation) { rv = mActualTarget->Exists(&exists); NS_ENSURE_SUCCESS(rv, rv); } if (exists) { // We are moving the previous target file to a different location. nsCOMPtr<nsIFile> renamedTargetParentDir; rv = renamedTarget->GetParent(getter_AddRefs(renamedTargetParentDir)); NS_ENSURE_SUCCESS(rv, rv); nsAutoString renamedTargetName; rv = renamedTarget->GetLeafName(renamedTargetName); NS_ENSURE_SUCCESS(rv, rv); // We must delete any existing target file before moving the current // one. rv = renamedTarget->Exists(&exists); NS_ENSURE_SUCCESS(rv, rv); if (exists) { rv = renamedTarget->Remove(false); NS_ENSURE_SUCCESS(rv, rv); } // Move the file. If this fails, we still reference the original file // in mActualTarget, so that it is deleted if requested. If this // succeeds, the nsIFile instance referenced by mActualTarget mutates // and starts pointing to the new file, but we'll discard the reference. rv = mActualTarget->MoveTo(renamedTargetParentDir, renamedTargetName); NS_ENSURE_SUCCESS(rv, rv); } // Now we can update the actual target file name. mActualTarget = renamedTarget; mActualTargetKeepPartial = renamedTargetKeepPartial; } } // Notify if the target file name actually changed. if (!equalToCurrent) { // We must clone the nsIFile instance because mActualTarget is not // immutable, it may change if the target is renamed later. nsCOMPtr<nsIFile> actualTargetToNotify; rv = mActualTarget->Clone(getter_AddRefs(actualTargetToNotify)); NS_ENSURE_SUCCESS(rv, rv); nsRefPtr<NotifyTargetChangeRunnable> event = new NotifyTargetChangeRunnable(this, actualTargetToNotify); NS_ENSURE_TRUE(event, NS_ERROR_FAILURE); rv = mControlThread->Dispatch(event, NS_DISPATCH_NORMAL); NS_ENSURE_SUCCESS(rv, rv); } if (isContinuation) { // The pending rename operation might be the last task before finishing. We // may return here only if we have already created the target file. if (CheckCompletion()) { return NS_OK; } // Even if the operation did not complete, the pipe input stream may be // empty and may have been closed already. We detect this case using the // Available property, because it never returns an error if there is more // data to be consumed. If the pipe input stream is closed, we just exit // and wait for more calls like SetTarget or Finish to be invoked on the // control thread. However, we still truncate the file or create the // initial digest context if we are expected to do that. uint64_t available; rv = mPipeInputStream->Available(&available); if (NS_FAILED(rv)) { return NS_OK; } } // Create the digest context if requested and NSS hasn't been shut down. if (sha256Enabled && !mDigestContext) { nsNSSShutDownPreventionLock lock; if (!isAlreadyShutDown()) { mDigestContext = PK11_CreateDigestContext(static_cast<SECOidTag>(SEC_OID_SHA256)); NS_ENSURE_TRUE(mDigestContext, NS_ERROR_OUT_OF_MEMORY); } } // When we are requested to append to an existing file, we should read the // existing data and ensure we include it as part of the final hash. if (mDigestContext && append && !isContinuation) { nsCOMPtr<nsIInputStream> inputStream; rv = NS_NewLocalFileInputStream(getter_AddRefs(inputStream), mActualTarget, PR_RDONLY | nsIFile::OS_READAHEAD); if (rv != NS_ERROR_FILE_NOT_FOUND) { NS_ENSURE_SUCCESS(rv, rv); char buffer[BUFFERED_IO_SIZE]; while (true) { uint32_t count; rv = inputStream->Read(buffer, BUFFERED_IO_SIZE, &count); NS_ENSURE_SUCCESS(rv, rv); if (count == 0) { // We reached the end of the file. break; } nsNSSShutDownPreventionLock lock; if (isAlreadyShutDown()) { return NS_ERROR_NOT_AVAILABLE; } nsresult rv = MapSECStatus(PK11_DigestOp(mDigestContext, uint8_t_ptr_cast(buffer), count)); NS_ENSURE_SUCCESS(rv, rv); } rv = inputStream->Close(); NS_ENSURE_SUCCESS(rv, rv); } } // We will append to the initial target file only if it was requested by the // caller, but we'll always append on subsequent accesses to the target file. int32_t creationIoFlags; if (isContinuation) { creationIoFlags = PR_APPEND; } else { creationIoFlags = (append ? PR_APPEND : PR_TRUNCATE) | PR_CREATE_FILE; } // Create the target file, or append to it if we already started writing it. nsCOMPtr<nsIOutputStream> outputStream; rv = NS_NewLocalFileOutputStream(getter_AddRefs(outputStream), mActualTarget, PR_WRONLY | creationIoFlags, 0644); NS_ENSURE_SUCCESS(rv, rv); outputStream = NS_BufferOutputStream(outputStream, BUFFERED_IO_SIZE); if (!outputStream) { return NS_ERROR_FAILURE; } // Wrap the output stream so that it feeds the digest context if needed. if (mDigestContext) { // No need to acquire the NSS lock here, DigestOutputStream must acquire it // in any case before each asynchronous write. Constructing the // DigestOutputStream cannot fail. Passing mDigestContext to // DigestOutputStream is safe, because BackgroundFileSaver always outlives // the outputStream. BackgroundFileSaver is reference-counted before the // call to AsyncCopy, and mDigestContext is never destroyed before // AsyncCopyCallback. outputStream = new DigestOutputStream(outputStream, mDigestContext); } // Start copying our input to the target file. No errors can be raised past // this point if the copy starts, since they should be handled by the thread. { MutexAutoLock lock(mLock); rv = NS_AsyncCopy(mPipeInputStream, outputStream, mWorkerThread, NS_ASYNCCOPY_VIA_READSEGMENTS, 4096, AsyncCopyCallback, this, false, true, getter_AddRefs(mAsyncCopyContext), GetProgressCallback()); if (NS_FAILED(rv)) { NS_WARNING("NS_AsyncCopy failed."); mAsyncCopyContext = nullptr; return rv; } } // If the operation succeeded, we must ensure that we keep this object alive // for the entire duration of the copy, since only the raw pointer will be // provided as the argument of the AsyncCopyCallback function. We can add the // reference now, after NS_AsyncCopy returned, because it always starts // processing asynchronously, and there is no risk that the callback is // invoked before we reach this point. If the operation failed instead, then // AsyncCopyCallback will never be called. NS_ADDREF_THIS(); return NS_OK; }
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; }
// TODO(bug 966856): support SHA-2 hashes Result KeyHash(TrustDomain& trustDomain, const SECItem& subjectPublicKeyInfo, /*out*/ uint8_t* hashBuf, size_t hashBufSize) { if (!hashBuf || hashBufSize != TrustDomain::DIGEST_LENGTH) { return Fail(FatalError, SEC_ERROR_LIBRARY_FAILURE); } // RFC 5280 Section 4.1 // // SubjectPublicKeyInfo ::= SEQUENCE { // algorithm AlgorithmIdentifier, // subjectPublicKey BIT STRING } Input spki; { // The scope of input is limited to reduce the possibility of confusing it // with spki in places we need to be using spki below. Input input; if (input.Init(subjectPublicKeyInfo.data, subjectPublicKeyInfo.len) != Success) { return MapSECStatus(SECFailure); } if (der::ExpectTagAndGetValue(input, der::SEQUENCE, spki) != Success) { return MapSECStatus(SECFailure); } if (der::End(input) != Success) { return MapSECStatus(SECFailure); } } // Skip AlgorithmIdentifier if (der::ExpectTagAndSkipValue(spki, der::SEQUENCE) != Success) { return MapSECStatus(SECFailure); } SECItem subjectPublicKey; if (der::ExpectTagAndGetValue(spki, der::BIT_STRING, subjectPublicKey) != Success) { return MapSECStatus(SECFailure); } if (der::End(spki) != Success) { return MapSECStatus(SECFailure); } // Assume/require that the number of unused bits in the public key is zero. if (subjectPublicKey.len == 0 || subjectPublicKey.data[0] != 0) { return Fail(RecoverableError, SEC_ERROR_BAD_DER); } ++subjectPublicKey.data; --subjectPublicKey.len; if (trustDomain.DigestBuf(subjectPublicKey, hashBuf, hashBufSize) != SECSuccess) { return MapSECStatus(SECFailure); } return Success; }
// CertID ::= SEQUENCE { // hashAlgorithm AlgorithmIdentifier, // issuerNameHash OCTET STRING, -- Hash of issuer's DN // issuerKeyHash OCTET STRING, -- Hash of issuer's public key // serialNumber CertificateSerialNumber } static inline Result CertID(Input& input, const Context& context, /*out*/ bool& match) { match = false; DigestAlgorithm hashAlgorithm; Result rv = der::DigestAlgorithmIdentifier(input, hashAlgorithm); if (rv != Success) { if (PR_GetError() == SEC_ERROR_INVALID_ALGORITHM) { // Skip entries that are hashed with algorithms we don't support. input.SkipToEnd(); return Success; } return rv; } SECItem issuerNameHash; rv = der::ExpectTagAndGetValue(input, der::OCTET_STRING, issuerNameHash); if (rv != Success) { return rv; } SECItem issuerKeyHash; rv = der::ExpectTagAndGetValue(input, der::OCTET_STRING, issuerKeyHash); if (rv != Success) { return rv; } SECItem serialNumber; rv = der::CertificateSerialNumber(input, serialNumber); if (rv != Success) { return rv; } if (!SECITEM_ItemsAreEqual(&serialNumber, &context.certID.serialNumber)) { // This does not reference the certificate we're interested in. // Consume the rest of the input and return successfully to // potentially continue processing other responses. input.SkipToEnd(); return Success; } // TODO: support SHA-2 hashes. if (hashAlgorithm != DigestAlgorithm::sha1) { // Again, not interested in this response. Consume input, return success. input.SkipToEnd(); return Success; } if (issuerNameHash.len != TrustDomain::DIGEST_LENGTH) { return Fail(SEC_ERROR_OCSP_MALFORMED_RESPONSE); } // From http://tools.ietf.org/html/rfc6960#section-4.1.1: // "The hash shall be calculated over the DER encoding of the // issuer's name field in the certificate being checked." uint8_t hashBuf[TrustDomain::DIGEST_LENGTH]; if (context.trustDomain.DigestBuf(context.certID.issuer, hashBuf, sizeof(hashBuf)) != SECSuccess) { return MapSECStatus(SECFailure); } if (memcmp(hashBuf, issuerNameHash.data, issuerNameHash.len)) { // Again, not interested in this response. Consume input, return success. input.SkipToEnd(); return Success; } return MatchKeyHash(context.trustDomain, issuerKeyHash, context.certID.issuerSubjectPublicKeyInfo, match); }
// 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; }