// Extracts whatever information we need out of fd (using SSL_*) and passes it // to SSLServerCertVerificationJob::Dispatch. SSLServerCertVerificationJob should // never do anything with fd except logging. SECStatus AuthCertificateHook(void *arg, PRFileDesc *fd, PRBool checkSig, PRBool isServer) { // Runs on the socket transport thread PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] starting AuthCertificateHook\n", fd)); // Modern libssl always passes PR_TRUE for checkSig, and we have no means of // doing verification without checking signatures. NS_ASSERTION(checkSig, "AuthCertificateHook: checkSig unexpectedly false"); // PSM never causes libssl to call this function with PR_TRUE for isServer, // and many things in PSM assume that we are a client. NS_ASSERTION(!isServer, "AuthCertificateHook: isServer unexpectedly true"); nsNSSSocketInfo *socketInfo = static_cast<nsNSSSocketInfo*>(arg); if (socketInfo) { // This is the first callback during full handshakes. socketInfo->SetFirstServerHelloReceived(); } CERTCertificate *serverCert = SSL_PeerCertificate(fd); CERTCertificateCleaner serverCertCleaner(serverCert); if (!checkSig || isServer || !socketInfo || !serverCert) { PR_SetError(PR_INVALID_STATE_ERROR, 0); return SECFailure; } if (BlockServerCertChangeForSpdy(socketInfo, serverCert) != SECSuccess) return SECFailure; bool onSTSThread; nsresult nrv; nsCOMPtr<nsIEventTarget> sts = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &nrv); if (NS_SUCCEEDED(nrv)) { nrv = sts->IsOnCurrentThread(&onSTSThread); } if (NS_FAILED(nrv)) { NS_ERROR("Could not get STS service or IsOnCurrentThread failed"); PR_SetError(PR_UNKNOWN_ERROR, 0); return SECFailure; } if (onSTSThread) { // We *must* do certificate verification on a background thread because // we need the socket transport thread to be free for our OCSP requests, // and we *want* to do certificate verification on a background thread // because of the performance benefits of doing so. socketInfo->SetCertVerificationWaiting(); SECStatus rv = SSLServerCertVerificationJob::Dispatch( static_cast<const void *>(fd), socketInfo, serverCert); return rv; } // We can't do certificate verification on a background thread, because the // thread doing the network I/O may not interrupt its network I/O on receipt // of our SSLServerCertVerificationResult event, and/or it might not even be // a non-blocking socket. SECStatus rv = AuthCertificate(socketInfo, serverCert); if (rv == SECSuccess) { return SECSuccess; } PRErrorCode error = PR_GetError(); if (error != 0) { nsRefPtr<CertErrorRunnable> runnable = CreateCertErrorRunnable( error, socketInfo, serverCert, static_cast<const void *>(fd)); if (!runnable) { // CreateCertErrorRunnable sets a new error code when it fails error = PR_GetError(); } else { // We have to return SECSuccess or SECFailure based on the result of the // override processing, so we must block this thread waiting for it. The // CertErrorRunnable will NOT dispatch the result at all, since we passed // false for CreateCertErrorRunnable's async parameter nrv = runnable->DispatchToMainThreadAndWait(); if (NS_FAILED(nrv)) { NS_ERROR("Failed to dispatch CertErrorRunnable"); PR_SetError(PR_INVALID_STATE_ERROR, 0); return SECFailure; } if (!runnable->mResult) { NS_ERROR("CertErrorRunnable did not set result"); PR_SetError(PR_INVALID_STATE_ERROR, 0); return SECFailure; } if (runnable->mResult->mErrorCode == 0) { return SECSuccess; // cert error override occurred. } // We must call SetCanceled here to set the error message type // in case it isn't PlainErrorMessage, which is what we would // default to if we just called // PR_SetError(runnable->mResult->mErrorCode, 0) and returned // SECFailure without doing this. socketInfo->SetCanceled(runnable->mResult->mErrorCode, runnable->mResult->mErrorMessageType); error = runnable->mResult->mErrorCode; } } if (error == 0) { NS_ERROR("error code not set"); error = PR_UNKNOWN_ERROR; } PR_SetError(error, 0); return SECFailure; }
SECStatus PR_CALLBACK AuthCertificateCallback(void* client_data, PRFileDesc* fd, PRBool checksig, PRBool isServer) { nsNSSShutDownPreventionLock locker; // first the default action SECStatus rv = SSL_AuthCertificate(CERT_GetDefaultCertDB(), fd, checksig, isServer); // We want to remember the CA certs in the temp db, so that the application can find the // complete chain at any time it might need it. // But we keep only those CA certs in the temp db, that we didn't already know. CERTCertificate *serverCert = SSL_PeerCertificate(fd); CERTCertificateCleaner serverCertCleaner(serverCert); if (serverCert) { nsNSSSocketInfo* infoObject = (nsNSSSocketInfo*) fd->higher->secret; nsRefPtr<nsSSLStatus> status = infoObject->SSLStatus(); nsRefPtr<nsNSSCertificate> nsc; if (!status || !status->mServerCert) { nsc = new nsNSSCertificate(serverCert); } if (SECSuccess == rv) { if (nsc) { PRBool dummyIsEV; nsc->GetIsExtendedValidation(&dummyIsEV); // the nsc object will cache the status } CERTCertList *certList = CERT_GetCertChainFromCert(serverCert, PR_Now(), certUsageSSLCA); nsCOMPtr<nsINSSComponent> nssComponent; for (CERTCertListNode *node = CERT_LIST_HEAD(certList); !CERT_LIST_END(node, certList); node = CERT_LIST_NEXT(node)) { if (node->cert->slot) { // This cert was found on a token, no need to remember it in the temp db. continue; } if (node->cert->isperm) { // We don't need to remember certs already stored in perm db. continue; } if (node->cert == serverCert) { // We don't want to remember the server cert, // the code that cares for displaying page info does this already. continue; } // We have found a signer cert that we want to remember. nsCAutoString nickname; nickname = nsNSSCertificate::defaultServerNickname(node->cert); if (!nickname.IsEmpty()) { PK11SlotInfo *slot = PK11_GetInternalKeySlot(); if (slot) { PK11_ImportCert(slot, node->cert, CK_INVALID_HANDLE, const_cast<char*>(nickname.get()), PR_FALSE); PK11_FreeSlot(slot); } } } CERT_DestroyCertList(certList); } // The connection may get terminated, for example, if the server requires // a client cert. Let's provide a minimal SSLStatus // to the caller that contains at least the cert and its status. if (!status) { status = new nsSSLStatus(); infoObject->SetSSLStatus(status); } if (status && !status->mServerCert) { status->mServerCert = nsc; PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("AuthCertificateCallback setting NEW cert %p\n", status->mServerCert.get())); } } return rv; }