michael@0: /* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*- michael@0: * michael@0: * This Source Code Form is subject to the terms of the Mozilla Public michael@0: * License, v. 2.0. If a copy of the MPL was not distributed with this michael@0: * file, You can obtain one at http://mozilla.org/MPL/2.0/. */ michael@0: michael@0: // For connections that are not processed on the socket transport thread, we do michael@0: // NOT use the async logic described below. Instead, we authenticate the michael@0: // certificate on the thread that the connection's I/O happens on, michael@0: // synchronously. This allows us to do certificate verification for blocking michael@0: // (not non-blocking) sockets and sockets that have their I/O processed on a michael@0: // thread other than the socket transport service thread. Also, we DO NOT michael@0: // support blocking sockets on the socket transport service thread at all. michael@0: // michael@0: // During certificate authentication, we call CERT_PKIXVerifyCert or michael@0: // CERT_VerifyCert. These functions may make zero or more HTTP requests michael@0: // for OCSP responses, CRLs, intermediate certificates, etc. Our fetching logic michael@0: // for these requests processes them on the socket transport service thread. michael@0: // michael@0: // If the connection for which we are verifying the certificate is happening michael@0: // on the socket transport thread (the usually case, at least for HTTP), then michael@0: // if our cert auth hook were to call the CERT_*Verify* functions directly, michael@0: // there would be a deadlock: The CERT_*Verify* function would cause an event michael@0: // to be asynchronously posted to the socket transport thread, and then it michael@0: // would block the socket transport thread waiting to be notified of the HTTP michael@0: // response. However, the HTTP request would never actually be processed michael@0: // because the socket transport thread would be blocked and so it wouldn't be michael@0: // able process HTTP requests. (i.e. Deadlock.) michael@0: // michael@0: // Consequently, when we are asked to verify a certificate on the socket michael@0: // transport service thread, we must always call the CERT_*Verify* cert michael@0: // functions on another thread. To accomplish this, our auth cert hook michael@0: // dispatches a SSLServerCertVerificationJob to a pool of background threads, michael@0: // and then immediatley return SECWouldBlock to libssl. These jobs are where michael@0: // the CERT_*Verify* functions are actually called. michael@0: // michael@0: // When our auth cert hook returns SECWouldBlock, libssl will carry on the michael@0: // handshake while we validate the certificate. This will free up the socket michael@0: // transport thread so that HTTP requests--in particular, the OCSP/CRL/cert michael@0: // requests needed for cert verification as mentioned above--can be processed. michael@0: // michael@0: // Once the CERT_*Verify* function returns, the cert verification job michael@0: // dispatches a SSLServerCertVerificationResult to the socket transport thread; michael@0: // the SSLServerCertVerificationResult will notify libssl that the certificate michael@0: // authentication is complete. Once libssl is notified that the authentication michael@0: // is complete, it will continue the SSL handshake (if it hasn't already michael@0: // finished) and it will begin allowing us to send/receive data on the michael@0: // connection. michael@0: // michael@0: // Timeline of events (for connections managed by the socket transport service): michael@0: // michael@0: // * libssl calls SSLServerCertVerificationJob::Dispatch on the socket michael@0: // transport thread. michael@0: // * SSLServerCertVerificationJob::Dispatch queues a job michael@0: // (instance of SSLServerCertVerificationJob) to its background thread michael@0: // pool and returns. michael@0: // * One of the background threads calls CERT_*Verify*, which may enqueue michael@0: // some HTTP request(s) onto the socket transport thread, and then michael@0: // blocks that background thread waiting for the responses and/or timeouts michael@0: // or errors for those requests. michael@0: // * Once those HTTP responses have all come back or failed, the michael@0: // CERT_*Verify* function returns a result indicating that the validation michael@0: // succeeded or failed. michael@0: // * If the validation succeeded, then a SSLServerCertVerificationResult michael@0: // event is posted to the socket transport thread, and the cert michael@0: // verification thread becomes free to verify other certificates. michael@0: // * Otherwise, a CertErrorRunnable is posted to the socket transport thread michael@0: // and then to the main thread (blocking both, see CertErrorRunnable) to michael@0: // do cert override processing and bad cert listener notification. Then michael@0: // the cert verification thread becomes free to verify other certificates. michael@0: // * After processing cert overrides, the CertErrorRunnable will dispatch a michael@0: // SSLServerCertVerificationResult event to the socket transport thread to michael@0: // notify it of the result of the override processing; then it returns, michael@0: // freeing up the main thread. michael@0: // * The SSLServerCertVerificationResult event will either wake up the michael@0: // socket (using SSL_RestartHandshakeAfterServerCert) if validation michael@0: // succeeded or there was an error override, or it will set an error flag michael@0: // so that the next I/O operation on the socket will fail, causing the michael@0: // socket transport thread to close the connection. michael@0: // michael@0: // Cert override processing must happen on the main thread because it accesses michael@0: // the nsICertOverrideService, and that service must be accessed on the main michael@0: // thread because some extensions (Selenium, in particular) replace it with a michael@0: // Javascript implementation, and chrome JS must always be run on the main michael@0: // thread. michael@0: // michael@0: // SSLServerCertVerificationResult must be dispatched to the socket transport michael@0: // thread because we must only call SSL_* functions on the socket transport michael@0: // thread since they may do I/O, because many parts of nsNSSSocketInfo (the michael@0: // subclass of TransportSecurityInfo used when validating certificates during michael@0: // an SSL handshake) and the PSM NSS I/O layer are not thread-safe, and because michael@0: // we need the event to interrupt the PR_Poll that may waiting for I/O on the michael@0: // socket for which we are validating the cert. michael@0: michael@0: #include "SSLServerCertVerification.h" michael@0: michael@0: #include michael@0: michael@0: #include "pkix/pkixtypes.h" michael@0: #include "CertVerifier.h" michael@0: #include "CryptoTask.h" michael@0: #include "ExtendedValidation.h" michael@0: #include "NSSCertDBTrustDomain.h" michael@0: #include "nsIBadCertListener2.h" michael@0: #include "nsICertOverrideService.h" michael@0: #include "nsISiteSecurityService.h" michael@0: #include "nsNSSComponent.h" michael@0: #include "nsNSSCleaner.h" michael@0: #include "nsRecentBadCerts.h" michael@0: #include "nsNSSIOLayer.h" michael@0: #include "nsNSSShutDown.h" michael@0: michael@0: #include "mozilla/Assertions.h" michael@0: #include "mozilla/Mutex.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #include "mozilla/unused.h" michael@0: #include "nsIThreadPool.h" michael@0: #include "nsNetUtil.h" michael@0: #include "nsXPCOMCIDInternal.h" michael@0: #include "nsComponentManagerUtils.h" michael@0: #include "nsServiceManagerUtils.h" michael@0: #include "PSMRunnable.h" michael@0: #include "SharedSSLState.h" michael@0: #include "nsContentUtils.h" michael@0: michael@0: #include "ssl.h" michael@0: #include "secerr.h" michael@0: #include "secport.h" michael@0: #include "sslerr.h" michael@0: #include "ocsp.h" michael@0: michael@0: #ifdef PR_LOGGING michael@0: extern PRLogModuleInfo* gPIPNSSLog; michael@0: #endif michael@0: michael@0: namespace mozilla { namespace psm { michael@0: michael@0: namespace { michael@0: michael@0: NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID); michael@0: michael@0: NSSCleanupAutoPtrClass(CERTCertificate, CERT_DestroyCertificate) michael@0: NSSCleanupAutoPtrClass_WithParam(PLArenaPool, PORT_FreeArena, FalseParam, false) michael@0: michael@0: // do not use a nsCOMPtr to avoid static initializer/destructor michael@0: nsIThreadPool* gCertVerificationThreadPool = nullptr; michael@0: michael@0: // We avoid using a mutex for the success case to avoid lock-related michael@0: // performance issues. However, we do use a lock in the error case to simplify michael@0: // the code, since performance in the error case is not important. michael@0: Mutex* gSSLVerificationTelemetryMutex = nullptr; michael@0: michael@0: // We add a mutex to serialize PKCS11 database operations michael@0: Mutex* gSSLVerificationPK11Mutex = nullptr; michael@0: michael@0: } // unnamed namespace michael@0: michael@0: // Called when the socket transport thread starts, to initialize the SSL cert michael@0: // verification thread pool. By tying the thread pool startup/shutdown directly michael@0: // to the STS thread's lifetime, we ensure that they are *always* available for michael@0: // SSL connections and that there are no races during startup and especially michael@0: // shutdown. (Previously, we have had multiple problems with races in PSM michael@0: // background threads, and the race-prevention/shutdown logic used there is michael@0: // brittle. Since this service is critical to things like downloading updates, michael@0: // we take no chances.) Also, by doing things this way, we avoid the need for michael@0: // locks, since gCertVerificationThreadPool is only ever accessed on the socket michael@0: // transport thread. michael@0: void michael@0: InitializeSSLServerCertVerificationThreads() michael@0: { michael@0: gSSLVerificationTelemetryMutex = new Mutex("SSLVerificationTelemetryMutex"); michael@0: gSSLVerificationPK11Mutex = new Mutex("SSLVerificationPK11Mutex"); michael@0: // TODO: tuning, make parameters preferences michael@0: // XXX: instantiate nsThreadPool directly, to make this more bulletproof. michael@0: // Currently, the nsThreadPool.h header isn't exported for us to do so. michael@0: nsresult rv = CallCreateInstance(NS_THREADPOOL_CONTRACTID, michael@0: &gCertVerificationThreadPool); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("Failed to create SSL cert verification threads."); michael@0: return; michael@0: } michael@0: michael@0: (void) gCertVerificationThreadPool->SetIdleThreadLimit(5); michael@0: (void) gCertVerificationThreadPool->SetIdleThreadTimeout(30 * 1000); michael@0: (void) gCertVerificationThreadPool->SetThreadLimit(5); michael@0: (void) gCertVerificationThreadPool->SetName(NS_LITERAL_CSTRING("SSL Cert")); michael@0: } michael@0: michael@0: // Called when the socket transport thread finishes, to destroy the thread michael@0: // pool. Since the socket transport service has stopped processing events, it michael@0: // will not attempt any more SSL I/O operations, so it is clearly safe to shut michael@0: // down the SSL cert verification infrastructure. Also, the STS will not michael@0: // dispatch many SSL verification result events at this point, so any pending michael@0: // cert verifications will (correctly) fail at the point they are dispatched. michael@0: // michael@0: // The other shutdown race condition that is possible is a race condition with michael@0: // shutdown of the nsNSSComponent service. We use the michael@0: // nsNSSShutdownPreventionLock where needed (not here) to prevent that. michael@0: void StopSSLServerCertVerificationThreads() michael@0: { michael@0: if (gCertVerificationThreadPool) { michael@0: gCertVerificationThreadPool->Shutdown(); michael@0: NS_RELEASE(gCertVerificationThreadPool); michael@0: } michael@0: if (gSSLVerificationTelemetryMutex) { michael@0: delete gSSLVerificationTelemetryMutex; michael@0: gSSLVerificationTelemetryMutex = nullptr; michael@0: } michael@0: if (gSSLVerificationPK11Mutex) { michael@0: delete gSSLVerificationPK11Mutex; michael@0: gSSLVerificationPK11Mutex = nullptr; michael@0: } michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: void michael@0: LogInvalidCertError(TransportSecurityInfo* socketInfo, michael@0: PRErrorCode errorCode, michael@0: ::mozilla::psm::SSLErrorMessageType errorMessageType) michael@0: { michael@0: nsString message; michael@0: socketInfo->GetErrorLogMessage(errorCode, errorMessageType, message); michael@0: if (!message.IsEmpty()) { michael@0: nsContentUtils::LogSimpleConsoleError(message, "SSL"); michael@0: } michael@0: } michael@0: michael@0: // Dispatched to the STS thread to notify the infoObject of the verification michael@0: // result. michael@0: // michael@0: // This will cause the PR_Poll in the STS thread to return, so things work michael@0: // correctly even if the STS thread is blocked polling (only) on the file michael@0: // descriptor that is waiting for this result. michael@0: class SSLServerCertVerificationResult : public nsRunnable michael@0: { michael@0: public: michael@0: NS_DECL_NSIRUNNABLE michael@0: michael@0: SSLServerCertVerificationResult(TransportSecurityInfo* infoObject, michael@0: PRErrorCode errorCode, michael@0: Telemetry::ID telemetryID = Telemetry::HistogramCount, michael@0: uint32_t telemetryValue = -1, michael@0: SSLErrorMessageType errorMessageType = michael@0: PlainErrorMessage); michael@0: michael@0: void Dispatch(); michael@0: private: michael@0: const RefPtr mInfoObject; michael@0: public: michael@0: const PRErrorCode mErrorCode; michael@0: const SSLErrorMessageType mErrorMessageType; michael@0: const Telemetry::ID mTelemetryID; michael@0: const uint32_t mTelemetryValue; michael@0: }; michael@0: michael@0: class CertErrorRunnable : public SyncRunnableBase michael@0: { michael@0: public: michael@0: CertErrorRunnable(const void* fdForLogging, michael@0: nsIX509Cert* cert, michael@0: TransportSecurityInfo* infoObject, michael@0: PRErrorCode defaultErrorCodeToReport, michael@0: uint32_t collectedErrors, michael@0: PRErrorCode errorCodeTrust, michael@0: PRErrorCode errorCodeMismatch, michael@0: PRErrorCode errorCodeExpired, michael@0: uint32_t providerFlags) michael@0: : mFdForLogging(fdForLogging), mCert(cert), mInfoObject(infoObject), michael@0: mDefaultErrorCodeToReport(defaultErrorCodeToReport), michael@0: mCollectedErrors(collectedErrors), michael@0: mErrorCodeTrust(errorCodeTrust), michael@0: mErrorCodeMismatch(errorCodeMismatch), michael@0: mErrorCodeExpired(errorCodeExpired), michael@0: mProviderFlags(providerFlags) michael@0: { michael@0: } michael@0: michael@0: virtual void RunOnTargetThread(); michael@0: RefPtr mResult; // out michael@0: private: michael@0: SSLServerCertVerificationResult* CheckCertOverrides(); michael@0: michael@0: const void* const mFdForLogging; // may become an invalid pointer; do not dereference michael@0: const nsCOMPtr mCert; michael@0: const RefPtr mInfoObject; michael@0: const PRErrorCode mDefaultErrorCodeToReport; michael@0: const uint32_t mCollectedErrors; michael@0: const PRErrorCode mErrorCodeTrust; michael@0: const PRErrorCode mErrorCodeMismatch; michael@0: const PRErrorCode mErrorCodeExpired; michael@0: const uint32_t mProviderFlags; michael@0: }; michael@0: michael@0: // A probe value of 1 means "no error". michael@0: uint32_t michael@0: MapCertErrorToProbeValue(PRErrorCode errorCode) michael@0: { michael@0: switch (errorCode) michael@0: { michael@0: case SEC_ERROR_UNKNOWN_ISSUER: return 2; michael@0: case SEC_ERROR_CA_CERT_INVALID: return 3; michael@0: case SEC_ERROR_UNTRUSTED_ISSUER: return 4; michael@0: case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: return 5; michael@0: case SEC_ERROR_UNTRUSTED_CERT: return 6; michael@0: case SEC_ERROR_INADEQUATE_KEY_USAGE: return 7; michael@0: case SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED: return 8; michael@0: case SSL_ERROR_BAD_CERT_DOMAIN: return 9; michael@0: case SEC_ERROR_EXPIRED_CERTIFICATE: return 10; michael@0: } michael@0: NS_WARNING("Unknown certificate error code. Does MapCertErrorToProbeValue " michael@0: "handle everything in PRErrorCodeToOverrideType?"); michael@0: return 0; michael@0: } michael@0: michael@0: SECStatus michael@0: MozillaPKIXDetermineCertOverrideErrors(CERTCertificate* cert, michael@0: const char* hostName, PRTime now, michael@0: PRErrorCode defaultErrorCodeToReport, michael@0: /*out*/ uint32_t& collectedErrors, michael@0: /*out*/ PRErrorCode& errorCodeTrust, michael@0: /*out*/ PRErrorCode& errorCodeMismatch, michael@0: /*out*/ PRErrorCode& errorCodeExpired) michael@0: { michael@0: MOZ_ASSERT(cert); michael@0: MOZ_ASSERT(hostName); michael@0: MOZ_ASSERT(collectedErrors == 0); michael@0: MOZ_ASSERT(errorCodeTrust == 0); michael@0: MOZ_ASSERT(errorCodeMismatch == 0); michael@0: MOZ_ASSERT(errorCodeExpired == 0); michael@0: michael@0: // Assumes the error prioritization described in mozilla::pkix's michael@0: // BuildForward function. Also assumes that CERT_VerifyCertName was only michael@0: // called if CertVerifier::VerifyCert succeeded. michael@0: switch (defaultErrorCodeToReport) { michael@0: case SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED: michael@0: case SEC_ERROR_CA_CERT_INVALID: michael@0: case SEC_ERROR_UNKNOWN_ISSUER: michael@0: { michael@0: collectedErrors = nsICertOverrideService::ERROR_UNTRUSTED; michael@0: errorCodeTrust = defaultErrorCodeToReport; michael@0: michael@0: SECCertTimeValidity validity = CERT_CheckCertValidTimes(cert, now, false); michael@0: if (validity == secCertTimeUndetermined) { michael@0: PR_SetError(defaultErrorCodeToReport, 0); michael@0: return SECFailure; michael@0: } michael@0: if (validity != secCertTimeValid) { michael@0: collectedErrors |= nsICertOverrideService::ERROR_TIME; michael@0: errorCodeExpired = SEC_ERROR_EXPIRED_CERTIFICATE; michael@0: } michael@0: break; michael@0: } michael@0: michael@0: case SEC_ERROR_EXPIRED_CERTIFICATE: michael@0: collectedErrors = nsICertOverrideService::ERROR_TIME; michael@0: errorCodeExpired = SEC_ERROR_EXPIRED_CERTIFICATE; michael@0: break; michael@0: michael@0: case SSL_ERROR_BAD_CERT_DOMAIN: michael@0: collectedErrors = nsICertOverrideService::ERROR_MISMATCH; michael@0: errorCodeMismatch = SSL_ERROR_BAD_CERT_DOMAIN; michael@0: break; michael@0: michael@0: case 0: michael@0: NS_ERROR("No error code set during certificate validation failure."); michael@0: PR_SetError(PR_INVALID_STATE_ERROR, 0); michael@0: return SECFailure; michael@0: michael@0: default: michael@0: PR_SetError(defaultErrorCodeToReport, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: if (defaultErrorCodeToReport != SSL_ERROR_BAD_CERT_DOMAIN) { michael@0: if (CERT_VerifyCertName(cert, hostName) != SECSuccess) { michael@0: if (PR_GetError() != SSL_ERROR_BAD_CERT_DOMAIN) { michael@0: PR_SetError(defaultErrorCodeToReport, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: collectedErrors |= nsICertOverrideService::ERROR_MISMATCH; michael@0: errorCodeMismatch = SSL_ERROR_BAD_CERT_DOMAIN; michael@0: } michael@0: } michael@0: michael@0: return SECSuccess; michael@0: } michael@0: michael@0: SSLServerCertVerificationResult* michael@0: CertErrorRunnable::CheckCertOverrides() michael@0: { michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p][%p] top of CheckCertOverrides\n", michael@0: mFdForLogging, this)); michael@0: // "Use" mFdForLogging in non-PR_LOGGING builds, too, to suppress michael@0: // clang's -Wunused-private-field build warning for this variable: michael@0: unused << mFdForLogging; michael@0: michael@0: if (!NS_IsMainThread()) { michael@0: NS_ERROR("CertErrorRunnable::CheckCertOverrides called off main thread"); michael@0: return new SSLServerCertVerificationResult(mInfoObject, michael@0: mDefaultErrorCodeToReport); michael@0: } michael@0: michael@0: int32_t port; michael@0: mInfoObject->GetPort(&port); michael@0: michael@0: nsCString hostWithPortString; michael@0: hostWithPortString.AppendASCII(mInfoObject->GetHostNameRaw()); michael@0: hostWithPortString.AppendLiteral(":"); michael@0: hostWithPortString.AppendInt(port); michael@0: michael@0: uint32_t remaining_display_errors = mCollectedErrors; michael@0: michael@0: nsresult nsrv; michael@0: michael@0: // Enforce Strict-Transport-Security for hosts that are "STS" hosts: michael@0: // connections must be dropped when there are any certificate errors michael@0: // (STS Spec section 7.3). michael@0: bool strictTransportSecurityEnabled = false; michael@0: nsCOMPtr sss michael@0: = do_GetService(NS_SSSERVICE_CONTRACTID, &nsrv); michael@0: if (NS_SUCCEEDED(nsrv)) { michael@0: nsrv = sss->IsSecureHost(nsISiteSecurityService::HEADER_HSTS, michael@0: mInfoObject->GetHostNameRaw(), michael@0: mProviderFlags, michael@0: &strictTransportSecurityEnabled); michael@0: } michael@0: if (NS_FAILED(nsrv)) { michael@0: return new SSLServerCertVerificationResult(mInfoObject, michael@0: mDefaultErrorCodeToReport); michael@0: } michael@0: michael@0: if (!strictTransportSecurityEnabled) { michael@0: nsCOMPtr overrideService = michael@0: do_GetService(NS_CERTOVERRIDE_CONTRACTID); michael@0: // it is fine to continue without the nsICertOverrideService michael@0: michael@0: uint32_t overrideBits = 0; michael@0: michael@0: if (overrideService) michael@0: { michael@0: bool haveOverride; michael@0: bool isTemporaryOverride; // we don't care michael@0: nsCString hostString(mInfoObject->GetHostName()); michael@0: nsrv = overrideService->HasMatchingOverride(hostString, port, michael@0: mCert, michael@0: &overrideBits, michael@0: &isTemporaryOverride, michael@0: &haveOverride); michael@0: if (NS_SUCCEEDED(nsrv) && haveOverride) michael@0: { michael@0: // remove the errors that are already overriden michael@0: remaining_display_errors &= ~overrideBits; michael@0: } michael@0: } michael@0: michael@0: if (!remaining_display_errors) { michael@0: // This can double- or triple-count one certificate with multiple michael@0: // different types of errors. Since this is telemetry and we just michael@0: // want a ballpark answer, we don't care. michael@0: if (mErrorCodeTrust != 0) { michael@0: uint32_t probeValue = MapCertErrorToProbeValue(mErrorCodeTrust); michael@0: Telemetry::Accumulate(Telemetry::SSL_CERT_ERROR_OVERRIDES, probeValue); michael@0: } michael@0: if (mErrorCodeMismatch != 0) { michael@0: uint32_t probeValue = MapCertErrorToProbeValue(mErrorCodeMismatch); michael@0: Telemetry::Accumulate(Telemetry::SSL_CERT_ERROR_OVERRIDES, probeValue); michael@0: } michael@0: if (mErrorCodeExpired != 0) { michael@0: uint32_t probeValue = MapCertErrorToProbeValue(mErrorCodeExpired); michael@0: Telemetry::Accumulate(Telemetry::SSL_CERT_ERROR_OVERRIDES, probeValue); michael@0: } michael@0: michael@0: // all errors are covered by override rules, so let's accept the cert michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, michael@0: ("[%p][%p] All errors covered by override rules\n", michael@0: mFdForLogging, this)); michael@0: return new SSLServerCertVerificationResult(mInfoObject, 0); michael@0: } michael@0: } else { michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, michael@0: ("[%p][%p] Strict-Transport-Security is violated: untrusted " michael@0: "transport layer\n", mFdForLogging, this)); michael@0: } michael@0: michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, michael@0: ("[%p][%p] Certificate error was not overridden\n", michael@0: mFdForLogging, this)); michael@0: michael@0: // Ok, this is a full stop. michael@0: // First, deliver the technical details of the broken SSL status. michael@0: michael@0: // Try to get a nsIBadCertListener2 implementation from the socket consumer. michael@0: nsCOMPtr sslSocketControl = do_QueryInterface( michael@0: NS_ISUPPORTS_CAST(nsITransportSecurityInfo*, mInfoObject)); michael@0: if (sslSocketControl) { michael@0: nsCOMPtr cb; michael@0: sslSocketControl->GetNotificationCallbacks(getter_AddRefs(cb)); michael@0: if (cb) { michael@0: nsCOMPtr bcl = do_GetInterface(cb); michael@0: if (bcl) { michael@0: nsIInterfaceRequestor* csi michael@0: = static_cast(mInfoObject); michael@0: bool suppressMessage = false; // obsolete, ignored michael@0: nsrv = bcl->NotifyCertProblem(csi, mInfoObject->SSLStatus(), michael@0: hostWithPortString, &suppressMessage); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr certdb = do_GetService(NS_X509CERTDB_CONTRACTID); michael@0: nsCOMPtr recentBadCertsService; michael@0: if (certdb) { michael@0: bool isPrivate = mProviderFlags & nsISocketProvider::NO_PERMANENT_STORAGE; michael@0: certdb->GetRecentBadCerts(isPrivate, getter_AddRefs(recentBadCertsService)); michael@0: } michael@0: michael@0: if (recentBadCertsService) { michael@0: NS_ConvertUTF8toUTF16 hostWithPortStringUTF16(hostWithPortString); michael@0: recentBadCertsService->AddBadCert(hostWithPortStringUTF16, michael@0: mInfoObject->SSLStatus()); michael@0: } michael@0: michael@0: // pick the error code to report by priority michael@0: PRErrorCode errorCodeToReport = mErrorCodeTrust ? mErrorCodeTrust michael@0: : mErrorCodeMismatch ? mErrorCodeMismatch michael@0: : mErrorCodeExpired ? mErrorCodeExpired michael@0: : mDefaultErrorCodeToReport; michael@0: michael@0: SSLServerCertVerificationResult* result = michael@0: new SSLServerCertVerificationResult(mInfoObject, michael@0: errorCodeToReport, michael@0: Telemetry::HistogramCount, michael@0: -1, michael@0: OverridableCertErrorMessage); michael@0: michael@0: LogInvalidCertError(mInfoObject, michael@0: result->mErrorCode, michael@0: result->mErrorMessageType); michael@0: michael@0: return result; michael@0: } michael@0: michael@0: void michael@0: CertErrorRunnable::RunOnTargetThread() michael@0: { michael@0: MOZ_ASSERT(NS_IsMainThread()); michael@0: michael@0: mResult = CheckCertOverrides(); michael@0: michael@0: MOZ_ASSERT(mResult); michael@0: } michael@0: michael@0: // Converts a PRErrorCode into one of michael@0: // nsICertOverrideService::ERROR_UNTRUSTED, michael@0: // nsICertOverrideService::ERROR_MISMATCH, michael@0: // nsICertOverrideService::ERROR_TIME michael@0: // if the given error code is an overridable error. michael@0: // If it is not, then 0 is returned. michael@0: uint32_t michael@0: PRErrorCodeToOverrideType(PRErrorCode errorCode) michael@0: { michael@0: switch (errorCode) michael@0: { michael@0: case SEC_ERROR_UNKNOWN_ISSUER: michael@0: case SEC_ERROR_UNTRUSTED_ISSUER: michael@0: case SEC_ERROR_EXPIRED_ISSUER_CERTIFICATE: michael@0: case SEC_ERROR_UNTRUSTED_CERT: michael@0: case SEC_ERROR_INADEQUATE_KEY_USAGE: michael@0: case SEC_ERROR_CERT_SIGNATURE_ALGORITHM_DISABLED: michael@0: case SEC_ERROR_CA_CERT_INVALID: michael@0: // We group all these errors as "cert not trusted" michael@0: return nsICertOverrideService::ERROR_UNTRUSTED; michael@0: case SSL_ERROR_BAD_CERT_DOMAIN: michael@0: return nsICertOverrideService::ERROR_MISMATCH; michael@0: case SEC_ERROR_EXPIRED_CERTIFICATE: michael@0: return nsICertOverrideService::ERROR_TIME; michael@0: default: michael@0: return 0; michael@0: } michael@0: } michael@0: michael@0: SECStatus michael@0: NSSDetermineCertOverrideErrors(CertVerifier& certVerifier, michael@0: CERTCertificate* cert, michael@0: const SECItem* stapledOCSPResponse, michael@0: TransportSecurityInfo* infoObject, michael@0: PRTime now, michael@0: PRErrorCode defaultErrorCodeToReport, michael@0: /*out*/ uint32_t& collectedErrors, michael@0: /*out*/ PRErrorCode& errorCodeTrust, michael@0: /*out*/ PRErrorCode& errorCodeMismatch, michael@0: /*out*/ PRErrorCode& errorCodeExpired) michael@0: { michael@0: MOZ_ASSERT(cert); michael@0: MOZ_ASSERT(infoObject); michael@0: MOZ_ASSERT(defaultErrorCodeToReport != 0); michael@0: MOZ_ASSERT(collectedErrors == 0); michael@0: MOZ_ASSERT(errorCodeTrust == 0); michael@0: MOZ_ASSERT(errorCodeMismatch == 0); michael@0: MOZ_ASSERT(errorCodeExpired == 0); michael@0: michael@0: if (defaultErrorCodeToReport == 0) { michael@0: NS_ERROR("No error code set during certificate validation failure."); michael@0: PR_SetError(PR_INVALID_STATE_ERROR, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: // We only allow overrides for certain errors. Return early if the error michael@0: // is not one of them. This is to avoid doing revocation fetching in the michael@0: // case of OCSP stapling and probably for other reasons. michael@0: if (PRErrorCodeToOverrideType(defaultErrorCodeToReport) == 0) { michael@0: PR_SetError(defaultErrorCodeToReport, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: PLArenaPool* log_arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); michael@0: PLArenaPoolCleanerFalseParam log_arena_cleaner(log_arena); michael@0: if (!log_arena) { michael@0: NS_ERROR("PORT_NewArena failed"); michael@0: return SECFailure; // PORT_NewArena set error code michael@0: } michael@0: michael@0: CERTVerifyLog* verify_log = PORT_ArenaZNew(log_arena, CERTVerifyLog); michael@0: if (!verify_log) { michael@0: NS_ERROR("PORT_ArenaZNew failed"); michael@0: return SECFailure; // PORT_ArenaZNew set error code michael@0: } michael@0: CERTVerifyLogContentsCleaner verify_log_cleaner(verify_log); michael@0: verify_log->arena = log_arena; michael@0: michael@0: // We ignore the result code of the cert verification (i.e. VerifyCert's rv) michael@0: // Either it is a failure, which is expected, and we'll process the michael@0: // verify log below. michael@0: // Or it is a success, then a domain mismatch is the only michael@0: // possible failure. michael@0: // XXX TODO: convert to VerifySSLServerCert michael@0: // XXX TODO: get rid of error log michael@0: certVerifier.VerifyCert(cert, certificateUsageSSLServer, michael@0: now, infoObject, infoObject->GetHostNameRaw(), michael@0: 0, stapledOCSPResponse, nullptr, nullptr, verify_log); michael@0: michael@0: // Check the name field against the desired hostname. michael@0: if (CERT_VerifyCertName(cert, infoObject->GetHostNameRaw()) != SECSuccess) { michael@0: collectedErrors |= nsICertOverrideService::ERROR_MISMATCH; michael@0: errorCodeMismatch = SSL_ERROR_BAD_CERT_DOMAIN; michael@0: } michael@0: michael@0: CERTVerifyLogNode* i_node; michael@0: for (i_node = verify_log->head; i_node; i_node = i_node->next) { michael@0: uint32_t overrideType = PRErrorCodeToOverrideType(i_node->error); michael@0: // If this isn't an overridable error, set the error and return. michael@0: if (overrideType == 0) { michael@0: PR_SetError(i_node->error, 0); michael@0: return SECFailure; michael@0: } michael@0: collectedErrors |= overrideType; michael@0: if (overrideType == nsICertOverrideService::ERROR_UNTRUSTED) { michael@0: if (errorCodeTrust == 0) { michael@0: errorCodeTrust = i_node->error; michael@0: } michael@0: } else if (overrideType == nsICertOverrideService::ERROR_MISMATCH) { michael@0: if (errorCodeMismatch == 0) { michael@0: errorCodeMismatch = i_node->error; michael@0: } michael@0: } else if (overrideType == nsICertOverrideService::ERROR_TIME) { michael@0: if (errorCodeExpired == 0) { michael@0: errorCodeExpired = i_node->error; michael@0: } michael@0: } else { michael@0: MOZ_CRASH("unexpected return value from PRErrorCodeToOverrideType"); michael@0: } michael@0: } michael@0: michael@0: return SECSuccess; michael@0: } michael@0: michael@0: // Returns null with the error code (PR_GetError()) set if it does not create michael@0: // the CertErrorRunnable. michael@0: CertErrorRunnable* michael@0: CreateCertErrorRunnable(CertVerifier& certVerifier, michael@0: PRErrorCode defaultErrorCodeToReport, michael@0: TransportSecurityInfo* infoObject, michael@0: CERTCertificate* cert, michael@0: const SECItem* stapledOCSPResponse, michael@0: const void* fdForLogging, michael@0: uint32_t providerFlags, michael@0: PRTime now) michael@0: { michael@0: MOZ_ASSERT(infoObject); michael@0: MOZ_ASSERT(cert); michael@0: michael@0: uint32_t collected_errors = 0; michael@0: PRErrorCode errorCodeTrust = 0; michael@0: PRErrorCode errorCodeMismatch = 0; michael@0: PRErrorCode errorCodeExpired = 0; michael@0: michael@0: SECStatus rv; michael@0: switch (certVerifier.mImplementation) { michael@0: case CertVerifier::classic: michael@0: #ifndef NSS_NO_LIBPKIX michael@0: case CertVerifier::libpkix: michael@0: #endif michael@0: rv = NSSDetermineCertOverrideErrors(certVerifier, cert, stapledOCSPResponse, michael@0: infoObject, now, michael@0: defaultErrorCodeToReport, michael@0: collected_errors, errorCodeTrust, michael@0: errorCodeMismatch, errorCodeExpired); michael@0: break; michael@0: michael@0: case CertVerifier::mozillapkix: michael@0: rv = MozillaPKIXDetermineCertOverrideErrors(cert, michael@0: infoObject->GetHostNameRaw(), michael@0: now, defaultErrorCodeToReport, michael@0: collected_errors, michael@0: errorCodeTrust, michael@0: errorCodeMismatch, michael@0: errorCodeExpired); michael@0: break; michael@0: michael@0: default: michael@0: MOZ_CRASH("unexpected CertVerifier implementation"); michael@0: PR_SetError(defaultErrorCodeToReport, 0); michael@0: return nullptr; michael@0: michael@0: } michael@0: if (rv != SECSuccess) { michael@0: return nullptr; michael@0: } michael@0: michael@0: RefPtr nssCert(nsNSSCertificate::Create(cert)); michael@0: if (!nssCert) { michael@0: NS_ERROR("nsNSSCertificate::Create failed"); michael@0: PR_SetError(SEC_ERROR_NO_MEMORY, 0); michael@0: return nullptr; michael@0: } michael@0: michael@0: if (!collected_errors) { michael@0: // This will happen when CERT_*Verify* only returned error(s) that are michael@0: // not on our whitelist of overridable certificate errors. michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] !collected_errors: %d\n", michael@0: fdForLogging, static_cast(defaultErrorCodeToReport))); michael@0: PR_SetError(defaultErrorCodeToReport, 0); michael@0: return nullptr; michael@0: } michael@0: michael@0: infoObject->SetStatusErrorBits(*nssCert, collected_errors); michael@0: michael@0: return new CertErrorRunnable(fdForLogging, michael@0: static_cast(nssCert.get()), michael@0: infoObject, defaultErrorCodeToReport, michael@0: collected_errors, errorCodeTrust, michael@0: errorCodeMismatch, errorCodeExpired, michael@0: providerFlags); michael@0: } michael@0: michael@0: // When doing async cert processing, we dispatch one of these runnables to the michael@0: // socket transport service thread, which blocks the socket transport michael@0: // service thread while it waits for the inner CertErrorRunnable to execute michael@0: // CheckCertOverrides on the main thread. CheckCertOverrides must block events michael@0: // on both of these threads because it calls TransportSecurityInfo::GetInterface(), michael@0: // which may call nsHttpConnection::GetInterface() through michael@0: // TransportSecurityInfo::mCallbacks. nsHttpConnection::GetInterface must always michael@0: // execute on the main thread, with the socket transport service thread michael@0: // blocked. michael@0: class CertErrorRunnableRunnable : public nsRunnable michael@0: { michael@0: public: michael@0: CertErrorRunnableRunnable(CertErrorRunnable* certErrorRunnable) michael@0: : mCertErrorRunnable(certErrorRunnable) michael@0: { michael@0: } michael@0: private: michael@0: NS_IMETHOD Run() michael@0: { michael@0: nsresult rv = mCertErrorRunnable->DispatchToMainThreadAndWait(); michael@0: // The result must run on the socket transport thread, which we are already michael@0: // on, so we can just run it directly, instead of dispatching it. michael@0: if (NS_SUCCEEDED(rv)) { michael@0: rv = mCertErrorRunnable->mResult ? mCertErrorRunnable->mResult->Run() michael@0: : NS_ERROR_UNEXPECTED; michael@0: } michael@0: return rv; michael@0: } michael@0: RefPtr mCertErrorRunnable; michael@0: }; michael@0: michael@0: class SSLServerCertVerificationJob : public nsRunnable michael@0: { michael@0: public: michael@0: // Must be called only on the socket transport thread michael@0: static SECStatus Dispatch(const RefPtr& certVerifier, michael@0: const void* fdForLogging, michael@0: TransportSecurityInfo* infoObject, michael@0: CERTCertificate* serverCert, michael@0: SECItem* stapledOCSPResponse, michael@0: uint32_t providerFlags, michael@0: PRTime time); michael@0: private: michael@0: NS_DECL_NSIRUNNABLE michael@0: michael@0: // Must be called only on the socket transport thread michael@0: SSLServerCertVerificationJob(const RefPtr& certVerifier, michael@0: const void* fdForLogging, michael@0: TransportSecurityInfo* infoObject, michael@0: CERTCertificate* cert, michael@0: SECItem* stapledOCSPResponse, michael@0: uint32_t providerFlags, michael@0: PRTime time); michael@0: const RefPtr mCertVerifier; michael@0: const void* const mFdForLogging; michael@0: const RefPtr mInfoObject; michael@0: const mozilla::pkix::ScopedCERTCertificate mCert; michael@0: const uint32_t mProviderFlags; michael@0: const PRTime mTime; michael@0: const TimeStamp mJobStartTime; michael@0: const ScopedSECItem mStapledOCSPResponse; michael@0: }; michael@0: michael@0: SSLServerCertVerificationJob::SSLServerCertVerificationJob( michael@0: const RefPtr& certVerifier, const void* fdForLogging, michael@0: TransportSecurityInfo* infoObject, CERTCertificate* cert, michael@0: SECItem* stapledOCSPResponse, uint32_t providerFlags, PRTime time) michael@0: : mCertVerifier(certVerifier) michael@0: , mFdForLogging(fdForLogging) michael@0: , mInfoObject(infoObject) michael@0: , mCert(CERT_DupCertificate(cert)) michael@0: , mProviderFlags(providerFlags) michael@0: , mTime(time) michael@0: , mJobStartTime(TimeStamp::Now()) michael@0: , mStapledOCSPResponse(SECITEM_DupItem(stapledOCSPResponse)) michael@0: { michael@0: } michael@0: michael@0: // This function assumes that we will only use the SPDY connection coalescing michael@0: // feature on connections where we have negotiated SPDY using NPN. If we ever michael@0: // talk SPDY without having negotiated it with SPDY, this code will give wrong michael@0: // and perhaps unsafe results. michael@0: // michael@0: // Returns SECSuccess on the initial handshake of all connections, on michael@0: // renegotiations for any connections where we did not negotiate SPDY, or on any michael@0: // SPDY connection where the server's certificate did not change. michael@0: // michael@0: // Prohibit changing the server cert only if we negotiated SPDY, michael@0: // in order to support SPDY's cross-origin connection pooling. michael@0: static SECStatus michael@0: BlockServerCertChangeForSpdy(nsNSSSocketInfo* infoObject, michael@0: CERTCertificate* serverCert) michael@0: { michael@0: // Get the existing cert. If there isn't one, then there is michael@0: // no cert change to worry about. michael@0: nsCOMPtr cert; michael@0: nsCOMPtr cert2; michael@0: michael@0: RefPtr status(infoObject->SSLStatus()); michael@0: if (!status) { michael@0: // If we didn't have a status, then this is the michael@0: // first handshake on this connection, not a michael@0: // renegotiation. michael@0: return SECSuccess; michael@0: } michael@0: michael@0: status->GetServerCert(getter_AddRefs(cert)); michael@0: cert2 = do_QueryInterface(cert); michael@0: if (!cert2) { michael@0: NS_NOTREACHED("every nsSSLStatus must have a cert" michael@0: "that implements nsIX509Cert2"); michael@0: PR_SetError(SEC_ERROR_LIBRARY_FAILURE, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: // Filter out sockets that did not neogtiate SPDY via NPN michael@0: nsAutoCString negotiatedNPN; michael@0: nsresult rv = infoObject->GetNegotiatedNPN(negotiatedNPN); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), michael@0: "GetNegotiatedNPN() failed during renegotiation"); michael@0: michael@0: if (NS_SUCCEEDED(rv) && !StringBeginsWith(negotiatedNPN, michael@0: NS_LITERAL_CSTRING("spdy/"))) michael@0: return SECSuccess; michael@0: michael@0: // If GetNegotiatedNPN() failed we will assume spdy for safety's safe michael@0: if (NS_FAILED(rv)) { michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, michael@0: ("BlockServerCertChangeForSpdy failed GetNegotiatedNPN() call." michael@0: " Assuming spdy.\n")); michael@0: } michael@0: michael@0: // Check to see if the cert has actually changed michael@0: ScopedCERTCertificate c(cert2->GetCert()); michael@0: NS_ASSERTION(c, "very bad and hopefully impossible state"); michael@0: bool sameCert = CERT_CompareCerts(c, serverCert); michael@0: if (sameCert) michael@0: return SECSuccess; michael@0: michael@0: // Report an error - changed cert is confirmed michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, michael@0: ("SPDY Refused to allow new cert during renegotiation\n")); michael@0: PR_SetError(SSL_ERROR_RENEGOTIATION_NOT_ALLOWED, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: SECStatus michael@0: AuthCertificate(CertVerifier& certVerifier, TransportSecurityInfo* infoObject, michael@0: CERTCertificate* cert, SECItem* stapledOCSPResponse, michael@0: uint32_t providerFlags, PRTime time) michael@0: { michael@0: MOZ_ASSERT(infoObject); michael@0: MOZ_ASSERT(cert); michael@0: michael@0: SECStatus rv; michael@0: michael@0: // TODO: Remove this after we switch to mozilla::pkix as the michael@0: // only option michael@0: if (certVerifier.mImplementation == CertVerifier::classic) { michael@0: if (stapledOCSPResponse) { michael@0: CERTCertDBHandle* handle = CERT_GetDefaultCertDB(); michael@0: rv = CERT_CacheOCSPResponseFromSideChannel(handle, cert, PR_Now(), michael@0: stapledOCSPResponse, michael@0: infoObject); michael@0: if (rv != SECSuccess) { michael@0: // Due to buggy servers that will staple expired OCSP responses michael@0: // (see for example http://trac.nginx.org/nginx/ticket/425), michael@0: // don't terminate the connection if the stapled response is expired. michael@0: // We will fall back to fetching revocation information. michael@0: PRErrorCode ocspErrorCode = PR_GetError(); michael@0: if (ocspErrorCode != SEC_ERROR_OCSP_OLD_RESPONSE) { michael@0: // stapled OCSP response present but invalid for some reason michael@0: Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 4); michael@0: return rv; michael@0: } else { michael@0: // stapled OCSP response present but expired michael@0: Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 3); michael@0: } michael@0: } else { michael@0: // stapled OCSP response present and good michael@0: Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 1); michael@0: } michael@0: } else { michael@0: // no stapled OCSP response michael@0: Telemetry::Accumulate(Telemetry::SSL_OCSP_STAPLING, 2); michael@0: michael@0: uint32_t reasonsForNotFetching = 0; michael@0: michael@0: char* ocspURI = CERT_GetOCSPAuthorityInfoAccessLocation(cert); michael@0: if (!ocspURI) { michael@0: reasonsForNotFetching |= 1; // invalid/missing OCSP URI michael@0: } else { michael@0: if (std::strncmp(ocspURI, "http://", 7)) { // approximation michael@0: reasonsForNotFetching |= 1; // invalid/missing OCSP URI michael@0: } michael@0: PORT_Free(ocspURI); michael@0: } michael@0: michael@0: if (!certVerifier.mOCSPDownloadEnabled) { michael@0: reasonsForNotFetching |= 2; michael@0: } michael@0: michael@0: Telemetry::Accumulate(Telemetry::SSL_OCSP_MAY_FETCH, michael@0: reasonsForNotFetching); michael@0: } michael@0: } michael@0: michael@0: // We want to avoid storing any intermediate cert information when browsing michael@0: // in private, transient contexts. michael@0: bool saveIntermediates = michael@0: !(providerFlags & nsISocketProvider::NO_PERMANENT_STORAGE); michael@0: michael@0: mozilla::pkix::ScopedCERTCertList certList; michael@0: SECOidTag evOidPolicy; michael@0: rv = certVerifier.VerifySSLServerCert(cert, stapledOCSPResponse, michael@0: time, infoObject, michael@0: infoObject->GetHostNameRaw(), michael@0: saveIntermediates, nullptr, michael@0: &evOidPolicy); michael@0: michael@0: // We want to remember the CA certs in the temp db, so that the application can find the michael@0: // complete chain at any time it might need it. michael@0: // But we keep only those CA certs in the temp db, that we didn't already know. michael@0: michael@0: RefPtr status(infoObject->SSLStatus()); michael@0: RefPtr nsc; michael@0: michael@0: if (!status || !status->mServerCert) { michael@0: if( rv == SECSuccess ){ michael@0: nsc = nsNSSCertificate::Create(cert, &evOidPolicy); michael@0: } michael@0: else { michael@0: nsc = nsNSSCertificate::Create(cert); michael@0: } michael@0: } michael@0: michael@0: if (rv == SECSuccess) { michael@0: // The connection may get terminated, for example, if the server requires michael@0: // a client cert. Let's provide a minimal SSLStatus michael@0: // to the caller that contains at least the cert and its status. michael@0: if (!status) { michael@0: status = new nsSSLStatus(); michael@0: infoObject->SetSSLStatus(status); michael@0: } michael@0: michael@0: if (rv == SECSuccess) { michael@0: // Certificate verification succeeded delete any potential record michael@0: // of certificate error bits. michael@0: RememberCertErrorsTable::GetInstance().RememberCertHasError(infoObject, michael@0: nullptr, rv); michael@0: } michael@0: else { michael@0: // Certificate verification failed, update the status' bits. michael@0: RememberCertErrorsTable::GetInstance().LookupCertErrorBits( michael@0: infoObject, status); michael@0: } michael@0: michael@0: if (status && !status->mServerCert) { michael@0: status->mServerCert = nsc; michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, michael@0: ("AuthCertificate setting NEW cert %p\n", status->mServerCert.get())); michael@0: } michael@0: } michael@0: michael@0: return rv; michael@0: } michael@0: michael@0: /*static*/ SECStatus michael@0: SSLServerCertVerificationJob::Dispatch( michael@0: const RefPtr& certVerifier, michael@0: const void* fdForLogging, michael@0: TransportSecurityInfo* infoObject, michael@0: CERTCertificate* serverCert, michael@0: SECItem* stapledOCSPResponse, michael@0: uint32_t providerFlags, michael@0: PRTime time) michael@0: { michael@0: // Runs on the socket transport thread michael@0: if (!certVerifier || !infoObject || !serverCert) { michael@0: NS_ERROR("Invalid parameters for SSL server cert validation"); michael@0: PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: RefPtr job( michael@0: new SSLServerCertVerificationJob(certVerifier, fdForLogging, infoObject, michael@0: serverCert, stapledOCSPResponse, michael@0: providerFlags, time)); michael@0: michael@0: nsresult nrv; michael@0: if (!gCertVerificationThreadPool) { michael@0: nrv = NS_ERROR_NOT_INITIALIZED; michael@0: } else { michael@0: nrv = gCertVerificationThreadPool->Dispatch(job, NS_DISPATCH_NORMAL); michael@0: } michael@0: if (NS_FAILED(nrv)) { michael@0: // We can't call SetCertVerificationResult here to change michael@0: // mCertVerificationState because SetCertVerificationResult will call michael@0: // libssl functions that acquire SSL locks that are already being held at michael@0: // this point. infoObject->mCertVerificationState will be stuck at michael@0: // waiting_for_cert_verification here, but that is OK because we already michael@0: // have to be able to handle cases where we encounter non-cert errors while michael@0: // in that state. michael@0: PRErrorCode error = nrv == NS_ERROR_OUT_OF_MEMORY michael@0: ? SEC_ERROR_NO_MEMORY michael@0: : PR_INVALID_STATE_ERROR; michael@0: PORT_SetError(error); michael@0: return SECFailure; michael@0: } michael@0: michael@0: PORT_SetError(PR_WOULD_BLOCK_ERROR); michael@0: return SECWouldBlock; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: SSLServerCertVerificationJob::Run() michael@0: { michael@0: // Runs on a cert verification thread michael@0: michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, michael@0: ("[%p] SSLServerCertVerificationJob::Run\n", mInfoObject.get())); michael@0: michael@0: PRErrorCode error; michael@0: michael@0: nsNSSShutDownPreventionLock nssShutdownPrevention; michael@0: if (mInfoObject->isAlreadyShutDown()) { michael@0: error = SEC_ERROR_USER_CANCELLED; michael@0: } else { michael@0: Telemetry::ID successTelemetry; michael@0: Telemetry::ID failureTelemetry; michael@0: switch (mCertVerifier->mImplementation) { michael@0: case CertVerifier::classic: michael@0: successTelemetry michael@0: = Telemetry::SSL_SUCCESFUL_CERT_VALIDATION_TIME_CLASSIC; michael@0: failureTelemetry michael@0: = Telemetry::SSL_INITIAL_FAILED_CERT_VALIDATION_TIME_CLASSIC; michael@0: break; michael@0: case CertVerifier::mozillapkix: michael@0: successTelemetry michael@0: = Telemetry::SSL_SUCCESFUL_CERT_VALIDATION_TIME_MOZILLAPKIX; michael@0: failureTelemetry michael@0: = Telemetry::SSL_INITIAL_FAILED_CERT_VALIDATION_TIME_MOZILLAPKIX; michael@0: break; michael@0: #ifndef NSS_NO_LIBPKIX michael@0: case CertVerifier::libpkix: michael@0: successTelemetry michael@0: = Telemetry::SSL_SUCCESFUL_CERT_VALIDATION_TIME_LIBPKIX; michael@0: failureTelemetry michael@0: = Telemetry::SSL_INITIAL_FAILED_CERT_VALIDATION_TIME_LIBPKIX; michael@0: break; michael@0: #endif michael@0: default: michael@0: MOZ_CRASH("Unknown CertVerifier mode"); michael@0: } michael@0: michael@0: // XXX michael@0: // Reset the error code here so we can detect if AuthCertificate fails to michael@0: // set the error code if/when it fails. michael@0: PR_SetError(0, 0); michael@0: SECStatus rv = AuthCertificate(*mCertVerifier, mInfoObject, mCert.get(), michael@0: mStapledOCSPResponse, mProviderFlags, michael@0: mTime); michael@0: if (rv == SECSuccess) { michael@0: uint32_t interval = (uint32_t) ((TimeStamp::Now() - mJobStartTime).ToMilliseconds()); michael@0: RefPtr restart( michael@0: new SSLServerCertVerificationResult(mInfoObject, 0, michael@0: successTelemetry, interval)); michael@0: restart->Dispatch(); michael@0: Telemetry::Accumulate(Telemetry::SSL_CERT_ERROR_OVERRIDES, 1); michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Note: the interval is not calculated once as PR_GetError MUST be called michael@0: // before any other function call michael@0: error = PR_GetError(); michael@0: { michael@0: TimeStamp now = TimeStamp::Now(); michael@0: MutexAutoLock telemetryMutex(*gSSLVerificationTelemetryMutex); michael@0: Telemetry::AccumulateTimeDelta(failureTelemetry, mJobStartTime, now); michael@0: } michael@0: if (error != 0) { michael@0: RefPtr runnable( michael@0: CreateCertErrorRunnable(*mCertVerifier, error, mInfoObject, michael@0: mCert.get(), mStapledOCSPResponse, michael@0: mFdForLogging, mProviderFlags, mTime)); michael@0: if (!runnable) { michael@0: // CreateCertErrorRunnable set a new error code michael@0: error = PR_GetError(); michael@0: } else { michael@0: // We must block the the socket transport service thread while the michael@0: // main thread executes the CertErrorRunnable. The CertErrorRunnable michael@0: // will dispatch the result asynchronously, so we don't have to block michael@0: // this thread waiting for it. michael@0: michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, michael@0: ("[%p][%p] Before dispatching CertErrorRunnable\n", michael@0: mFdForLogging, runnable.get())); michael@0: michael@0: nsresult nrv; michael@0: nsCOMPtr stsTarget michael@0: = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &nrv); michael@0: if (NS_SUCCEEDED(nrv)) { michael@0: nrv = stsTarget->Dispatch(new CertErrorRunnableRunnable(runnable), michael@0: NS_DISPATCH_NORMAL); michael@0: } michael@0: if (NS_SUCCEEDED(nrv)) { michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_ERROR("Failed to dispatch CertErrorRunnable"); michael@0: error = PR_INVALID_STATE_ERROR; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (error == 0) { michael@0: NS_NOTREACHED("no error set during certificate validation failure"); michael@0: error = PR_INVALID_STATE_ERROR; michael@0: } michael@0: michael@0: RefPtr failure( michael@0: new SSLServerCertVerificationResult(mInfoObject, error)); michael@0: failure->Dispatch(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: } // unnamed namespace michael@0: michael@0: // Extracts whatever information we need out of fd (using SSL_*) and passes it michael@0: // to SSLServerCertVerificationJob::Dispatch. SSLServerCertVerificationJob should michael@0: // never do anything with fd except logging. michael@0: SECStatus michael@0: AuthCertificateHook(void* arg, PRFileDesc* fd, PRBool checkSig, PRBool isServer) michael@0: { michael@0: RefPtr certVerifier(GetDefaultCertVerifier()); michael@0: if (!certVerifier) { michael@0: PR_SetError(SEC_ERROR_NOT_INITIALIZED, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: // Runs on the socket transport thread michael@0: michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, michael@0: ("[%p] starting AuthCertificateHook\n", fd)); michael@0: michael@0: // Modern libssl always passes PR_TRUE for checkSig, and we have no means of michael@0: // doing verification without checking signatures. michael@0: NS_ASSERTION(checkSig, "AuthCertificateHook: checkSig unexpectedly false"); michael@0: michael@0: // PSM never causes libssl to call this function with PR_TRUE for isServer, michael@0: // and many things in PSM assume that we are a client. michael@0: NS_ASSERTION(!isServer, "AuthCertificateHook: isServer unexpectedly true"); michael@0: michael@0: nsNSSSocketInfo* socketInfo = static_cast(arg); michael@0: michael@0: ScopedCERTCertificate serverCert(SSL_PeerCertificate(fd)); michael@0: michael@0: if (!checkSig || isServer || !socketInfo || !serverCert) { michael@0: PR_SetError(PR_INVALID_STATE_ERROR, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: socketInfo->SetFullHandshake(); michael@0: michael@0: // This value of "now" is used both here for OCSP stapling and later michael@0: // when calling CreateCertErrorRunnable. michael@0: PRTime now = PR_Now(); michael@0: michael@0: if (BlockServerCertChangeForSpdy(socketInfo, serverCert) != SECSuccess) michael@0: return SECFailure; michael@0: michael@0: bool onSTSThread; michael@0: nsresult nrv; michael@0: nsCOMPtr sts michael@0: = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &nrv); michael@0: if (NS_SUCCEEDED(nrv)) { michael@0: nrv = sts->IsOnCurrentThread(&onSTSThread); michael@0: } michael@0: michael@0: if (NS_FAILED(nrv)) { michael@0: NS_ERROR("Could not get STS service or IsOnCurrentThread failed"); michael@0: PR_SetError(PR_UNKNOWN_ERROR, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: // SSL_PeerStapledOCSPResponses will never return a non-empty response if michael@0: // OCSP stapling wasn't enabled because libssl wouldn't have let the server michael@0: // return a stapled OCSP response. michael@0: // We don't own these pointers. michael@0: const SECItemArray* csa = SSL_PeerStapledOCSPResponses(fd); michael@0: SECItem* stapledOCSPResponse = nullptr; michael@0: // we currently only support single stapled responses michael@0: if (csa && csa->len == 1) { michael@0: stapledOCSPResponse = &csa->items[0]; michael@0: } michael@0: michael@0: uint32_t providerFlags = 0; michael@0: socketInfo->GetProviderFlags(&providerFlags); michael@0: michael@0: if (onSTSThread) { michael@0: michael@0: // We *must* do certificate verification on a background thread because michael@0: // we need the socket transport thread to be free for our OCSP requests, michael@0: // and we *want* to do certificate verification on a background thread michael@0: // because of the performance benefits of doing so. michael@0: socketInfo->SetCertVerificationWaiting(); michael@0: SECStatus rv = SSLServerCertVerificationJob::Dispatch( michael@0: certVerifier, static_cast(fd), socketInfo, michael@0: serverCert, stapledOCSPResponse, providerFlags, now); michael@0: return rv; michael@0: } michael@0: michael@0: // We can't do certificate verification on a background thread, because the michael@0: // thread doing the network I/O may not interrupt its network I/O on receipt michael@0: // of our SSLServerCertVerificationResult event, and/or it might not even be michael@0: // a non-blocking socket. michael@0: michael@0: SECStatus rv = AuthCertificate(*certVerifier, socketInfo, serverCert, michael@0: stapledOCSPResponse, providerFlags, now); michael@0: if (rv == SECSuccess) { michael@0: Telemetry::Accumulate(Telemetry::SSL_CERT_ERROR_OVERRIDES, 1); michael@0: return SECSuccess; michael@0: } michael@0: michael@0: PRErrorCode error = PR_GetError(); michael@0: if (error != 0) { michael@0: RefPtr runnable( michael@0: CreateCertErrorRunnable(*certVerifier, error, socketInfo, serverCert, michael@0: stapledOCSPResponse, michael@0: static_cast(fd), providerFlags, michael@0: now)); michael@0: if (!runnable) { michael@0: // CreateCertErrorRunnable sets a new error code when it fails michael@0: error = PR_GetError(); michael@0: } else { michael@0: // We have to return SECSuccess or SECFailure based on the result of the michael@0: // override processing, so we must block this thread waiting for it. The michael@0: // CertErrorRunnable will NOT dispatch the result at all, since we passed michael@0: // false for CreateCertErrorRunnable's async parameter michael@0: nrv = runnable->DispatchToMainThreadAndWait(); michael@0: if (NS_FAILED(nrv)) { michael@0: NS_ERROR("Failed to dispatch CertErrorRunnable"); michael@0: PR_SetError(PR_INVALID_STATE_ERROR, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: if (!runnable->mResult) { michael@0: NS_ERROR("CertErrorRunnable did not set result"); michael@0: PR_SetError(PR_INVALID_STATE_ERROR, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: if (runnable->mResult->mErrorCode == 0) { michael@0: return SECSuccess; // cert error override occurred. michael@0: } michael@0: michael@0: // We must call SetCanceled here to set the error message type michael@0: // in case it isn't PlainErrorMessage, which is what we would michael@0: // default to if we just called michael@0: // PR_SetError(runnable->mResult->mErrorCode, 0) and returned michael@0: // SECFailure without doing this. michael@0: socketInfo->SetCanceled(runnable->mResult->mErrorCode, michael@0: runnable->mResult->mErrorMessageType); michael@0: error = runnable->mResult->mErrorCode; michael@0: } michael@0: } michael@0: michael@0: if (error == 0) { michael@0: NS_ERROR("error code not set"); michael@0: error = PR_UNKNOWN_ERROR; michael@0: } michael@0: michael@0: PR_SetError(error, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: #ifndef MOZ_NO_EV_CERTS michael@0: class InitializeIdentityInfo : public CryptoTask michael@0: { michael@0: virtual nsresult CalculateResult() MOZ_OVERRIDE michael@0: { michael@0: EnsureIdentityInfoLoaded(); michael@0: return NS_OK; michael@0: } michael@0: michael@0: virtual void ReleaseNSSResources() MOZ_OVERRIDE { } // no-op michael@0: virtual void CallCallback(nsresult rv) MOZ_OVERRIDE { } // no-op michael@0: }; michael@0: #endif michael@0: michael@0: void EnsureServerVerificationInitialized() michael@0: { michael@0: #ifndef MOZ_NO_EV_CERTS michael@0: // Should only be called from socket transport thread due to the static michael@0: // variable and the reference to gCertVerificationThreadPool michael@0: michael@0: static bool triggeredCertVerifierInit = false; michael@0: if (triggeredCertVerifierInit) michael@0: return; michael@0: triggeredCertVerifierInit = true; michael@0: michael@0: RefPtr initJob = new InitializeIdentityInfo(); michael@0: if (gCertVerificationThreadPool) michael@0: gCertVerificationThreadPool->Dispatch(initJob, NS_DISPATCH_NORMAL); michael@0: #endif michael@0: } michael@0: michael@0: SSLServerCertVerificationResult::SSLServerCertVerificationResult( michael@0: TransportSecurityInfo* infoObject, PRErrorCode errorCode, michael@0: Telemetry::ID telemetryID, uint32_t telemetryValue, michael@0: SSLErrorMessageType errorMessageType) michael@0: : mInfoObject(infoObject) michael@0: , mErrorCode(errorCode) michael@0: , mErrorMessageType(errorMessageType) michael@0: , mTelemetryID(telemetryID) michael@0: , mTelemetryValue(telemetryValue) michael@0: { michael@0: // We accumulate telemetry for (only) successful validations on the main thread michael@0: // to avoid adversely affecting performance by acquiring the mutex that we use michael@0: // when accumulating the telemetry for unsuccessful validations. Unsuccessful michael@0: // validations times are accumulated elsewhere. michael@0: MOZ_ASSERT(telemetryID == Telemetry::HistogramCount || errorCode == 0); michael@0: } michael@0: michael@0: void michael@0: SSLServerCertVerificationResult::Dispatch() michael@0: { michael@0: nsresult rv; michael@0: nsCOMPtr stsTarget michael@0: = do_GetService(NS_SOCKETTRANSPORTSERVICE_CONTRACTID, &rv); michael@0: NS_ASSERTION(stsTarget, michael@0: "Failed to get socket transport service event target"); michael@0: rv = stsTarget->Dispatch(this, NS_DISPATCH_NORMAL); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), michael@0: "Failed to dispatch SSLServerCertVerificationResult"); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: SSLServerCertVerificationResult::Run() michael@0: { michael@0: // TODO: Assert that we're on the socket transport thread michael@0: if (mTelemetryID != Telemetry::HistogramCount) { michael@0: Telemetry::Accumulate(mTelemetryID, mTelemetryValue); michael@0: } michael@0: // XXX: This cast will be removed by the next patch michael@0: ((nsNSSSocketInfo*) mInfoObject.get()) michael@0: ->SetCertVerificationResult(mErrorCode, mErrorMessageType); michael@0: return NS_OK; michael@0: } michael@0: michael@0: } } // namespace mozilla::psm