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: #include "nsNSSCallbacks.h" michael@0: #include "pkix/pkixtypes.h" michael@0: #include "mozilla/Telemetry.h" michael@0: #include "mozilla/TimeStamp.h" michael@0: #include "nsNSSComponent.h" michael@0: #include "nsNSSIOLayer.h" michael@0: #include "nsIWebProgressListener.h" michael@0: #include "nsProtectedAuthThread.h" michael@0: #include "nsITokenDialogs.h" michael@0: #include "nsIUploadChannel.h" michael@0: #include "nsIPrompt.h" michael@0: #include "nsProxyRelease.h" michael@0: #include "PSMRunnable.h" michael@0: #include "nsContentUtils.h" michael@0: #include "nsIHttpChannelInternal.h" michael@0: #include "nsISupportsPriority.h" michael@0: #include "nsNetUtil.h" michael@0: #include "SharedSSLState.h" michael@0: #include "ssl.h" michael@0: #include "sslproto.h" michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::psm; michael@0: michael@0: #ifdef PR_LOGGING michael@0: extern PRLogModuleInfo* gPIPNSSLog; michael@0: #endif michael@0: michael@0: static void AccumulateCipherSuite(Telemetry::ID probe, michael@0: const SSLChannelInfo& channelInfo); michael@0: michael@0: namespace { michael@0: michael@0: // Bits in bit mask for SSL_REASONS_FOR_NOT_FALSE_STARTING telemetry probe michael@0: // These bits are numbered so that the least subtle issues have higher values. michael@0: // This should make it easier for us to interpret the results. michael@0: const uint32_t NPN_NOT_NEGOTIATED = 64; michael@0: const uint32_t KEA_NOT_FORWARD_SECRET = 32; michael@0: const uint32_t KEA_NOT_SAME_AS_EXPECTED = 16; michael@0: const uint32_t KEA_NOT_ALLOWED = 8; michael@0: const uint32_t POSSIBLE_VERSION_DOWNGRADE = 4; michael@0: const uint32_t POSSIBLE_CIPHER_SUITE_DOWNGRADE = 2; michael@0: const uint32_t KEA_NOT_SUPPORTED = 1; michael@0: michael@0: } michael@0: michael@0: class nsHTTPDownloadEvent : public nsRunnable { michael@0: public: michael@0: nsHTTPDownloadEvent(); michael@0: ~nsHTTPDownloadEvent(); michael@0: michael@0: NS_IMETHOD Run(); michael@0: michael@0: nsNSSHttpRequestSession *mRequestSession; michael@0: michael@0: nsRefPtr mListener; michael@0: bool mResponsibleForDoneSignal; michael@0: TimeStamp mStartTime; michael@0: }; michael@0: michael@0: nsHTTPDownloadEvent::nsHTTPDownloadEvent() michael@0: :mResponsibleForDoneSignal(true) michael@0: { michael@0: } michael@0: michael@0: nsHTTPDownloadEvent::~nsHTTPDownloadEvent() michael@0: { michael@0: if (mResponsibleForDoneSignal && mListener) michael@0: mListener->send_done_signal(); michael@0: michael@0: mRequestSession->Release(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTTPDownloadEvent::Run() michael@0: { michael@0: if (!mListener) michael@0: return NS_OK; michael@0: michael@0: nsresult rv; michael@0: michael@0: nsCOMPtr ios = do_GetIOService(); michael@0: NS_ENSURE_STATE(ios); michael@0: michael@0: nsCOMPtr chan; michael@0: ios->NewChannel(mRequestSession->mURL, nullptr, nullptr, getter_AddRefs(chan)); michael@0: NS_ENSURE_STATE(chan); michael@0: michael@0: // Security operations scheduled through normal HTTP channels are given michael@0: // high priority to accommodate real time OCSP transactions. Background CRL michael@0: // fetches happen through a different path (CRLDownloadEvent). michael@0: nsCOMPtr priorityChannel = do_QueryInterface(chan); michael@0: if (priorityChannel) michael@0: priorityChannel->AdjustPriority(nsISupportsPriority::PRIORITY_HIGHEST); michael@0: michael@0: chan->SetLoadFlags(nsIRequest::LOAD_ANONYMOUS); michael@0: michael@0: // Create a loadgroup for this new channel. This way if the channel michael@0: // is redirected, we'll have a way to cancel the resulting channel. michael@0: nsCOMPtr lg = do_CreateInstance(NS_LOADGROUP_CONTRACTID); michael@0: chan->SetLoadGroup(lg); michael@0: michael@0: if (mRequestSession->mHasPostData) michael@0: { michael@0: nsCOMPtr uploadStream; michael@0: rv = NS_NewPostDataStream(getter_AddRefs(uploadStream), michael@0: false, michael@0: mRequestSession->mPostData); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: nsCOMPtr uploadChannel(do_QueryInterface(chan)); michael@0: NS_ENSURE_STATE(uploadChannel); michael@0: michael@0: rv = uploadChannel->SetUploadStream(uploadStream, michael@0: mRequestSession->mPostContentType, michael@0: -1); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: // Do not use SPDY for internal security operations. It could result michael@0: // in the silent upgrade to ssl, which in turn could require an SSL michael@0: // operation to fufill something like a CRL fetch, which is an michael@0: // endless loop. michael@0: nsCOMPtr internalChannel = do_QueryInterface(chan); michael@0: if (internalChannel) { michael@0: rv = internalChannel->SetAllowSpdy(false); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: } michael@0: michael@0: nsCOMPtr hchan = do_QueryInterface(chan); michael@0: NS_ENSURE_STATE(hchan); michael@0: michael@0: rv = hchan->SetRequestMethod(mRequestSession->mRequestMethod); michael@0: NS_ENSURE_SUCCESS(rv, rv); michael@0: michael@0: mResponsibleForDoneSignal = false; michael@0: mListener->mResponsibleForDoneSignal = true; michael@0: michael@0: mListener->mLoadGroup = lg.get(); michael@0: NS_ADDREF(mListener->mLoadGroup); michael@0: mListener->mLoadGroupOwnerThread = PR_GetCurrentThread(); michael@0: michael@0: rv = NS_NewStreamLoader(getter_AddRefs(mListener->mLoader), michael@0: mListener); michael@0: michael@0: if (NS_SUCCEEDED(rv)) { michael@0: mStartTime = TimeStamp::Now(); michael@0: rv = hchan->AsyncOpen(mListener->mLoader, nullptr); michael@0: } michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: mListener->mResponsibleForDoneSignal = false; michael@0: mResponsibleForDoneSignal = true; michael@0: michael@0: NS_RELEASE(mListener->mLoadGroup); michael@0: mListener->mLoadGroup = nullptr; michael@0: mListener->mLoadGroupOwnerThread = nullptr; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: struct nsCancelHTTPDownloadEvent : nsRunnable { michael@0: nsRefPtr mListener; michael@0: michael@0: NS_IMETHOD Run() { michael@0: mListener->FreeLoadGroup(true); michael@0: mListener = nullptr; michael@0: return NS_OK; michael@0: } michael@0: }; michael@0: michael@0: SECStatus nsNSSHttpServerSession::createSessionFcn(const char *host, michael@0: uint16_t portnum, michael@0: SEC_HTTP_SERVER_SESSION *pSession) michael@0: { michael@0: if (!host || !pSession) michael@0: return SECFailure; michael@0: michael@0: nsNSSHttpServerSession *hss = new nsNSSHttpServerSession; michael@0: if (!hss) michael@0: return SECFailure; michael@0: michael@0: hss->mHost = host; michael@0: hss->mPort = portnum; michael@0: michael@0: *pSession = hss; michael@0: return SECSuccess; michael@0: } michael@0: michael@0: SECStatus nsNSSHttpRequestSession::createFcn(SEC_HTTP_SERVER_SESSION session, michael@0: const char *http_protocol_variant, michael@0: const char *path_and_query_string, michael@0: const char *http_request_method, michael@0: const PRIntervalTime timeout, michael@0: SEC_HTTP_REQUEST_SESSION *pRequest) michael@0: { michael@0: if (!session || !http_protocol_variant || !path_and_query_string || michael@0: !http_request_method || !pRequest) michael@0: return SECFailure; michael@0: michael@0: nsNSSHttpServerSession* hss = static_cast(session); michael@0: if (!hss) michael@0: return SECFailure; michael@0: michael@0: nsNSSHttpRequestSession *rs = new nsNSSHttpRequestSession; michael@0: if (!rs) michael@0: return SECFailure; michael@0: michael@0: rs->mTimeoutInterval = timeout; michael@0: michael@0: // Use a maximum timeout value of 10 seconds because of bug 404059. michael@0: // FIXME: Use a better approach once 406120 is ready. michael@0: uint32_t maxBug404059Timeout = PR_TicksPerSecond() * 10; michael@0: if (timeout > maxBug404059Timeout) { michael@0: rs->mTimeoutInterval = maxBug404059Timeout; michael@0: } michael@0: michael@0: rs->mURL.Assign(http_protocol_variant); michael@0: rs->mURL.AppendLiteral("://"); michael@0: rs->mURL.Append(hss->mHost); michael@0: rs->mURL.AppendLiteral(":"); michael@0: rs->mURL.AppendInt(hss->mPort); michael@0: rs->mURL.Append(path_and_query_string); michael@0: michael@0: rs->mRequestMethod = http_request_method; michael@0: michael@0: *pRequest = (void*)rs; michael@0: return SECSuccess; michael@0: } michael@0: michael@0: SECStatus nsNSSHttpRequestSession::setPostDataFcn(const char *http_data, michael@0: const uint32_t http_data_len, michael@0: const char *http_content_type) michael@0: { michael@0: mHasPostData = true; michael@0: mPostData.Assign(http_data, http_data_len); michael@0: mPostContentType.Assign(http_content_type); michael@0: michael@0: return SECSuccess; michael@0: } michael@0: michael@0: SECStatus nsNSSHttpRequestSession::addHeaderFcn(const char *http_header_name, michael@0: const char *http_header_value) michael@0: { michael@0: return SECFailure; // not yet implemented michael@0: michael@0: // All http code needs to be postponed to the UI thread. michael@0: // Once this gets implemented, we need to add a string list member to michael@0: // nsNSSHttpRequestSession and queue up the headers, michael@0: // so they can be added in HandleHTTPDownloadPLEvent. michael@0: // michael@0: // The header will need to be set using michael@0: // mHttpChannel->SetRequestHeader(nsDependentCString(http_header_name), michael@0: // nsDependentCString(http_header_value), michael@0: // false))); michael@0: } michael@0: michael@0: SECStatus nsNSSHttpRequestSession::trySendAndReceiveFcn(PRPollDesc **pPollDesc, michael@0: uint16_t *http_response_code, michael@0: const char **http_response_content_type, michael@0: const char **http_response_headers, michael@0: const char **http_response_data, michael@0: uint32_t *http_response_data_len) michael@0: { michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, michael@0: ("nsNSSHttpRequestSession::trySendAndReceiveFcn to %s\n", mURL.get())); 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_FAILED(nrv)) { michael@0: NS_ERROR("Could not get STS service"); michael@0: PR_SetError(PR_INVALID_STATE_ERROR, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: nrv = sts->IsOnCurrentThread(&onSTSThread); michael@0: if (NS_FAILED(nrv)) { michael@0: NS_ERROR("IsOnCurrentThread failed"); michael@0: PR_SetError(PR_INVALID_STATE_ERROR, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: if (onSTSThread) { michael@0: NS_ERROR("nsNSSHttpRequestSession::trySendAndReceiveFcn called on socket " michael@0: "thread; this will not work."); michael@0: PR_SetError(PR_INVALID_STATE_ERROR, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: const int max_retries = 2; michael@0: int retry_count = 0; michael@0: bool retryable_error = false; michael@0: SECStatus result_sec_status = SECFailure; michael@0: michael@0: do michael@0: { michael@0: if (retry_count > 0) michael@0: { michael@0: if (retryable_error) michael@0: { michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, michael@0: ("nsNSSHttpRequestSession::trySendAndReceiveFcn - sleeping and retrying: %d of %d\n", michael@0: retry_count, max_retries)); michael@0: } michael@0: michael@0: PR_Sleep( PR_MillisecondsToInterval(300) * retry_count ); michael@0: } michael@0: michael@0: ++retry_count; michael@0: retryable_error = false; michael@0: michael@0: result_sec_status = michael@0: internal_send_receive_attempt(retryable_error, pPollDesc, http_response_code, michael@0: http_response_content_type, http_response_headers, michael@0: http_response_data, http_response_data_len); michael@0: } michael@0: while (retryable_error && michael@0: retry_count < max_retries); michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (retry_count > 1) michael@0: { michael@0: if (retryable_error) michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, michael@0: ("nsNSSHttpRequestSession::trySendAndReceiveFcn - still failing, giving up...\n")); michael@0: else michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, michael@0: ("nsNSSHttpRequestSession::trySendAndReceiveFcn - success at attempt %d\n", michael@0: retry_count)); michael@0: } michael@0: #endif michael@0: michael@0: return result_sec_status; michael@0: } michael@0: michael@0: void michael@0: nsNSSHttpRequestSession::AddRef() michael@0: { michael@0: ++mRefCount; michael@0: } michael@0: michael@0: void michael@0: nsNSSHttpRequestSession::Release() michael@0: { michael@0: int32_t newRefCount = --mRefCount; michael@0: if (!newRefCount) { michael@0: delete this; michael@0: } michael@0: } michael@0: michael@0: SECStatus michael@0: nsNSSHttpRequestSession::internal_send_receive_attempt(bool &retryable_error, michael@0: PRPollDesc **pPollDesc, michael@0: uint16_t *http_response_code, michael@0: const char **http_response_content_type, michael@0: const char **http_response_headers, michael@0: const char **http_response_data, michael@0: uint32_t *http_response_data_len) michael@0: { michael@0: if (pPollDesc) *pPollDesc = nullptr; michael@0: if (http_response_code) *http_response_code = 0; michael@0: if (http_response_content_type) *http_response_content_type = 0; michael@0: if (http_response_headers) *http_response_headers = 0; michael@0: if (http_response_data) *http_response_data = 0; michael@0: michael@0: uint32_t acceptableResultSize = 0; michael@0: michael@0: if (http_response_data_len) michael@0: { michael@0: acceptableResultSize = *http_response_data_len; michael@0: *http_response_data_len = 0; michael@0: } michael@0: michael@0: if (!mListener) michael@0: return SECFailure; michael@0: michael@0: Mutex& waitLock = mListener->mLock; michael@0: CondVar& waitCondition = mListener->mCondition; michael@0: volatile bool &waitFlag = mListener->mWaitFlag; michael@0: waitFlag = true; michael@0: michael@0: RefPtr event(new nsHTTPDownloadEvent); michael@0: if (!event) michael@0: return SECFailure; michael@0: michael@0: event->mListener = mListener; michael@0: this->AddRef(); michael@0: event->mRequestSession = this; michael@0: michael@0: nsresult rv = NS_DispatchToMainThread(event); michael@0: if (NS_FAILED(rv)) michael@0: { michael@0: event->mResponsibleForDoneSignal = false; michael@0: return SECFailure; michael@0: } michael@0: michael@0: bool request_canceled = false; michael@0: michael@0: { michael@0: MutexAutoLock locker(waitLock); michael@0: michael@0: const PRIntervalTime start_time = PR_IntervalNow(); michael@0: PRIntervalTime wait_interval; michael@0: michael@0: bool running_on_main_thread = NS_IsMainThread(); michael@0: if (running_on_main_thread) michael@0: { michael@0: // The result of running this on the main thread michael@0: // is a series of small timeouts mixed with spinning the michael@0: // event loop - this is always dangerous as there is so much main michael@0: // thread code that does not expect to be called re-entrantly. Your michael@0: // app really shouldn't do that. michael@0: NS_WARNING("Security network blocking I/O on Main Thread"); michael@0: michael@0: // let's process events quickly michael@0: wait_interval = PR_MicrosecondsToInterval(50); michael@0: } michael@0: else michael@0: { michael@0: // On a secondary thread, it's fine to wait some more for michael@0: // for the condition variable. michael@0: wait_interval = PR_MillisecondsToInterval(250); michael@0: } michael@0: michael@0: while (waitFlag) michael@0: { michael@0: if (running_on_main_thread) michael@0: { michael@0: // Networking runs on the main thread, which we happen to block here. michael@0: // Processing events will allow the OCSP networking to run while we michael@0: // are waiting. Thanks a lot to Darin Fisher for rewriting the michael@0: // thread manager. Thanks a lot to Christian Biesinger who michael@0: // made me aware of this possibility. (kaie) michael@0: michael@0: MutexAutoUnlock unlock(waitLock); michael@0: NS_ProcessNextEvent(nullptr); michael@0: } michael@0: michael@0: waitCondition.Wait(wait_interval); michael@0: michael@0: if (!waitFlag) michael@0: break; michael@0: michael@0: if (!request_canceled) michael@0: { michael@0: bool timeout = michael@0: (PRIntervalTime)(PR_IntervalNow() - start_time) > mTimeoutInterval; michael@0: michael@0: if (timeout) michael@0: { michael@0: request_canceled = true; michael@0: michael@0: RefPtr cancelevent( michael@0: new nsCancelHTTPDownloadEvent); michael@0: cancelevent->mListener = mListener; michael@0: rv = NS_DispatchToMainThread(cancelevent); michael@0: if (NS_FAILED(rv)) { michael@0: NS_WARNING("cannot post cancel event"); michael@0: } michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!event->mStartTime.IsNull()) { michael@0: if (request_canceled) { michael@0: Telemetry::Accumulate(Telemetry::CERT_VALIDATION_HTTP_REQUEST_RESULT, 0); michael@0: Telemetry::AccumulateTimeDelta( michael@0: Telemetry::CERT_VALIDATION_HTTP_REQUEST_CANCELED_TIME, michael@0: event->mStartTime, TimeStamp::Now()); michael@0: } michael@0: else if (NS_SUCCEEDED(mListener->mResultCode) && michael@0: mListener->mHttpResponseCode == 200) { michael@0: Telemetry::Accumulate(Telemetry::CERT_VALIDATION_HTTP_REQUEST_RESULT, 1); michael@0: Telemetry::AccumulateTimeDelta( michael@0: Telemetry::CERT_VALIDATION_HTTP_REQUEST_SUCCEEDED_TIME, michael@0: event->mStartTime, TimeStamp::Now()); michael@0: } michael@0: else { michael@0: Telemetry::Accumulate(Telemetry::CERT_VALIDATION_HTTP_REQUEST_RESULT, 2); michael@0: Telemetry::AccumulateTimeDelta( michael@0: Telemetry::CERT_VALIDATION_HTTP_REQUEST_FAILED_TIME, michael@0: event->mStartTime, TimeStamp::Now()); michael@0: } michael@0: } michael@0: else { michael@0: Telemetry::Accumulate(Telemetry::CERT_VALIDATION_HTTP_REQUEST_RESULT, 3); michael@0: } michael@0: michael@0: if (request_canceled) michael@0: return SECFailure; michael@0: michael@0: if (NS_FAILED(mListener->mResultCode)) michael@0: { michael@0: if (mListener->mResultCode == NS_ERROR_CONNECTION_REFUSED michael@0: || michael@0: mListener->mResultCode == NS_ERROR_NET_RESET) michael@0: { michael@0: retryable_error = true; michael@0: } michael@0: return SECFailure; michael@0: } michael@0: michael@0: if (http_response_code) michael@0: *http_response_code = mListener->mHttpResponseCode; michael@0: michael@0: if (mListener->mHttpRequestSucceeded && http_response_data && http_response_data_len) { michael@0: michael@0: *http_response_data_len = mListener->mResultLen; michael@0: michael@0: // acceptableResultSize == 0 means: any size is acceptable michael@0: if (acceptableResultSize != 0 michael@0: && michael@0: acceptableResultSize < mListener->mResultLen) michael@0: { michael@0: return SECFailure; michael@0: } michael@0: michael@0: // return data by reference, result data will be valid michael@0: // until "this" gets destroyed by NSS michael@0: *http_response_data = (const char*)mListener->mResultData; michael@0: } michael@0: michael@0: if (mListener->mHttpRequestSucceeded && http_response_content_type) { michael@0: if (mListener->mHttpResponseContentType.Length()) { michael@0: *http_response_content_type = mListener->mHttpResponseContentType.get(); michael@0: } michael@0: } michael@0: michael@0: return SECSuccess; michael@0: } michael@0: michael@0: SECStatus nsNSSHttpRequestSession::cancelFcn() michael@0: { michael@0: // As of today, only the blocking variant of the http interface michael@0: // has been implemented. Implementing cancelFcn will be necessary michael@0: // as soon as we implement the nonblocking variant. michael@0: return SECSuccess; michael@0: } michael@0: michael@0: SECStatus nsNSSHttpRequestSession::freeFcn() michael@0: { michael@0: Release(); michael@0: return SECSuccess; michael@0: } michael@0: michael@0: nsNSSHttpRequestSession::nsNSSHttpRequestSession() michael@0: : mRefCount(1), michael@0: mHasPostData(false), michael@0: mTimeoutInterval(0), michael@0: mListener(new nsHTTPListener) michael@0: { michael@0: } michael@0: michael@0: nsNSSHttpRequestSession::~nsNSSHttpRequestSession() michael@0: { michael@0: } michael@0: michael@0: SEC_HttpClientFcn nsNSSHttpInterface::sNSSInterfaceTable; michael@0: michael@0: void nsNSSHttpInterface::initTable() michael@0: { michael@0: sNSSInterfaceTable.version = 1; michael@0: SEC_HttpClientFcnV1 &v1 = sNSSInterfaceTable.fcnTable.ftable1; michael@0: v1.createSessionFcn = createSessionFcn; michael@0: v1.keepAliveSessionFcn = keepAliveFcn; michael@0: v1.freeSessionFcn = freeSessionFcn; michael@0: v1.createFcn = createFcn; michael@0: v1.setPostDataFcn = setPostDataFcn; michael@0: v1.addHeaderFcn = addHeaderFcn; michael@0: v1.trySendAndReceiveFcn = trySendAndReceiveFcn; michael@0: v1.cancelFcn = cancelFcn; michael@0: v1.freeFcn = freeFcn; michael@0: } michael@0: michael@0: void nsNSSHttpInterface::registerHttpClient() michael@0: { michael@0: SEC_RegisterDefaultHttpClient(&sNSSInterfaceTable); michael@0: } michael@0: michael@0: void nsNSSHttpInterface::unregisterHttpClient() michael@0: { michael@0: SEC_RegisterDefaultHttpClient(nullptr); michael@0: } michael@0: michael@0: nsHTTPListener::nsHTTPListener() michael@0: : mResultData(nullptr), michael@0: mResultLen(0), michael@0: mLock("nsHTTPListener.mLock"), michael@0: mCondition(mLock, "nsHTTPListener.mCondition"), michael@0: mWaitFlag(true), michael@0: mResponsibleForDoneSignal(false), michael@0: mLoadGroup(nullptr), michael@0: mLoadGroupOwnerThread(nullptr) michael@0: { michael@0: } michael@0: michael@0: nsHTTPListener::~nsHTTPListener() michael@0: { michael@0: if (mResponsibleForDoneSignal) michael@0: send_done_signal(); michael@0: michael@0: if (mResultData) { michael@0: NS_Free(const_cast(mResultData)); michael@0: } michael@0: michael@0: if (mLoader) { michael@0: nsCOMPtr mainThread(do_GetMainThread()); michael@0: NS_ProxyRelease(mainThread, mLoader); michael@0: } michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS(nsHTTPListener, nsIStreamLoaderObserver) michael@0: michael@0: void michael@0: nsHTTPListener::FreeLoadGroup(bool aCancelLoad) michael@0: { michael@0: nsILoadGroup *lg = nullptr; michael@0: michael@0: MutexAutoLock locker(mLock); michael@0: michael@0: if (mLoadGroup) { michael@0: if (mLoadGroupOwnerThread != PR_GetCurrentThread()) { michael@0: NS_ASSERTION(false, michael@0: "attempt to access nsHTTPDownloadEvent::mLoadGroup on multiple threads, leaking it!"); michael@0: } michael@0: else { michael@0: lg = mLoadGroup; michael@0: mLoadGroup = nullptr; michael@0: } michael@0: } michael@0: michael@0: if (lg) { michael@0: if (aCancelLoad) { michael@0: lg->Cancel(NS_ERROR_ABORT); michael@0: } michael@0: NS_RELEASE(lg); michael@0: } michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsHTTPListener::OnStreamComplete(nsIStreamLoader* aLoader, michael@0: nsISupports* aContext, michael@0: nsresult aStatus, michael@0: uint32_t stringLen, michael@0: const uint8_t* string) michael@0: { michael@0: mResultCode = aStatus; michael@0: michael@0: FreeLoadGroup(false); michael@0: michael@0: nsCOMPtr req; michael@0: nsCOMPtr hchan; michael@0: michael@0: nsresult rv = aLoader->GetRequest(getter_AddRefs(req)); michael@0: michael@0: #ifdef PR_LOGGING michael@0: if (NS_FAILED(aStatus)) michael@0: { michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, michael@0: ("nsHTTPListener::OnStreamComplete status failed %d", aStatus)); michael@0: } michael@0: #endif michael@0: michael@0: if (NS_SUCCEEDED(rv)) michael@0: hchan = do_QueryInterface(req, &rv); michael@0: michael@0: if (NS_SUCCEEDED(rv)) michael@0: { michael@0: rv = hchan->GetRequestSucceeded(&mHttpRequestSucceeded); michael@0: if (NS_FAILED(rv)) michael@0: mHttpRequestSucceeded = false; michael@0: michael@0: mResultLen = stringLen; michael@0: mResultData = string; // take ownership of allocation michael@0: aStatus = NS_SUCCESS_ADOPTED_DATA; michael@0: michael@0: unsigned int rcode; michael@0: rv = hchan->GetResponseStatus(&rcode); michael@0: if (NS_FAILED(rv)) michael@0: mHttpResponseCode = 500; michael@0: else michael@0: mHttpResponseCode = rcode; michael@0: michael@0: hchan->GetResponseHeader(NS_LITERAL_CSTRING("Content-Type"), michael@0: mHttpResponseContentType); michael@0: } michael@0: michael@0: if (mResponsibleForDoneSignal) michael@0: send_done_signal(); michael@0: michael@0: return aStatus; michael@0: } michael@0: michael@0: void nsHTTPListener::send_done_signal() michael@0: { michael@0: mResponsibleForDoneSignal = false; michael@0: michael@0: { michael@0: MutexAutoLock locker(mLock); michael@0: mWaitFlag = false; michael@0: mCondition.NotifyAll(); michael@0: } michael@0: } michael@0: michael@0: static char* michael@0: ShowProtectedAuthPrompt(PK11SlotInfo* slot, nsIInterfaceRequestor *ir) michael@0: { michael@0: if (!NS_IsMainThread()) { michael@0: NS_ERROR("ShowProtectedAuthPrompt called off the main thread"); michael@0: return nullptr; michael@0: } michael@0: michael@0: char* protAuthRetVal = nullptr; michael@0: michael@0: // Get protected auth dialogs michael@0: nsITokenDialogs* dialogs = 0; michael@0: nsresult nsrv = getNSSDialogs((void**)&dialogs, michael@0: NS_GET_IID(nsITokenDialogs), michael@0: NS_TOKENDIALOGS_CONTRACTID); michael@0: if (NS_SUCCEEDED(nsrv)) michael@0: { michael@0: nsProtectedAuthThread* protectedAuthRunnable = new nsProtectedAuthThread(); michael@0: if (protectedAuthRunnable) michael@0: { michael@0: NS_ADDREF(protectedAuthRunnable); michael@0: michael@0: protectedAuthRunnable->SetParams(slot); michael@0: michael@0: nsCOMPtr runnable = do_QueryInterface(protectedAuthRunnable); michael@0: if (runnable) michael@0: { michael@0: nsrv = dialogs->DisplayProtectedAuth(ir, runnable); michael@0: michael@0: // We call join on the thread, michael@0: // so we can be sure that no simultaneous access will happen. michael@0: protectedAuthRunnable->Join(); michael@0: michael@0: if (NS_SUCCEEDED(nsrv)) michael@0: { michael@0: SECStatus rv = protectedAuthRunnable->GetResult(); michael@0: switch (rv) michael@0: { michael@0: case SECSuccess: michael@0: protAuthRetVal = ToNewCString(nsDependentCString(PK11_PW_AUTHENTICATED)); michael@0: break; michael@0: case SECWouldBlock: michael@0: protAuthRetVal = ToNewCString(nsDependentCString(PK11_PW_RETRY)); michael@0: break; michael@0: default: michael@0: protAuthRetVal = nullptr; michael@0: break; michael@0: michael@0: } michael@0: } michael@0: } michael@0: michael@0: NS_RELEASE(protectedAuthRunnable); michael@0: } michael@0: michael@0: NS_RELEASE(dialogs); michael@0: } michael@0: michael@0: return protAuthRetVal; michael@0: } michael@0: michael@0: class PK11PasswordPromptRunnable : public SyncRunnableBase michael@0: { michael@0: public: michael@0: PK11PasswordPromptRunnable(PK11SlotInfo* slot, michael@0: nsIInterfaceRequestor* ir) michael@0: : mResult(nullptr), michael@0: mSlot(slot), michael@0: mIR(ir) michael@0: { michael@0: } michael@0: char * mResult; // out michael@0: virtual void RunOnTargetThread(); michael@0: private: michael@0: PK11SlotInfo* const mSlot; // in michael@0: nsIInterfaceRequestor* const mIR; // in michael@0: }; michael@0: michael@0: void PK11PasswordPromptRunnable::RunOnTargetThread() michael@0: { michael@0: static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID); michael@0: michael@0: nsNSSShutDownPreventionLock locker; michael@0: nsresult rv = NS_OK; michael@0: char16_t *password = nullptr; michael@0: bool value = false; michael@0: nsCOMPtr prompt; michael@0: michael@0: /* TODO: Retry should generate a different dialog message */ michael@0: /* michael@0: if (retry) michael@0: return nullptr; michael@0: */ michael@0: michael@0: if (!mIR) michael@0: { michael@0: nsNSSComponent::GetNewPrompter(getter_AddRefs(prompt)); michael@0: } michael@0: else michael@0: { michael@0: prompt = do_GetInterface(mIR); michael@0: NS_ASSERTION(prompt, "callbacks does not implement nsIPrompt"); michael@0: } michael@0: michael@0: if (!prompt) michael@0: return; michael@0: michael@0: if (PK11_ProtectedAuthenticationPath(mSlot)) { michael@0: mResult = ShowProtectedAuthPrompt(mSlot, mIR); michael@0: return; michael@0: } michael@0: michael@0: nsAutoString promptString; michael@0: nsCOMPtr nssComponent(do_GetService(kNSSComponentCID, &rv)); michael@0: michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: const char16_t* formatStrings[1] = { michael@0: ToNewUnicode(NS_ConvertUTF8toUTF16(PK11_GetTokenName(mSlot))) michael@0: }; michael@0: rv = nssComponent->PIPBundleFormatStringFromName("CertPassPrompt", michael@0: formatStrings, 1, michael@0: promptString); michael@0: nsMemory::Free(const_cast(formatStrings[0])); michael@0: michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: { michael@0: nsPSMUITracker tracker; michael@0: if (tracker.isUIForbidden()) { michael@0: rv = NS_ERROR_NOT_AVAILABLE; michael@0: } michael@0: else { michael@0: // Although the exact value is ignored, we must not pass invalid michael@0: // bool values through XPConnect. michael@0: bool checkState = false; michael@0: rv = prompt->PromptPassword(nullptr, promptString.get(), michael@0: &password, nullptr, &checkState, &value); michael@0: } michael@0: } michael@0: michael@0: if (NS_SUCCEEDED(rv) && value) { michael@0: mResult = ToNewUTF8String(nsDependentString(password)); michael@0: NS_Free(password); michael@0: } michael@0: } michael@0: michael@0: char* michael@0: PK11PasswordPrompt(PK11SlotInfo* slot, PRBool retry, void* arg) michael@0: { michael@0: RefPtr runnable( michael@0: new PK11PasswordPromptRunnable(slot, michael@0: static_cast(arg))); michael@0: runnable->DispatchToMainThreadAndWait(); michael@0: return runnable->mResult; michael@0: } michael@0: michael@0: // call with shutdown prevention lock held michael@0: static void michael@0: PreliminaryHandshakeDone(PRFileDesc* fd) michael@0: { michael@0: nsNSSSocketInfo* infoObject = (nsNSSSocketInfo*) fd->higher->secret; michael@0: if (!infoObject) michael@0: return; michael@0: michael@0: if (infoObject->IsPreliminaryHandshakeDone()) michael@0: return; michael@0: michael@0: infoObject->SetPreliminaryHandshakeDone(); michael@0: michael@0: SSLChannelInfo channelInfo; michael@0: if (SSL_GetChannelInfo(fd, &channelInfo, sizeof(channelInfo)) == SECSuccess) { michael@0: infoObject->SetSSLVersionUsed(channelInfo.protocolVersion); michael@0: } michael@0: michael@0: // Get the NPN value. michael@0: SSLNextProtoState state; michael@0: unsigned char npnbuf[256]; michael@0: unsigned int npnlen; michael@0: michael@0: if (SSL_GetNextProto(fd, &state, npnbuf, &npnlen, 256) == SECSuccess) { michael@0: if (state == SSL_NEXT_PROTO_NEGOTIATED || michael@0: state == SSL_NEXT_PROTO_SELECTED) { michael@0: infoObject->SetNegotiatedNPN(reinterpret_cast(npnbuf), npnlen); michael@0: } michael@0: else { michael@0: infoObject->SetNegotiatedNPN(nullptr, 0); michael@0: } michael@0: mozilla::Telemetry::Accumulate(Telemetry::SSL_NPN_TYPE, state); michael@0: } michael@0: else { michael@0: infoObject->SetNegotiatedNPN(nullptr, 0); michael@0: } michael@0: } michael@0: michael@0: SECStatus michael@0: CanFalseStartCallback(PRFileDesc* fd, void* client_data, PRBool *canFalseStart) michael@0: { michael@0: *canFalseStart = false; michael@0: michael@0: nsNSSShutDownPreventionLock locker; michael@0: michael@0: nsNSSSocketInfo* infoObject = (nsNSSSocketInfo*) fd->higher->secret; michael@0: if (!infoObject) { michael@0: PR_SetError(PR_INVALID_STATE_ERROR, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: infoObject->SetFalseStartCallbackCalled(); michael@0: michael@0: if (infoObject->isAlreadyShutDown()) { michael@0: MOZ_CRASH("SSL socket used after NSS shut down"); michael@0: PR_SetError(PR_INVALID_STATE_ERROR, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: PreliminaryHandshakeDone(fd); michael@0: michael@0: uint32_t reasonsForNotFalseStarting = 0; michael@0: michael@0: SSLChannelInfo channelInfo; michael@0: if (SSL_GetChannelInfo(fd, &channelInfo, sizeof(channelInfo)) != SECSuccess) { michael@0: return SECSuccess; michael@0: } michael@0: michael@0: SSLCipherSuiteInfo cipherInfo; michael@0: if (SSL_GetCipherSuiteInfo(channelInfo.cipherSuite, &cipherInfo, michael@0: sizeof (cipherInfo)) != SECSuccess) { michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("CanFalseStartCallback [%p] failed - " michael@0: " KEA %d\n", fd, michael@0: static_cast(cipherInfo.keaType))); michael@0: return SECSuccess; michael@0: } michael@0: michael@0: nsSSLIOLayerHelpers& helpers = infoObject->SharedState().IOLayerHelpers(); michael@0: michael@0: // Prevent version downgrade attacks from TLS 1.x to SSL 3.0. michael@0: // TODO(bug 861310): If we negotiate less than our highest-supported version, michael@0: // then check that a previously-completed handshake negotiated that version; michael@0: // eventually, require that the highest-supported version of TLS is used. michael@0: if (channelInfo.protocolVersion < SSL_LIBRARY_VERSION_TLS_1_0) { michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("CanFalseStartCallback [%p] failed - " michael@0: "SSL Version must be >= TLS1 %x\n", fd, michael@0: static_cast(channelInfo.protocolVersion))); michael@0: reasonsForNotFalseStarting |= POSSIBLE_VERSION_DOWNGRADE; michael@0: } michael@0: michael@0: // never do false start without one of these key exchange algorithms michael@0: if (cipherInfo.keaType != ssl_kea_rsa && michael@0: cipherInfo.keaType != ssl_kea_dh && michael@0: cipherInfo.keaType != ssl_kea_ecdh) { michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("CanFalseStartCallback [%p] failed - " michael@0: "unsupported KEA %d\n", fd, michael@0: static_cast(cipherInfo.keaType))); michael@0: reasonsForNotFalseStarting |= KEA_NOT_SUPPORTED; michael@0: } michael@0: michael@0: // XXX: This assumes that all TLS_DH_* and TLS_ECDH_* cipher suites michael@0: // are disabled. michael@0: if (cipherInfo.keaType != ssl_kea_ecdh && michael@0: cipherInfo.keaType != ssl_kea_dh) { michael@0: if (helpers.mFalseStartRequireForwardSecrecy) { michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, michael@0: ("CanFalseStartCallback [%p] failed - KEA used is %d, but " michael@0: "require-forward-secrecy configured.\n", fd, michael@0: static_cast(cipherInfo.keaType))); michael@0: reasonsForNotFalseStarting |= KEA_NOT_FORWARD_SECRET; michael@0: } else if (cipherInfo.keaType == ssl_kea_rsa) { michael@0: // Make sure we've seen the same kea from this host in the past, to limit michael@0: // the potential for downgrade attacks. michael@0: int16_t expected = infoObject->GetKEAExpected(); michael@0: if (cipherInfo.keaType != expected) { michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, michael@0: ("CanFalseStartCallback [%p] failed - " michael@0: "KEA used is %d, expected %d\n", fd, michael@0: static_cast(cipherInfo.keaType), michael@0: static_cast(expected))); michael@0: reasonsForNotFalseStarting |= KEA_NOT_SAME_AS_EXPECTED; michael@0: } michael@0: } else { michael@0: reasonsForNotFalseStarting |= KEA_NOT_ALLOWED; michael@0: } michael@0: } michael@0: michael@0: // Prevent downgrade attacks on the symmetric cipher. We accept downgrades michael@0: // from 256-bit keys to 128-bit keys and we treat AES and Camellia as being michael@0: // equally secure. We consider every message authentication mechanism that we michael@0: // support *for these ciphers* to be equally-secure. We assume that for CBC michael@0: // mode, that the server has implemented all the same mitigations for michael@0: // published attacks that we have, or that those attacks are not relevant in michael@0: // the decision to false start. michael@0: if (cipherInfo.symCipher != ssl_calg_aes_gcm && michael@0: cipherInfo.symCipher != ssl_calg_aes && michael@0: cipherInfo.symCipher != ssl_calg_camellia) { michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, michael@0: ("CanFalseStartCallback [%p] failed - Symmetric cipher used, %d, " michael@0: "is not supported with False Start.\n", fd, michael@0: static_cast(cipherInfo.symCipher))); michael@0: reasonsForNotFalseStarting |= POSSIBLE_CIPHER_SUITE_DOWNGRADE; michael@0: } michael@0: michael@0: // XXX: An attacker can choose which protocols are advertised in the michael@0: // NPN extension. TODO(Bug 861311): We should restrict the ability michael@0: // of an attacker leverage this capability by restricting false start michael@0: // to the same protocol we previously saw for the server, after the michael@0: // first successful connection to the server. michael@0: michael@0: // Enforce NPN to do false start if policy requires it. Do this as an michael@0: // indicator if server compatibility. michael@0: if (helpers.mFalseStartRequireNPN) { michael@0: nsAutoCString negotiatedNPN; michael@0: if (NS_FAILED(infoObject->GetNegotiatedNPN(negotiatedNPN)) || michael@0: !negotiatedNPN.Length()) { michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("CanFalseStartCallback [%p] failed - " michael@0: "NPN cannot be verified\n", fd)); michael@0: reasonsForNotFalseStarting |= NPN_NOT_NEGOTIATED; michael@0: } michael@0: } michael@0: michael@0: Telemetry::Accumulate(Telemetry::SSL_REASONS_FOR_NOT_FALSE_STARTING, michael@0: reasonsForNotFalseStarting); michael@0: michael@0: if (reasonsForNotFalseStarting == 0) { michael@0: *canFalseStart = PR_TRUE; michael@0: infoObject->SetFalseStarted(); michael@0: infoObject->NoteTimeUntilReady(); michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("CanFalseStartCallback [%p] ok\n", fd)); michael@0: } michael@0: michael@0: return SECSuccess; michael@0: } michael@0: michael@0: static void michael@0: AccumulateNonECCKeySize(Telemetry::ID probe, uint32_t bits) michael@0: { michael@0: unsigned int value = bits < 512 ? 1 : bits == 512 ? 2 michael@0: : bits < 768 ? 3 : bits == 768 ? 4 michael@0: : bits < 1024 ? 5 : bits == 1024 ? 6 michael@0: : bits < 1280 ? 7 : bits == 1280 ? 8 michael@0: : bits < 1536 ? 9 : bits == 1536 ? 10 michael@0: : bits < 2048 ? 11 : bits == 2048 ? 12 michael@0: : bits < 3072 ? 13 : bits == 3072 ? 14 michael@0: : bits < 4096 ? 15 : bits == 4096 ? 16 michael@0: : bits < 8192 ? 17 : bits == 8192 ? 18 michael@0: : bits < 16384 ? 19 : bits == 16384 ? 20 michael@0: : 0; michael@0: Telemetry::Accumulate(probe, value); michael@0: } michael@0: michael@0: // XXX: This attempts to map a bit count to an ECC named curve identifier. In michael@0: // the vast majority of situations, we only have the Suite B curves available. michael@0: // In that case, this mapping works fine. If we were to have more curves michael@0: // available, the mapping would be ambiguous since there could be multiple michael@0: // named curves for a given size (e.g. secp256k1 vs. secp256r1). We punt on michael@0: // that for now. See also NSS bug 323674. michael@0: static void michael@0: AccumulateECCCurve(Telemetry::ID probe, uint32_t bits) michael@0: { michael@0: unsigned int value = bits == 256 ? 23 // P-256 michael@0: : bits == 384 ? 24 // P-384 michael@0: : bits == 521 ? 25 // P-521 michael@0: : 0; // Unknown michael@0: Telemetry::Accumulate(probe, value); michael@0: } michael@0: michael@0: static void michael@0: AccumulateCipherSuite(Telemetry::ID probe, const SSLChannelInfo& channelInfo) michael@0: { michael@0: uint32_t value; michael@0: switch (channelInfo.cipherSuite) { michael@0: // ECDHE key exchange michael@0: case TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256: value = 1; break; michael@0: case TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256: value = 2; break; michael@0: case TLS_ECDHE_RSA_WITH_AES_128_CBC_SHA: value = 3; break; michael@0: case TLS_ECDHE_ECDSA_WITH_AES_128_CBC_SHA: value = 4; break; michael@0: case TLS_ECDHE_RSA_WITH_AES_256_CBC_SHA: value = 5; break; michael@0: case TLS_ECDHE_ECDSA_WITH_AES_256_CBC_SHA: value = 6; break; michael@0: case TLS_ECDHE_RSA_WITH_3DES_EDE_CBC_SHA: value = 7; break; michael@0: case TLS_ECDHE_RSA_WITH_RC4_128_SHA: value = 8; break; michael@0: case TLS_ECDHE_ECDSA_WITH_RC4_128_SHA: value = 9; break; michael@0: case TLS_ECDHE_ECDSA_WITH_3DES_EDE_CBC_SHA: value = 10; break; michael@0: // DHE key exchange michael@0: case TLS_DHE_RSA_WITH_AES_128_CBC_SHA: value = 21; break; michael@0: case TLS_DHE_RSA_WITH_CAMELLIA_128_CBC_SHA: value = 22; break; michael@0: case TLS_DHE_RSA_WITH_AES_256_CBC_SHA: value = 23; break; michael@0: case TLS_DHE_RSA_WITH_CAMELLIA_256_CBC_SHA: value = 24; break; michael@0: case TLS_DHE_RSA_WITH_3DES_EDE_CBC_SHA: value = 25; break; michael@0: case TLS_DHE_DSS_WITH_AES_128_CBC_SHA: value = 26; break; michael@0: case TLS_DHE_DSS_WITH_CAMELLIA_128_CBC_SHA: value = 27; break; michael@0: case TLS_DHE_DSS_WITH_AES_256_CBC_SHA: value = 28; break; michael@0: case TLS_DHE_DSS_WITH_CAMELLIA_256_CBC_SHA: value = 29; break; michael@0: case TLS_DHE_DSS_WITH_3DES_EDE_CBC_SHA: value = 30; break; michael@0: // ECDH key exchange michael@0: case TLS_ECDH_ECDSA_WITH_AES_128_CBC_SHA: value = 41; break; michael@0: case TLS_ECDH_RSA_WITH_AES_128_CBC_SHA: value = 42; break; michael@0: case TLS_ECDH_ECDSA_WITH_AES_256_CBC_SHA: value = 43; break; michael@0: case TLS_ECDH_RSA_WITH_AES_256_CBC_SHA: value = 44; break; michael@0: case TLS_ECDH_ECDSA_WITH_3DES_EDE_CBC_SHA: value = 45; break; michael@0: case TLS_ECDH_RSA_WITH_3DES_EDE_CBC_SHA: value = 46; break; michael@0: case TLS_ECDH_ECDSA_WITH_RC4_128_SHA: value = 47; break; michael@0: case TLS_ECDH_RSA_WITH_RC4_128_SHA: value = 48; break; michael@0: // RSA key exchange michael@0: case TLS_RSA_WITH_AES_128_CBC_SHA: value = 61; break; michael@0: case TLS_RSA_WITH_CAMELLIA_128_CBC_SHA: value = 62; break; michael@0: case TLS_RSA_WITH_AES_256_CBC_SHA: value = 63; break; michael@0: case TLS_RSA_WITH_CAMELLIA_256_CBC_SHA: value = 64; break; michael@0: case SSL_RSA_FIPS_WITH_3DES_EDE_CBC_SHA: value = 65; break; michael@0: case TLS_RSA_WITH_3DES_EDE_CBC_SHA: value = 66; break; michael@0: case TLS_RSA_WITH_SEED_CBC_SHA: value = 67; break; michael@0: case TLS_RSA_WITH_RC4_128_SHA: value = 68; break; michael@0: case TLS_RSA_WITH_RC4_128_MD5: value = 69; break; michael@0: // unknown michael@0: default: michael@0: value = 0; michael@0: break; michael@0: } michael@0: MOZ_ASSERT(value != 0); michael@0: Telemetry::Accumulate(probe, value); michael@0: } michael@0: michael@0: void HandshakeCallback(PRFileDesc* fd, void* client_data) { michael@0: nsNSSShutDownPreventionLock locker; michael@0: SECStatus rv; michael@0: michael@0: nsNSSSocketInfo* infoObject = (nsNSSSocketInfo*) fd->higher->secret; michael@0: michael@0: // Do the bookkeeping that needs to be done after the michael@0: // server's ServerHello...ServerHelloDone have been processed, but that doesn't michael@0: // need the handshake to be completed. michael@0: PreliminaryHandshakeDone(fd); michael@0: michael@0: nsSSLIOLayerHelpers& ioLayerHelpers michael@0: = infoObject->SharedState().IOLayerHelpers(); michael@0: michael@0: SSLVersionRange versions(infoObject->GetTLSVersionRange()); michael@0: michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, michael@0: ("[%p] HandshakeCallback: succeeded using TLS version range (0x%04x,0x%04x)\n", michael@0: fd, static_cast(versions.min), michael@0: static_cast(versions.max))); michael@0: michael@0: // If the handshake completed, then we know the site is TLS tolerant michael@0: ioLayerHelpers.rememberTolerantAtVersion(infoObject->GetHostName(), michael@0: infoObject->GetPort(), michael@0: versions.max); michael@0: michael@0: PRBool siteSupportsSafeRenego; michael@0: rv = SSL_HandshakeNegotiatedExtension(fd, ssl_renegotiation_info_xtn, michael@0: &siteSupportsSafeRenego); michael@0: MOZ_ASSERT(rv == SECSuccess); michael@0: if (rv != SECSuccess) { michael@0: siteSupportsSafeRenego = false; michael@0: } michael@0: michael@0: if (siteSupportsSafeRenego || michael@0: !ioLayerHelpers.treatUnsafeNegotiationAsBroken()) { michael@0: infoObject->SetSecurityState(nsIWebProgressListener::STATE_IS_SECURE | michael@0: nsIWebProgressListener::STATE_SECURE_HIGH); michael@0: } else { michael@0: infoObject->SetSecurityState(nsIWebProgressListener::STATE_IS_BROKEN); michael@0: } michael@0: michael@0: // XXX Bug 883674: We shouldn't be formatting messages here in PSM; instead, michael@0: // we should set a flag on the channel that higher (UI) level code can check michael@0: // to log the warning. In particular, these warnings should go to the web michael@0: // console instead of to the error console. Also, the warning is not michael@0: // localized. michael@0: if (!siteSupportsSafeRenego && michael@0: ioLayerHelpers.getWarnLevelMissingRFC5746() > 0) { michael@0: nsXPIDLCString hostName; michael@0: infoObject->GetHostName(getter_Copies(hostName)); michael@0: michael@0: nsAutoString msg; michael@0: msg.Append(NS_ConvertASCIItoUTF16(hostName)); michael@0: msg.Append(NS_LITERAL_STRING(" : server does not support RFC 5746, see CVE-2009-3555")); michael@0: michael@0: nsContentUtils::LogSimpleConsoleError(msg, "SSL"); michael@0: } michael@0: michael@0: mozilla::pkix::ScopedCERTCertificate serverCert(SSL_PeerCertificate(fd)); michael@0: michael@0: /* Set the SSL Status information */ michael@0: RefPtr status(infoObject->SSLStatus()); michael@0: if (!status) { michael@0: status = new nsSSLStatus(); michael@0: infoObject->SetSSLStatus(status); michael@0: } michael@0: michael@0: RememberCertErrorsTable::GetInstance().LookupCertErrorBits(infoObject, michael@0: status); michael@0: michael@0: RefPtr nssc(nsNSSCertificate::Create(serverCert.get())); michael@0: nsCOMPtr prevcert; michael@0: infoObject->GetPreviousCert(getter_AddRefs(prevcert)); michael@0: michael@0: bool equals_previous = false; michael@0: if (prevcert && nssc) { michael@0: nsresult rv = nssc->Equals(prevcert, &equals_previous); michael@0: if (NS_FAILED(rv)) { michael@0: equals_previous = false; michael@0: } michael@0: } michael@0: michael@0: if (equals_previous) { michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, michael@0: ("HandshakeCallback using PREV cert %p\n", prevcert.get())); michael@0: status->mServerCert = prevcert; michael@0: } michael@0: else { michael@0: if (status->mServerCert) { michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, michael@0: ("HandshakeCallback KEEPING cert %p\n", status->mServerCert.get())); michael@0: } michael@0: else { michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, michael@0: ("HandshakeCallback using NEW cert %p\n", nssc.get())); michael@0: status->mServerCert = nssc; michael@0: } michael@0: } michael@0: michael@0: SSLChannelInfo channelInfo; michael@0: rv = SSL_GetChannelInfo(fd, &channelInfo, sizeof(channelInfo)); michael@0: MOZ_ASSERT(rv == SECSuccess); michael@0: if (rv == SECSuccess) { michael@0: // Get the protocol version for telemetry michael@0: // 0=ssl3, 1=tls1, 2=tls1.1, 3=tls1.2 michael@0: unsigned int versionEnum = channelInfo.protocolVersion & 0xFF; michael@0: Telemetry::Accumulate(Telemetry::SSL_HANDSHAKE_VERSION, versionEnum); michael@0: AccumulateCipherSuite( michael@0: infoObject->IsFullHandshake() ? Telemetry::SSL_CIPHER_SUITE_FULL michael@0: : Telemetry::SSL_CIPHER_SUITE_RESUMED, michael@0: channelInfo); michael@0: michael@0: SSLCipherSuiteInfo cipherInfo; michael@0: rv = SSL_GetCipherSuiteInfo(channelInfo.cipherSuite, &cipherInfo, michael@0: sizeof cipherInfo); michael@0: MOZ_ASSERT(rv == SECSuccess); michael@0: if (rv == SECSuccess) { michael@0: status->mHaveKeyLengthAndCipher = true; michael@0: status->mKeyLength = cipherInfo.symKeyBits; michael@0: status->mSecretKeyLength = cipherInfo.effectiveKeyBits; michael@0: status->mCipherName.Assign(cipherInfo.cipherSuiteName); michael@0: michael@0: // keyExchange null=0, rsa=1, dh=2, fortezza=3, ecdh=4 michael@0: Telemetry::Accumulate( michael@0: infoObject->IsFullHandshake() michael@0: ? Telemetry::SSL_KEY_EXCHANGE_ALGORITHM_FULL michael@0: : Telemetry::SSL_KEY_EXCHANGE_ALGORITHM_RESUMED, michael@0: cipherInfo.keaType); michael@0: infoObject->SetKEAUsed(cipherInfo.keaType); michael@0: michael@0: if (infoObject->IsFullHandshake()) { michael@0: switch (cipherInfo.keaType) { michael@0: case ssl_kea_rsa: michael@0: AccumulateNonECCKeySize(Telemetry::SSL_KEA_RSA_KEY_SIZE_FULL, michael@0: channelInfo.keaKeyBits); michael@0: break; michael@0: case ssl_kea_dh: michael@0: AccumulateNonECCKeySize(Telemetry::SSL_KEA_DHE_KEY_SIZE_FULL, michael@0: channelInfo.keaKeyBits); michael@0: break; michael@0: case ssl_kea_ecdh: michael@0: AccumulateECCCurve(Telemetry::SSL_KEA_ECDHE_CURVE_FULL, michael@0: channelInfo.keaKeyBits); michael@0: break; michael@0: default: michael@0: MOZ_CRASH("impossible KEA"); michael@0: break; michael@0: } michael@0: michael@0: Telemetry::Accumulate(Telemetry::SSL_AUTH_ALGORITHM_FULL, michael@0: cipherInfo.authAlgorithm); michael@0: michael@0: // RSA key exchange doesn't use a signature for auth. michael@0: if (cipherInfo.keaType != ssl_kea_rsa) { michael@0: switch (cipherInfo.authAlgorithm) { michael@0: case ssl_auth_rsa: michael@0: AccumulateNonECCKeySize(Telemetry::SSL_AUTH_RSA_KEY_SIZE_FULL, michael@0: channelInfo.authKeyBits); michael@0: break; michael@0: case ssl_auth_dsa: michael@0: AccumulateNonECCKeySize(Telemetry::SSL_AUTH_DSA_KEY_SIZE_FULL, michael@0: channelInfo.authKeyBits); michael@0: break; michael@0: case ssl_auth_ecdsa: michael@0: AccumulateECCCurve(Telemetry::SSL_AUTH_ECDSA_CURVE_FULL, michael@0: channelInfo.authKeyBits); michael@0: break; michael@0: default: michael@0: MOZ_CRASH("impossible auth algorithm"); michael@0: break; michael@0: } michael@0: } michael@0: } michael@0: michael@0: Telemetry::Accumulate( michael@0: infoObject->IsFullHandshake() michael@0: ? Telemetry::SSL_SYMMETRIC_CIPHER_FULL michael@0: : Telemetry::SSL_SYMMETRIC_CIPHER_RESUMED, michael@0: cipherInfo.symCipher); michael@0: } michael@0: } michael@0: michael@0: infoObject->NoteTimeUntilReady(); michael@0: infoObject->SetHandshakeCompleted(); michael@0: }