From b8838b211d69ce5952c0ae9e49424e60ff98c992 Mon Sep 17 00:00:00 2001 From: Brian Smith Date: Thu, 1 Dec 2011 14:37:57 -0800 Subject: [PATCH] Bug 674147 (Remove the SSL Thread) Part 2: Everything else, r=honzab --- .../base/src/nsSocketTransportService2.cpp | 12 + security/manager/ssl/src/Makefile.in | 1 - .../ssl/src/SSLServerCertVerification.cpp | 536 ++++++-- .../ssl/src/SSLServerCertVerification.h | 89 ++ security/manager/ssl/src/nsNSSCallbacks.cpp | 13 +- security/manager/ssl/src/nsNSSCallbacks.h | 5 - security/manager/ssl/src/nsNSSComponent.cpp | 24 +- security/manager/ssl/src/nsNSSComponent.h | 2 - security/manager/ssl/src/nsNSSIOLayer.cpp | 867 +++++++------ security/manager/ssl/src/nsNSSIOLayer.h | 123 +- security/manager/ssl/src/nsSSLThread.cpp | 1150 ----------------- security/manager/ssl/src/nsSSLThread.h | 158 --- 12 files changed, 1029 insertions(+), 1951 deletions(-) create mode 100644 security/manager/ssl/src/SSLServerCertVerification.h delete mode 100644 security/manager/ssl/src/nsSSLThread.cpp delete mode 100644 security/manager/ssl/src/nsSSLThread.h diff --git a/netwerk/base/src/nsSocketTransportService2.cpp b/netwerk/base/src/nsSocketTransportService2.cpp index 1f1f516f657..2fcf46decf6 100644 --- a/netwerk/base/src/nsSocketTransportService2.cpp +++ b/netwerk/base/src/nsSocketTransportService2.cpp @@ -54,6 +54,14 @@ #include "mozilla/FunctionTimer.h" +// XXX: There is no good header file to put these in. :( +namespace mozilla { namespace psm { + +void InitializeSSLServerCertVerificationThreads(); +void StopSSLServerCertVerificationThreads(); + +} } // namespace mozilla::psm + using namespace mozilla; #if defined(PR_LOGGING) @@ -609,6 +617,8 @@ nsSocketTransportService::Run() { SOCKET_LOG(("STS thread init\n")); + psm::InitializeSSLServerCertVerificationThreads(); + gSocketThread = PR_GetCurrentThread(); // add thread event to poll list (mThreadEvent may be NULL) @@ -665,6 +675,8 @@ nsSocketTransportService::Run() gSocketThread = nsnull; + psm::StopSSLServerCertVerificationThreads(); + SOCKET_LOG(("STS thread exit\n")); return NS_OK; } diff --git a/security/manager/ssl/src/Makefile.in b/security/manager/ssl/src/Makefile.in index 8b12876368d..5a3c81ff86e 100644 --- a/security/manager/ssl/src/Makefile.in +++ b/security/manager/ssl/src/Makefile.in @@ -60,7 +60,6 @@ CPPSRCS = \ nsRecentBadCerts.cpp \ nsClientAuthRemember.cpp \ nsPSMBackgroundThread.cpp \ - nsSSLThread.cpp \ nsCertVerificationThread.cpp \ nsProtectedAuthThread.cpp \ nsNSSCallbacks.cpp \ diff --git a/security/manager/ssl/src/SSLServerCertVerification.cpp b/security/manager/ssl/src/SSLServerCertVerification.cpp index e068912944b..03fc43c5908 100644 --- a/security/manager/ssl/src/SSLServerCertVerification.cpp +++ b/security/manager/ssl/src/SSLServerCertVerification.cpp @@ -40,44 +40,213 @@ * the terms of any one of the MPL, the GPL or the LGPL. * * ***** END LICENSE BLOCK ***** */ + +/* + * All I/O is done on the socket transport thread, including all calls into + * libssl. That is, all SSL_* functions must be called on the socket transport + * thread. This also means that all SSL callback functions will be called on + * the socket transport thread, including in particular the auth certificate + * hook. + * + * During certificate authentication, we call CERT_PKIXVerifyCert or + * CERT_VerifyCert. These functions may make zero or more HTTP requests + * for OCSP responses, CRLs, intermediate certificates, etc. + * + * If our cert auth hook were to call the CERT_*Verify* functions directly, + * there would be a deadlock: The CERT_*Verify* function would cause an event + * to be asynchronously posted to the socket transport thread, and then it + * would block the socket transport thread waiting to be notified of the HTTP + * response. However, the HTTP request would never actually be processed + * because the socket transport thread would be blocked and so it wouldn't be + * able process HTTP requests. (i.e. Deadlock.) + * + * Consequently, we must always call the CERT_*Verify* cert functions off the + * socket transport thread. To accomplish this, our auth cert hook dispatches a + * SSLServerCertVerificationJob to a pool of background threads, and then + * immediatley return SECWouldBlock to libssl. These jobs are where the + * CERT_*Verify* functions are actually called. + * + * When our auth cert hook returns SECWouldBlock, libssl will carry on the + * handshake while we validate the certificate. This will free up the socket + * transport thread so that HTTP requests--in particular, the OCSP/CRL/cert + * requests needed for cert verification as mentioned above--can be processed. + * + * Once the CERT_*Verify* function returns, the cert verification job + * dispatches a SSLServerCertVerificationResult to the socket transport thread; + * the SSLServerCertVerificationResult will notify libssl that the certificate + * authentication is complete. Once libssl is notified that the authentication + * is complete, it will continue the SSL handshake (if it hasn't already + * finished) and it will begin allowing us to send/receive data on the + * connection. + * + * Timeline of events: + * + * * libssl calls SSLServerCertVerificationJob::Dispatch on the socket + * transport thread. + * * SSLServerCertVerificationJob::Dispatch queues a job + * (instance of SSLServerCertVerificationJob) to its background thread + * pool and returns. + * * One of the background threads calls CERT_*Verify*, which may enqueue + * some HTTP request(s) onto the socket transport thread, and then + * blocks that background thread waiting for the responses and/or timeouts + * or errors for those requests. + * * Once those HTTP responses have all come back or failed, the + * CERT_*Verify* function returns a result indicating that the validation + * succeeded or failed. + * * If the validation succeeded, then a SSLServerCertVerificationResult + * event is posted to the socket transport thread, and the cert + * verification thread becomes free to verify other certificates. + * * Otherwise, a CertErrorRunnable is posted to the socket transport thread + * and then to the main thread (blocking both, see CertErrorRunnable) to + * do cert override processing and bad cert listener notification. Then + * the cert verification thread becomes free to verify other certificates. + * * After processing cert overrides, the CertErrorRunnable will dispatch a + * SSLServerCertVerificationResult event to the socket transport thread to + * notify it of the result of the override processing; then it returns, + * freeing up the main thread. + * * The SSLServerCertVerificationResult event will either wake up the + * socket (using SSL_RestartHandshakeAfterServerCert) if validation + * succeeded or there was an error override, or it will set an error flag + * so that the next I/O operation on the socket will fail, causing the + * socket transport thread to close the connection. + * + * Cert override processing must happen on the main thread because it accesses + * the nsICertOverrideService, and that service must be accessed on the main + * thread because some extensions (Selenium, in particular) replace it with a + * Javascript implementation, and chrome JS must always be run on the main + * thread. + * + * SSLServerCertVerificationResult must be dispatched to the socket transport + * thread because we must only call SSL_* functions on the socket transport + * thread since they may do I/O, because many parts of nsNSSSocketInfo and + * the PSM NSS I/O layer are not thread-safe, and because we need the event to + * interrupt the PR_Poll that may waiting for I/O on the socket for which we + * are validating the cert. + */ + +#include "SSLServerCertVerification.h" #include "nsNSSComponent.h" #include "nsNSSCertificate.h" -#include "nsNSSCleaner.h" #include "nsNSSIOLayer.h" +#include "nsIThreadPool.h" +#include "nsXPCOMCIDInternal.h" +#include "nsComponentManagerUtils.h" +#include "nsServiceManagerUtils.h" + #include "ssl.h" -#include "cert.h" #include "secerr.h" #include "sslerr.h" -using namespace mozilla; - -static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID); -NSSCleanupAutoPtrClass(CERTCertificate, CERT_DestroyCertificate) - #ifdef PR_LOGGING extern PRLogModuleInfo* gPIPNSSLog; #endif +namespace mozilla { namespace psm { + +namespace { +// do not use a nsCOMPtr to avoid static initializer/destructor +nsIThreadPool * gCertVerificationThreadPool = nsnull; +} // unnamed namespace + +// Called when the socket transport thread starts, to initialize the SSL cert +// verification thread pool. By tying the thread pool startup/shutdown directly +// to the STS thread's lifetime, we ensure that they are *always* available for +// SSL connections and that there are no races during startup and especially +// shutdown. (Previously, we have had multiple problems with races in PSM +// background threads, and the race-prevention/shutdown logic used there is +// brittle. Since this service is critical to things like downloading updates, +// we take no chances.) Also, by doing things this way, we avoid the need for +// locks, since gCertVerificationThreadPool is only ever accessed on the socket +// transport thread. +void +InitializeSSLServerCertVerificationThreads() +{ + // TODO: tuning, make parameters preferences + // XXX: instantiate nsThreadPool directly, to make this more bulletproof. + // Currently, the nsThreadPool.h header isn't exported for us to do so. + nsresult rv = CallCreateInstance(NS_THREADPOOL_CONTRACTID, + &gCertVerificationThreadPool); + if (NS_FAILED(rv)) { + NS_WARNING("Failed to create SSL cert verification threads."); + return; + } + + (void) gCertVerificationThreadPool->SetIdleThreadLimit(5); + (void) gCertVerificationThreadPool->SetIdleThreadTimeout(30 * 1000); + (void) gCertVerificationThreadPool->SetThreadLimit(5); +} + +// Called when the socket transport thread finishes, to destroy the thread +// pool. Since the socket transport service has stopped processing events, it +// will not attempt any more SSL I/O operations, so it is clearly safe to shut +// down the SSL cert verification infrastructure. Also, the STS will not +// dispatch many SSL verification result events at this point, so any pending +// cert verifications will (correctly) fail at the point they are dispatched. +// +// The other shutdown race condition that is possible is a race condition with +// shutdown of the nsNSSComponent service. We use the +// nsNSSShutdownPreventionLock where needed (not here) to prevent that. +void StopSSLServerCertVerificationThreads() +{ + if (gCertVerificationThreadPool) { + gCertVerificationThreadPool->Shutdown(); + NS_RELEASE(gCertVerificationThreadPool); + } +} + +namespace { + +class SSLServerCertVerificationJob : public nsRunnable +{ +public: + // Must be called only on the socket transport thread + static SECStatus Dispatch(const void * fdForLogging, + nsNSSSocketInfo * infoObject, + CERTCertificate * serverCert); +private: + NS_DECL_NSIRUNNABLE + + // Must be called only on the socket transport thread + SSLServerCertVerificationJob(const void * fdForLogging, + nsNSSSocketInfo & socketInfo, + CERTCertificate & cert); + ~SSLServerCertVerificationJob(); + + // Runs on one of the background threads + SECStatus AuthCertificate(const nsNSSShutDownPreventionLock & proofOfLock); + + const void * const mFdForLogging; + const nsRefPtr mSocketInfo; + CERTCertificate * const mCert; +}; + +SSLServerCertVerificationJob::SSLServerCertVerificationJob( + const void * fdForLogging, nsNSSSocketInfo & socketInfo, + CERTCertificate & cert) + : mFdForLogging(fdForLogging) + , mSocketInfo(&socketInfo) + , mCert(CERT_DupCertificate(&cert)) +{ +} + +SSLServerCertVerificationJob::~SSLServerCertVerificationJob() +{ + CERT_DestroyCertificate(mCert); +} + +static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID); + SECStatus -PSM_SSL_PKIX_AuthCertificate(PRFileDesc *fd, CERTCertificate *peerCert, bool checksig, bool isServer) +PSM_SSL_PKIX_AuthCertificate(CERTCertificate *peerCert, void * pinarg, + const char * hostname, + const nsNSSShutDownPreventionLock & /*proofOfLock*/) { SECStatus rv; - SECCertUsage certUsage; - SECCertificateUsage certificateusage; - void * pinarg; - char * hostname; - pinarg = SSL_RevealPinArg(fd); - hostname = SSL_RevealURL(fd); - - /* this may seem backwards, but isn't. */ - certUsage = isServer ? certUsageSSLClient : certUsageSSLServer; - certificateusage = isServer ? certificateUsageSSLClient : certificateUsageSSLServer; - if (!nsNSSComponent::globalConstFlagUsePKIXVerification) { - rv = CERT_VerifyCertNow(CERT_GetDefaultCertDB(), peerCert, checksig, certUsage, - pinarg); + rv = CERT_VerifyCertNow(CERT_GetDefaultCertDB(), peerCert, true, + certUsageSSLServer, pinarg); } else { nsresult nsrv; @@ -91,12 +260,12 @@ PSM_SSL_PKIX_AuthCertificate(PRFileDesc *fd, CERTCertificate *peerCert, bool che CERTValOutParam cvout[1]; cvout[0].type = cert_po_end; - rv = CERT_PKIXVerifyCert(peerCert, certificateusage, + rv = CERT_PKIXVerifyCert(peerCert, certificateUsageSSLServer, survivingParams->GetRawPointerForNSS(), cvout, pinarg); } - if ( rv == SECSuccess && !isServer ) { + if (rv == SECSuccess) { /* cert is OK. This is the client side of an SSL connection. * Now check the name field in the cert against the desired hostname. * NB: This is our only defense against Man-In-The-Middle (MITM) attacks! @@ -109,7 +278,6 @@ PSM_SSL_PKIX_AuthCertificate(PRFileDesc *fd, CERTCertificate *peerCert, bool che PORT_SetError(SSL_ERROR_BAD_CERT_DOMAIN); } - PORT_Free(hostname); return rv; } @@ -197,22 +365,17 @@ PSM_SSL_BlacklistDigiNotar(CERTCertificate * serverCert, return 0; } - -SECStatus PR_CALLBACK AuthCertificateCallback(void* client_data, PRFileDesc* fd, - PRBool checksig, PRBool isServer) { - nsNSSShutDownPreventionLock locker; - - CERTCertificate *serverCert = SSL_PeerCertificate(fd); - CERTCertificateCleaner serverCertCleaner(serverCert); - - if (serverCert && - serverCert->serialNumber.data && - serverCert->issuerName && - !strcmp(serverCert->issuerName, +SECStatus +SSLServerCertVerificationJob::AuthCertificate( + nsNSSShutDownPreventionLock const & nssShutdownPreventionLock) +{ + if (mCert->serialNumber.data && + mCert->issuerName && + !strcmp(mCert->issuerName, "CN=UTN-USERFirst-Hardware,OU=http://www.usertrust.com,O=The USERTRUST Network,L=Salt Lake City,ST=UT,C=US")) { - unsigned char *server_cert_comparison_start = (unsigned char*)serverCert->serialNumber.data; - unsigned int server_cert_comparison_len = serverCert->serialNumber.len; + unsigned char *server_cert_comparison_start = mCert->serialNumber.data; + unsigned int server_cert_comparison_len = mCert->serialNumber.len; while (server_cert_comparison_len) { if (*server_cert_comparison_start != 0) @@ -244,87 +407,85 @@ SECStatus PR_CALLBACK AuthCertificateCallback(void* client_data, PRFileDesc* fd, } } - SECStatus rv = PSM_SSL_PKIX_AuthCertificate(fd, serverCert, checksig, isServer); + SECStatus rv = PSM_SSL_PKIX_AuthCertificate(mCert, mSocketInfo, + mSocketInfo->GetHostName(), + nssShutdownPreventionLock); // 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. - if (serverCert) { - nsNSSSocketInfo* infoObject = (nsNSSSocketInfo*) fd->higher->secret; - nsRefPtr status = infoObject->SSLStatus(); - nsRefPtr nsc; + nsRefPtr status = mSocketInfo->SSLStatus(); + nsRefPtr nsc; - if (!status || !status->mServerCert) { - nsc = nsNSSCertificate::Create(serverCert); + if (!status || !status->mServerCert) { + nsc = nsNSSCertificate::Create(mCert); + } + + CERTCertList *certList = nsnull; + certList = CERT_GetCertChainFromCert(mCert, PR_Now(), certUsageSSLCA); + if (!certList) { + rv = SECFailure; + } else { + PRErrorCode blacklistErrorCode; + if (rv == SECSuccess) { // PSM_SSL_PKIX_AuthCertificate said "valid cert" + blacklistErrorCode = PSM_SSL_BlacklistDigiNotar(mCert, certList); + } else { // PSM_SSL_PKIX_AuthCertificate said "invalid cert" + PRErrorCode savedErrorCode = PORT_GetError(); + // Check if we want to worsen the error code to "revoked". + blacklistErrorCode = PSM_SSL_DigiNotarTreatAsRevoked(mCert, certList); + if (blacklistErrorCode == 0) { + // we don't worsen the code, let's keep the original error code from NSS + PORT_SetError(savedErrorCode); + } } - - CERTCertList *certList = nsnull; - certList = CERT_GetCertChainFromCert(serverCert, PR_Now(), certUsageSSLCA); - if (!certList) { + + if (blacklistErrorCode != 0) { + mSocketInfo->SetCertIssuerBlacklisted(); + PORT_SetError(blacklistErrorCode); rv = SECFailure; - } else { - PRErrorCode blacklistErrorCode; - if (rv == SECSuccess) { // PSM_SSL_PKIX_AuthCertificate said "valid cert" - blacklistErrorCode = PSM_SSL_BlacklistDigiNotar(serverCert, certList); - } else { // PSM_SSL_PKIX_AuthCertificate said "invalid cert" - PRErrorCode savedErrorCode = PORT_GetError(); - // Check if we want to worsen the error code to "revoked". - blacklistErrorCode = PSM_SSL_DigiNotarTreatAsRevoked(serverCert, certList); - if (blacklistErrorCode == 0) { - // we don't worsen the code, let's keep the original error code from NSS - PORT_SetError(savedErrorCode); - } - } - - if (blacklistErrorCode != 0) { - infoObject->SetCertIssuerBlacklisted(); - PORT_SetError(blacklistErrorCode); - rv = SECFailure; - } } + } - if (rv == SECSuccess) { - if (nsc) { - bool dummyIsEV; - nsc->GetIsExtendedValidation(&dummyIsEV); // the nsc object will cache the status - } + if (rv == SECSuccess) { + if (nsc) { + bool dummyIsEV; + nsc->GetIsExtendedValidation(&dummyIsEV); // the nsc object will cache the status + } - nsCOMPtr nssComponent; + nsCOMPtr nssComponent; - for (CERTCertListNode *node = CERT_LIST_HEAD(certList); - !CERT_LIST_END(node, certList); - node = CERT_LIST_NEXT(node)) { + 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. - char* nickname = nsNSSCertificate::defaultServerNickname(node->cert); - if (nickname && *nickname) { - PK11SlotInfo *slot = PK11_GetInternalKeySlot(); - if (slot) { - PK11_ImportCert(slot, node->cert, CK_INVALID_HANDLE, - nickname, false); - PK11_FreeSlot(slot); - } - } - PR_FREEIF(nickname); + 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 == mCert) { + // 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. + char* nickname = nsNSSCertificate::defaultServerNickname(node->cert); + if (nickname && *nickname) { + PK11SlotInfo *slot = PK11_GetInternalKeySlot(); + if (slot) { + PK11_ImportCert(slot, node->cert, CK_INVALID_HANDLE, + nickname, false); + PK11_FreeSlot(slot); + } + } + PR_FREEIF(nickname); } if (certList) { @@ -336,27 +497,186 @@ SECStatus PR_CALLBACK AuthCertificateCallback(void* client_data, PRFileDesc* fd, // to the caller that contains at least the cert and its status. if (!status) { status = new nsSSLStatus(); - infoObject->SetSSLStatus(status); + mSocketInfo->SetSSLStatus(status); } if (rv == SECSuccess) { // Certificate verification succeeded delete any potential record // of certificate error bits. nsSSLIOLayerHelpers::mHostsWithCertErrors->RememberCertHasError( - infoObject, nsnull, rv); + mSocketInfo, nsnull, rv); } else { // Certificate verification failed, update the status' bits. nsSSLIOLayerHelpers::mHostsWithCertErrors->LookupCertErrorBits( - infoObject, status); + mSocketInfo, status); } if (status && !status->mServerCert) { status->mServerCert = nsc; PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, - ("AuthCertificateCallback setting NEW cert %p\n", status->mServerCert.get())); + ("AuthCertificate setting NEW cert %p\n", status->mServerCert.get())); } } return rv; } + +/*static*/ SECStatus +SSLServerCertVerificationJob::Dispatch(const void * fdForLogging, + nsNSSSocketInfo * socketInfo, + CERTCertificate * serverCert) +{ + // Runs on the socket transport thread + + if (!socketInfo || !serverCert) { + NS_ERROR("Invalid parameters for SSL server cert validation"); + socketInfo->SetCertVerificationResult(PR_INVALID_STATE_ERROR, + PlainErrorMessage); + PR_SetError(PR_INVALID_STATE_ERROR, 0); + return SECFailure; + } + + nsRefPtr job + = new SSLServerCertVerificationJob(fdForLogging, *socketInfo, *serverCert); + + socketInfo->SetCertVerificationWaiting(); + nsresult nrv; + if (!gCertVerificationThreadPool) { + nrv = NS_ERROR_NOT_INITIALIZED; + } else { + nrv = gCertVerificationThreadPool->Dispatch(job, NS_DISPATCH_NORMAL); + } + if (NS_FAILED(nrv)) { + PRErrorCode error = nrv == NS_ERROR_OUT_OF_MEMORY + ? SEC_ERROR_NO_MEMORY + : PR_INVALID_STATE_ERROR; + socketInfo->SetCertVerificationResult(error, PlainErrorMessage); + PORT_SetError(error); + return SECFailure; + } + + PORT_SetError(PR_WOULD_BLOCK_ERROR); + return SECWouldBlock; +} + +NS_IMETHODIMP +SSLServerCertVerificationJob::Run() +{ + // Runs on a cert verification thread + + PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, + ("[%p] SSLServerCertVerificationJob::Run\n", mSocketInfo.get())); + + PRErrorCode error; + + nsNSSShutDownPreventionLock nssShutdownPrevention; + if (mSocketInfo->isAlreadyShutDown()) { + error = SEC_ERROR_USER_CANCELLED; + } else { + // Reset the error code here so we can detect if AuthCertificate fails to + // set the error code if/when it fails. + PR_SetError(0, 0); + SECStatus rv = AuthCertificate(nssShutdownPrevention); + if (rv == SECSuccess) { + nsRefPtr restart + = new SSLServerCertVerificationResult(*mSocketInfo, 0); + restart->Dispatch(); + return NS_OK; + } + + error = PR_GetError(); + if (error != 0) { + rv = HandleBadCertificate(error, mSocketInfo, *mCert, mFdForLogging, + nssShutdownPrevention); + if (rv == SECSuccess) { + // The CertErrorRunnable will run on the main thread and it will dispatch + // the cert verification result to the socket transport thread, so we + // don't have to. This way, this verification thread doesn't need to + // wait for the CertErrorRunnable to complete. + return NS_OK; + } + // DispatchCertErrorRunnable set a new error code. + error = PR_GetError(); + } + } + + if (error == 0) { + NS_NOTREACHED("no error set during certificate validation failure"); + error = PR_INVALID_STATE_ERROR; + } + + nsRefPtr failure + = new SSLServerCertVerificationResult(*mSocketInfo, error); + failure->Dispatch(); + return NS_OK; +} + +} // unnamed namespace + +// 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"); + + if (!checkSig || isServer) { + PR_SetError(PR_INVALID_STATE_ERROR, 0); + return SECFailure; + } + + CERTCertificate *serverCert = SSL_PeerCertificate(fd); + + nsNSSSocketInfo *socketInfo = static_cast(arg); + SECStatus rv = SSLServerCertVerificationJob::Dispatch( + static_cast(fd), socketInfo, serverCert); + + CERT_DestroyCertificate(serverCert); + + return rv; +} + +SSLServerCertVerificationResult::SSLServerCertVerificationResult( + nsNSSSocketInfo & socketInfo, PRErrorCode errorCode, + SSLErrorMessageType errorMessageType) + : mSocketInfo(&socketInfo) + , mErrorCode(errorCode) + , mErrorMessageType(errorMessageType) +{ +} + +void +SSLServerCertVerificationResult::Dispatch() +{ + nsresult rv; + nsCOMPtr stsTarget + = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); + NS_ASSERTION(stsTarget, + "Failed to get socket transport service event target"); + rv = stsTarget->Dispatch(this, NS_DISPATCH_NORMAL); + NS_ASSERTION(NS_SUCCEEDED(rv), + "Failed to dispatch SSLServerCertVerificationResult"); +} + +NS_IMETHODIMP +SSLServerCertVerificationResult::Run() +{ + // TODO: Assert that we're on the socket transport thread + mSocketInfo->SetCertVerificationResult(mErrorCode, mErrorMessageType); + return NS_OK; +} + +} } // namespace mozilla::psm diff --git a/security/manager/ssl/src/SSLServerCertVerification.h b/security/manager/ssl/src/SSLServerCertVerification.h new file mode 100644 index 00000000000..18e05869722 --- /dev/null +++ b/security/manager/ssl/src/SSLServerCertVerification.h @@ -0,0 +1,89 @@ +/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- + * + * ***** BEGIN LICENSE BLOCK ***** + * Version: MPL 1.1/GPL 2.0/LGPL 2.1 + * + * The contents of this file are subject to the Mozilla Public License Version + * 1.1 (the "License"); you may not use this file except in compliance with + * the License. You may obtain a copy of the License at + * http://www.mozilla.org/MPL/ + * + * Software distributed under the License is distributed on an "AS IS" basis, + * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License + * for the specific language governing rights and limitations under the + * License. + * + * The Original Code is mozilla.org code. + * + * The Initial Developer of the Original Code is + * Netscape Communications Corporation. + * Portions created by the Initial Developer are Copyright (C) 1998 + * the Initial Developer. All Rights Reserved. + * + * Contributor(s): + * + * Alternatively, the contents of this file may be used under the terms of + * either the GNU General Public License Version 2 or later (the "GPL"), or + * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), + * in which case the provisions of the GPL or the LGPL are applicable instead + * of those above. If you wish to allow use of your version of this file only + * under the terms of either the GPL or the LGPL, and not to allow others to + * use your version of this file under the terms of the MPL, indicate your + * decision by deleting the provisions above and replace them with the notice + * and other provisions required by the GPL or the LGPL. If you do not delete + * the provisions above, a recipient may use your version of this file under + * the terms of any one of the MPL, the GPL or the LGPL. + * + * ***** END LICENSE BLOCK ***** */ +#ifndef _SSLSERVERCERTVERIFICATION_H +#define _SSLSERVERCERTVERIFICATION_H + +#include "seccomon.h" +#include "nsAutoPtr.h" +#include "nsThreadUtils.h" +#include "nsIRunnable.h" +#include "prerror.h" +#include "nsNSSIOLayer.h" + +typedef struct PRFileDesc PRFileDesc; +typedef struct CERTCertificateStr CERTCertificate; +class nsNSSSocketInfo; +class nsNSSShutDownPreventionLock; + +namespace mozilla { namespace psm { + +SECStatus AuthCertificateHook(void *arg, PRFileDesc *fd, + PRBool checkSig, PRBool isServer); + +SECStatus HandleBadCertificate(PRErrorCode defaultErrorCodeToReport, + nsNSSSocketInfo * socketInfo, + CERTCertificate & cert, + const void * fdForLogging, + const nsNSSShutDownPreventionLock &); + +// Dispatched from a cert verification thread to the STS thread to notify the +// socketInfo of the verification result. +// +// This will cause the PR_Poll in the STS thread to return, so things work +// correctly even if the STS thread is blocked polling (only) on the file +// descriptor that is waiting for this result. +class SSLServerCertVerificationResult : public nsRunnable +{ +public: + NS_DECL_NSIRUNNABLE + + SSLServerCertVerificationResult(nsNSSSocketInfo & socketInfo, + PRErrorCode errorCode, + SSLErrorMessageType errorMessageType = + PlainErrorMessage); + + void Dispatch(); +private: + const nsRefPtr mSocketInfo; + const PRErrorCode mErrorCode; + const SSLErrorMessageType mErrorMessageType; +}; + +} } // namespace mozilla::psm + +#endif diff --git a/security/manager/ssl/src/nsNSSCallbacks.cpp b/security/manager/ssl/src/nsNSSCallbacks.cpp index 854f47ef10e..4ca12219b63 100644 --- a/security/manager/ssl/src/nsNSSCallbacks.cpp +++ b/security/manager/ssl/src/nsNSSCallbacks.cpp @@ -48,7 +48,6 @@ #include "nsITokenDialogs.h" #include "nsNSSShutDown.h" #include "nsIUploadChannel.h" -#include "nsSSLThread.h" #include "nsThreadUtils.h" #include "nsIPrompt.h" #include "nsProxyRelease.h" @@ -420,11 +419,10 @@ nsNSSHttpRequestSession::internal_send_receive_attempt(bool &retryable_error, if (!request_canceled) { - bool wantExit = nsSSLThread::stoppedOrStopping(); bool timeout = (PRIntervalTime)(PR_IntervalNow() - start_time) > mTimeoutInterval; - - if (wantExit || timeout) + + if (timeout) { request_canceled = true; @@ -821,6 +819,12 @@ void PR_CALLBACK HandshakeCallback(PRFileDesc* fd, void* client_data) { nsresult rv; PRInt32 encryptBits; + nsNSSSocketInfo* infoObject = (nsNSSSocketInfo*) fd->higher->secret; + + // If the handshake completed, then we know the site is TLS tolerant (if this + // was a TLS connection). + nsSSLIOLayerHelpers::rememberTolerantSite(fd, infoObject); + if (SECSuccess != SSL_SecurityStatus(fd, &sslStatus, &cipherName, &keyLength, &encryptBits, &signer, nsnull)) { return; @@ -842,7 +846,6 @@ void PR_CALLBACK HandshakeCallback(PRFileDesc* fd, void* client_data) { bool wantWarning = (nsSSLIOLayerHelpers::getWarnLevelMissingRFC5746() > 0); - nsNSSSocketInfo* infoObject = (nsNSSSocketInfo*) fd->higher->secret; nsCOMPtr console; if (infoObject && wantWarning) { console = do_GetService(NS_CONSOLESERVICE_CONTRACTID); diff --git a/security/manager/ssl/src/nsNSSCallbacks.h b/security/manager/ssl/src/nsNSSCallbacks.h index 5a6d2f26bdf..3425a35aa8f 100644 --- a/security/manager/ssl/src/nsNSSCallbacks.h +++ b/security/manager/ssl/src/nsNSSCallbacks.h @@ -52,11 +52,6 @@ char* PR_CALLBACK PK11PasswordPrompt(PK11SlotInfo *slot, PRBool retry, void* arg); void PR_CALLBACK HandshakeCallback(PRFileDesc *fd, void *client_data); -SECStatus PR_CALLBACK AuthCertificateCallback(void* client_data, PRFileDesc* fd, - PRBool checksig, PRBool isServer); - -PRErrorCode PSM_SSL_BlacklistDigiNotar(CERTCertificate * serverCert, - CERTCertList * serverCertChain); SECStatus RegisterMyOCSPAIAInfoCallback(); SECStatus UnregisterMyOCSPAIAInfoCallback(); diff --git a/security/manager/ssl/src/nsNSSComponent.cpp b/security/manager/ssl/src/nsNSSComponent.cpp index a08c4efde87..7506ab1a707 100644 --- a/security/manager/ssl/src/nsNSSComponent.cpp +++ b/security/manager/ssl/src/nsNSSComponent.cpp @@ -47,7 +47,6 @@ #include "nsNSSComponent.h" #include "nsNSSCallbacks.h" #include "nsNSSIOLayer.h" -#include "nsSSLThread.h" #include "nsCertVerificationThread.h" #include "nsNetUtil.h" @@ -364,7 +363,7 @@ nsNSSComponent::nsNSSComponent() mNSSInitialized(false), mCrlTimerLock("nsNSSComponent.mCrlTimerLock"), mThreadList(nsnull), - mSSLThread(NULL), mCertVerificationThread(NULL) + mCertVerificationThread(NULL) { #ifdef PR_LOGGING if (!gPIPNSSLog) @@ -391,12 +390,6 @@ nsNSSComponent::nsNSSComponent() void nsNSSComponent::deleteBackgroundThreads() { - if (mSSLThread) - { - mSSLThread->requestExit(); - delete mSSLThread; - mSSLThread = nsnull; - } if (mCertVerificationThread) { mCertVerificationThread->requestExit(); @@ -408,20 +401,11 @@ nsNSSComponent::deleteBackgroundThreads() void nsNSSComponent::createBackgroundThreads() { - NS_ASSERTION(mSSLThread == nsnull, "SSL thread already created."); NS_ASSERTION(mCertVerificationThread == nsnull, "Cert verification thread already created."); - mSSLThread = new nsSSLThread; - nsresult rv = mSSLThread->startThread(); - if (NS_FAILED(rv)) { - delete mSSLThread; - mSSLThread = nsnull; - return; - } - mCertVerificationThread = new nsCertVerificationThread; - rv = mCertVerificationThread->startThread(); + nsresult rv = mCertVerificationThread->startThread(); if (NS_FAILED(rv)) { delete mCertVerificationThread; mCertVerificationThread = nsnull; @@ -1999,7 +1983,7 @@ nsNSSComponent::Init() mClientAuthRememberService->Init(); createBackgroundThreads(); - if (!mSSLThread || !mCertVerificationThread) + if (!mCertVerificationThread) { PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("NSS init, could not create threads\n")); @@ -2549,8 +2533,6 @@ nsNSSComponent::DoProfileApproveChange(nsISupports* aSubject) void nsNSSComponent::DoProfileChangeNetTeardown() { - if (mSSLThread) - mSSLThread->requestExit(); if (mCertVerificationThread) mCertVerificationThread->requestExit(); mIsNetworkDown = true; diff --git a/security/manager/ssl/src/nsNSSComponent.h b/security/manager/ssl/src/nsNSSComponent.h index 35f0e6ea5dc..7e771cfd6a0 100644 --- a/security/manager/ssl/src/nsNSSComponent.h +++ b/security/manager/ssl/src/nsNSSComponent.h @@ -237,7 +237,6 @@ private: }; class nsNSSShutDownList; -class nsSSLThread; class nsCertVerificationThread; // Implementation of the PSM component interface. @@ -357,7 +356,6 @@ private: void deleteBackgroundThreads(); void createBackgroundThreads(); - nsSSLThread *mSSLThread; nsCertVerificationThread *mCertVerificationThread; nsNSSHttpInterface mHttpForNSS; diff --git a/security/manager/ssl/src/nsNSSIOLayer.cpp b/security/manager/ssl/src/nsNSSIOLayer.cpp index 0fef3712321..5454fee9b36 100644 --- a/security/manager/ssl/src/nsNSSIOLayer.cpp +++ b/security/manager/ssl/src/nsNSSIOLayer.cpp @@ -70,7 +70,7 @@ #include "nsCRT.h" #include "nsAutoPtr.h" #include "nsPrintfCString.h" -#include "nsSSLThread.h" +#include "SSLServerCertVerification.h" #include "nsNSSShutDown.h" #include "nsSSLStatus.h" #include "nsNSSCertHelper.h" @@ -110,8 +110,8 @@ using namespace mozilla::psm; //we always write out to our own //file. + NSSCleanupAutoPtrClass(CERTCertificate, CERT_DestroyCertificate) -NSSCleanupAutoPtrClass(char, PL_strfree) NSSCleanupAutoPtrClass(void, PR_FREEIF) NSSCleanupAutoPtrClass_WithParam(PRArenaPool, PORT_FreeArena, FalseParam, false) @@ -150,56 +150,10 @@ void MyLogFunction(const char *fmt, ...) #define PR_LOG(module,level,args) MyLogFunction args #endif - -nsSSLSocketThreadData::nsSSLSocketThreadData() -: mSSLState(ssl_idle) -, mPRErrorCode(PR_SUCCESS) -, mSSLDataBuffer(nsnull) -, mSSLDataBufferAllocatedSize(0) -, mSSLRequestedTransferAmount(0) -, mSSLRemainingReadResultData(nsnull) -, mSSLResultRemainingBytes(0) -, mReplacedSSLFileDesc(nsnull) -, mOneBytePendingFromEarlierWrite(false) -, mThePendingByte(0) -, mOriginalRequestedTransferAmount(0) -{ -} - -nsSSLSocketThreadData::~nsSSLSocketThreadData() -{ - NS_ASSERTION(mSSLState != ssl_pending_write - && - mSSLState != ssl_pending_read, - "oops??? ssl socket is not idle at the time it is being destroyed"); - if (mSSLDataBuffer) { - nsMemory::Free(mSSLDataBuffer); - } -} - -bool nsSSLSocketThreadData::ensure_buffer_size(PRInt32 amount) -{ - if (amount > mSSLDataBufferAllocatedSize) { - if (mSSLDataBuffer) { - mSSLDataBuffer = (char*)nsMemory::Realloc(mSSLDataBuffer, amount); - } - else { - mSSLDataBuffer = (char*)nsMemory::Alloc(amount); - } - - if (!mSSLDataBuffer) - return false; - - mSSLDataBufferAllocatedSize = amount; - } - - return true; -} - nsNSSSocketInfo::nsNSSSocketInfo() : mMutex("nsNSSSocketInfo::nsNSSSocketInfo"), mFd(nsnull), - mBlockingState(blocking_state_unknown), + mCertVerificationState(before_cert_verification), mSecurityState(nsIWebProgressListener::STATE_IS_INSECURE), mSubRequestsHighSecurity(0), mSubRequestsLowSecurity(0), @@ -217,13 +171,10 @@ nsNSSSocketInfo::nsNSSSocketInfo() mPort(0), mIsCertIssuerBlacklisted(false) { - mThreadData = new nsSSLSocketThreadData; } nsNSSSocketInfo::~nsNSSSocketInfo() { - delete mThreadData; - nsNSSShutDownPreventionLock locker; if (isAlreadyShutDown()) return; @@ -297,7 +248,7 @@ nsNSSSocketInfo::GetErrorCode() const void nsNSSSocketInfo::SetCanceled(PRErrorCode errorCode, - ErrorMessageType errorMessageType) + SSLErrorMessageType errorMessageType) { MutexAutoLock lock(mMutex); @@ -346,7 +297,6 @@ nsNSSSocketInfo::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) } mCallbacks = aCallbacks; - mDocShellDependentStuffKnown = false; return NS_OK; } @@ -545,9 +495,6 @@ NS_IMETHODIMP nsNSSSocketInfo::GetInterface(const nsIID & uuid, void * *result) rv = ir->GetInterface(uuid, result); } else { - if (nsSSLThread::stoppedOrStopping()) - return NS_ERROR_FAILURE; - rv = mCallbacks->GetInterface(uuid, result); } return rv; @@ -822,10 +769,10 @@ nsresult nsNSSSocketInfo::ActivateSSL() if (isAlreadyShutDown()) return NS_ERROR_NOT_AVAILABLE; - nsresult rv = nsSSLThread::requestActivateSSL(this); - - if (NS_FAILED(rv)) - return rv; + if (SECSuccess != SSL_OptionSet(mFd, SSL_SECURITY, true)) + return NS_ERROR_FAILURE; + if (SECSuccess != SSL_ResetHandshake(mFd, false)) + return NS_ERROR_FAILURE; mHandshakePending = true; @@ -887,6 +834,44 @@ void nsNSSSocketInfo::GetPreviousCert(nsIX509Cert** _result) runnable->mPreviousCert.forget(_result); } +void +nsNSSSocketInfo::SetCertVerificationWaiting() +{ + // mCertVerificationState may be before_cert_verification for the first + // handshake on the connection, or after_cert_verification for subsequent + // renegotiation handshakes. + NS_ASSERTION(mCertVerificationState != waiting_for_cert_verification, + "Invalid state transition to waiting_for_cert_verification"); + mCertVerificationState = waiting_for_cert_verification; +} + +void +nsNSSSocketInfo::SetCertVerificationResult(PRErrorCode errorCode, + SSLErrorMessageType errorMessageType) +{ + NS_ASSERTION(mCertVerificationState == waiting_for_cert_verification, + "Invalid state transition to cert_verification_finished"); + + if (errorCode != 0) { + SetCanceled(errorCode, errorMessageType); + } else if (mFd) { + // We haven't closed the connection already, so restart it + SECStatus rv = SSL_RestartHandshakeAfterAuthCertificate(mFd); + if (rv != SECSuccess) { + errorCode = PR_GetError(); + if (errorCode == 0) { + NS_ERROR("SSL_RestartHandshakeAfterAuthCertificate didn't set error code"); + errorCode = PR_INVALID_STATE_ERROR; + } + SetCanceled(errorCode, PlainErrorMessage); + } + } else { + // If we closed the connection alreay, we don't have anything to do + } + + mCertVerificationState = after_cert_verification; +} + nsresult nsNSSSocketInfo::GetSSLStatus(nsISSLStatus** _result) { NS_ENSURE_ARG_POINTER(_result); @@ -947,9 +932,6 @@ void nsSSLIOLayerHelpers::Cleanup() mRenegoUnrestrictedSites = nsnull; } - if (mSharedPollableEvent) - PR_DestroyPollableEvent(mSharedPollableEvent); - if (mutex) { delete mutex; mutex = nsnull; @@ -1432,19 +1414,16 @@ nsHandleSSLError(nsNSSSocketInfo *socketInfo, PRErrorCode err) return; } - // SetCanceled is only called by the main thread or the SSL thread. Whenever - // this function is called, the SSL thread is waiting on this thread (the - // main thread). So, no mutex is necessary for SetCanceled()/GetError*(). + // SetCanceled is only called by the main thread or the socket transport + // thread. Whenever this function is called on the main thread, the SSL + // thread is blocked on it. So, no mutex is necessary for + // SetCanceled()/GetError*(). if (socketInfo->GetErrorCode()) { // If the socket has been flagged as canceled, // the code who did was responsible for setting the error code. return; } - if (nsSSLThread::stoppedOrStopping()) { - return; - } - nsresult rv; NS_DEFINE_CID(nssComponentCID, NS_NSSCOMPONENT_CID); nsCOMPtr nssComponent(do_GetService(nssComponentCID, &rv)); @@ -1473,61 +1452,59 @@ nsHandleSSLError(nsNSSSocketInfo *socketInfo, PRErrorCode err) } } - socketInfo->SetCanceled(err, nsNSSSocketInfo::PlainErrorMessage); + socketInfo->SetCanceled(err, PlainErrorMessage); } +namespace { + +nsNSSSocketInfo * +getSocketInfoIfRunning(PRFileDesc * fd, + const nsNSSShutDownPreventionLock & /*proofOfLock*/) +{ + if (!fd || !fd->lower || !fd->secret || + fd->identity != nsSSLIOLayerHelpers::nsSSLIOLayerIdentity) { + NS_ERROR("bad file descriptor passed to getSocketInfoIfRunning"); + PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0); + return nsnull; + } + + nsNSSSocketInfo *socketInfo = (nsNSSSocketInfo*)fd->secret; + + if (socketInfo->isAlreadyShutDown() || socketInfo->isPK11LoggedOut()) { + PR_SetError(PR_SOCKET_SHUTDOWN_ERROR, 0); + return nsnull; + } + + if (socketInfo->GetErrorCode()) { + PRErrorCode err = socketInfo->GetErrorCode(); + // If we get here, it is probably because cert verification failed and this + // is the first I/O attempt since that failure. + PR_SetError(err, 0); + return nsnull; + } + + return socketInfo; +} + +} // unnnamed namespace + static PRStatus PR_CALLBACK nsSSLIOLayerConnect(PRFileDesc* fd, const PRNetAddr* addr, PRIntervalTime timeout) { PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] connecting SSL socket\n", (void*)fd)); nsNSSShutDownPreventionLock locker; - if (!fd || !fd->lower) + if (!getSocketInfoIfRunning(fd, locker)) return PR_FAILURE; - PRStatus status = PR_SUCCESS; - -#if defined(XP_BEOS) - // Due to BeOS net_server's lack of support for nonblocking sockets, - // we must execute this entire connect as a blocking operation - bug 70217 - - PRSocketOptionData sockopt; - sockopt.option = PR_SockOpt_Nonblocking; - PR_GetSocketOption(fd, &sockopt); - bool oldBlockVal = sockopt.value.non_blocking; - sockopt.option = PR_SockOpt_Nonblocking; - sockopt.value.non_blocking = false; - PR_SetSocketOption(fd, &sockopt); -#endif - - status = fd->lower->methods->connect(fd->lower, addr, -#if defined(XP_BEOS) // bug 70217 - PR_INTERVAL_NO_TIMEOUT); -#else - timeout); -#endif + PRStatus status = fd->lower->methods->connect(fd->lower, addr, timeout); if (status != PR_SUCCESS) { PR_LOG(gPIPNSSLog, PR_LOG_ERROR, ("[%p] Lower layer connect error: %d\n", (void*)fd, PR_GetError())); -#if defined(XP_BEOS) // bug 70217 - goto loser; -#else return status; -#endif } - + PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] Connect\n", (void*)fd)); - -#if defined(XP_BEOS) // bug 70217 - loser: - // We put the Nonblocking bit back to the value it was when - // we entered this function. - NS_ASSERTION(sockopt.option == PR_SockOpt_Nonblocking, - "sockopt.option was re-set to an unexpected value"); - sockopt.value.non_blocking = oldBlockVal; - PR_SetSocketOption(fd, &sockopt); -#endif - return status; } @@ -1693,18 +1670,24 @@ nsSSLIOLayerClose(PRFileDesc *fd) nsNSSSocketInfo *socketInfo = (nsNSSSocketInfo*)fd->secret; NS_ASSERTION(socketInfo,"nsNSSSocketInfo was null for an fd"); - return nsSSLThread::requestClose(socketInfo); + return socketInfo->CloseSocketAndDestroy(locker); } -PRStatus nsNSSSocketInfo::CloseSocketAndDestroy() +PRStatus nsNSSSocketInfo::CloseSocketAndDestroy( + const nsNSSShutDownPreventionLock & /*proofOfLock*/) { - nsNSSShutDownPreventionLock locker; - nsNSSShutDownList::trackSSLSocketClose(); PRFileDesc* popped = PR_PopIOLayer(mFd, PR_TOP_IO_LAYER); PRStatus status = mFd->methods->close(mFd); + + // the nsNSSSocketInfo instance can out-live the connection, so we need some + // indication that the connection has been closed. mFd == nsnull is that + // indication. This is needed, for example, when the connection is closed + // before we have finished validating the server's certificate. + mFd = nsnull; + if (status != PR_SUCCESS) return status; popped->identity = PR_INVALID_IO_LAYER; @@ -1842,11 +1825,9 @@ class SSLErrorRunnable : public SyncRunnableBase const PRErrorCode mErrorCode; }; -PRInt32 -nsSSLThread::checkHandshake(PRInt32 bytesTransfered, - bool wasReading, - PRFileDesc* ssl_layer_fd, - nsNSSSocketInfo *socketInfo) +PRInt32 checkHandshake(PRInt32 bytesTransfered, bool wasReading, + PRFileDesc* ssl_layer_fd, + nsNSSSocketInfo *socketInfo) { // This is where we work around all of those SSL servers that don't // conform to the SSL spec and shutdown a connection when we request @@ -1900,9 +1881,17 @@ nsSSLThread::checkHandshake(PRInt32 bytesTransfered, } } - // This is the common place where we trigger an error message on a SSL socket. - // This might be reached at any time of the connection. - if (!wantRetry && (IS_SSL_ERROR(err) || IS_SEC_ERROR(err))) { + // This is the common place where we trigger non-cert-errors on a SSL + // socket. This might be reached at any time of the connection. + // + // The socketInfo->GetErrorCode() check is here to ensure we don't try to + // do the synchronous dispatch to the main thread unnecessarily after we've + // already handled a certificate error. (SSLErrorRunnable calls + // nsHandleSSLError, which has logic to avoid replacing the error message, + // so without the !socketInfo->GetErrorCode(), it would just be an + // expensive no-op.) + if (!wantRetry && (IS_SSL_ERROR(err) || IS_SEC_ERROR(err)) && + !socketInfo->GetErrorCode()) { nsRefPtr runnable = new SSLErrorRunnable(socketInfo, err); (void) runnable->DispatchToMainThreadAndWait(); @@ -1940,9 +1929,8 @@ nsSSLThread::checkHandshake(PRInt32 bytesTransfered, } static PRInt16 PR_CALLBACK -nsSSLIOLayerPoll(PRFileDesc *fd, PRInt16 in_flags, PRInt16 *out_flags) +nsSSLIOLayerPoll(PRFileDesc * fd, PRInt16 in_flags, PRInt16 *out_flags) { - PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] polling SSL socket\n", (void*)fd)); nsNSSShutDownPreventionLock locker; if (!out_flags) @@ -1953,16 +1941,45 @@ nsSSLIOLayerPoll(PRFileDesc *fd, PRInt16 in_flags, PRInt16 *out_flags) *out_flags = 0; - if (!fd) - { - NS_WARNING("nsSSLIOLayerPoll called with null fd"); - return 0; + nsNSSSocketInfo * socketInfo = getSocketInfoIfRunning(fd, locker); + if (!socketInfo) { + // If we get here, it is probably because certificate validation failed + // and this is the first I/O operation after the failure. + PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, + ("[%p] polling SSL socket right after certificate verification failed " + "or NSS shutdown or SDR logout %d\n", + fd, (int) in_flags)); + + NS_ASSERTION(in_flags & PR_POLL_EXCEPT, + "caller did not poll for EXCEPT (canceled)"); + // Since this poll method cannot return errors, we want the caller to call + // PR_Send/PR_Recv right away to get the error, so we tell that we are + // ready for whatever I/O they are asking for. (See getSocketInfoIfRunning). + *out_flags = in_flags | PR_POLL_EXCEPT; // see also bug 480619 + return in_flags; } - nsNSSSocketInfo *socketInfo = (nsNSSSocketInfo*)fd->secret; - NS_ASSERTION(socketInfo,"nsNSSSocketInfo was null for an fd"); + PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, + (socketInfo->IsWaitingForCertVerification() + ? "[%p] polling SSL socket during certificate verification using lower %d\n" + : "[%p] poll SSL socket using lower %d\n", + fd, (int) in_flags)); - return nsSSLThread::requestPoll(socketInfo, in_flags, out_flags); + if (socketInfo->HandshakeTimeout()) { + NS_ASSERTION(in_flags & PR_POLL_EXCEPT, + "caller did not poll for EXCEPT (handshake timeout)"); + *out_flags = in_flags | PR_POLL_EXCEPT; + return in_flags; + } + + // We want the handshake to continue during certificate validation, so we + // don't need to do anything special here. libssl automatically blocks when + // it reaches any point that would be unsafe to send/receive something before + // cert validation is complete. + PRInt16 result = fd->lower->methods->poll(fd->lower, in_flags, out_flags); + PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] poll SSL socket returned %d\n", + (void*)fd, (int) result)); + return result; } bool nsSSLIOLayerHelpers::nsSSLIOLayerInitialized = false; @@ -1975,9 +1992,6 @@ nsPSMRememberCertErrorsTable *nsSSLIOLayerHelpers::mHostsWithCertErrors = nsnull nsCStringHashSet *nsSSLIOLayerHelpers::mRenegoUnrestrictedSites = nsnull; bool nsSSLIOLayerHelpers::mTreatUnsafeNegotiationAsBroken = false; PRInt32 nsSSLIOLayerHelpers::mWarnLevelMissingRFC5746 = 1; -PRFileDesc *nsSSLIOLayerHelpers::mSharedPollableEvent = nsnull; -nsNSSSocketInfo *nsSSLIOLayerHelpers::mSocketOwningPollableEvent = nsnull; -bool nsSSLIOLayerHelpers::mPollableEventCurrentlySet = false; static PRIntn _PSM_InvalidInt(void) { @@ -2010,99 +2024,90 @@ static PRFileDesc *_PSM_InvalidDesc(void) static PRStatus PR_CALLBACK PSMGetsockname(PRFileDesc *fd, PRNetAddr *addr) { nsNSSShutDownPreventionLock locker; - if (!fd || !fd->lower) { + if (!getSocketInfoIfRunning(fd, locker)) return PR_FAILURE; - } - nsNSSSocketInfo *socketInfo = (nsNSSSocketInfo*)fd->secret; - NS_ASSERTION(socketInfo,"nsNSSSocketInfo was null for an fd"); - - return nsSSLThread::requestGetsockname(socketInfo, addr); + return fd->lower->methods->getsockname(fd->lower, addr); } static PRStatus PR_CALLBACK PSMGetpeername(PRFileDesc *fd, PRNetAddr *addr) { nsNSSShutDownPreventionLock locker; - if (!fd || !fd->lower) { + if (!getSocketInfoIfRunning(fd, locker)) return PR_FAILURE; - } - nsNSSSocketInfo *socketInfo = (nsNSSSocketInfo*)fd->secret; - NS_ASSERTION(socketInfo,"nsNSSSocketInfo was null for an fd"); - - return nsSSLThread::requestGetpeername(socketInfo, addr); + return fd->lower->methods->getpeername(fd->lower, addr); } static PRStatus PR_CALLBACK PSMGetsocketoption(PRFileDesc *fd, PRSocketOptionData *data) { nsNSSShutDownPreventionLock locker; - if (!fd || !fd->lower) { + if (!getSocketInfoIfRunning(fd, locker)) return PR_FAILURE; - } - nsNSSSocketInfo *socketInfo = (nsNSSSocketInfo*)fd->secret; - NS_ASSERTION(socketInfo,"nsNSSSocketInfo was null for an fd"); - - return nsSSLThread::requestGetsocketoption(socketInfo, data); + return fd->lower->methods->getsocketoption(fd, data); } static PRStatus PR_CALLBACK PSMSetsocketoption(PRFileDesc *fd, const PRSocketOptionData *data) { nsNSSShutDownPreventionLock locker; - if (!fd || !fd->lower) { + if (!getSocketInfoIfRunning(fd, locker)) return PR_FAILURE; - } - nsNSSSocketInfo *socketInfo = (nsNSSSocketInfo*)fd->secret; - NS_ASSERTION(socketInfo,"nsNSSSocketInfo was null for an fd"); - - return nsSSLThread::requestSetsocketoption(socketInfo, data); + return fd->lower->methods->setsocketoption(fd, data); } static PRInt32 PR_CALLBACK PSMRecv(PRFileDesc *fd, void *buf, PRInt32 amount, PRIntn flags, PRIntervalTime timeout) { nsNSSShutDownPreventionLock locker; - if (!fd || !fd->lower) { - PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0); + nsNSSSocketInfo *socketInfo = getSocketInfoIfRunning(fd, locker); + if (!socketInfo) return -1; - } - nsNSSSocketInfo *socketInfo = (nsNSSSocketInfo*)fd->secret; - NS_ASSERTION(socketInfo,"nsNSSSocketInfo was null for an fd"); - - if (flags == PR_MSG_PEEK) { - return nsSSLThread::requestRecvMsgPeek(socketInfo, buf, amount, flags, timeout); - } - - if (flags != 0) { + if (flags != PR_MSG_PEEK && flags != 0) { PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0); return -1; } - return nsSSLThread::requestRead(socketInfo, buf, amount, timeout); + PRInt32 bytesRead = fd->lower->methods->recv(fd->lower, buf, amount, flags, + timeout); + + PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] read %d bytes\n", (void*)fd, bytesRead)); + +#ifdef DEBUG_SSL_VERBOSE + DEBUG_DUMP_BUFFER((unsigned char*)buf, bytesRead); +#endif + + return checkHandshake(bytesRead, true, fd, socketInfo); } static PRInt32 PR_CALLBACK PSMSend(PRFileDesc *fd, const void *buf, PRInt32 amount, PRIntn flags, PRIntervalTime timeout) { nsNSSShutDownPreventionLock locker; - if (!fd || !fd->lower) { - PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0); + nsNSSSocketInfo *socketInfo = getSocketInfoIfRunning(fd, locker); + if (!socketInfo) return -1; - } if (flags != 0) { PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0); return -1; } - nsNSSSocketInfo *socketInfo = (nsNSSSocketInfo*)fd->secret; - NS_ASSERTION(socketInfo,"nsNSSSocketInfo was null for an fd"); +#ifdef DEBUG_SSL_VERBOSE + DEBUG_DUMP_BUFFER((unsigned char*)buf, amount); +#endif - return nsSSLThread::requestWrite(socketInfo, buf, amount, timeout); + PRInt32 bytesWritten = fd->lower->methods->send(fd->lower, buf, amount, + flags, timeout); + + PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] wrote %d bytes\n", + fd, bytesWritten)); + + return checkHandshake(bytesWritten, false, fd, socketInfo); } static PRInt32 PR_CALLBACK @@ -2120,14 +2125,11 @@ nsSSLIOLayerWrite(PRFileDesc* fd, const void* buf, PRInt32 amount) static PRStatus PR_CALLBACK PSMConnectcontinue(PRFileDesc *fd, PRInt16 out_flags) { nsNSSShutDownPreventionLock locker; - if (!fd || !fd->lower) { + if (!getSocketInfoIfRunning(fd, locker)) { return PR_FAILURE; } - nsNSSSocketInfo *socketInfo = (nsNSSSocketInfo*)fd->secret; - NS_ASSERTION(socketInfo,"nsNSSSocketInfo was null for an fd"); - - return nsSSLThread::requestConnectcontinue(socketInfo, out_flags); + return fd->lower->methods->connectcontinue(fd, out_flags); } nsresult nsSSLIOLayerHelpers::Init() @@ -2172,10 +2174,6 @@ nsresult nsSSLIOLayerHelpers::Init() mutex = new Mutex("nsSSLIOLayerHelpers.mutex"); - mSharedPollableEvent = PR_NewPollableEvent(); - - // if we can not get a pollable event, we'll have to do busy waiting - mTLSIntolerantSites = new nsCStringHashSet(); if (!mTLSIntolerantSites) return NS_ERROR_OUT_OF_MEMORY; @@ -2845,7 +2843,7 @@ SECStatus nsNSS_SSLGetClientAuthData(void* arg, PRFileDesc* socket, } if (runnable->mRV != SECSuccess) { - PORT_SetError(runnable->mErrorCodeToReport); + PR_SetError(runnable->mErrorCodeToReport, 0); } return runnable->mRV; @@ -3275,273 +3273,263 @@ class CertErrorRunnable : public SyncRunnableBase CertErrorRunnable(const void * fdForLogging, nsIX509Cert * cert, nsNSSSocketInfo * infoObject, - const CERTVerifyLog * verify_log, - bool hasCertNameMismatch, - PRErrorCode defaultErrorCodeToReport) + PRErrorCode defaultErrorCodeToReport, + PRUint32 collectedErrors, + PRErrorCode errorCodeTrust, + PRErrorCode errorCodeMismatch, + PRErrorCode errorCodeExpired) : mFdForLogging(fdForLogging), mCert(cert), mInfoObject(infoObject), - mVerifyLog(verify_log), mHasCertNameMismatch(hasCertNameMismatch), - mRv(SECFailure), mErrorCodeToReport(defaultErrorCodeToReport) + mDefaultErrorCodeToReport(defaultErrorCodeToReport), + mCollectedErrors(collectedErrors), + mErrorCodeTrust(errorCodeTrust), + mErrorCodeMismatch(errorCodeMismatch), + mErrorCodeExpired(errorCodeExpired) { } + NS_DECL_NSIRUNNABLE virtual void RunOnTargetThread(); + nsCOMPtr mResult; // out +private: + SSLServerCertVerificationResult* CheckCertOverrides(); - // in - const void * const mFdForLogging; - nsCOMPtr mCert; - nsNSSSocketInfo * const mInfoObject; - const CERTVerifyLog * const mVerifyLog; - const bool mHasCertNameMismatch; - nsXPIDLCString mHostname; - - SECStatus mRv; // out - PRErrorCode mErrorCodeToReport; // in/out + const void * const mFdForLogging; // may become an invalid pointer; do not dereference + const nsCOMPtr mCert; + const nsRefPtr mInfoObject; + const PRErrorCode mDefaultErrorCodeToReport; + const PRUint32 mCollectedErrors; + const PRErrorCode mErrorCodeTrust; + const PRErrorCode mErrorCodeMismatch; + const PRErrorCode mErrorCodeExpired; }; -static SECStatus -cancel_and_failure(PRErrorCode errorCode, nsNSSSocketInfo* infoObject) -{ - infoObject->SetCanceled(errorCode, nsNSSSocketInfo::PlainErrorMessage); - PR_SetError(errorCode, 0); - return SECFailure; -} +namespace mozilla { namespace psm { -static SECStatus -nsNSSBadCertHandler(void *arg, PRFileDesc *sslSocket) +// Returns SECSuccess if it dispatched the CertErrorRunnable. In that case, +// the caller should NOT dispatch its own SSLServerCertVerificationResult; +// the CertErrorRunnable will do it instead. +// +// Returns SECFailure with the error code set if it does not dispatch the +// CertErrorRunnable. In that case, the caller should dispatch its own +// SSLServerCertVerificationResult with the error code from PR_GetError(). +SECStatus +HandleBadCertificate(PRErrorCode defaultErrorCodeToReport, + nsNSSSocketInfo * socketInfo, CERTCertificate & cert, + const void * fdForLogging, + const nsNSSShutDownPreventionLock & /*proofOfLock*/) { // cert was revoked, don't do anything else - PRErrorCode defaultErrorCodeToReport = PR_GetError(); - if (defaultErrorCodeToReport == SEC_ERROR_REVOKED_CERTIFICATE) + if (defaultErrorCodeToReport == SEC_ERROR_REVOKED_CERTIFICATE) { + PR_SetError(SEC_ERROR_REVOKED_CERTIFICATE, 0); return SECFailure; + } if (defaultErrorCodeToReport == 0) { NS_ERROR("No error code set during certificate validation failure."); - defaultErrorCodeToReport = SEC_ERROR_CERT_NOT_VALID; + PR_SetError(PR_INVALID_STATE_ERROR, 0); + return SECFailure; } - nsNSSShutDownPreventionLock locker; - nsNSSSocketInfo* infoObject = (nsNSSSocketInfo *)arg; - if (!infoObject) - return SECFailure; - - if (nsSSLThread::stoppedOrStopping()) - return cancel_and_failure(PR_INVALID_STATE_ERROR, infoObject); - - CERTCertificate *peerCert = nsnull; - CERTCertificateCleaner peerCertCleaner(peerCert); - peerCert = SSL_PeerCertificate(sslSocket); - if (!peerCert) - return cancel_and_failure(SEC_ERROR_NO_MEMORY, infoObject); - nsRefPtr nssCert; - nssCert = nsNSSCertificate::Create(peerCert); - if (!nssCert) - return cancel_and_failure(SEC_ERROR_NO_MEMORY, infoObject); + nssCert = nsNSSCertificate::Create(&cert); + if (!nssCert) { + NS_ERROR("nsNSSCertificate::Create failed in DispatchCertErrorRunnable"); + PR_SetError(SEC_ERROR_NO_MEMORY, 0); + return SECFailure; + } SECStatus srv; nsresult nsrv; nsCOMPtr inss = do_GetService(kNSSComponentCID, &nsrv); - if (!inss) - return cancel_and_failure(SEC_ERROR_NO_MEMORY, infoObject); - nsRefPtr survivingParams; - nsrv = inss->GetDefaultCERTValInParam(survivingParams); - if (NS_FAILED(nsrv)) - return cancel_and_failure(PR_INVALID_STATE_ERROR, infoObject); - - char *hostname = SSL_RevealURL(sslSocket); - if (!hostname) - return cancel_and_failure(SEC_ERROR_NO_MEMORY, infoObject); - - charCleaner hostnameCleaner(hostname); - - // Check the name field against the desired hostname. - bool hasCertNameMismatch = - hostname[0] && CERT_VerifyCertName(peerCert, hostname) != SECSuccess; - - { - PRArenaPool *log_arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); - if (!log_arena) - return cancel_and_failure(SEC_ERROR_NO_MEMORY, infoObject); - - PRArenaPoolCleanerFalseParam log_arena_cleaner(log_arena); - - CERTVerifyLog *verify_log = PORT_ArenaZNew(log_arena, CERTVerifyLog); - if (!verify_log) - return cancel_and_failure(SEC_ERROR_NO_MEMORY, infoObject); - - CERTVerifyLogContentsCleaner verify_log_cleaner(verify_log); - - verify_log->arena = log_arena; - - if (!nsNSSComponent::globalConstFlagUsePKIXVerification) { - srv = CERT_VerifyCertificate(CERT_GetDefaultCertDB(), peerCert, - true, certificateUsageSSLServer, - PR_Now(), (void*)infoObject, - verify_log, NULL); - } - else { - CERTValOutParam cvout[2]; - cvout[0].type = cert_po_errorLog; - cvout[0].value.pointer.log = verify_log; - cvout[1].type = cert_po_end; - - srv = CERT_PKIXVerifyCert(peerCert, certificateUsageSSLServer, - survivingParams->GetRawPointerForNSS(), - cvout, (void*)infoObject); - } - - // We ignore the result code of the cert verification. - // Either it is a failure, which is expected, and we'll process the - // verify log below. - // Or it is a success, then a domain mismatch is the only - // possible failure. - - nsRefPtr runnable = - new CertErrorRunnable(static_cast(sslSocket), - static_cast(nssCert.get()), - infoObject, verify_log, hasCertNameMismatch, - defaultErrorCodeToReport); - - // now grab the host name to pass to the STS Service - nsrv = infoObject->GetHostName(getter_Copies(runnable->mHostname)); - if (NS_FAILED(nsrv)) { - PR_SetError(defaultErrorCodeToReport, 0); - return SECFailure; - } - - PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, - ("[%p][%p] Before dispatching CertErrorRunnable\n", - sslSocket, runnable.get())); - - // Dispatch SYNC since the result is used below - (void) runnable->DispatchToMainThreadAndWait(); - - PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, - ("[%p][%p] After dispatching CertErrorRunnable\n", - sslSocket, runnable.get())); - - if (runnable->mRv == SECSuccess) - return SECSuccess; - - NS_ASSERTION(runnable->mErrorCodeToReport != 0, - "CertErrorRunnable did not set error code."); - PR_SetError(runnable->mErrorCodeToReport ? runnable->mErrorCodeToReport - : defaultErrorCodeToReport, 0); + if (!inss) { + NS_ERROR("do_GetService(kNSSComponentCID) failed in DispatchCertErrorRunnable"); + PR_SetError(defaultErrorCodeToReport, 0); return SECFailure; } -} -void CertErrorRunnable::RunOnTargetThread() -{ - PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p][%p] top of CertErrorRunnable::Run\n", - mFdForLogging, this)); - - if (!NS_IsMainThread()) { - NS_ERROR("CertErrorRunnable::RunOnTargetThread called off main thread"); - return; + nsRefPtr survivingParams; + nsrv = inss->GetDefaultCERTValInParam(survivingParams); + if (NS_FAILED(nsrv)) { + NS_ERROR("GetDefaultCERTValInParam failed in DispatchCertErrorRunnable"); + PR_SetError(defaultErrorCodeToReport, 0); + return SECFailure; + } + + PRArenaPool *log_arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); + PRArenaPoolCleanerFalseParam log_arena_cleaner(log_arena); + if (!log_arena) { + NS_ERROR("PORT_NewArena failed in DispatchCertErrorRunnable"); + return SECFailure; // PORT_NewArena set error code } - if (nsSSLThread::stoppedOrStopping()) - return; - + CERTVerifyLog *verify_log = PORT_ArenaZNew(log_arena, CERTVerifyLog); + if (!verify_log) { + NS_ERROR("PORT_ArenaZNew failed in DispatchCertErrorRunnable"); + return SECFailure; // PORT_ArenaZNew set error code + } + CERTVerifyLogContentsCleaner verify_log_cleaner(verify_log); + verify_log->arena = log_arena; + + if (!nsNSSComponent::globalConstFlagUsePKIXVerification) { + srv = CERT_VerifyCertificate(CERT_GetDefaultCertDB(), &cert, + true, certificateUsageSSLServer, + PR_Now(), static_cast(socketInfo), + verify_log, NULL); + } + else { + CERTValOutParam cvout[2]; + cvout[0].type = cert_po_errorLog; + cvout[0].value.pointer.log = verify_log; + cvout[1].type = cert_po_end; + + srv = CERT_PKIXVerifyCert(&cert, certificateUsageSSLServer, + survivingParams->GetRawPointerForNSS(), + cvout, static_cast(socketInfo)); + } + + // We ignore the result code of the cert verification. + // Either it is a failure, which is expected, and we'll process the + // verify log below. + // Or it is a success, then a domain mismatch is the only + // possible failure. + PRErrorCode errorCodeMismatch = 0; PRErrorCode errorCodeTrust = 0; PRErrorCode errorCodeExpired = 0; PRUint32 collected_errors = 0; - if (mInfoObject->IsCertIssuerBlacklisted()) { + if (socketInfo->IsCertIssuerBlacklisted()) { collected_errors |= nsICertOverrideService::ERROR_UNTRUSTED; - errorCodeTrust = mErrorCodeToReport; + errorCodeTrust = defaultErrorCodeToReport; } - if (mHasCertNameMismatch) { + // Check the name field against the desired hostname. + if (CERT_VerifyCertName(&cert, socketInfo->GetHostName()) != SECSuccess) { collected_errors |= nsICertOverrideService::ERROR_MISMATCH; errorCodeMismatch = SSL_ERROR_BAD_CERT_DOMAIN; } + CERTVerifyLogNode *i_node; + for (i_node = verify_log->head; i_node; i_node = i_node->next) { - CERTVerifyLogNode *i_node; - for (i_node = mVerifyLog->head; i_node; i_node = i_node->next) + switch (i_node->error) { - switch (i_node->error) - { - case SEC_ERROR_UNKNOWN_ISSUER: - case SEC_ERROR_CA_CERT_INVALID: - case SEC_ERROR_UNTRUSTED_ISSUER: - case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: - case SEC_ERROR_UNTRUSTED_CERT: - case SEC_ERROR_INADEQUATE_KEY_USAGE: - // We group all these errors as "cert not trusted" - collected_errors |= nsICertOverrideService::ERROR_UNTRUSTED; - if (errorCodeTrust == SECSuccess) { - errorCodeTrust = i_node->error; - } - break; - case SSL_ERROR_BAD_CERT_DOMAIN: - collected_errors |= nsICertOverrideService::ERROR_MISMATCH; - if (errorCodeMismatch == SECSuccess) { - errorCodeMismatch = i_node->error; - } - break; - case SEC_ERROR_EXPIRED_CERTIFICATE: - collected_errors |= nsICertOverrideService::ERROR_TIME; - if (errorCodeExpired == SECSuccess) { - errorCodeExpired = i_node->error; - } - break; - default: - // we are not willing to continue on any other error - nsHandleSSLError(mInfoObject, i_node->error); - // this error is our stop condition, so let's make sure - // this error code will be reported to the external world. - mErrorCodeToReport = i_node->error; - return; - } + case SEC_ERROR_UNKNOWN_ISSUER: + case SEC_ERROR_CA_CERT_INVALID: + case SEC_ERROR_UNTRUSTED_ISSUER: + case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: + case SEC_ERROR_UNTRUSTED_CERT: + case SEC_ERROR_INADEQUATE_KEY_USAGE: + // We group all these errors as "cert not trusted" + collected_errors |= nsICertOverrideService::ERROR_UNTRUSTED; + if (errorCodeTrust == SECSuccess) { + errorCodeTrust = i_node->error; + } + break; + case SSL_ERROR_BAD_CERT_DOMAIN: + collected_errors |= nsICertOverrideService::ERROR_MISMATCH; + if (errorCodeMismatch == SECSuccess) { + errorCodeMismatch = i_node->error; + } + break; + case SEC_ERROR_EXPIRED_CERTIFICATE: + collected_errors |= nsICertOverrideService::ERROR_TIME; + if (errorCodeExpired == SECSuccess) { + errorCodeExpired = i_node->error; + } + break; + default: + PR_SetError(i_node->error, 0); + return SECFailure; } } if (!collected_errors) { - NS_NOTREACHED("why did NSS call our bad cert handler if all looks good? Let's cancel the connection"); - PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p][%p] !collected_errors\n", - mFdForLogging, this)); - return; + // This will happen when CERT_*Verify* only returned error(s) that are + // not on our whitelist of overridable certificate errors. + PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] !collected_errors: %d\n", + fdForLogging, static_cast(defaultErrorCodeToReport))); + PR_SetError(defaultErrorCodeToReport, 0); + return SECFailure; } - nsRefPtr status = mInfoObject->SSLStatus(); - if (!status) { - status = new nsSSLStatus(); - mInfoObject->SetSSLStatus(status); + socketInfo->SetStatusErrorBits(*nssCert, collected_errors); + + nsRefPtr runnable = + new CertErrorRunnable(fdForLogging, + static_cast(nssCert.get()), + socketInfo, defaultErrorCodeToReport, + collected_errors, errorCodeTrust, + errorCodeMismatch, errorCodeExpired); + + PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, + ("[%p][%p] Before dispatching CertErrorRunnable\n", + fdForLogging, runnable.get())); + + nsresult nrv; + nsCOMPtr stsTarget + = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &nrv); + if (NS_SUCCEEDED(nrv)) { + nrv = stsTarget->Dispatch(runnable, NS_DISPATCH_NORMAL); + } + if (NS_FAILED(nrv)) { + NS_ERROR("Failed to dispatch CertErrorRunnable"); + PR_SetError(defaultErrorCodeToReport, 0); + return SECFailure; } - if (status) { - if (!status->mServerCert) { - status->mServerCert = mCert; - } + return SECSuccess; +} - status->mHaveCertErrorBits = true; - status->mIsDomainMismatch = collected_errors & nsICertOverrideService::ERROR_MISMATCH; - status->mIsNotValidAtThisTime = collected_errors & nsICertOverrideService::ERROR_TIME; - status->mIsUntrusted = collected_errors & nsICertOverrideService::ERROR_UNTRUSTED; +} } // namespace mozilla::psm - nsSSLIOLayerHelpers::mHostsWithCertErrors->RememberCertHasError( - mInfoObject, status, SECFailure); +void +nsNSSSocketInfo::SetStatusErrorBits(nsIX509Cert & cert, + PRUint32 collected_errors) +{ + MutexAutoLock lock(mMutex); + + if (!mSSLStatus) + mSSLStatus = new nsSSLStatus(); + + mSSLStatus->mServerCert = &cert; + + mSSLStatus->mHaveCertErrorBits = true; + mSSLStatus->mIsDomainMismatch = + collected_errors & nsICertOverrideService::ERROR_MISMATCH; + mSSLStatus->mIsNotValidAtThisTime = + collected_errors & nsICertOverrideService::ERROR_TIME; + mSSLStatus->mIsUntrusted = + collected_errors & nsICertOverrideService::ERROR_UNTRUSTED; + + nsSSLIOLayerHelpers::mHostsWithCertErrors->RememberCertHasError( + this, mSSLStatus, SECFailure); +} + +SSLServerCertVerificationResult * +CertErrorRunnable::CheckCertOverrides() +{ + PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p][%p] top of CertErrorRunnable::Run\n", + mFdForLogging, this)); + + if (!NS_IsMainThread()) { + NS_ERROR("CertErrorRunnable::CheckCertOverrides called off main thread"); + return new SSLServerCertVerificationResult(*mInfoObject, + mDefaultErrorCodeToReport); } - nsDependentCString hostString(mHostname); - PRInt32 port; mInfoObject->GetPort(&port); - nsCString hostWithPortString = hostString; + nsCString hostWithPortString; + hostWithPortString.AppendASCII(mInfoObject->GetHostName()); hostWithPortString.AppendLiteral(":"); hostWithPortString.AppendInt(port); - NS_ConvertUTF8toUTF16 hostWithPortStringUTF16(hostWithPortString); - - PRUint32 remaining_display_errors = collected_errors; + PRUint32 remaining_display_errors = mCollectedErrors; nsresult nsrv; @@ -3552,10 +3540,13 @@ void CertErrorRunnable::RunOnTargetThread() nsCOMPtr stss = do_GetService(NS_STSSERVICE_CONTRACTID, &nsrv); if (NS_SUCCEEDED(nsrv)) { - nsrv = stss->IsStsHost(mHostname, &strictTransportSecurityEnabled); + nsrv = stss->IsStsHost(mInfoObject->GetHostName(), + &strictTransportSecurityEnabled); + } + if (NS_FAILED(nsrv)) { + return new SSLServerCertVerificationResult(*mInfoObject, + mDefaultErrorCodeToReport); } - if (NS_FAILED(nsrv)) - return; // use default rv and errorCodeToReport if (!strictTransportSecurityEnabled) { nsCOMPtr overrideService = @@ -3568,7 +3559,7 @@ void CertErrorRunnable::RunOnTargetThread() { bool haveOverride; bool isTemporaryOverride; // we don't care - + nsCString hostString(mInfoObject->GetHostName()); nsrv = overrideService->HasMatchingOverride(hostString, port, mCert, &overrideBits, @@ -3576,7 +3567,7 @@ void CertErrorRunnable::RunOnTargetThread() &haveOverride); if (NS_SUCCEEDED(nsrv) && haveOverride) { - // remove the errors that are already overriden + // remove the errors that are already overriden remaining_display_errors -= overrideBits; } } @@ -3586,8 +3577,7 @@ void CertErrorRunnable::RunOnTargetThread() PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p][%p] All errors covered by override rules\n", mFdForLogging, this)); - mRv = SECSuccess; - return; + return new SSLServerCertVerificationResult(*mInfoObject, 0); } } else { PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, @@ -3610,31 +3600,71 @@ void CertErrorRunnable::RunOnTargetThread() if (bcl) { nsIInterfaceRequestor *csi = static_cast(mInfoObject); bool suppressMessage = false; // obsolete, ignored - nsrv = bcl->NotifyCertProblem(csi, status, hostWithPortString, - &suppressMessage); + nsrv = bcl->NotifyCertProblem(csi, mInfoObject->SSLStatus(), + hostWithPortString, &suppressMessage); } } nsCOMPtr recentBadCertsService = do_GetService(NS_RECENTBADCERTS_CONTRACTID); - + if (recentBadCertsService) { - recentBadCertsService->AddBadCert(hostWithPortStringUTF16, status); + NS_ConvertUTF8toUTF16 hostWithPortStringUTF16(hostWithPortString); + recentBadCertsService->AddBadCert(hostWithPortStringUTF16, + mInfoObject->SSLStatus()); } // pick the error code to report by priority - mErrorCodeToReport = 0; - if (remaining_display_errors & nsICertOverrideService::ERROR_UNTRUSTED) - mErrorCodeToReport = errorCodeTrust; - else if (remaining_display_errors & nsICertOverrideService::ERROR_MISMATCH) - mErrorCodeToReport = errorCodeMismatch; - else if (remaining_display_errors & nsICertOverrideService::ERROR_TIME) - mErrorCodeToReport = errorCodeExpired; + PRErrorCode errorCodeToReport = mErrorCodeTrust ? mErrorCodeTrust + : mErrorCodeMismatch ? mErrorCodeMismatch + : mErrorCodeExpired ? mErrorCodeExpired + : mDefaultErrorCodeToReport; - mInfoObject->SetCanceled(mErrorCodeToReport, - nsNSSSocketInfo::OverridableCertErrorMessage); + return new SSLServerCertVerificationResult(*mInfoObject, errorCodeToReport, + OverridableCertErrorMessage); } +NS_IMETHODIMP +CertErrorRunnable::Run() +{ + // This code is confusing: First, Run() is called on the socket transport + // thread. Then we re-dispatch it to the main thread synchronously (step 1). + // On the main thread, we call CheckCertOverrides (step 2). Then we return + // from the main thread and are back on the socket transport thread. There, + // we run the result runnable directly (step 3). + if (!NS_IsMainThread()) { + // We are running on the socket transport thread. We need to re-dispatch + // ourselves synchronously to the main thread. + DispatchToMainThreadAndWait(); // step 1 + + // step 3 + if (!mResult) { + // Either the dispatch failed or CheckCertOverrides wrongly returned null + NS_ERROR("Did not create a SSLServerCertVerificationResult"); + mResult = new SSLServerCertVerificationResult(*mInfoObject, + PR_INVALID_STATE_ERROR); + } + return mResult->Run(); + } else { + // block this thread (the socket transport thread) until RunOnTargetThread + // is complete. + return SyncRunnableBase::Run(); // step 2 + } +} + +void +CertErrorRunnable::RunOnTargetThread() +{ + // Now we are running on the main thread, blocking the socket tranposrt + // thread. This is exactly the state we need to be in to call + // CheckCertOverrides; CheckCertOverrides must block events on both of + // these threads because it calls nsNSSSocketInfo::GetInterface(), + // which calls nsHttpConnection::GetInterface() through + // nsNSSSocketInfo::mCallbacks. nsHttpConnection::GetInterface must always + // execute on the main thread, with the socket transport thread blocked. + mResult = CheckCertOverrides(); +} + static PRFileDesc* nsSSLIOLayerImportFD(PRFileDesc *fd, nsNSSSocketInfo *infoObject, @@ -3658,11 +3688,14 @@ nsSSLIOLayerImportFD(PRFileDesc *fd, (SSLGetClientAuthData)nsNSS_SSLGetClientAuthData, infoObject); } - SSL_AuthCertificateHook(sslSock, AuthCertificateCallback, 0); + if (SECSuccess != SSL_AuthCertificateHook(sslSock, AuthCertificateHook, + infoObject)) { + NS_NOTREACHED("failed to configure AuthCertificateHook"); + goto loser; + } - PRInt32 ret = SSL_SetURL(sslSock, host); - if (ret == -1) { - NS_ASSERTION(false, "NSS: Error setting server name"); + if (SECSuccess != SSL_SetURL(sslSock, host)) { + NS_NOTREACHED("SSL_SetURL failed"); goto loser; } return sslSock; @@ -3708,10 +3741,6 @@ nsSSLIOLayerSetOptions(PRFileDesc *fd, bool forSTARTTLS, if (SECSuccess != SSL_OptionSet(fd, SSL_HANDSHAKE_AS_CLIENT, true)) { return NS_ERROR_FAILURE; } - if (SECSuccess != SSL_BadCertHook(fd, (SSLBadCertHandler) nsNSSBadCertHandler, - infoObject)) { - return NS_ERROR_FAILURE; - } if (nsSSLIOLayerHelpers::isRenegoUnrestrictedSite(nsDependentCString(host))) { if (SECSuccess != SSL_OptionSet(fd, SSL_REQUIRE_SAFE_NEGOTIATION, false)) { diff --git a/security/manager/ssl/src/nsNSSIOLayer.h b/security/manager/ssl/src/nsNSSIOLayer.h index 6daddc5dd6a..33fdcbbfc39 100644 --- a/security/manager/ssl/src/nsNSSIOLayer.h +++ b/security/manager/ssl/src/nsNSSIOLayer.h @@ -60,73 +60,21 @@ #include "nsNSSCertificate.h" #include "nsDataHashtable.h" -class nsIChannel; -class nsSSLThread; -class ::mozilla::MutexAutoLock; +namespace mozilla { -/* - * This class is used to store SSL socket I/O state information, - * that is not being executed directly, but defered to - * the separate SSL thread. - */ -class nsSSLSocketThreadData -{ -public: - nsSSLSocketThreadData(); - ~nsSSLSocketThreadData(); +class MutexAutoLock; - bool ensure_buffer_size(PRInt32 amount); - - enum ssl_state { - ssl_invalid, // used for initializating, should never occur - ssl_idle, // not in use by SSL thread, no activity pending - ssl_pending_write, // waiting for SSL thread to complete writing - ssl_pending_read, // waiting for SSL thread to complete reading - ssl_writing_done, // SSL write completed, results are ready - ssl_reading_done // SSL read completed, results are ready - }; - - ssl_state mSSLState; +namespace psm { - // Used to transport I/O error codes between SSL thread - // and initial caller thread. - PRErrorCode mPRErrorCode; - - // A buffer used to transfer I/O data between threads - char *mSSLDataBuffer; - PRInt32 mSSLDataBufferAllocatedSize; - - // The amount requested to read or write by the caller. - PRInt32 mSSLRequestedTransferAmount; - - // A pointer into our buffer, to the first byte - // that has not yet been delivered to the caller. - // Necessary, as the caller of the read function - // might request smaller chunks. - const char *mSSLRemainingReadResultData; - - // The caller previously requested to read or write. - // As the initial request to read or write is defered, - // the caller might (in theory) request smaller chunks - // in subsequent calls. - // This variable stores the amount of bytes successfully - // transfered, that have not yet been reported to the caller. - PRInt32 mSSLResultRemainingBytes; - - // When defering SSL read/write activity to another thread, - // we switch the SSL level file descriptor of the original - // layered file descriptor to a pollable event, - // so we can wake up the original caller of the I/O function - // as soon as data is ready. - // This variable is used to save the SSL level file descriptor, - // to allow us to restore the original file descriptor layering. - PRFileDesc *mReplacedSSLFileDesc; - - bool mOneBytePendingFromEarlierWrite; - unsigned char mThePendingByte; - PRInt32 mOriginalRequestedTransferAmount; +enum SSLErrorMessageType { + OverridableCertErrorMessage = 1, // for *overridable* certificate errors + PlainErrorMessage = 2 // all other errors (or "no error") }; +} // namespace psm + +} // namespace mozilla + class nsNSSSocketInfo : public nsITransportSecurityInfo, public nsISSLSocketControl, public nsIInterfaceRequestor, @@ -164,6 +112,9 @@ public: nsresult GetHandshakePending(bool *aHandshakePending); nsresult SetHandshakePending(bool aHandshakePending); + const char * GetHostName() const { + return mHostName.get(); + } nsresult GetHostName(char **aHostName); nsresult SetHostName(const char *aHostName); @@ -172,12 +123,9 @@ public: void GetPreviousCert(nsIX509Cert** _result); - enum ErrorMessageType { - OverridableCertErrorMessage = 1, // for *overridable* certificate errors - PlainErrorMessage = 2, // all other errors - }; - void SetCanceled(PRErrorCode errorCode, ErrorMessageType errorMessageType); PRErrorCode GetErrorCode() const; + void SetCanceled(PRErrorCode errorCode, + ::mozilla::psm::SSLErrorMessageType errorMessageType); void SetHasCleartextPhase(bool aHasCleartextPhase); bool GetHasCleartextPhase(); @@ -193,8 +141,10 @@ public: /* Set SSL Status values */ nsresult SetSSLStatus(nsSSLStatus *aSSLStatus); nsSSLStatus* SSLStatus() { return mSSLStatus; } - - PRStatus CloseSocketAndDestroy(); + void SetStatusErrorBits(nsIX509Cert & cert, PRUint32 collected_errors); + + PRStatus CloseSocketAndDestroy( + const nsNSSShutDownPreventionLock & proofOfLock); bool IsCertIssuerBlacklisted() const { return mIsCertIssuerBlacklisted; @@ -202,14 +152,32 @@ public: void SetCertIssuerBlacklisted() { mIsCertIssuerBlacklisted = true; } + + // XXX: These are only used on for diagnostic purposes + enum CertVerificationState { + before_cert_verification, + waiting_for_cert_verification, + after_cert_verification + }; + void SetCertVerificationWaiting(); + // Use errorCode == 0 to indicate success; in that case, errorMessageType is + // ignored. + void SetCertVerificationResult(PRErrorCode errorCode, + ::mozilla::psm::SSLErrorMessageType errorMessageType); + + // for logging only + PRBool IsWaitingForCertVerification() const + { + return mCertVerificationState == waiting_for_cert_verification; + } + + protected: mutable ::mozilla::Mutex mMutex; nsCOMPtr mCallbacks; PRFileDesc* mFd; - enum { - blocking_state_unknown, is_nonblocking_socket, is_blocking_socket - } mBlockingState; + CertVerificationState mCertVerificationState; PRUint32 mSecurityState; PRInt32 mSubRequestsHighSecurity; PRInt32 mSubRequestsLowSecurity; @@ -218,7 +186,7 @@ protected: nsString mShortDesc; PRErrorCode mErrorCode; - ErrorMessageType mErrorMessageType; + ::mozilla::psm::SSLErrorMessageType mErrorMessageType; nsString mErrorMessageCached; nsresult formatErrorMessage(::mozilla::MutexAutoLock const & proofOfLock); @@ -240,13 +208,9 @@ protected: nsresult ActivateSSL(); - nsSSLSocketThreadData *mThreadData; - private: virtual void virtualDestroyNSSReference(); void destructorSafeDestroyNSSReference(); - -friend class nsSSLThread; }; class nsCStringHashSet; @@ -311,11 +275,6 @@ public: static void setRenegoUnrestrictedSites(const nsCString &str); static bool isRenegoUnrestrictedSite(const nsCString &str); - - static PRFileDesc *mSharedPollableEvent; - static nsNSSSocketInfo *mSocketOwningPollableEvent; - - static bool mPollableEventCurrentlySet; }; nsresult nsSSLIOLayerNewSocket(PRInt32 family, diff --git a/security/manager/ssl/src/nsSSLThread.cpp b/security/manager/ssl/src/nsSSLThread.cpp deleted file mode 100644 index 9818446d556..00000000000 --- a/security/manager/ssl/src/nsSSLThread.cpp +++ /dev/null @@ -1,1150 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is mozilla.org code. - * - * The Initial Developer of the Original Code is - * Red Hat, Inc. - * Portions created by the Initial Developer are Copyright (C) 2006 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Kai Engert - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#include "nsThreadUtils.h" -#include "nsSSLThread.h" -#include "nsNSSIOLayer.h" - -#include "ssl.h" - -using namespace mozilla; - -#ifdef PR_LOGGING -extern PRLogModuleInfo* gPIPNSSLog; -#endif - -nsSSLThread::nsSSLThread() -: mBusySocket(nsnull), - mSocketScheduledToBeDestroyed(nsnull) -{ - NS_ASSERTION(!ssl_thread_singleton, "nsSSLThread is a singleton, caller attempts to create another instance!"); - - ssl_thread_singleton = this; -} - -nsSSLThread::~nsSSLThread() -{ - ssl_thread_singleton = nsnull; -} - -PRFileDesc *nsSSLThread::getRealSSLFD(nsNSSSocketInfo *si) -{ - if (!ssl_thread_singleton || !si || !ssl_thread_singleton->mThreadHandle) - return nsnull; - - MutexAutoLock threadLock(ssl_thread_singleton->mMutex); - - if (si->mThreadData->mReplacedSSLFileDesc) - { - return si->mThreadData->mReplacedSSLFileDesc; - } - else - { - return si->mFd->lower; - } -} - -PRStatus nsSSLThread::requestGetsockname(nsNSSSocketInfo *si, PRNetAddr *addr) -{ - PRFileDesc *fd = getRealSSLFD(si); - if (!fd) - return PR_FAILURE; - - return fd->methods->getsockname(fd, addr); -} - -PRStatus nsSSLThread::requestGetpeername(nsNSSSocketInfo *si, PRNetAddr *addr) -{ - PRFileDesc *fd = getRealSSLFD(si); - if (!fd) - return PR_FAILURE; - - return fd->methods->getpeername(fd, addr); -} - -PRStatus nsSSLThread::requestGetsocketoption(nsNSSSocketInfo *si, - PRSocketOptionData *data) -{ - PRFileDesc *fd = getRealSSLFD(si); - if (!fd) - return PR_FAILURE; - - return fd->methods->getsocketoption(fd, data); -} - -PRStatus nsSSLThread::requestSetsocketoption(nsNSSSocketInfo *si, - const PRSocketOptionData *data) -{ - PRFileDesc *fd = getRealSSLFD(si); - if (!fd) - return PR_FAILURE; - - return fd->methods->setsocketoption(fd, data); -} - -PRStatus nsSSLThread::requestConnectcontinue(nsNSSSocketInfo *si, - PRInt16 out_flags) -{ - PRFileDesc *fd = getRealSSLFD(si); - if (!fd) - return PR_FAILURE; - - return fd->methods->connectcontinue(fd, out_flags); -} - -PRInt32 nsSSLThread::requestRecvMsgPeek(nsNSSSocketInfo *si, void *buf, PRInt32 amount, - PRIntn flags, PRIntervalTime timeout) -{ - if (!ssl_thread_singleton || !si || !ssl_thread_singleton->mThreadHandle) - { - PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0); - return -1; - } - - // Socket is unusable - set error and return -1. See bug #480619. - if (si->isPK11LoggedOut() || si->isAlreadyShutDown()) - { - PR_SetError(PR_SOCKET_SHUTDOWN_ERROR, 0); - return -1; - } - - PRFileDesc *realSSLFD; - - { - MutexAutoLock threadLock(ssl_thread_singleton->mMutex); - - if (si == ssl_thread_singleton->mBusySocket) - { - PORT_SetError(PR_WOULD_BLOCK_ERROR); - return -1; - } - - switch (si->mThreadData->mSSLState) - { - case nsSSLSocketThreadData::ssl_idle: - break; - - case nsSSLSocketThreadData::ssl_reading_done: - { - // we have data available that we can return - - // if there was a failure, just return the failure, - // but do not yet clear our state, that should happen - // in the call to "read". - - if (si->mThreadData->mSSLResultRemainingBytes < 0) { - if (si->mThreadData->mPRErrorCode != PR_SUCCESS) { - PR_SetError(si->mThreadData->mPRErrorCode, 0); - } - - return si->mThreadData->mSSLResultRemainingBytes; - } - - PRInt32 return_amount = NS_MIN(amount, si->mThreadData->mSSLResultRemainingBytes); - - memcpy(buf, si->mThreadData->mSSLRemainingReadResultData, return_amount); - - return return_amount; - } - - case nsSSLSocketThreadData::ssl_writing_done: - case nsSSLSocketThreadData::ssl_pending_write: - case nsSSLSocketThreadData::ssl_pending_read: - - // for safety reasons, also return would_block on any other state, - // although this switch statement should be complete and list - // the appropriate behaviour for each state. - default: - { - PORT_SetError(PR_WOULD_BLOCK_ERROR); - return -1; - } - } - - if (si->mThreadData->mReplacedSSLFileDesc) - { - realSSLFD = si->mThreadData->mReplacedSSLFileDesc; - } - else - { - realSSLFD = si->mFd->lower; - } - } - - return realSSLFD->methods->recv(realSSLFD, buf, amount, flags, timeout); -} - -nsresult nsSSLThread::requestActivateSSL(nsNSSSocketInfo *si) -{ - PRFileDesc *fd = getRealSSLFD(si); - if (!fd) - return NS_ERROR_FAILURE; - - if (SECSuccess != SSL_OptionSet(fd, SSL_SECURITY, true)) - return NS_ERROR_FAILURE; - - if (SECSuccess != SSL_ResetHandshake(fd, false)) - return NS_ERROR_FAILURE; - - return NS_OK; -} - -PRInt16 nsSSLThread::requestPoll(nsNSSSocketInfo *si, PRInt16 in_flags, PRInt16 *out_flags) -{ - if (!ssl_thread_singleton || !si || !ssl_thread_singleton->mThreadHandle) - return 0; - - *out_flags = 0; - - // Socket is unusable - set EXCEPT-flag and return. See bug #480619. - if (si->isPK11LoggedOut() || si->isAlreadyShutDown()) - { - *out_flags |= PR_POLL_EXCEPT; - return in_flags; - } - - bool want_sleep_and_wakeup_on_any_socket_activity = false; - bool handshake_timeout = false; - - { - MutexAutoLock threadLock(ssl_thread_singleton->mMutex); - - if (ssl_thread_singleton->mBusySocket) - { - // If there is currently any socket busy on the SSL thread, - // use our own poll method implementation. - - switch (si->mThreadData->mSSLState) - { - case nsSSLSocketThreadData::ssl_writing_done: - { - if (in_flags & PR_POLL_WRITE) - { - *out_flags |= PR_POLL_WRITE; - } - - return in_flags; - } - break; - - case nsSSLSocketThreadData::ssl_reading_done: - { - if (in_flags & PR_POLL_READ) - { - *out_flags |= PR_POLL_READ; - } - - return in_flags; - } - break; - - case nsSSLSocketThreadData::ssl_pending_write: - case nsSSLSocketThreadData::ssl_pending_read: - { - if (si == ssl_thread_singleton->mBusySocket) - { - if (nsSSLIOLayerHelpers::mSharedPollableEvent) - { - // The lower layer of the socket is currently the pollable event, - // which signals the readable state. - - return PR_POLL_READ; - } - else - { - // Unfortunately we do not have a pollable event - // that we could use to wake up the caller, as soon - // as the previously requested I/O operation has completed. - // Therefore we must use a kind of busy wait, - // we want the caller to check again, whenever any - // activity is detected on the associated socket. - // Unfortunately this could mean, the caller will detect - // activity very often, until we are finally done with - // the previously requested action and are able to - // return the buffered result. - // As our real I/O activity is happening on the other thread - // let's sleep some cycles, in order to not waste all CPU - // resources. - // But let's make sure we do not hold our shared mutex - // while waiting, so let's leave this block first. - - want_sleep_and_wakeup_on_any_socket_activity = true; - break; - } - } - else - { - // We should never get here, well, at least not with the current - // implementation of SSL thread, where we have one worker only. - // While another socket is busy, this socket "si" - // can not be marked with pending I/O at the same time. - - NS_NOTREACHED("Socket not busy on SSL thread marked as pending"); - return 0; - } - } - break; - - case nsSSLSocketThreadData::ssl_idle: - { - if (si->mThreadData->mOneBytePendingFromEarlierWrite) - { - if (in_flags & PR_POLL_WRITE) - { - // In this scenario we always want the caller to immediately - // try a write again, because it might not wake up otherwise. - *out_flags |= PR_POLL_WRITE; - return in_flags; - } - } - - handshake_timeout = si->HandshakeTimeout(); - - if (si != ssl_thread_singleton->mBusySocket) - { - // Some other socket is currently busy on the SSL thread. - // It is possible that busy socket is currently blocked (e.g. by UI). - // Therefore we should not report "si" as being readable/writeable, - // regardless whether it is. - // (Because if we did report readable/writeable to the caller, - // the caller would repeatedly request us to do I/O, - // although our read/write function would not be able to fulfil - // the request, because our single worker is blocked). - // To avoid the unnecessary busy loop in that scenario, - // for socket "si" we report "not ready" to the caller. - // We do this by faking our caller did not ask for neither - // readable nor writeable when querying the lower layer. - // (this will leave querying for exceptions enabled) - - in_flags &= ~(PR_POLL_READ | PR_POLL_WRITE); - } - } - break; - - default: - break; - } - } - else - { - handshake_timeout = si->HandshakeTimeout(); - } - - if (handshake_timeout) - { - NS_ASSERTION(in_flags & PR_POLL_EXCEPT, "nsSSLThread::requestPoll handshake timeout, but caller did not poll for EXCEPT"); - - *out_flags |= PR_POLL_EXCEPT; - return in_flags; - } - } - - if (want_sleep_and_wakeup_on_any_socket_activity) - { - // This is where we wait for any socket activity, - // because we do not have a pollable event. - // XXX Will this really cause us to wake up - // whatever happens? - - PR_Sleep( PR_MillisecondsToInterval(1) ); - return PR_POLL_READ | PR_POLL_WRITE | PR_POLL_EXCEPT; - } - - return si->mFd->lower->methods->poll(si->mFd->lower, in_flags, out_flags); -} - -PRStatus nsSSLThread::requestClose(nsNSSSocketInfo *si) -{ - if (!ssl_thread_singleton || !si) - return PR_FAILURE; - - bool close_later = false; - nsCOMPtr requestToCancel; - - { - MutexAutoLock threadLock(ssl_thread_singleton->mMutex); - - if (ssl_thread_singleton->mBusySocket == si) { - - // That's tricky, SSL thread is currently busy with this socket, - // and might even be blocked on it (UI or OCSP). - // We should not close the socket directly, but rather - // schedule closing it, at the time the SSL thread is done. - // If there is indeed a depending OCSP request pending, - // we should cancel it now. - - if (ssl_thread_singleton->mPendingHTTPRequest) - { - requestToCancel.swap(ssl_thread_singleton->mPendingHTTPRequest); - } - - close_later = true; - ssl_thread_singleton->mSocketScheduledToBeDestroyed = si; - - ssl_thread_singleton->mCond.NotifyAll(); - } - } - - if (requestToCancel) - { - if (NS_IsMainThread()) - { - requestToCancel->Cancel(NS_ERROR_ABORT); - } - else - { - NS_WARNING("Attempt to close SSL socket from a thread that is not the main thread. Can not cancel pending HTTP request from NSS"); - } - - requestToCancel = nsnull; - } - - if (!close_later) - { - return si->CloseSocketAndDestroy(); - } - - return PR_SUCCESS; -} - -void nsSSLThread::restoreOriginalSocket_locked(nsNSSSocketInfo *si) -{ - if (si->mThreadData->mReplacedSSLFileDesc) - { - if (nsSSLIOLayerHelpers::mPollableEventCurrentlySet) - { - nsSSLIOLayerHelpers::mPollableEventCurrentlySet = false; - if (nsSSLIOLayerHelpers::mSharedPollableEvent) - { - PR_WaitForPollableEvent(nsSSLIOLayerHelpers::mSharedPollableEvent); - } - } - - if (nsSSLIOLayerHelpers::mSharedPollableEvent) - { - // need to restore - si->mFd->lower = si->mThreadData->mReplacedSSLFileDesc; - si->mThreadData->mReplacedSSLFileDesc = nsnull; - } - - nsSSLIOLayerHelpers::mSocketOwningPollableEvent = nsnull; - } -} - -PRStatus nsSSLThread::getRealFDIfBlockingSocket_locked(nsNSSSocketInfo *si, - PRFileDesc *&out_fd) -{ - out_fd = nsnull; - - PRFileDesc *realFD = - (si->mThreadData->mReplacedSSLFileDesc) ? - si->mThreadData->mReplacedSSLFileDesc : si->mFd->lower; - - if (si->mBlockingState == nsNSSSocketInfo::blocking_state_unknown) - { - PRSocketOptionData sod; - sod.option = PR_SockOpt_Nonblocking; - if (PR_GetSocketOption(realFD, &sod) == PR_FAILURE) - return PR_FAILURE; - - si->mBlockingState = sod.value.non_blocking ? - nsNSSSocketInfo::is_nonblocking_socket : nsNSSSocketInfo::is_blocking_socket; - } - - if (si->mBlockingState == nsNSSSocketInfo::is_blocking_socket) - { - out_fd = realFD; - } - - return PR_SUCCESS; -} - -PRInt32 nsSSLThread::requestRead(nsNSSSocketInfo *si, void *buf, PRInt32 amount, - PRIntervalTime timeout) -{ - if (!ssl_thread_singleton || !si || !buf || !amount || !ssl_thread_singleton->mThreadHandle) - { - PR_SetError(PR_UNKNOWN_ERROR, 0); - return -1; - } - - bool this_socket_is_busy = false; - bool some_other_socket_is_busy = false; - nsSSLSocketThreadData::ssl_state my_ssl_state = nsSSLSocketThreadData::ssl_invalid; - PRFileDesc *blockingFD = nsnull; - - { - MutexAutoLock threadLock(ssl_thread_singleton->mMutex); - - if (ssl_thread_singleton->exitRequested(threadLock)) { - PR_SetError(PR_UNKNOWN_ERROR, 0); - return -1; - } - - if (getRealFDIfBlockingSocket_locked(si, blockingFD) == PR_FAILURE) { - return -1; - } - - if (!blockingFD) - { - my_ssl_state = si->mThreadData->mSSLState; - - if (ssl_thread_singleton->mBusySocket == si) - { - this_socket_is_busy = true; - - if (my_ssl_state == nsSSLSocketThreadData::ssl_reading_done) - { - // we will now care for the data that's ready, - // the socket is no longer busy on the ssl thread - - restoreOriginalSocket_locked(si); - - ssl_thread_singleton->mBusySocket = nsnull; - - // We'll handle the results further down, - // while not holding the lock. - } - } - else if (ssl_thread_singleton->mBusySocket) - { - some_other_socket_is_busy = true; - } - - if (!this_socket_is_busy && si->HandshakeTimeout()) - { - restoreOriginalSocket_locked(si); - PR_SetError(PR_CONNECT_RESET_ERROR, 0); - checkHandshake(-1, true, si->mFd->lower, si); - return -1; - } - } - // leave this mutex protected scope before the blockingFD handling - } - - if (blockingFD) - { - // this is an exception, we do not use our SSL thread at all, - // just pass the call through to libssl. - return blockingFD->methods->recv(blockingFD, buf, amount, 0, timeout); - } - - switch (my_ssl_state) - { - case nsSSLSocketThreadData::ssl_idle: - { - NS_ASSERTION(!this_socket_is_busy, "oops, unexpected incosistency"); - - if (some_other_socket_is_busy) - { - PORT_SetError(PR_WOULD_BLOCK_ERROR); - return -1; - } - - // ssl thread is not busy, we'll continue below - } - break; - - case nsSSLSocketThreadData::ssl_reading_done: - // there has been a previous request to read, that is now done! - { - // failure ? - if (si->mThreadData->mSSLResultRemainingBytes < 0) { - if (si->mThreadData->mPRErrorCode != PR_SUCCESS) { - PR_SetError(si->mThreadData->mPRErrorCode, 0); - si->mThreadData->mPRErrorCode = PR_SUCCESS; - } - - si->mThreadData->mSSLState = nsSSLSocketThreadData::ssl_idle; - return si->mThreadData->mSSLResultRemainingBytes; - } - - PRInt32 return_amount = NS_MIN(amount, si->mThreadData->mSSLResultRemainingBytes); - - memcpy(buf, si->mThreadData->mSSLRemainingReadResultData, return_amount); - - si->mThreadData->mSSLResultRemainingBytes -= return_amount; - - if (!si->mThreadData->mSSLResultRemainingBytes) { - si->mThreadData->mSSLRemainingReadResultData = nsnull; - si->mThreadData->mSSLState = nsSSLSocketThreadData::ssl_idle; - } - else { - si->mThreadData->mSSLRemainingReadResultData += return_amount; - } - - return return_amount; - } - // we never arrive here, see return statement above - break; - - - // We should not see the following events here, - // because we have not yet signaled Necko that we are - // readable/writable again, so if we end up here, - // it means that Necko decided to try read/write again, - // for whatever reason. No problem, just return would_block, - case nsSSLSocketThreadData::ssl_pending_write: - case nsSSLSocketThreadData::ssl_pending_read: - - // We should not see this state here, because Necko has previously - // requested us to write, Necko is not yet aware that it's done, - // (although it meanwhile is), but Necko now tries to read? - // If that ever happens, it's confusing, but not a problem, - // just let Necko know we can not do that now and return would_block. - case nsSSLSocketThreadData::ssl_writing_done: - - // for safety reasons, also return would_block on any other state, - // although this switch statement should be complete and list - // the appropriate behaviour for each state. - default: - { - PORT_SetError(PR_WOULD_BLOCK_ERROR); - return -1; - } - // we never arrive here, see return statement above - break; - } - - if (si->isPK11LoggedOut() || si->isAlreadyShutDown()) { - PR_SetError(PR_SOCKET_SHUTDOWN_ERROR, 0); - return -1; - } - - if (si->GetErrorCode()) { - return PR_FAILURE; - } - - // si is idle and good, and no other socket is currently busy, - // so it's fine to continue with the request. - - if (!si->mThreadData->ensure_buffer_size(amount)) - { - PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0); - return -1; - } - - si->mThreadData->mSSLRequestedTransferAmount = amount; - si->mThreadData->mSSLState = nsSSLSocketThreadData::ssl_pending_read; - - // Remember we are operating on a layered file descriptor, that consists of - // a PSM code layer (nsNSSIOLayer), a NSS code layer (SSL protocol logic), - // and the raw socket at the bottommost layer. - // - // We don't want to call the SSL layer read/write directly on this thread, - // because it might block, should a callback to UI (for user confirmation) - // or Necko (for retrieving OCSP verification data) be necessary. - // As Necko is single threaded, it is currently waiting for this - // function to return, and a callback into Necko from NSS couldn't succeed. - // - // Therefore we must defer the request to read/write to a separate SSL thread. - // We will return WOULD_BLOCK to Necko, and will return the real results - // once the I/O operation on the SSL thread is ready. - // - // The tricky part is to wake up Necko, as soon as the I/O operation - // on the SSL thread is done. - // - // In order to achieve that, we manipulate the layering of the file - // descriptor. Usually the PSM layer points to the SSL layer as its lower - // layer. We change that to a pollable event file descriptor. - // - // Once we return from this original read/write function, Necko will - // poll/select on the file descriptor. As result data is not yet ready, we will - // instruct Necko to select on the bottommost file descriptor - // (by using appropriate flags in PSM's layer implementation of the - // poll method), which is the pollable event. - // - // Once the SSL thread is done with the call to the SSL layer, it will - // "set" the pollable event, causing Necko to wake up on the file descriptor - // and call read/write again. Now that the file descriptor is in the done state, - // we'll arrive in this read/write function again. We'll detect the socket is - // in the done state, and restore the original SSL level file descriptor. - // Finally, we return the data obtained on the SSL thread back to our caller. - - { - MutexAutoLock threadLock(ssl_thread_singleton->mMutex); - - if (nsSSLIOLayerHelpers::mSharedPollableEvent) - { - NS_ASSERTION(!nsSSLIOLayerHelpers::mSocketOwningPollableEvent, - "oops, some other socket still owns our shared pollable event"); - - NS_ASSERTION(!si->mThreadData->mReplacedSSLFileDesc, "oops"); - - si->mThreadData->mReplacedSSLFileDesc = si->mFd->lower; - si->mFd->lower = nsSSLIOLayerHelpers::mSharedPollableEvent; - } - - nsSSLIOLayerHelpers::mSocketOwningPollableEvent = si; - ssl_thread_singleton->mBusySocket = si; - - // notify the thread - ssl_thread_singleton->mCond.NotifyAll(); - } - - PORT_SetError(PR_WOULD_BLOCK_ERROR); - return -1; -} - -PRInt32 nsSSLThread::requestWrite(nsNSSSocketInfo *si, const void *buf, PRInt32 amount, - PRIntervalTime timeout) -{ - if (!ssl_thread_singleton || !si || !buf || !amount || !ssl_thread_singleton->mThreadHandle) - { - PR_SetError(PR_UNKNOWN_ERROR, 0); - return -1; - } - - bool this_socket_is_busy = false; - bool some_other_socket_is_busy = false; - nsSSLSocketThreadData::ssl_state my_ssl_state = nsSSLSocketThreadData::ssl_invalid; - PRFileDesc *blockingFD = nsnull; - - { - MutexAutoLock threadLock(ssl_thread_singleton->mMutex); - - if (ssl_thread_singleton->exitRequested(threadLock)) { - PR_SetError(PR_UNKNOWN_ERROR, 0); - return -1; - } - - if (getRealFDIfBlockingSocket_locked(si, blockingFD) == PR_FAILURE) { - return -1; - } - - if (!blockingFD) - { - my_ssl_state = si->mThreadData->mSSLState; - - if (ssl_thread_singleton->mBusySocket == si) - { - this_socket_is_busy = true; - - if (my_ssl_state == nsSSLSocketThreadData::ssl_writing_done) - { - // we will now care for the data that's ready, - // the socket is no longer busy on the ssl thread - - restoreOriginalSocket_locked(si); - - ssl_thread_singleton->mBusySocket = nsnull; - - // We'll handle the results further down, - // while not holding the lock. - } - } - else if (ssl_thread_singleton->mBusySocket) - { - some_other_socket_is_busy = true; - } - - if (!this_socket_is_busy && si->HandshakeTimeout()) - { - restoreOriginalSocket_locked(si); - PR_SetError(PR_CONNECT_RESET_ERROR, 0); - checkHandshake(-1, false, si->mFd->lower, si); - return -1; - } - } - // leave this mutex protected scope before the blockingFD handling - } - - if (blockingFD) - { - // this is an exception, we do not use our SSL thread at all, - // just pass the call through to libssl. - return blockingFD->methods->send(blockingFD, buf, amount, 0, timeout); - } - - switch (my_ssl_state) - { - case nsSSLSocketThreadData::ssl_idle: - { - NS_ASSERTION(!this_socket_is_busy, "oops, unexpected incosistency"); - - if (some_other_socket_is_busy) - { - PORT_SetError(PR_WOULD_BLOCK_ERROR); - return -1; - } - - // ssl thread is not busy, we'll continue below - } - break; - - case nsSSLSocketThreadData::ssl_writing_done: - // there has been a previous request to write, that is now done! - { - // failure ? - if (si->mThreadData->mSSLResultRemainingBytes < 0) { - if (si->mThreadData->mPRErrorCode != PR_SUCCESS) { - PR_SetError(si->mThreadData->mPRErrorCode, 0); - si->mThreadData->mPRErrorCode = PR_SUCCESS; - } - - si->mThreadData->mSSLState = nsSSLSocketThreadData::ssl_idle; - return si->mThreadData->mSSLResultRemainingBytes; - } - - nsSSLIOLayerHelpers::rememberTolerantSite(si->mFd, si); - - PRInt32 return_amount = NS_MIN(amount, si->mThreadData->mSSLResultRemainingBytes); - - si->mThreadData->mSSLResultRemainingBytes -= return_amount; - - if (!si->mThreadData->mSSLResultRemainingBytes) { - si->mThreadData->mSSLState = nsSSLSocketThreadData::ssl_idle; - } - - return return_amount; - } - break; - - // We should not see the following events here, - // because we have not yet signaled Necko that we are - // readable/writable again, so if we end up here, - // it means that Necko decided to try read/write again, - // for whatever reason. No problem, just return would_block, - case nsSSLSocketThreadData::ssl_pending_write: - case nsSSLSocketThreadData::ssl_pending_read: - - // We should not see this state here, because Necko has previously - // requested us to read, Necko is not yet aware that it's done, - // (although it meanwhile is), but Necko now tries to write? - // If that ever happens, it's confusing, but not a problem, - // just let Necko know we can not do that now and return would_block. - case nsSSLSocketThreadData::ssl_reading_done: - - // for safety reasons, also return would_block on any other state, - // although this switch statement should be complete and list - // the appropriate behaviour for each state. - default: - { - PORT_SetError(PR_WOULD_BLOCK_ERROR); - return -1; - } - // we never arrive here, see return statement above - break; - } - - if (si->isPK11LoggedOut() || si->isAlreadyShutDown()) { - PR_SetError(PR_SOCKET_SHUTDOWN_ERROR, 0); - return -1; - } - - if (si->GetErrorCode()) { - return PR_FAILURE; - } - - // si is idle and good, and no other socket is currently busy, - // so it's fine to continue with the request. - - // However, use special handling for the - // mOneBytePendingFromEarlierWrite - // scenario, where we will not change any of our buffers at this point, - // as we are waiting for completion of the earlier write. - - if (!si->mThreadData->mOneBytePendingFromEarlierWrite) - { - if (!si->mThreadData->ensure_buffer_size(amount)) - { - PR_SetError(PR_OUT_OF_MEMORY_ERROR, 0); - return -1; - } - - memcpy(si->mThreadData->mSSLDataBuffer, buf, amount); - si->mThreadData->mSSLRequestedTransferAmount = amount; - } - - si->mThreadData->mSSLState = nsSSLSocketThreadData::ssl_pending_write; - - { - MutexAutoLock threadLock(ssl_thread_singleton->mMutex); - - if (nsSSLIOLayerHelpers::mSharedPollableEvent) - { - NS_ASSERTION(!nsSSLIOLayerHelpers::mSocketOwningPollableEvent, - "oops, some other socket still owns our shared pollable event"); - - NS_ASSERTION(!si->mThreadData->mReplacedSSLFileDesc, "oops"); - - si->mThreadData->mReplacedSSLFileDesc = si->mFd->lower; - si->mFd->lower = nsSSLIOLayerHelpers::mSharedPollableEvent; - } - - nsSSLIOLayerHelpers::mSocketOwningPollableEvent = si; - ssl_thread_singleton->mBusySocket = si; - - ssl_thread_singleton->mCond.NotifyAll(); - } - - PORT_SetError(PR_WOULD_BLOCK_ERROR); - return -1; -} - -void nsSSLThread::Run(void) -{ - // Helper variable, we don't want to call destroy - // while holding the mutex. - nsNSSSocketInfo *socketToDestroy = nsnull; - - while (true) - { - if (socketToDestroy) - { - socketToDestroy->CloseSocketAndDestroy(); - socketToDestroy = nsnull; - } - - // remember whether we'll write or read - nsSSLSocketThreadData::ssl_state busy_socket_ssl_state; - - { - // In this scope we need mutex protection, - // as we find out what needs to be done. - - MutexAutoLock threadLock(ssl_thread_singleton->mMutex); - - if (mSocketScheduledToBeDestroyed) - { - if (mBusySocket == mSocketScheduledToBeDestroyed) - { - // That's rare, but it happens. - // We have received a request to close the socket, - // although I/O results have not yet been consumed. - - restoreOriginalSocket_locked(mBusySocket); - - mBusySocket->mThreadData->mSSLState = nsSSLSocketThreadData::ssl_idle; - mBusySocket = nsnull; - } - - socketToDestroy = mSocketScheduledToBeDestroyed; - mSocketScheduledToBeDestroyed = nsnull; - continue; // go back and finally destroy it, before doing anything else - } - - if (exitRequested(threadLock)) - break; - - bool pending_work = false; - - do - { - if (mBusySocket - && - (mBusySocket->mThreadData->mSSLState == nsSSLSocketThreadData::ssl_pending_read - || - mBusySocket->mThreadData->mSSLState == nsSSLSocketThreadData::ssl_pending_write)) - { - pending_work = true; - } - - if (!pending_work) - { - // no work to do ? let's wait a moment - - mCond.Wait(); - } - - } while (!pending_work && !exitRequested(threadLock) && - !mSocketScheduledToBeDestroyed); - - if (mSocketScheduledToBeDestroyed) - continue; - - if (exitRequested(threadLock)) - break; - - if (!pending_work) - continue; - - busy_socket_ssl_state = mBusySocket->mThreadData->mSSLState; - } - - { - // In this scope we need to make sure NSS does not go away - // while we are busy. - nsNSSShutDownPreventionLock locker; - - // Reference for shorter code and to avoid multiple dereferencing. - nsSSLSocketThreadData &bstd = *mBusySocket->mThreadData; - - PRFileDesc *realFileDesc = bstd.mReplacedSSLFileDesc; - if (!realFileDesc) - { - realFileDesc = mBusySocket->mFd->lower; - } - - if (nsSSLSocketThreadData::ssl_pending_write == busy_socket_ssl_state) - { - PRInt32 bytesWritten = 0; - - if (bstd.mOneBytePendingFromEarlierWrite) - { - // Let's try to flush the final pending byte (that libSSL might already have - // processed). Let's be correct and send the final byte from our buffer. - bytesWritten = realFileDesc->methods - ->write(realFileDesc, &bstd.mThePendingByte, 1); - -#ifdef DEBUG_SSL_VERBOSE - PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] wrote %d bytes\n", (void*)realFileDesc, bytesWritten)); -#endif - - bytesWritten = checkHandshake(bytesWritten, false, realFileDesc, mBusySocket); - if (bytesWritten < 0) { - // give the error back to caller - bstd.mPRErrorCode = PR_GetError(); - } - else if (bytesWritten == 1) { - // Cool, all flushed now. We can exit the one-byte-pending mode, - // and report the full amount back to the caller. - bytesWritten = bstd.mOriginalRequestedTransferAmount; - bstd.mOriginalRequestedTransferAmount = 0; - bstd.mOneBytePendingFromEarlierWrite = false; - } - } - else - { - // standard code, try to write the buffer we've been given just now - bytesWritten = realFileDesc->methods - ->write(realFileDesc, - bstd.mSSLDataBuffer, - bstd.mSSLRequestedTransferAmount); - -#ifdef DEBUG_SSL_VERBOSE - PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] wrote %d bytes (out of %d)\n", - (void*)realFileDesc, bytesWritten, bstd.mSSLRequestedTransferAmount)); -#endif - - bytesWritten = checkHandshake(bytesWritten, false, realFileDesc, mBusySocket); - if (bytesWritten < 0) { - // give the error back to caller - bstd.mPRErrorCode = PR_GetError(); - } - else if (bstd.mSSLRequestedTransferAmount > 1 && - bytesWritten == (bstd.mSSLRequestedTransferAmount - 1)) { - // libSSL signaled us a short write. - // While libSSL accepted all data, not all bytes were flushed to the OS socket. - bstd.mThePendingByte = *(bstd.mSSLDataBuffer + (bstd.mSSLRequestedTransferAmount-1)); - bytesWritten = -1; - bstd.mPRErrorCode = PR_WOULD_BLOCK_ERROR; - bstd.mOneBytePendingFromEarlierWrite = true; - bstd.mOriginalRequestedTransferAmount = bstd.mSSLRequestedTransferAmount; - } - } - - bstd.mSSLResultRemainingBytes = bytesWritten; - busy_socket_ssl_state = nsSSLSocketThreadData::ssl_writing_done; - } - else if (nsSSLSocketThreadData::ssl_pending_read == busy_socket_ssl_state) - { - PRInt32 bytesRead = realFileDesc->methods - ->read(realFileDesc, - bstd.mSSLDataBuffer, - bstd.mSSLRequestedTransferAmount); - -#ifdef DEBUG_SSL_VERBOSE - PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] read %d bytes\n", (void*)realFileDesc, bytesRead)); -#endif - bytesRead = checkHandshake(bytesRead, true, realFileDesc, mBusySocket); - if (bytesRead < 0) { - // give the error back to caller - bstd.mPRErrorCode = PR_GetError(); - } - - bstd.mSSLResultRemainingBytes = bytesRead; - bstd.mSSLRemainingReadResultData = bstd.mSSLDataBuffer; - busy_socket_ssl_state = nsSSLSocketThreadData::ssl_reading_done; - } - } - - // avoid setting event repeatedly - bool needToSetPollableEvent = false; - - { - MutexAutoLock threadLock(ssl_thread_singleton->mMutex); - - mBusySocket->mThreadData->mSSLState = busy_socket_ssl_state; - - if (!nsSSLIOLayerHelpers::mPollableEventCurrentlySet) - { - needToSetPollableEvent = true; - nsSSLIOLayerHelpers::mPollableEventCurrentlySet = true; - } - } - - if (needToSetPollableEvent && nsSSLIOLayerHelpers::mSharedPollableEvent) - { - // Wake up the file descriptor on the Necko thread, - // so it can fetch the results from the SSL I/O call - // that we just completed. - PR_SetPollableEvent(nsSSLIOLayerHelpers::mSharedPollableEvent); - - // if we don't have a pollable event, we'll have to wake up - // the caller by other means. - } - } - - { - MutexAutoLock threadLock(ssl_thread_singleton->mMutex); - if (mBusySocket) - { - restoreOriginalSocket_locked(mBusySocket); - mBusySocket = nsnull; - } - if (!nsSSLIOLayerHelpers::mPollableEventCurrentlySet) - { - nsSSLIOLayerHelpers::mPollableEventCurrentlySet = true; - if (nsSSLIOLayerHelpers::mSharedPollableEvent) - { - PR_SetPollableEvent(nsSSLIOLayerHelpers::mSharedPollableEvent); - } - } - postStoppedEventToMainThread(threadLock); - } -} - -bool nsSSLThread::stoppedOrStopping() -{ - if (!ssl_thread_singleton) - return false; - - return ssl_thread_singleton->exitRequestedNoLock(); -} - -nsSSLThread *nsSSLThread::ssl_thread_singleton = nsnull; diff --git a/security/manager/ssl/src/nsSSLThread.h b/security/manager/ssl/src/nsSSLThread.h deleted file mode 100644 index ea80117f884..00000000000 --- a/security/manager/ssl/src/nsSSLThread.h +++ /dev/null @@ -1,158 +0,0 @@ -/* ***** BEGIN LICENSE BLOCK ***** - * Version: MPL 1.1/GPL 2.0/LGPL 2.1 - * - * The contents of this file are subject to the Mozilla Public License Version - * 1.1 (the "License"); you may not use this file except in compliance with - * the License. You may obtain a copy of the License at - * http://www.mozilla.org/MPL/ - * - * Software distributed under the License is distributed on an "AS IS" basis, - * WITHOUT WARRANTY OF ANY KIND, either express or implied. See the License - * for the specific language governing rights and limitations under the - * License. - * - * The Original Code is mozilla.org code. - * - * The Initial Developer of the Original Code is - * Red Hat, Inc. - * Portions created by the Initial Developer are Copyright (C) 2006 - * the Initial Developer. All Rights Reserved. - * - * Contributor(s): - * Kai Engert - * - * Alternatively, the contents of this file may be used under the terms of - * either the GNU General Public License Version 2 or later (the "GPL"), or - * the GNU Lesser General Public License Version 2.1 or later (the "LGPL"), - * in which case the provisions of the GPL or the LGPL are applicable instead - * of those above. If you wish to allow use of your version of this file only - * under the terms of either the GPL or the LGPL, and not to allow others to - * use your version of this file under the terms of the MPL, indicate your - * decision by deleting the provisions above and replace them with the notice - * and other provisions required by the GPL or the LGPL. If you do not delete - * the provisions above, a recipient may use your version of this file under - * the terms of any one of the MPL, the GPL or the LGPL. - * - * ***** END LICENSE BLOCK ***** */ - -#ifndef _NSSSLTHREAD_H_ -#define _NSSSLTHREAD_H_ - -#include "nsCOMPtr.h" -#include "nsIRequest.h" -#include "nsPSMBackgroundThread.h" - -class nsNSSSocketInfo; -class nsIHttpChannel; - -class nsSSLThread : public nsPSMBackgroundThread -{ -private: - // We use mMutex contained in our base class - // to protect access to these variables: - // mBusySocket, mSocketScheduledToBeDestroyed - // and to nsSSLSocketThreadData::mSSLState - // while a socket is the busy socket. - - // We use mCond contained in our base class - // to notify the SSL thread that a new SSL I/O - // request has been queued for processing. - // It can be found in the mBusySocket variable, - // containing all details in its member. - - // A socket that is currently owned by the SSL thread - // and has pending SSL I/O activity or I/O results - // not yet fetched by the original caller. - nsNSSSocketInfo *mBusySocket; - - // A socket that should be closed and destroyed - // as soon as possible. The request was initiated by - // Necko, but it happened at a time when the SSL - // thread had ownership of the socket, so the request - // was delayed. It's now the responsibility of the - // SSL thread to close and destroy this socket. - nsNSSSocketInfo *mSocketScheduledToBeDestroyed; - - // Did we receive a request from NSS to fetch HTTP - // data on behalf of NSS? (Most likely this is a OCSP request) - // We track a handle to the HTTP request sent to Necko. - // As this HTTP request depends on some original SSL socket, - // we can use this handle to cancel the dependent HTTP request, - // should we be asked to close the original SSL socket. - nsCOMPtr mPendingHTTPRequest; - - virtual void Run(void); - - // Called from SSL thread only - static PRInt32 checkHandshake(PRInt32 bytesTransfered, - bool wasReading, - PRFileDesc* fd, - nsNSSSocketInfo *socketInfo); - - // Function can be called from either Necko or SSL thread - // Caller must lock mMutex before this call. - static void restoreOriginalSocket_locked(nsNSSSocketInfo *si); - - // Helper for requestSomething functions, - // caled from the Necko thread only. - static PRFileDesc *getRealSSLFD(nsNSSSocketInfo *si); - - // Support of blocking sockets is very rudimentary. - // We only support it because Mozilla's LDAP code requires blocking I/O. - // We do not support switching the blocking mode of a socket. - // We require the blocking state has been set prior to the first - // read/write call, and will stay that way for the remainder of the socket's lifetime. - // This function must be called while holding the lock. - // If the socket is a blocking socket, out_fd will contain the real FD, - // on a non-blocking socket out_fd will be nsnull. - // If there is a failure in obtaining the status of the socket, - // the function will return PR_FAILURE. - static PRStatus getRealFDIfBlockingSocket_locked(nsNSSSocketInfo *si, - PRFileDesc *&out_fd); -public: - nsSSLThread(); - ~nsSSLThread(); - - static nsSSLThread *ssl_thread_singleton; - - // All requestSomething functions are called from - // the Necko thread only. - - static PRInt32 requestRead(nsNSSSocketInfo *si, - void *buf, - PRInt32 amount, - PRIntervalTime timeout); - - static PRInt32 requestWrite(nsNSSSocketInfo *si, - const void *buf, - PRInt32 amount, - PRIntervalTime timeout); - - static PRInt16 requestPoll(nsNSSSocketInfo *si, - PRInt16 in_flags, - PRInt16 *out_flags); - - static PRInt32 requestRecvMsgPeek(nsNSSSocketInfo *si, void *buf, PRInt32 amount, - PRIntn flags, PRIntervalTime timeout); - - static PRStatus requestClose(nsNSSSocketInfo *si); - - static PRStatus requestGetsockname(nsNSSSocketInfo *si, PRNetAddr *addr); - - static PRStatus requestGetpeername(nsNSSSocketInfo *si, PRNetAddr *addr); - - static PRStatus requestGetsocketoption(nsNSSSocketInfo *si, - PRSocketOptionData *data); - - static PRStatus requestSetsocketoption(nsNSSSocketInfo *si, - const PRSocketOptionData *data); - - static PRStatus requestConnectcontinue(nsNSSSocketInfo *si, - PRInt16 out_flags); - - static nsresult requestActivateSSL(nsNSSSocketInfo *si); - - static bool stoppedOrStopping(); -}; - -#endif //_NSSSLTHREAD_H_