SECStatus CertVerifier::VerifySSLServerCert(CERTCertificate* peerCert, /*optional*/ const SECItem* stapledOCSPResponse, Time time, /*optional*/ void* pinarg, const char* hostname, bool saveIntermediatesInPermanentDatabase, Flags flags, /*optional out*/ ScopedCERTCertList* builtChain, /*optional out*/ SECOidTag* evOidPolicy) { PR_ASSERT(peerCert); // XXX: PR_ASSERT(pinarg) PR_ASSERT(hostname); PR_ASSERT(hostname[0]); if (builtChain) { *builtChain = nullptr; } if (evOidPolicy) { *evOidPolicy = SEC_OID_UNKNOWN; } if (!hostname || !hostname[0]) { PR_SetError(SSL_ERROR_BAD_CERT_DOMAIN, 0); return SECFailure; } ScopedCERTCertList builtChainTemp; // CreateCertErrorRunnable assumes that CERT_VerifyCertName is only called // if VerifyCert succeeded. SECStatus rv = VerifyCert(peerCert, certificateUsageSSLServer, time, pinarg, hostname, flags, stapledOCSPResponse, &builtChainTemp, evOidPolicy); if (rv != SECSuccess) { return rv; } rv = CERT_VerifyCertName(peerCert, hostname); if (rv != SECSuccess) { return rv; } if (saveIntermediatesInPermanentDatabase) { SaveIntermediateCerts(builtChainTemp); } if (builtChain) { *builtChain = builtChainTemp.forget(); } return SECSuccess; }
NS_IMETHODIMP nsNSSCertificateDB::ImportEmailCertificate(uint8_t * data, uint32_t length, nsIInterfaceRequestor *ctx) { nsNSSShutDownPreventionLock locker; if (isAlreadyShutDown()) { return NS_ERROR_NOT_AVAILABLE; } SECStatus srv = SECFailure; nsresult nsrv = NS_OK; CERTCertDBHandle *certdb; CERTCertificate **certArray = nullptr; ScopedCERTCertList certList; CERTCertListNode *node; SECItem **rawArray; int numcerts; int i; PLArenaPool *arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); if (!arena) return NS_ERROR_OUT_OF_MEMORY; CERTDERCerts *certCollection = getCertsFromPackage(arena, data, length, locker); if (!certCollection) { PORT_FreeArena(arena, false); return NS_ERROR_FAILURE; } RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier()); NS_ENSURE_TRUE(certVerifier, NS_ERROR_UNEXPECTED); certdb = CERT_GetDefaultCertDB(); numcerts = certCollection->numcerts; rawArray = (SECItem **) PORT_Alloc(sizeof(SECItem *) * numcerts); if ( !rawArray ) { nsrv = NS_ERROR_FAILURE; goto loser; } for (i=0; i < numcerts; i++) { rawArray[i] = &certCollection->rawCerts[i]; } srv = CERT_ImportCerts(certdb, certUsageEmailRecipient, numcerts, rawArray, &certArray, false, false, nullptr); PORT_Free(rawArray); rawArray = nullptr; if (srv != SECSuccess) { nsrv = NS_ERROR_FAILURE; goto loser; } // build a CertList for filtering certList = CERT_NewCertList(); if (!certList) { nsrv = NS_ERROR_FAILURE; goto loser; } for (i=0; i < numcerts; i++) { CERTCertificate *cert = certArray[i]; if (cert) cert = CERT_DupCertificate(cert); if (cert) CERT_AddCertToListTail(certList.get(), cert); } /* go down the remaining list of certs and verify that they have * valid chains, then import them. */ for (node = CERT_LIST_HEAD(certList); !CERT_LIST_END(node,certList); node = CERT_LIST_NEXT(node)) { if (!node->cert) { continue; } ScopedCERTCertList certChain; SECStatus rv = certVerifier->VerifyCert(node->cert, certificateUsageEmailRecipient, mozilla::pkix::Now(), ctx, nullptr, certChain); if (rv != SECSuccess) { nsCOMPtr<nsIX509Cert> certToShow = nsNSSCertificate::Create(node->cert); DisplayCertificateAlert(ctx, "NotImportingUnverifiedCert", certToShow, locker); continue; } rv = ImportCertsIntoPermanentStorage(certChain, certUsageEmailRecipient, false); if (rv != SECSuccess) { goto loser; } CERT_SaveSMimeProfile(node->cert, nullptr, nullptr); } loser: if (certArray) { CERT_DestroyCertArray(certArray, numcerts); } if (arena) PORT_FreeArena(arena, true); return nsrv; }
// 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); }
SECStatus CertVerifier::VerifySSLServerCert(CERTCertificate* peerCert, /*optional*/ const SECItem* stapledOCSPResponse, Time time, /*optional*/ void* pinarg, const char* hostname, bool saveIntermediatesInPermanentDatabase, Flags flags, /*optional out*/ ScopedCERTCertList* builtChain, /*optional out*/ SECOidTag* evOidPolicy, /*optional out*/ OCSPStaplingStatus* ocspStaplingStatus, /*optional out*/ KeySizeStatus* keySizeStatus) { PR_ASSERT(peerCert); // XXX: PR_ASSERT(pinarg) PR_ASSERT(hostname); PR_ASSERT(hostname[0]); if (builtChain) { *builtChain = nullptr; } if (evOidPolicy) { *evOidPolicy = SEC_OID_UNKNOWN; } if (!hostname || !hostname[0]) { PR_SetError(SSL_ERROR_BAD_CERT_DOMAIN, 0); return SECFailure; } ScopedCERTCertList builtChainTemp; // CreateCertErrorRunnable assumes that CheckCertHostname is only called // if VerifyCert succeeded. SECStatus rv = VerifyCert(peerCert, certificateUsageSSLServer, time, pinarg, hostname, flags, stapledOCSPResponse, &builtChainTemp, evOidPolicy, ocspStaplingStatus, keySizeStatus); if (rv != SECSuccess) { return rv; } Input peerCertInput; Result result = peerCertInput.Init(peerCert->derCert.data, peerCert->derCert.len); if (result != Success) { PR_SetError(MapResultToPRErrorCode(result), 0); return SECFailure; } Input hostnameInput; result = hostnameInput.Init(uint8_t_ptr_cast(hostname), strlen(hostname)); if (result != Success) { PR_SetError(SEC_ERROR_INVALID_ARGS, 0); return SECFailure; } result = CheckCertHostname(peerCertInput, hostnameInput); if (result != Success) { PR_SetError(MapResultToPRErrorCode(result), 0); return SECFailure; } if (saveIntermediatesInPermanentDatabase) { SaveIntermediateCerts(builtChainTemp); } if (builtChain) { *builtChain = builtChainTemp.forget(); } return SECSuccess; }
// 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 // 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); }