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 "nsNSSIOLayer.h" michael@0: michael@0: #include "pkix/pkixtypes.h" michael@0: #include "nsNSSComponent.h" michael@0: #include "mozilla/Casting.h" michael@0: #include "mozilla/DebugOnly.h" michael@0: #include "mozilla/Telemetry.h" michael@0: michael@0: #include "prlog.h" michael@0: #include "prnetdb.h" michael@0: #include "nsIPrefService.h" michael@0: #include "nsIClientAuthDialogs.h" michael@0: #include "nsClientAuthRemember.h" michael@0: #include "nsISSLErrorListener.h" michael@0: michael@0: #include "nsNetUtil.h" michael@0: #include "nsPrintfCString.h" michael@0: #include "SSLServerCertVerification.h" michael@0: #include "nsNSSCertHelper.h" michael@0: #include "nsNSSCleaner.h" michael@0: michael@0: #ifndef MOZ_NO_EV_CERTS michael@0: #include "nsIDocShell.h" michael@0: #include "nsIDocShellTreeItem.h" michael@0: #include "nsISecureBrowserUI.h" michael@0: #include "nsIInterfaceRequestorUtils.h" michael@0: #endif michael@0: michael@0: #include "nsCharSeparatedTokenizer.h" michael@0: #include "nsIConsoleService.h" michael@0: #include "PSMRunnable.h" michael@0: #include "ScopedNSSTypes.h" michael@0: #include "SharedSSLState.h" michael@0: #include "mozilla/Preferences.h" michael@0: #include "nsContentUtils.h" michael@0: michael@0: #include "ssl.h" michael@0: #include "sslproto.h" michael@0: #include "secerr.h" michael@0: #include "sslerr.h" michael@0: #include "secder.h" michael@0: #include "keyhi.h" michael@0: michael@0: #include michael@0: michael@0: using namespace mozilla; michael@0: using namespace mozilla::psm; michael@0: michael@0: //#define DEBUG_SSL_VERBOSE //Enable this define to get minimal michael@0: //reports when doing SSL read/write michael@0: michael@0: //#define DUMP_BUFFER //Enable this define along with michael@0: //DEBUG_SSL_VERBOSE to dump SSL michael@0: //read/write buffer to a log. michael@0: //Uses PR_LOG except on Mac where michael@0: //we always write out to our own michael@0: //file. michael@0: michael@0: namespace { michael@0: michael@0: NSSCleanupAutoPtrClass(void, PR_FREEIF) michael@0: michael@0: void michael@0: getSiteKey(const nsACString& hostName, uint16_t port, michael@0: /*out*/ nsCSubstring& key) michael@0: { michael@0: key = hostName; michael@0: key.AppendASCII(":"); michael@0: key.AppendInt(port); michael@0: } michael@0: michael@0: // SSM_UserCertChoice: enum for cert choice info michael@0: typedef enum {ASK, AUTO} SSM_UserCertChoice; michael@0: michael@0: // Forward secrecy provides us with a proof of posession of the private key michael@0: // from the server. Without of proof of posession of the private key of the michael@0: // server, any MitM can force us to false start in a connection that the real michael@0: // server never participates in, since with RSA key exchange a MitM can michael@0: // complete the server's first round of the handshake without knowing the michael@0: // server's public key This would be used, for example, to greatly accelerate michael@0: // the attacks on RC4 or other attacks that allow a MitM to decrypt encrypted michael@0: // data without having the server's private key. Without false start, such michael@0: // attacks are naturally rate limited by network latency and may also be rate michael@0: // limited explicitly by the server's DoS or other security mechanisms. michael@0: // Further, because the server that has the private key must participate in the michael@0: // handshake, the server could detect these kinds of attacks if they they are michael@0: // repeated rapidly and/or frequently, by noticing lots of invalid or michael@0: // incomplete handshakes. michael@0: // michael@0: // With this in mind, when we choose not to require forward secrecy (when the michael@0: // pref's value is false), then we will still only false start for RSA key michael@0: // exchange only if the most recent handshake we've previously done used RSA michael@0: // key exchange. This way, we prevent any (EC)DHE-to-RSA downgrade attacks for michael@0: // servers that consistently choose (EC)DHE key exchange. In order to prevent michael@0: // downgrade from ECDHE_*_GCM cipher suites, we need to also consider downgrade michael@0: // from TLS 1.2 to earlier versions (bug 861310). michael@0: static const bool FALSE_START_REQUIRE_FORWARD_SECRECY_DEFAULT = true; michael@0: michael@0: // XXX(perf bug 940787): We currently require NPN because there is a very michael@0: // high (perfect so far) correlation between servers that are false-start- michael@0: // tolerant and servers that support NPN, according to Google. Without this, we michael@0: // will run into interop issues with a small percentage of servers that stop michael@0: // responding when we attempt to false start. michael@0: static const bool FALSE_START_REQUIRE_NPN_DEFAULT = true; michael@0: michael@0: } // unnamed namespace michael@0: michael@0: #ifdef PR_LOGGING michael@0: extern PRLogModuleInfo* gPIPNSSLog; michael@0: #endif michael@0: michael@0: nsNSSSocketInfo::nsNSSSocketInfo(SharedSSLState& aState, uint32_t providerFlags) michael@0: : mFd(nullptr), michael@0: mCertVerificationState(before_cert_verification), michael@0: mSharedState(aState), michael@0: mForSTARTTLS(false), michael@0: mHandshakePending(true), michael@0: mRememberClientAuthCertificate(false), michael@0: mPreliminaryHandshakeDone(false), michael@0: mNPNCompleted(false), michael@0: mFalseStartCallbackCalled(false), michael@0: mFalseStarted(false), michael@0: mIsFullHandshake(false), michael@0: mHandshakeCompleted(false), michael@0: mJoined(false), michael@0: mSentClientCert(false), michael@0: mNotedTimeUntilReady(false), michael@0: mKEAUsed(nsISSLSocketControl::KEY_EXCHANGE_UNKNOWN), michael@0: mKEAExpected(nsISSLSocketControl::KEY_EXCHANGE_UNKNOWN), michael@0: mSSLVersionUsed(nsISSLSocketControl::SSL_VERSION_UNKNOWN), michael@0: mProviderFlags(providerFlags), michael@0: mSocketCreationTimestamp(TimeStamp::Now()), michael@0: mPlaintextBytesRead(0) michael@0: { michael@0: mTLSVersionRange.min = 0; michael@0: mTLSVersionRange.max = 0; michael@0: } michael@0: michael@0: NS_IMPL_ISUPPORTS_INHERITED(nsNSSSocketInfo, TransportSecurityInfo, michael@0: nsISSLSocketControl, michael@0: nsIClientAuthUserDecision) michael@0: michael@0: NS_IMETHODIMP michael@0: nsNSSSocketInfo::GetProviderFlags(uint32_t* aProviderFlags) michael@0: { michael@0: *aProviderFlags = mProviderFlags; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsNSSSocketInfo::GetKEAUsed(int16_t* aKea) michael@0: { michael@0: *aKea = mKEAUsed; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsNSSSocketInfo::GetKEAExpected(int16_t* aKea) michael@0: { michael@0: *aKea = mKEAExpected; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsNSSSocketInfo::SetKEAExpected(int16_t aKea) michael@0: { michael@0: mKEAExpected = aKea; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsNSSSocketInfo::GetSSLVersionUsed(int16_t* aSSLVersionUsed) michael@0: { michael@0: *aSSLVersionUsed = mSSLVersionUsed; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsNSSSocketInfo::GetRememberClientAuthCertificate(bool* aRemember) michael@0: { michael@0: NS_ENSURE_ARG_POINTER(aRemember); michael@0: *aRemember = mRememberClientAuthCertificate; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsNSSSocketInfo::SetRememberClientAuthCertificate(bool aRemember) michael@0: { michael@0: mRememberClientAuthCertificate = aRemember; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsNSSSocketInfo::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks) michael@0: { michael@0: *aCallbacks = mCallbacks; michael@0: NS_IF_ADDREF(*aCallbacks); michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsNSSSocketInfo::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks) michael@0: { michael@0: if (!aCallbacks) { michael@0: mCallbacks = nullptr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: mCallbacks = aCallbacks; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: #ifndef MOZ_NO_EV_CERTS michael@0: static void michael@0: getSecureBrowserUI(nsIInterfaceRequestor* callbacks, michael@0: nsISecureBrowserUI** result) michael@0: { michael@0: NS_ASSERTION(result, "result parameter to getSecureBrowserUI is null"); michael@0: *result = nullptr; michael@0: michael@0: NS_ASSERTION(NS_IsMainThread(), michael@0: "getSecureBrowserUI called off the main thread"); michael@0: michael@0: if (!callbacks) michael@0: return; michael@0: michael@0: nsCOMPtr secureUI = do_GetInterface(callbacks); michael@0: if (secureUI) { michael@0: secureUI.forget(result); michael@0: return; michael@0: } michael@0: michael@0: nsCOMPtr item = do_GetInterface(callbacks); michael@0: if (item) { michael@0: nsCOMPtr rootItem; michael@0: (void) item->GetSameTypeRootTreeItem(getter_AddRefs(rootItem)); michael@0: michael@0: nsCOMPtr docShell = do_QueryInterface(rootItem); michael@0: if (docShell) { michael@0: (void) docShell->GetSecurityUI(result); michael@0: } michael@0: } michael@0: } michael@0: #endif michael@0: michael@0: void michael@0: nsNSSSocketInfo::NoteTimeUntilReady() michael@0: { michael@0: if (mNotedTimeUntilReady) michael@0: return; michael@0: michael@0: mNotedTimeUntilReady = true; michael@0: michael@0: // This will include TCP and proxy tunnel wait time michael@0: Telemetry::AccumulateTimeDelta(Telemetry::SSL_TIME_UNTIL_READY, michael@0: mSocketCreationTimestamp, TimeStamp::Now()); michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, michael@0: ("[%p] nsNSSSocketInfo::NoteTimeUntilReady\n", mFd)); michael@0: } michael@0: michael@0: void michael@0: nsNSSSocketInfo::SetHandshakeCompleted() michael@0: { michael@0: if (!mHandshakeCompleted) { michael@0: enum HandshakeType { michael@0: Resumption = 1, michael@0: FalseStarted = 2, michael@0: ChoseNotToFalseStart = 3, michael@0: NotAllowedToFalseStart = 4, michael@0: }; michael@0: michael@0: HandshakeType handshakeType = !IsFullHandshake() ? Resumption michael@0: : mFalseStarted ? FalseStarted michael@0: : mFalseStartCallbackCalled ? ChoseNotToFalseStart michael@0: : NotAllowedToFalseStart; michael@0: michael@0: // This will include TCP and proxy tunnel wait time michael@0: Telemetry::AccumulateTimeDelta(Telemetry::SSL_TIME_UNTIL_HANDSHAKE_FINISHED, michael@0: mSocketCreationTimestamp, TimeStamp::Now()); michael@0: michael@0: // If the handshake is completed for the first time from just 1 callback michael@0: // that means that TLS session resumption must have been used. michael@0: Telemetry::Accumulate(Telemetry::SSL_RESUMED_SESSION, michael@0: handshakeType == Resumption); michael@0: Telemetry::Accumulate(Telemetry::SSL_HANDSHAKE_TYPE, handshakeType); michael@0: } michael@0: michael@0: michael@0: // Remove the plain text layer as it is not needed anymore. michael@0: // The plain text layer is not always present - so its not a fatal error michael@0: // if it cannot be removed michael@0: PRFileDesc* poppedPlaintext = michael@0: PR_GetIdentitiesLayer(mFd, nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity); michael@0: if (poppedPlaintext) { michael@0: PR_PopIOLayer(mFd, nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity); michael@0: poppedPlaintext->dtor(poppedPlaintext); michael@0: } michael@0: michael@0: mHandshakeCompleted = true; michael@0: michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, michael@0: ("[%p] nsNSSSocketInfo::SetHandshakeCompleted\n", (void*) mFd)); michael@0: michael@0: mIsFullHandshake = false; // reset for next handshake on this connection michael@0: } michael@0: michael@0: void michael@0: nsNSSSocketInfo::SetNegotiatedNPN(const char* value, uint32_t length) michael@0: { michael@0: if (!value) { michael@0: mNegotiatedNPN.Truncate(); michael@0: } else { michael@0: mNegotiatedNPN.Assign(value, length); michael@0: } michael@0: mNPNCompleted = true; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsNSSSocketInfo::GetNegotiatedNPN(nsACString& aNegotiatedNPN) michael@0: { michael@0: if (!mNPNCompleted) michael@0: return NS_ERROR_NOT_CONNECTED; michael@0: michael@0: aNegotiatedNPN = mNegotiatedNPN; michael@0: return NS_OK; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsNSSSocketInfo::JoinConnection(const nsACString& npnProtocol, michael@0: const nsACString& hostname, michael@0: int32_t port, michael@0: bool* _retval) michael@0: { michael@0: *_retval = false; michael@0: michael@0: // Different ports may not be joined together michael@0: if (port != GetPort()) michael@0: return NS_OK; michael@0: michael@0: // Make sure NPN has been completed and matches requested npnProtocol michael@0: if (!mNPNCompleted || !mNegotiatedNPN.Equals(npnProtocol)) michael@0: return NS_OK; michael@0: michael@0: // If this is the same hostname then the certicate status does not michael@0: // need to be considered. They are joinable. michael@0: if (hostname.Equals(GetHostName())) { michael@0: *_retval = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Before checking the server certificate we need to make sure the michael@0: // handshake has completed. michael@0: if (!mHandshakeCompleted || !SSLStatus() || !SSLStatus()->mServerCert) michael@0: return NS_OK; michael@0: michael@0: // If the cert has error bits (e.g. it is untrusted) then do not join. michael@0: // The value of mHaveCertErrorBits is only reliable because we know that michael@0: // the handshake completed. michael@0: if (SSLStatus()->mHaveCertErrorBits) michael@0: return NS_OK; michael@0: michael@0: // If the connection is using client certificates then do not join michael@0: // because the user decides on whether to send client certs to hosts on a michael@0: // per-domain basis. michael@0: if (mSentClientCert) michael@0: return NS_OK; michael@0: michael@0: // Ensure that the server certificate covers the hostname that would michael@0: // like to join this connection michael@0: michael@0: ScopedCERTCertificate nssCert; michael@0: michael@0: nsCOMPtr cert2 = do_QueryInterface(SSLStatus()->mServerCert); michael@0: if (cert2) michael@0: nssCert = cert2->GetCert(); michael@0: michael@0: if (!nssCert) michael@0: return NS_OK; michael@0: michael@0: if (CERT_VerifyCertName(nssCert, PromiseFlatCString(hostname).get()) != michael@0: SECSuccess) michael@0: return NS_OK; michael@0: michael@0: // All tests pass - this is joinable michael@0: mJoined = true; michael@0: *_retval = true; michael@0: return NS_OK; michael@0: } michael@0: michael@0: bool michael@0: nsNSSSocketInfo::GetForSTARTTLS() michael@0: { michael@0: return mForSTARTTLS; michael@0: } michael@0: michael@0: void michael@0: nsNSSSocketInfo::SetForSTARTTLS(bool aForSTARTTLS) michael@0: { michael@0: mForSTARTTLS = aForSTARTTLS; michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsNSSSocketInfo::ProxyStartSSL() michael@0: { michael@0: return ActivateSSL(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsNSSSocketInfo::StartTLS() michael@0: { michael@0: return ActivateSSL(); michael@0: } michael@0: michael@0: NS_IMETHODIMP michael@0: nsNSSSocketInfo::SetNPNList(nsTArray& protocolArray) michael@0: { michael@0: nsNSSShutDownPreventionLock locker; michael@0: if (isAlreadyShutDown()) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: if (!mFd) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: // the npn list is a concatenated list of 8 bit byte strings. michael@0: nsCString npnList; michael@0: michael@0: for (uint32_t index = 0; index < protocolArray.Length(); ++index) { michael@0: if (protocolArray[index].IsEmpty() || michael@0: protocolArray[index].Length() > 255) michael@0: return NS_ERROR_ILLEGAL_VALUE; michael@0: michael@0: npnList.Append(protocolArray[index].Length()); michael@0: npnList.Append(protocolArray[index]); michael@0: } michael@0: michael@0: if (SSL_SetNextProtoNego( michael@0: mFd, michael@0: reinterpret_cast(npnList.get()), michael@0: npnList.Length()) != SECSuccess) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsNSSSocketInfo::ActivateSSL() michael@0: { michael@0: nsNSSShutDownPreventionLock locker; michael@0: if (isAlreadyShutDown()) michael@0: return NS_ERROR_NOT_AVAILABLE; michael@0: michael@0: if (SECSuccess != SSL_OptionSet(mFd, SSL_SECURITY, true)) michael@0: return NS_ERROR_FAILURE; michael@0: if (SECSuccess != SSL_ResetHandshake(mFd, false)) michael@0: return NS_ERROR_FAILURE; michael@0: michael@0: mHandshakePending = true; michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsNSSSocketInfo::GetFileDescPtr(PRFileDesc** aFilePtr) michael@0: { michael@0: *aFilePtr = mFd; michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsNSSSocketInfo::SetFileDescPtr(PRFileDesc* aFilePtr) michael@0: { michael@0: mFd = aFilePtr; michael@0: return NS_OK; michael@0: } michael@0: michael@0: #ifndef MOZ_NO_EV_CERTS michael@0: class PreviousCertRunnable : public SyncRunnableBase michael@0: { michael@0: public: michael@0: PreviousCertRunnable(nsIInterfaceRequestor* callbacks) michael@0: : mCallbacks(callbacks) michael@0: { michael@0: } michael@0: michael@0: virtual void RunOnTargetThread() michael@0: { michael@0: nsCOMPtr secureUI; michael@0: getSecureBrowserUI(mCallbacks, getter_AddRefs(secureUI)); michael@0: nsCOMPtr statusProvider = do_QueryInterface(secureUI); michael@0: if (statusProvider) { michael@0: nsCOMPtr status; michael@0: (void) statusProvider->GetSSLStatus(getter_AddRefs(status)); michael@0: if (status) { michael@0: (void) status->GetServerCert(getter_AddRefs(mPreviousCert)); michael@0: } michael@0: } michael@0: } michael@0: michael@0: nsCOMPtr mPreviousCert; // out michael@0: private: michael@0: nsCOMPtr mCallbacks; // in michael@0: }; michael@0: #endif michael@0: michael@0: void michael@0: nsNSSSocketInfo::GetPreviousCert(nsIX509Cert** _result) michael@0: { michael@0: NS_ASSERTION(_result, "_result parameter to GetPreviousCert is null"); michael@0: *_result = nullptr; michael@0: michael@0: #ifndef MOZ_NO_EV_CERTS michael@0: RefPtr runnable(new PreviousCertRunnable(mCallbacks)); michael@0: DebugOnly rv = runnable->DispatchToMainThreadAndWait(); michael@0: NS_ASSERTION(NS_SUCCEEDED(rv), "runnable->DispatchToMainThreadAndWait() failed"); michael@0: runnable->mPreviousCert.forget(_result); michael@0: #endif michael@0: } michael@0: michael@0: void michael@0: nsNSSSocketInfo::SetCertVerificationWaiting() michael@0: { michael@0: // mCertVerificationState may be before_cert_verification for the first michael@0: // handshake on the connection, or after_cert_verification for subsequent michael@0: // renegotiation handshakes. michael@0: NS_ASSERTION(mCertVerificationState != waiting_for_cert_verification, michael@0: "Invalid state transition to waiting_for_cert_verification"); michael@0: mCertVerificationState = waiting_for_cert_verification; michael@0: } michael@0: michael@0: // Be careful that SetCertVerificationResult does NOT get called while we are michael@0: // processing a SSL callback function, because SSL_AuthCertificateComplete will michael@0: // attempt to acquire locks that are already held by libssl when it calls michael@0: // callbacks. michael@0: void michael@0: nsNSSSocketInfo::SetCertVerificationResult(PRErrorCode errorCode, michael@0: SSLErrorMessageType errorMessageType) michael@0: { michael@0: NS_ASSERTION(mCertVerificationState == waiting_for_cert_verification, michael@0: "Invalid state transition to cert_verification_finished"); michael@0: michael@0: if (mFd) { michael@0: SECStatus rv = SSL_AuthCertificateComplete(mFd, errorCode); michael@0: // Only replace errorCode if there was originally no error michael@0: if (rv != SECSuccess && errorCode == 0) { michael@0: errorCode = PR_GetError(); michael@0: errorMessageType = PlainErrorMessage; michael@0: if (errorCode == 0) { michael@0: NS_ERROR("SSL_AuthCertificateComplete didn't set error code"); michael@0: errorCode = PR_INVALID_STATE_ERROR; michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (errorCode) { michael@0: SetCanceled(errorCode, errorMessageType); michael@0: } michael@0: michael@0: if (mPlaintextBytesRead && !errorCode) { michael@0: Telemetry::Accumulate(Telemetry::SSL_BYTES_BEFORE_CERT_CALLBACK, michael@0: SafeCast(mPlaintextBytesRead)); michael@0: } michael@0: michael@0: mCertVerificationState = after_cert_verification; michael@0: } michael@0: michael@0: SharedSSLState& michael@0: nsNSSSocketInfo::SharedState() michael@0: { michael@0: return mSharedState; michael@0: } michael@0: michael@0: void nsSSLIOLayerHelpers::Cleanup() michael@0: { michael@0: mTLSIntoleranceInfo.Clear(); michael@0: michael@0: if (mRenegoUnrestrictedSites) { michael@0: delete mRenegoUnrestrictedSites; michael@0: mRenegoUnrestrictedSites = nullptr; michael@0: } michael@0: } michael@0: michael@0: static void michael@0: nsHandleSSLError(nsNSSSocketInfo* socketInfo, michael@0: ::mozilla::psm::SSLErrorMessageType errtype, michael@0: PRErrorCode err) michael@0: { michael@0: if (!NS_IsMainThread()) { michael@0: NS_ERROR("nsHandleSSLError called off the main thread"); michael@0: return; michael@0: } michael@0: michael@0: // SetCanceled is only called by the main thread or the socket transport michael@0: // thread. Whenever this function is called on the main thread, the SSL michael@0: // thread is blocked on it. So, no mutex is necessary for michael@0: // SetCanceled()/GetError*(). michael@0: if (socketInfo->GetErrorCode()) { michael@0: // If the socket has been flagged as canceled, michael@0: // the code who did was responsible for setting the error code. michael@0: return; michael@0: } michael@0: michael@0: nsresult rv; michael@0: NS_DEFINE_CID(nssComponentCID, NS_NSSCOMPONENT_CID); michael@0: nsCOMPtr nssComponent(do_GetService(nssComponentCID, &rv)); michael@0: if (NS_FAILED(rv)) michael@0: return; michael@0: michael@0: // Try to get a nsISSLErrorListener implementation from the socket consumer. michael@0: nsCOMPtr cb; michael@0: socketInfo->GetNotificationCallbacks(getter_AddRefs(cb)); michael@0: if (cb) { michael@0: nsCOMPtr sel = do_GetInterface(cb); michael@0: if (sel) { michael@0: nsIInterfaceRequestor* csi = static_cast(socketInfo); michael@0: michael@0: nsCString hostWithPortString; michael@0: getSiteKey(socketInfo->GetHostName(), socketInfo->GetPort(), michael@0: hostWithPortString); michael@0: michael@0: bool suppressMessage = false; // obsolete, ignored michael@0: rv = sel->NotifySSLError(csi, err, hostWithPortString, &suppressMessage); michael@0: } michael@0: } michael@0: michael@0: // We must cancel first, which sets the error code. michael@0: socketInfo->SetCanceled(err, PlainErrorMessage); michael@0: nsXPIDLString errorString; michael@0: socketInfo->GetErrorLogMessage(err, errtype, errorString); michael@0: michael@0: if (!errorString.IsEmpty()) { michael@0: nsContentUtils::LogSimpleConsoleError(errorString, "SSL"); michael@0: } michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: enum Operation { reading, writing, not_reading_or_writing }; michael@0: michael@0: int32_t checkHandshake(int32_t bytesTransfered, bool wasReading, michael@0: PRFileDesc* ssl_layer_fd, michael@0: nsNSSSocketInfo* socketInfo); michael@0: michael@0: nsNSSSocketInfo* michael@0: getSocketInfoIfRunning(PRFileDesc* fd, Operation op, michael@0: const nsNSSShutDownPreventionLock& /*proofOfLock*/) michael@0: { michael@0: if (!fd || !fd->lower || !fd->secret || michael@0: fd->identity != nsSSLIOLayerHelpers::nsSSLIOLayerIdentity) { michael@0: NS_ERROR("bad file descriptor passed to getSocketInfoIfRunning"); michael@0: PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0); michael@0: return nullptr; michael@0: } michael@0: michael@0: nsNSSSocketInfo* socketInfo = (nsNSSSocketInfo*) fd->secret; michael@0: michael@0: if (socketInfo->isAlreadyShutDown() || socketInfo->isPK11LoggedOut()) { michael@0: PR_SetError(PR_SOCKET_SHUTDOWN_ERROR, 0); michael@0: return nullptr; michael@0: } michael@0: michael@0: if (socketInfo->GetErrorCode()) { michael@0: PRErrorCode err = socketInfo->GetErrorCode(); michael@0: PR_SetError(err, 0); michael@0: if (op == reading || op == writing) { michael@0: // We must do TLS intolerance checks for reads and writes, for timeouts michael@0: // in particular. michael@0: (void) checkHandshake(-1, op == reading, fd, socketInfo); michael@0: } michael@0: michael@0: // If we get here, it is probably because cert verification failed and this michael@0: // is the first I/O attempt since that failure. michael@0: return nullptr; michael@0: } michael@0: michael@0: return socketInfo; michael@0: } michael@0: michael@0: } // unnnamed namespace michael@0: michael@0: static PRStatus michael@0: nsSSLIOLayerConnect(PRFileDesc* fd, const PRNetAddr* addr, michael@0: PRIntervalTime timeout) michael@0: { michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] connecting SSL socket\n", michael@0: (void*) fd)); michael@0: nsNSSShutDownPreventionLock locker; michael@0: if (!getSocketInfoIfRunning(fd, not_reading_or_writing, locker)) michael@0: return PR_FAILURE; michael@0: michael@0: PRStatus status = fd->lower->methods->connect(fd->lower, addr, timeout); michael@0: if (status != PR_SUCCESS) { michael@0: PR_LOG(gPIPNSSLog, PR_LOG_ERROR, ("[%p] Lower layer connect error: %d\n", michael@0: (void*) fd, PR_GetError())); michael@0: return status; michael@0: } michael@0: michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] Connect\n", (void*) fd)); michael@0: return status; michael@0: } michael@0: michael@0: void michael@0: nsSSLIOLayerHelpers::rememberTolerantAtVersion(const nsACString& hostName, michael@0: int16_t port, uint16_t tolerant) michael@0: { michael@0: nsCString key; michael@0: getSiteKey(hostName, port, key); michael@0: michael@0: MutexAutoLock lock(mutex); michael@0: michael@0: IntoleranceEntry entry; michael@0: if (mTLSIntoleranceInfo.Get(key, &entry)) { michael@0: entry.AssertInvariant(); michael@0: entry.tolerant = std::max(entry.tolerant, tolerant); michael@0: if (entry.intolerant != 0 && entry.intolerant <= entry.tolerant) { michael@0: entry.intolerant = entry.tolerant + 1; michael@0: } michael@0: } else { michael@0: entry.tolerant = tolerant; michael@0: entry.intolerant = 0; michael@0: } michael@0: michael@0: entry.AssertInvariant(); michael@0: michael@0: mTLSIntoleranceInfo.Put(key, entry); michael@0: } michael@0: michael@0: // returns true if we should retry the handshake michael@0: bool michael@0: nsSSLIOLayerHelpers::rememberIntolerantAtVersion(const nsACString& hostName, michael@0: int16_t port, michael@0: uint16_t minVersion, michael@0: uint16_t intolerant) michael@0: { michael@0: nsCString key; michael@0: getSiteKey(hostName, port, key); michael@0: michael@0: MutexAutoLock lock(mutex); michael@0: michael@0: if (intolerant <= minVersion) { michael@0: // We can't fall back any further. Assume that intolerance isn't the issue. michael@0: IntoleranceEntry entry; michael@0: if (mTLSIntoleranceInfo.Get(key, &entry)) { michael@0: entry.AssertInvariant(); michael@0: entry.intolerant = 0; michael@0: entry.AssertInvariant(); michael@0: mTLSIntoleranceInfo.Put(key, entry); michael@0: } michael@0: michael@0: return false; michael@0: } michael@0: michael@0: IntoleranceEntry entry; michael@0: if (mTLSIntoleranceInfo.Get(key, &entry)) { michael@0: entry.AssertInvariant(); michael@0: if (intolerant <= entry.tolerant) { michael@0: // We already know the server is tolerant at an equal or higher version. michael@0: return false; michael@0: } michael@0: if ((entry.intolerant != 0 && intolerant >= entry.intolerant)) { michael@0: // We already know that the server is intolerant at a lower version. michael@0: return true; michael@0: } michael@0: } else { michael@0: entry.tolerant = 0; michael@0: } michael@0: michael@0: entry.intolerant = intolerant; michael@0: entry.AssertInvariant(); michael@0: mTLSIntoleranceInfo.Put(key, entry); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: void michael@0: nsSSLIOLayerHelpers::adjustForTLSIntolerance(const nsACString& hostName, michael@0: int16_t port, michael@0: /*in/out*/ SSLVersionRange& range) michael@0: { michael@0: IntoleranceEntry entry; michael@0: michael@0: { michael@0: nsCString key; michael@0: getSiteKey(hostName, port, key); michael@0: michael@0: MutexAutoLock lock(mutex); michael@0: if (!mTLSIntoleranceInfo.Get(key, &entry)) { michael@0: return; michael@0: } michael@0: } michael@0: michael@0: entry.AssertInvariant(); michael@0: michael@0: if (entry.intolerant != 0) { michael@0: // We've tried connecting at a higher range but failed, so try at the michael@0: // version we haven't tried yet, unless we have reached the minimum. michael@0: if (range.min < entry.intolerant) { michael@0: range.max = entry.intolerant - 1; michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool nsSSLIOLayerHelpers::nsSSLIOLayerInitialized = false; michael@0: PRDescIdentity nsSSLIOLayerHelpers::nsSSLIOLayerIdentity; michael@0: PRDescIdentity nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity; michael@0: PRIOMethods nsSSLIOLayerHelpers::nsSSLIOLayerMethods; michael@0: PRIOMethods nsSSLIOLayerHelpers::nsSSLPlaintextLayerMethods; michael@0: michael@0: static PRStatus michael@0: nsSSLIOLayerClose(PRFileDesc* fd) michael@0: { michael@0: nsNSSShutDownPreventionLock locker; michael@0: if (!fd) michael@0: return PR_FAILURE; michael@0: michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] Shutting down socket\n", michael@0: (void*) fd)); michael@0: michael@0: nsNSSSocketInfo* socketInfo = (nsNSSSocketInfo*) fd->secret; michael@0: NS_ASSERTION(socketInfo,"nsNSSSocketInfo was null for an fd"); michael@0: michael@0: return socketInfo->CloseSocketAndDestroy(locker); michael@0: } michael@0: michael@0: PRStatus michael@0: nsNSSSocketInfo::CloseSocketAndDestroy( michael@0: const nsNSSShutDownPreventionLock& /*proofOfLock*/) michael@0: { michael@0: nsNSSShutDownList::trackSSLSocketClose(); michael@0: michael@0: PRFileDesc* popped = PR_PopIOLayer(mFd, PR_TOP_IO_LAYER); michael@0: NS_ASSERTION(popped && michael@0: popped->identity == nsSSLIOLayerHelpers::nsSSLIOLayerIdentity, michael@0: "SSL Layer not on top of stack"); michael@0: michael@0: // The plain text layer is not always present - so its not a fatal error michael@0: // if it cannot be removed michael@0: PRFileDesc* poppedPlaintext = michael@0: PR_GetIdentitiesLayer(mFd, nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity); michael@0: if (poppedPlaintext) { michael@0: PR_PopIOLayer(mFd, nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity); michael@0: poppedPlaintext->dtor(poppedPlaintext); michael@0: } michael@0: michael@0: PRStatus status = mFd->methods->close(mFd); michael@0: michael@0: // the nsNSSSocketInfo instance can out-live the connection, so we need some michael@0: // indication that the connection has been closed. mFd == nullptr is that michael@0: // indication. This is needed, for example, when the connection is closed michael@0: // before we have finished validating the server's certificate. michael@0: mFd = nullptr; michael@0: michael@0: if (status != PR_SUCCESS) return status; michael@0: michael@0: popped->identity = PR_INVALID_IO_LAYER; michael@0: NS_RELEASE_THIS(); michael@0: popped->dtor(popped); michael@0: michael@0: return PR_SUCCESS; michael@0: } michael@0: michael@0: #if defined(DEBUG_SSL_VERBOSE) && defined(DUMP_BUFFER) michael@0: // Dumps a (potentially binary) buffer using SSM_DEBUG. (We could have used michael@0: // the version in ssltrace.c, but that's specifically tailored to SSLTRACE.) michael@0: #define DUMPBUF_LINESIZE 24 michael@0: static void michael@0: nsDumpBuffer(unsigned char* buf, int len) michael@0: { michael@0: char hexbuf[DUMPBUF_LINESIZE*3+1]; michael@0: char chrbuf[DUMPBUF_LINESIZE+1]; michael@0: static const char* hex = "0123456789abcdef"; michael@0: int i = 0; michael@0: int l = 0; michael@0: char ch; michael@0: char* c; michael@0: char* h; michael@0: if (len == 0) michael@0: return; michael@0: hexbuf[DUMPBUF_LINESIZE*3] = '\0'; michael@0: chrbuf[DUMPBUF_LINESIZE] = '\0'; michael@0: (void) memset(hexbuf, 0x20, DUMPBUF_LINESIZE*3); michael@0: (void) memset(chrbuf, 0x20, DUMPBUF_LINESIZE); michael@0: h = hexbuf; michael@0: c = chrbuf; michael@0: michael@0: while (i < len) { michael@0: ch = buf[i]; michael@0: michael@0: if (l == DUMPBUF_LINESIZE) { michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("%s%s\n", hexbuf, chrbuf)); michael@0: (void) memset(hexbuf, 0x20, DUMPBUF_LINESIZE*3); michael@0: (void) memset(chrbuf, 0x20, DUMPBUF_LINESIZE); michael@0: h = hexbuf; michael@0: c = chrbuf; michael@0: l = 0; michael@0: } michael@0: michael@0: // Convert a character to hex. michael@0: *h++ = hex[(ch >> 4) & 0xf]; michael@0: *h++ = hex[ch & 0xf]; michael@0: h++; michael@0: michael@0: // Put the character (if it's printable) into the character buffer. michael@0: if ((ch >= 0x20) && (ch <= 0x7e)) { michael@0: *c++ = ch; michael@0: } else { michael@0: *c++ = '.'; michael@0: } michael@0: i++; l++; michael@0: } michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("%s%s\n", hexbuf, chrbuf)); michael@0: } michael@0: michael@0: #define DEBUG_DUMP_BUFFER(buf,len) nsDumpBuffer(buf,len) michael@0: #else michael@0: #define DEBUG_DUMP_BUFFER(buf,len) michael@0: #endif michael@0: michael@0: class SSLErrorRunnable : public SyncRunnableBase michael@0: { michael@0: public: michael@0: SSLErrorRunnable(nsNSSSocketInfo* infoObject, michael@0: ::mozilla::psm::SSLErrorMessageType errtype, michael@0: PRErrorCode errorCode) michael@0: : mInfoObject(infoObject) michael@0: , mErrType(errtype) michael@0: , mErrorCode(errorCode) michael@0: { michael@0: } michael@0: michael@0: virtual void RunOnTargetThread() michael@0: { michael@0: nsHandleSSLError(mInfoObject, mErrType, mErrorCode); michael@0: } michael@0: michael@0: RefPtr mInfoObject; michael@0: ::mozilla::psm::SSLErrorMessageType mErrType; michael@0: const PRErrorCode mErrorCode; michael@0: }; michael@0: michael@0: namespace { michael@0: michael@0: bool michael@0: retryDueToTLSIntolerance(PRErrorCode err, nsNSSSocketInfo* socketInfo) michael@0: { michael@0: // This function is supposed to decide which error codes should michael@0: // be used to conclude server is TLS intolerant. michael@0: // Note this only happens during the initial SSL handshake. michael@0: michael@0: SSLVersionRange range = socketInfo->GetTLSVersionRange(); michael@0: michael@0: uint32_t reason; michael@0: switch (err) { michael@0: case SSL_ERROR_BAD_MAC_ALERT: reason = 1; break; michael@0: case SSL_ERROR_BAD_MAC_READ: reason = 2; break; michael@0: case SSL_ERROR_HANDSHAKE_FAILURE_ALERT: reason = 3; break; michael@0: case SSL_ERROR_HANDSHAKE_UNEXPECTED_ALERT: reason = 4; break; michael@0: case SSL_ERROR_CLIENT_KEY_EXCHANGE_FAILURE: reason = 5; break; michael@0: case SSL_ERROR_ILLEGAL_PARAMETER_ALERT: reason = 6; break; michael@0: case SSL_ERROR_NO_CYPHER_OVERLAP: reason = 7; break; michael@0: case SSL_ERROR_BAD_SERVER: reason = 8; break; michael@0: case SSL_ERROR_BAD_BLOCK_PADDING: reason = 9; break; michael@0: case SSL_ERROR_UNSUPPORTED_VERSION: reason = 10; break; michael@0: case SSL_ERROR_PROTOCOL_VERSION_ALERT: reason = 11; break; michael@0: case SSL_ERROR_RX_MALFORMED_FINISHED: reason = 12; break; michael@0: case SSL_ERROR_BAD_HANDSHAKE_HASH_VALUE: reason = 13; break; michael@0: case SSL_ERROR_DECODE_ERROR_ALERT: reason = 14; break; michael@0: case SSL_ERROR_RX_UNKNOWN_ALERT: reason = 15; break; michael@0: michael@0: case PR_CONNECT_RESET_ERROR: reason = 16; goto conditional; michael@0: case PR_END_OF_FILE_ERROR: reason = 17; goto conditional; michael@0: michael@0: // When not using a proxy we'll see a connection reset error. michael@0: // When using a proxy, we'll see an end of file error. michael@0: // In addition check for some error codes where it is reasonable michael@0: // to retry without TLS. michael@0: michael@0: // Don't allow STARTTLS connections to fall back on connection resets or michael@0: // EOF. Also, don't fall back from TLS 1.0 to SSL 3.0 for connection michael@0: // resets, because connection resets have too many false positives, michael@0: // and we want to maximize how often we send TLS 1.0+ with extensions michael@0: // if at all reasonable. Unfortunately, it appears we have to allow michael@0: // fallback from TLS 1.2 and TLS 1.1 for connection resets due to bad michael@0: // servers and possibly bad intermediaries. michael@0: conditional: michael@0: if ((err == PR_CONNECT_RESET_ERROR && michael@0: range.max <= SSL_LIBRARY_VERSION_TLS_1_0) || michael@0: socketInfo->GetForSTARTTLS()) { michael@0: return false; michael@0: } michael@0: break; michael@0: michael@0: default: michael@0: return false; michael@0: } michael@0: michael@0: Telemetry::ID pre; michael@0: Telemetry::ID post; michael@0: switch (range.max) { michael@0: case SSL_LIBRARY_VERSION_TLS_1_2: michael@0: pre = Telemetry::SSL_TLS12_INTOLERANCE_REASON_PRE; michael@0: post = Telemetry::SSL_TLS12_INTOLERANCE_REASON_POST; michael@0: break; michael@0: case SSL_LIBRARY_VERSION_TLS_1_1: michael@0: pre = Telemetry::SSL_TLS11_INTOLERANCE_REASON_PRE; michael@0: post = Telemetry::SSL_TLS11_INTOLERANCE_REASON_POST; michael@0: break; michael@0: case SSL_LIBRARY_VERSION_TLS_1_0: michael@0: pre = Telemetry::SSL_TLS10_INTOLERANCE_REASON_PRE; michael@0: post = Telemetry::SSL_TLS10_INTOLERANCE_REASON_POST; michael@0: break; michael@0: case SSL_LIBRARY_VERSION_3_0: michael@0: pre = Telemetry::SSL_SSL30_INTOLERANCE_REASON_PRE; michael@0: post = Telemetry::SSL_SSL30_INTOLERANCE_REASON_POST; michael@0: break; michael@0: default: michael@0: MOZ_CRASH("impossible TLS version"); michael@0: return false; michael@0: } michael@0: michael@0: // The difference between _PRE and _POST represents how often we avoided michael@0: // TLS intolerance fallback due to remembered tolerance. michael@0: Telemetry::Accumulate(pre, reason); michael@0: michael@0: if (!socketInfo->SharedState().IOLayerHelpers() michael@0: .rememberIntolerantAtVersion(socketInfo->GetHostName(), michael@0: socketInfo->GetPort(), michael@0: range.min, range.max)) { michael@0: return false; michael@0: } michael@0: michael@0: Telemetry::Accumulate(post, reason); michael@0: michael@0: return true; michael@0: } michael@0: michael@0: int32_t michael@0: checkHandshake(int32_t bytesTransfered, bool wasReading, michael@0: PRFileDesc* ssl_layer_fd, nsNSSSocketInfo* socketInfo) michael@0: { michael@0: const PRErrorCode originalError = PR_GetError(); michael@0: PRErrorCode err = originalError; michael@0: michael@0: // This is where we work around all of those SSL servers that don't michael@0: // conform to the SSL spec and shutdown a connection when we request michael@0: // SSL v3.1 (aka TLS). The spec says the client says what version michael@0: // of the protocol we're willing to perform, in our case SSL v3.1 michael@0: // In its response, the server says which version it wants to perform. michael@0: // Many servers out there only know how to do v3.0. Next, we're supposed michael@0: // to send back the version of the protocol we requested (ie v3.1). At michael@0: // this point many servers's implementations are broken and they shut michael@0: // down the connection when they don't see the version they sent back. michael@0: // This is supposed to prevent a man in the middle from forcing one michael@0: // side to dumb down to a lower level of the protocol. Unfortunately, michael@0: // there are enough broken servers out there that such a gross work-around michael@0: // is necessary. :( michael@0: michael@0: // Do NOT assume TLS intolerance on a closed connection after bad cert ui was shown. michael@0: // Simply retry. michael@0: // This depends on the fact that Cert UI will not be shown again, michael@0: // should the user override the bad cert. michael@0: michael@0: bool handleHandshakeResultNow = socketInfo->IsHandshakePending(); michael@0: michael@0: bool wantRetry = false; michael@0: michael@0: if (0 > bytesTransfered) { michael@0: if (handleHandshakeResultNow) { michael@0: if (PR_WOULD_BLOCK_ERROR == err) { michael@0: PR_SetError(err, 0); michael@0: return bytesTransfered; michael@0: } michael@0: michael@0: wantRetry = retryDueToTLSIntolerance(err, socketInfo); michael@0: } michael@0: michael@0: // This is the common place where we trigger non-cert-errors on a SSL michael@0: // socket. This might be reached at any time of the connection. michael@0: // michael@0: // The socketInfo->GetErrorCode() check is here to ensure we don't try to michael@0: // do the synchronous dispatch to the main thread unnecessarily after we've michael@0: // already handled a certificate error. (SSLErrorRunnable calls michael@0: // nsHandleSSLError, which has logic to avoid replacing the error message, michael@0: // so without the !socketInfo->GetErrorCode(), it would just be an michael@0: // expensive no-op.) michael@0: if (!wantRetry && (IS_SSL_ERROR(err) || IS_SEC_ERROR(err)) && michael@0: !socketInfo->GetErrorCode()) { michael@0: RefPtr runnable(new SSLErrorRunnable(socketInfo, michael@0: PlainErrorMessage, michael@0: err)); michael@0: (void) runnable->DispatchToMainThreadAndWait(); michael@0: } michael@0: } else if (wasReading && 0 == bytesTransfered) { michael@0: // zero bytes on reading, socket closed michael@0: if (handleHandshakeResultNow) { michael@0: wantRetry = retryDueToTLSIntolerance(PR_END_OF_FILE_ERROR, socketInfo); michael@0: } michael@0: } michael@0: michael@0: if (wantRetry) { michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, michael@0: ("[%p] checkHandshake: will retry with lower max TLS version\n", michael@0: ssl_layer_fd)); michael@0: // We want to cause the network layer to retry the connection. michael@0: err = PR_CONNECT_RESET_ERROR; michael@0: if (wasReading) michael@0: bytesTransfered = -1; michael@0: } michael@0: michael@0: // TLS intolerant servers only cause the first transfer to fail, so let's michael@0: // set the HandshakePending attribute to false so that we don't try the logic michael@0: // above again in a subsequent transfer. michael@0: if (handleHandshakeResultNow) { michael@0: socketInfo->SetHandshakeNotPending(); michael@0: } michael@0: michael@0: if (bytesTransfered < 0) { michael@0: // Remember that we encountered an error so that getSocketInfoIfRunning michael@0: // will correctly cause us to fail if another part of Gecko michael@0: // (erroneously) calls an I/O function (PR_Send/PR_Recv/etc.) again on michael@0: // this socket. Note that we use the original error because if we use michael@0: // PR_CONNECT_RESET_ERROR, we'll repeated try to reconnect. michael@0: if (originalError != PR_WOULD_BLOCK_ERROR && !socketInfo->GetErrorCode()) { michael@0: socketInfo->SetCanceled(originalError, PlainErrorMessage); michael@0: } michael@0: PR_SetError(err, 0); michael@0: } michael@0: michael@0: return bytesTransfered; michael@0: } michael@0: michael@0: } michael@0: michael@0: static int16_t michael@0: nsSSLIOLayerPoll(PRFileDesc* fd, int16_t in_flags, int16_t* out_flags) michael@0: { michael@0: nsNSSShutDownPreventionLock locker; michael@0: michael@0: if (!out_flags) { michael@0: NS_WARNING("nsSSLIOLayerPoll called with null out_flags"); michael@0: return 0; michael@0: } michael@0: michael@0: *out_flags = 0; michael@0: michael@0: nsNSSSocketInfo* socketInfo = michael@0: getSocketInfoIfRunning(fd, not_reading_or_writing, locker); michael@0: michael@0: if (!socketInfo) { michael@0: // If we get here, it is probably because certificate validation failed michael@0: // and this is the first I/O operation after the failure. michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, michael@0: ("[%p] polling SSL socket right after certificate verification failed " michael@0: "or NSS shutdown or SDR logout %d\n", michael@0: fd, (int) in_flags)); michael@0: michael@0: NS_ASSERTION(in_flags & PR_POLL_EXCEPT, michael@0: "caller did not poll for EXCEPT (canceled)"); michael@0: // Since this poll method cannot return errors, we want the caller to call michael@0: // PR_Send/PR_Recv right away to get the error, so we tell that we are michael@0: // ready for whatever I/O they are asking for. (See getSocketInfoIfRunning). michael@0: *out_flags = in_flags | PR_POLL_EXCEPT; // see also bug 480619 michael@0: return in_flags; michael@0: } michael@0: michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, michael@0: (socketInfo->IsWaitingForCertVerification() michael@0: ? "[%p] polling SSL socket during certificate verification using lower %d\n" michael@0: : "[%p] poll SSL socket using lower %d\n", michael@0: fd, (int) in_flags)); michael@0: michael@0: // We want the handshake to continue during certificate validation, so we michael@0: // don't need to do anything special here. libssl automatically blocks when michael@0: // it reaches any point that would be unsafe to send/receive something before michael@0: // cert validation is complete. michael@0: int16_t result = fd->lower->methods->poll(fd->lower, in_flags, out_flags); michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] poll SSL socket returned %d\n", michael@0: (void*) fd, (int) result)); michael@0: return result; michael@0: } michael@0: michael@0: nsSSLIOLayerHelpers::nsSSLIOLayerHelpers() michael@0: : mRenegoUnrestrictedSites(nullptr) michael@0: , mTreatUnsafeNegotiationAsBroken(false) michael@0: , mWarnLevelMissingRFC5746(1) michael@0: , mTLSIntoleranceInfo(16) michael@0: , mFalseStartRequireNPN(true) michael@0: , mFalseStartRequireForwardSecrecy(false) michael@0: , mutex("nsSSLIOLayerHelpers.mutex") michael@0: { michael@0: } michael@0: michael@0: static int michael@0: _PSM_InvalidInt(void) michael@0: { michael@0: PR_ASSERT(!"I/O method is invalid"); michael@0: PR_SetError(PR_INVALID_METHOD_ERROR, 0); michael@0: return -1; michael@0: } michael@0: michael@0: static int64_t michael@0: _PSM_InvalidInt64(void) michael@0: { michael@0: PR_ASSERT(!"I/O method is invalid"); michael@0: PR_SetError(PR_INVALID_METHOD_ERROR, 0); michael@0: return -1; michael@0: } michael@0: michael@0: static PRStatus michael@0: _PSM_InvalidStatus(void) michael@0: { michael@0: PR_ASSERT(!"I/O method is invalid"); michael@0: PR_SetError(PR_INVALID_METHOD_ERROR, 0); michael@0: return PR_FAILURE; michael@0: } michael@0: michael@0: static PRFileDesc* michael@0: _PSM_InvalidDesc(void) michael@0: { michael@0: PR_ASSERT(!"I/O method is invalid"); michael@0: PR_SetError(PR_INVALID_METHOD_ERROR, 0); michael@0: return nullptr; michael@0: } michael@0: michael@0: static PRStatus michael@0: PSMGetsockname(PRFileDesc* fd, PRNetAddr* addr) michael@0: { michael@0: nsNSSShutDownPreventionLock locker; michael@0: if (!getSocketInfoIfRunning(fd, not_reading_or_writing, locker)) michael@0: return PR_FAILURE; michael@0: michael@0: return fd->lower->methods->getsockname(fd->lower, addr); michael@0: } michael@0: michael@0: static PRStatus michael@0: PSMGetpeername(PRFileDesc* fd, PRNetAddr* addr) michael@0: { michael@0: nsNSSShutDownPreventionLock locker; michael@0: if (!getSocketInfoIfRunning(fd, not_reading_or_writing, locker)) michael@0: return PR_FAILURE; michael@0: michael@0: return fd->lower->methods->getpeername(fd->lower, addr); michael@0: } michael@0: michael@0: static PRStatus michael@0: PSMGetsocketoption(PRFileDesc* fd, PRSocketOptionData* data) michael@0: { michael@0: nsNSSShutDownPreventionLock locker; michael@0: if (!getSocketInfoIfRunning(fd, not_reading_or_writing, locker)) michael@0: return PR_FAILURE; michael@0: michael@0: return fd->lower->methods->getsocketoption(fd, data); michael@0: } michael@0: michael@0: static PRStatus michael@0: PSMSetsocketoption(PRFileDesc* fd, const PRSocketOptionData* data) michael@0: { michael@0: nsNSSShutDownPreventionLock locker; michael@0: if (!getSocketInfoIfRunning(fd, not_reading_or_writing, locker)) michael@0: return PR_FAILURE; michael@0: michael@0: return fd->lower->methods->setsocketoption(fd, data); michael@0: } michael@0: michael@0: static int32_t michael@0: PSMRecv(PRFileDesc* fd, void* buf, int32_t amount, int flags, michael@0: PRIntervalTime timeout) michael@0: { michael@0: nsNSSShutDownPreventionLock locker; michael@0: nsNSSSocketInfo* socketInfo = getSocketInfoIfRunning(fd, reading, locker); michael@0: if (!socketInfo) michael@0: return -1; michael@0: michael@0: if (flags != PR_MSG_PEEK && flags != 0) { michael@0: PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0); michael@0: return -1; michael@0: } michael@0: michael@0: int32_t bytesRead = fd->lower->methods->recv(fd->lower, buf, amount, flags, michael@0: timeout); michael@0: michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] read %d bytes\n", (void*) fd, michael@0: bytesRead)); michael@0: michael@0: #ifdef DEBUG_SSL_VERBOSE michael@0: DEBUG_DUMP_BUFFER((unsigned char*) buf, bytesRead); michael@0: #endif michael@0: michael@0: return checkHandshake(bytesRead, true, fd, socketInfo); michael@0: } michael@0: michael@0: static int32_t michael@0: PSMSend(PRFileDesc* fd, const void* buf, int32_t amount, int flags, michael@0: PRIntervalTime timeout) michael@0: { michael@0: nsNSSShutDownPreventionLock locker; michael@0: nsNSSSocketInfo* socketInfo = getSocketInfoIfRunning(fd, writing, locker); michael@0: if (!socketInfo) michael@0: return -1; michael@0: michael@0: if (flags != 0) { michael@0: PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0); michael@0: return -1; michael@0: } michael@0: michael@0: #ifdef DEBUG_SSL_VERBOSE michael@0: DEBUG_DUMP_BUFFER((unsigned char*) buf, amount); michael@0: #endif michael@0: michael@0: int32_t bytesWritten = fd->lower->methods->send(fd->lower, buf, amount, michael@0: flags, timeout); michael@0: michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] wrote %d bytes\n", michael@0: fd, bytesWritten)); michael@0: michael@0: return checkHandshake(bytesWritten, false, fd, socketInfo); michael@0: } michael@0: michael@0: static int32_t michael@0: nsSSLIOLayerRead(PRFileDesc* fd, void* buf, int32_t amount) michael@0: { michael@0: return PSMRecv(fd, buf, amount, 0, PR_INTERVAL_NO_TIMEOUT); michael@0: } michael@0: michael@0: static int32_t michael@0: nsSSLIOLayerWrite(PRFileDesc* fd, const void* buf, int32_t amount) michael@0: { michael@0: return PSMSend(fd, buf, amount, 0, PR_INTERVAL_NO_TIMEOUT); michael@0: } michael@0: michael@0: static PRStatus michael@0: PSMConnectcontinue(PRFileDesc* fd, int16_t out_flags) michael@0: { michael@0: nsNSSShutDownPreventionLock locker; michael@0: if (!getSocketInfoIfRunning(fd, not_reading_or_writing, locker)) { michael@0: return PR_FAILURE; michael@0: } michael@0: michael@0: return fd->lower->methods->connectcontinue(fd, out_flags); michael@0: } michael@0: michael@0: static int michael@0: PSMAvailable(void) michael@0: { michael@0: // This is called through PR_Available(), but is not implemented in PSM michael@0: PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0); michael@0: return -1; michael@0: } michael@0: michael@0: static int64_t michael@0: PSMAvailable64(void) michael@0: { michael@0: // This is called through PR_Available(), but is not implemented in PSM michael@0: PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0); michael@0: return -1; michael@0: } michael@0: michael@0: namespace { michael@0: michael@0: class PrefObserver : public nsIObserver { michael@0: public: michael@0: NS_DECL_THREADSAFE_ISUPPORTS michael@0: NS_DECL_NSIOBSERVER michael@0: PrefObserver(nsSSLIOLayerHelpers* aOwner) : mOwner(aOwner) {} michael@0: virtual ~PrefObserver() {} michael@0: private: michael@0: nsSSLIOLayerHelpers* mOwner; michael@0: }; michael@0: michael@0: } // unnamed namespace michael@0: michael@0: NS_IMPL_ISUPPORTS(PrefObserver, nsIObserver) michael@0: michael@0: NS_IMETHODIMP michael@0: PrefObserver::Observe(nsISupports* aSubject, const char* aTopic, michael@0: const char16_t* someData) michael@0: { michael@0: if (nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) { michael@0: NS_ConvertUTF16toUTF8 prefName(someData); michael@0: michael@0: if (prefName.Equals("security.ssl.renego_unrestricted_hosts")) { michael@0: nsCString unrestricted_hosts; michael@0: Preferences::GetCString("security.ssl.renego_unrestricted_hosts", &unrestricted_hosts); michael@0: if (!unrestricted_hosts.IsEmpty()) { michael@0: mOwner->setRenegoUnrestrictedSites(unrestricted_hosts); michael@0: } michael@0: } else if (prefName.Equals("security.ssl.treat_unsafe_negotiation_as_broken")) { michael@0: bool enabled; michael@0: Preferences::GetBool("security.ssl.treat_unsafe_negotiation_as_broken", &enabled); michael@0: mOwner->setTreatUnsafeNegotiationAsBroken(enabled); michael@0: } else if (prefName.Equals("security.ssl.warn_missing_rfc5746")) { michael@0: int32_t warnLevel = 1; michael@0: Preferences::GetInt("security.ssl.warn_missing_rfc5746", &warnLevel); michael@0: mOwner->setWarnLevelMissingRFC5746(warnLevel); michael@0: } else if (prefName.Equals("security.ssl.false_start.require-npn")) { michael@0: mOwner->mFalseStartRequireNPN = michael@0: Preferences::GetBool("security.ssl.false_start.require-npn", michael@0: FALSE_START_REQUIRE_NPN_DEFAULT); michael@0: } else if (prefName.Equals("security.ssl.false_start.require-forward-secrecy")) { michael@0: mOwner->mFalseStartRequireForwardSecrecy = michael@0: Preferences::GetBool("security.ssl.false_start.require-forward-secrecy", michael@0: FALSE_START_REQUIRE_FORWARD_SECRECY_DEFAULT); michael@0: } michael@0: } michael@0: return NS_OK; michael@0: } michael@0: michael@0: static int32_t michael@0: PlaintextRecv(PRFileDesc* fd, void* buf, int32_t amount, int flags, michael@0: PRIntervalTime timeout) michael@0: { michael@0: // The shutdownlocker is not needed here because it will already be michael@0: // held higher in the stack michael@0: nsNSSSocketInfo* socketInfo = nullptr; michael@0: michael@0: int32_t bytesRead = fd->lower->methods->recv(fd->lower, buf, amount, flags, michael@0: timeout); michael@0: if (fd->identity == nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity) michael@0: socketInfo = (nsNSSSocketInfo*) fd->secret; michael@0: michael@0: if ((bytesRead > 0) && socketInfo) michael@0: socketInfo->AddPlaintextBytesRead(bytesRead); michael@0: return bytesRead; michael@0: } michael@0: michael@0: nsSSLIOLayerHelpers::~nsSSLIOLayerHelpers() michael@0: { michael@0: // mPrefObserver will only be set if this->Init was called. The GTest tests michael@0: // do not call Init. michael@0: if (mPrefObserver) { michael@0: Preferences::RemoveObserver(mPrefObserver, michael@0: "security.ssl.renego_unrestricted_hosts"); michael@0: Preferences::RemoveObserver(mPrefObserver, michael@0: "security.ssl.treat_unsafe_negotiation_as_broken"); michael@0: Preferences::RemoveObserver(mPrefObserver, michael@0: "security.ssl.warn_missing_rfc5746"); michael@0: Preferences::RemoveObserver(mPrefObserver, michael@0: "security.ssl.false_start.require-npn"); michael@0: Preferences::RemoveObserver(mPrefObserver, michael@0: "security.ssl.false_start.require-forward-secrecy"); michael@0: } michael@0: } michael@0: michael@0: nsresult michael@0: nsSSLIOLayerHelpers::Init() michael@0: { michael@0: if (!nsSSLIOLayerInitialized) { michael@0: nsSSLIOLayerInitialized = true; michael@0: nsSSLIOLayerIdentity = PR_GetUniqueIdentity("NSS layer"); michael@0: nsSSLIOLayerMethods = *PR_GetDefaultIOMethods(); michael@0: michael@0: nsSSLIOLayerMethods.available = (PRAvailableFN) PSMAvailable; michael@0: nsSSLIOLayerMethods.available64 = (PRAvailable64FN) PSMAvailable64; michael@0: nsSSLIOLayerMethods.fsync = (PRFsyncFN) _PSM_InvalidStatus; michael@0: nsSSLIOLayerMethods.seek = (PRSeekFN) _PSM_InvalidInt; michael@0: nsSSLIOLayerMethods.seek64 = (PRSeek64FN) _PSM_InvalidInt64; michael@0: nsSSLIOLayerMethods.fileInfo = (PRFileInfoFN) _PSM_InvalidStatus; michael@0: nsSSLIOLayerMethods.fileInfo64 = (PRFileInfo64FN) _PSM_InvalidStatus; michael@0: nsSSLIOLayerMethods.writev = (PRWritevFN) _PSM_InvalidInt; michael@0: nsSSLIOLayerMethods.accept = (PRAcceptFN) _PSM_InvalidDesc; michael@0: nsSSLIOLayerMethods.bind = (PRBindFN) _PSM_InvalidStatus; michael@0: nsSSLIOLayerMethods.listen = (PRListenFN) _PSM_InvalidStatus; michael@0: nsSSLIOLayerMethods.shutdown = (PRShutdownFN) _PSM_InvalidStatus; michael@0: nsSSLIOLayerMethods.recvfrom = (PRRecvfromFN) _PSM_InvalidInt; michael@0: nsSSLIOLayerMethods.sendto = (PRSendtoFN) _PSM_InvalidInt; michael@0: nsSSLIOLayerMethods.acceptread = (PRAcceptreadFN) _PSM_InvalidInt; michael@0: nsSSLIOLayerMethods.transmitfile = (PRTransmitfileFN) _PSM_InvalidInt; michael@0: nsSSLIOLayerMethods.sendfile = (PRSendfileFN) _PSM_InvalidInt; michael@0: michael@0: nsSSLIOLayerMethods.getsockname = PSMGetsockname; michael@0: nsSSLIOLayerMethods.getpeername = PSMGetpeername; michael@0: nsSSLIOLayerMethods.getsocketoption = PSMGetsocketoption; michael@0: nsSSLIOLayerMethods.setsocketoption = PSMSetsocketoption; michael@0: nsSSLIOLayerMethods.recv = PSMRecv; michael@0: nsSSLIOLayerMethods.send = PSMSend; michael@0: nsSSLIOLayerMethods.connectcontinue = PSMConnectcontinue; michael@0: michael@0: nsSSLIOLayerMethods.connect = nsSSLIOLayerConnect; michael@0: nsSSLIOLayerMethods.close = nsSSLIOLayerClose; michael@0: nsSSLIOLayerMethods.write = nsSSLIOLayerWrite; michael@0: nsSSLIOLayerMethods.read = nsSSLIOLayerRead; michael@0: nsSSLIOLayerMethods.poll = nsSSLIOLayerPoll; michael@0: michael@0: nsSSLPlaintextLayerIdentity = PR_GetUniqueIdentity("Plaintxext PSM layer"); michael@0: nsSSLPlaintextLayerMethods = *PR_GetDefaultIOMethods(); michael@0: nsSSLPlaintextLayerMethods.recv = PlaintextRecv; michael@0: } michael@0: michael@0: mRenegoUnrestrictedSites = new nsTHashtable(16); michael@0: michael@0: nsCString unrestricted_hosts; michael@0: Preferences::GetCString("security.ssl.renego_unrestricted_hosts", &unrestricted_hosts); michael@0: if (!unrestricted_hosts.IsEmpty()) { michael@0: setRenegoUnrestrictedSites(unrestricted_hosts); michael@0: } michael@0: michael@0: bool enabled = false; michael@0: Preferences::GetBool("security.ssl.treat_unsafe_negotiation_as_broken", &enabled); michael@0: setTreatUnsafeNegotiationAsBroken(enabled); michael@0: michael@0: int32_t warnLevel = 1; michael@0: Preferences::GetInt("security.ssl.warn_missing_rfc5746", &warnLevel); michael@0: setWarnLevelMissingRFC5746(warnLevel); michael@0: michael@0: mFalseStartRequireNPN = michael@0: Preferences::GetBool("security.ssl.false_start.require-npn", michael@0: FALSE_START_REQUIRE_NPN_DEFAULT); michael@0: mFalseStartRequireForwardSecrecy = michael@0: Preferences::GetBool("security.ssl.false_start.require-forward-secrecy", michael@0: FALSE_START_REQUIRE_FORWARD_SECRECY_DEFAULT); michael@0: michael@0: mPrefObserver = new PrefObserver(this); michael@0: Preferences::AddStrongObserver(mPrefObserver, michael@0: "security.ssl.renego_unrestricted_hosts"); michael@0: Preferences::AddStrongObserver(mPrefObserver, michael@0: "security.ssl.treat_unsafe_negotiation_as_broken"); michael@0: Preferences::AddStrongObserver(mPrefObserver, michael@0: "security.ssl.warn_missing_rfc5746"); michael@0: Preferences::AddStrongObserver(mPrefObserver, michael@0: "security.ssl.false_start.require-npn"); michael@0: Preferences::AddStrongObserver(mPrefObserver, michael@0: "security.ssl.false_start.require-forward-secrecy"); michael@0: return NS_OK; michael@0: } michael@0: michael@0: void michael@0: nsSSLIOLayerHelpers::clearStoredData() michael@0: { michael@0: mRenegoUnrestrictedSites->Clear(); michael@0: mTLSIntoleranceInfo.Clear(); michael@0: } michael@0: michael@0: void michael@0: nsSSLIOLayerHelpers::setRenegoUnrestrictedSites(const nsCString& str) michael@0: { michael@0: MutexAutoLock lock(mutex); michael@0: michael@0: if (mRenegoUnrestrictedSites) { michael@0: delete mRenegoUnrestrictedSites; michael@0: mRenegoUnrestrictedSites = nullptr; michael@0: } michael@0: michael@0: mRenegoUnrestrictedSites = new nsTHashtable(); michael@0: if (!mRenegoUnrestrictedSites) michael@0: return; michael@0: michael@0: nsCCharSeparatedTokenizer toker(str, ','); michael@0: michael@0: while (toker.hasMoreTokens()) { michael@0: const nsCSubstring& host = toker.nextToken(); michael@0: if (!host.IsEmpty()) { michael@0: mRenegoUnrestrictedSites->PutEntry(host); michael@0: } michael@0: } michael@0: } michael@0: michael@0: bool michael@0: nsSSLIOLayerHelpers::isRenegoUnrestrictedSite(const nsCString& str) michael@0: { michael@0: MutexAutoLock lock(mutex); michael@0: return mRenegoUnrestrictedSites->Contains(str); michael@0: } michael@0: michael@0: void michael@0: nsSSLIOLayerHelpers::setTreatUnsafeNegotiationAsBroken(bool broken) michael@0: { michael@0: MutexAutoLock lock(mutex); michael@0: mTreatUnsafeNegotiationAsBroken = broken; michael@0: } michael@0: michael@0: bool michael@0: nsSSLIOLayerHelpers::treatUnsafeNegotiationAsBroken() michael@0: { michael@0: MutexAutoLock lock(mutex); michael@0: return mTreatUnsafeNegotiationAsBroken; michael@0: } michael@0: michael@0: void michael@0: nsSSLIOLayerHelpers::setWarnLevelMissingRFC5746(int32_t level) michael@0: { michael@0: MutexAutoLock lock(mutex); michael@0: mWarnLevelMissingRFC5746 = level; michael@0: } michael@0: michael@0: int32_t michael@0: nsSSLIOLayerHelpers::getWarnLevelMissingRFC5746() michael@0: { michael@0: MutexAutoLock lock(mutex); michael@0: return mWarnLevelMissingRFC5746; michael@0: } michael@0: michael@0: nsresult michael@0: nsSSLIOLayerNewSocket(int32_t family, michael@0: const char* host, michael@0: int32_t port, michael@0: nsIProxyInfo *proxy, michael@0: PRFileDesc** fd, michael@0: nsISupports** info, michael@0: bool forSTARTTLS, michael@0: uint32_t flags) michael@0: { michael@0: michael@0: PRFileDesc* sock = PR_OpenTCPSocket(family); michael@0: if (!sock) return NS_ERROR_OUT_OF_MEMORY; michael@0: michael@0: nsresult rv = nsSSLIOLayerAddToSocket(family, host, port, proxy, michael@0: sock, info, forSTARTTLS, flags); michael@0: if (NS_FAILED(rv)) { michael@0: PR_Close(sock); michael@0: return rv; michael@0: } michael@0: michael@0: *fd = sock; michael@0: return NS_OK; michael@0: } michael@0: michael@0: // Creates CA names strings from (CERTDistNames* caNames) michael@0: // michael@0: // - arena: arena to allocate strings on michael@0: // - caNameStrings: filled with CA names strings on return michael@0: // - caNames: CERTDistNames to extract strings from michael@0: // - return: SECSuccess if successful; error code otherwise michael@0: // michael@0: // Note: copied in its entirety from Nova code michael@0: static SECStatus michael@0: nsConvertCANamesToStrings(PLArenaPool* arena, char** caNameStrings, michael@0: CERTDistNames* caNames) michael@0: { michael@0: SECItem* dername; michael@0: SECStatus rv; michael@0: int headerlen; michael@0: uint32_t contentlen; michael@0: SECItem newitem; michael@0: int n; michael@0: char* namestring; michael@0: michael@0: for (n = 0; n < caNames->nnames; n++) { michael@0: newitem.data = nullptr; michael@0: dername = &caNames->names[n]; michael@0: michael@0: rv = DER_Lengths(dername, &headerlen, &contentlen); michael@0: michael@0: if (rv != SECSuccess) { michael@0: goto loser; michael@0: } michael@0: michael@0: if (headerlen + contentlen != dername->len) { michael@0: // This must be from an enterprise 2.x server, which sent michael@0: // incorrectly formatted der without the outer wrapper of type and michael@0: // length. Fix it up by adding the top level header. michael@0: if (dername->len <= 127) { michael@0: newitem.data = (unsigned char*) PR_Malloc(dername->len + 2); michael@0: if (!newitem.data) { michael@0: goto loser; michael@0: } michael@0: newitem.data[0] = (unsigned char) 0x30; michael@0: newitem.data[1] = (unsigned char) dername->len; michael@0: (void) memcpy(&newitem.data[2], dername->data, dername->len); michael@0: } else if (dername->len <= 255) { michael@0: newitem.data = (unsigned char*) PR_Malloc(dername->len + 3); michael@0: if (!newitem.data) { michael@0: goto loser; michael@0: } michael@0: newitem.data[0] = (unsigned char) 0x30; michael@0: newitem.data[1] = (unsigned char) 0x81; michael@0: newitem.data[2] = (unsigned char) dername->len; michael@0: (void) memcpy(&newitem.data[3], dername->data, dername->len); michael@0: } else { michael@0: // greater than 256, better be less than 64k michael@0: newitem.data = (unsigned char*) PR_Malloc(dername->len + 4); michael@0: if (!newitem.data) { michael@0: goto loser; michael@0: } michael@0: newitem.data[0] = (unsigned char) 0x30; michael@0: newitem.data[1] = (unsigned char) 0x82; michael@0: newitem.data[2] = (unsigned char) ((dername->len >> 8) & 0xff); michael@0: newitem.data[3] = (unsigned char) (dername->len & 0xff); michael@0: memcpy(&newitem.data[4], dername->data, dername->len); michael@0: } michael@0: dername = &newitem; michael@0: } michael@0: michael@0: namestring = CERT_DerNameToAscii(dername); michael@0: if (!namestring) { michael@0: // XXX - keep going until we fail to convert the name michael@0: caNameStrings[n] = const_cast(""); michael@0: } else { michael@0: caNameStrings[n] = PORT_ArenaStrdup(arena, namestring); michael@0: PR_Free(namestring); michael@0: if (!caNameStrings[n]) { michael@0: goto loser; michael@0: } michael@0: } michael@0: michael@0: if (newitem.data) { michael@0: PR_Free(newitem.data); michael@0: } michael@0: } michael@0: michael@0: return SECSuccess; michael@0: loser: michael@0: if (newitem.data) { michael@0: PR_Free(newitem.data); michael@0: } michael@0: return SECFailure; michael@0: } michael@0: michael@0: // Sets certChoice by reading the preference michael@0: // michael@0: // If done properly, this function will read the identifier strings for ASK and michael@0: // AUTO modes read the selected strings from the preference, compare the michael@0: // strings, and determine in which mode it is in. We currently use ASK mode for michael@0: // UI apps and AUTO mode for UI-less apps without really asking for michael@0: // preferences. michael@0: nsresult michael@0: nsGetUserCertChoice(SSM_UserCertChoice* certChoice) michael@0: { michael@0: char* mode = nullptr; michael@0: nsresult ret; michael@0: michael@0: NS_ENSURE_ARG_POINTER(certChoice); michael@0: michael@0: nsCOMPtr pref = do_GetService(NS_PREFSERVICE_CONTRACTID); michael@0: michael@0: ret = pref->GetCharPref("security.default_personal_cert", &mode); michael@0: if (NS_FAILED(ret)) { michael@0: goto loser; michael@0: } michael@0: michael@0: if (PL_strcmp(mode, "Select Automatically") == 0) { michael@0: *certChoice = AUTO; michael@0: } else if (PL_strcmp(mode, "Ask Every Time") == 0) { michael@0: *certChoice = ASK; michael@0: } else { michael@0: // Most likely we see a nickname from a migrated cert. michael@0: // We do not currently support that, ask the user which cert to use. michael@0: *certChoice = ASK; michael@0: } michael@0: michael@0: loser: michael@0: if (mode) { michael@0: nsMemory::Free(mode); michael@0: } michael@0: return ret; michael@0: } michael@0: michael@0: static bool michael@0: hasExplicitKeyUsageNonRepudiation(CERTCertificate* cert) michael@0: { michael@0: // There is no extension, v1 or v2 certificate michael@0: if (!cert->extensions) michael@0: return false; michael@0: michael@0: SECStatus srv; michael@0: SECItem keyUsageItem; michael@0: keyUsageItem.data = nullptr; michael@0: michael@0: srv = CERT_FindKeyUsageExtension(cert, &keyUsageItem); michael@0: if (srv == SECFailure) michael@0: return false; michael@0: michael@0: unsigned char keyUsage = keyUsageItem.data[0]; michael@0: PORT_Free (keyUsageItem.data); michael@0: michael@0: return !!(keyUsage & KU_NON_REPUDIATION); michael@0: } michael@0: michael@0: class ClientAuthDataRunnable : public SyncRunnableBase michael@0: { michael@0: public: michael@0: ClientAuthDataRunnable(CERTDistNames* caNames, michael@0: CERTCertificate** pRetCert, michael@0: SECKEYPrivateKey** pRetKey, michael@0: nsNSSSocketInfo* info, michael@0: CERTCertificate* serverCert) michael@0: : mRV(SECFailure) michael@0: , mErrorCodeToReport(SEC_ERROR_NO_MEMORY) michael@0: , mPRetCert(pRetCert) michael@0: , mPRetKey(pRetKey) michael@0: , mCANames(caNames) michael@0: , mSocketInfo(info) michael@0: , mServerCert(serverCert) michael@0: { michael@0: } michael@0: michael@0: SECStatus mRV; // out michael@0: PRErrorCode mErrorCodeToReport; // out michael@0: CERTCertificate** const mPRetCert; // in/out michael@0: SECKEYPrivateKey** const mPRetKey; // in/out michael@0: protected: michael@0: virtual void RunOnTargetThread(); michael@0: private: michael@0: CERTDistNames* const mCANames; // in michael@0: nsNSSSocketInfo* const mSocketInfo; // in michael@0: CERTCertificate* const mServerCert; // in michael@0: }; michael@0: michael@0: // This callback function is used to pull client certificate michael@0: // information upon server request michael@0: // michael@0: // - arg: SSL data connection michael@0: // - socket: SSL socket we're dealing with michael@0: // - caNames: list of CA names michael@0: // - pRetCert: returns a pointer to a pointer to a valid certificate if michael@0: // successful; otherwise nullptr michael@0: // - pRetKey: returns a pointer to a pointer to the corresponding key if michael@0: // successful; otherwise nullptr michael@0: SECStatus michael@0: nsNSS_SSLGetClientAuthData(void* arg, PRFileDesc* socket, michael@0: CERTDistNames* caNames, CERTCertificate** pRetCert, michael@0: SECKEYPrivateKey** pRetKey) michael@0: { michael@0: nsNSSShutDownPreventionLock locker; michael@0: michael@0: if (!socket || !caNames || !pRetCert || !pRetKey) { michael@0: PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: RefPtr info( michael@0: reinterpret_cast(socket->higher->secret)); michael@0: michael@0: CERTCertificate* serverCert = SSL_PeerCertificate(socket); michael@0: if (!serverCert) { michael@0: NS_NOTREACHED("Missing server certificate should have been detected during " michael@0: "server cert authentication."); michael@0: PR_SetError(SSL_ERROR_NO_CERTIFICATE, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: if (info->GetJoined()) { michael@0: // We refuse to send a client certificate when there are multiple hostnames michael@0: // joined on this connection, because we only show the user one hostname michael@0: // (mHostName) in the client certificate UI. michael@0: michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, michael@0: ("[%p] Not returning client cert due to previous join\n", socket)); michael@0: *pRetCert = nullptr; michael@0: *pRetKey = nullptr; michael@0: return SECSuccess; michael@0: } michael@0: michael@0: // XXX: This should be done asynchronously; see bug 696976 michael@0: RefPtr runnable( michael@0: new ClientAuthDataRunnable(caNames, pRetCert, pRetKey, info, serverCert)); michael@0: nsresult rv = runnable->DispatchToMainThreadAndWait(); michael@0: if (NS_FAILED(rv)) { michael@0: PR_SetError(SEC_ERROR_NO_MEMORY, 0); michael@0: return SECFailure; michael@0: } michael@0: michael@0: if (runnable->mRV != SECSuccess) { michael@0: PR_SetError(runnable->mErrorCodeToReport, 0); michael@0: } else if (*runnable->mPRetCert || *runnable->mPRetKey) { michael@0: // Make joinConnection prohibit joining after we've sent a client cert michael@0: info->SetSentClientCert(); michael@0: } michael@0: michael@0: return runnable->mRV; michael@0: } michael@0: michael@0: void michael@0: ClientAuthDataRunnable::RunOnTargetThread() michael@0: { michael@0: PLArenaPool* arena = nullptr; michael@0: char** caNameStrings; michael@0: mozilla::pkix::ScopedCERTCertificate cert; michael@0: ScopedSECKEYPrivateKey privKey; michael@0: mozilla::pkix::ScopedCERTCertList certList; michael@0: CERTCertListNode* node; michael@0: ScopedCERTCertNicknames nicknames; michael@0: int keyError = 0; // used for private key retrieval error michael@0: SSM_UserCertChoice certChoice; michael@0: int32_t NumberOfCerts = 0; michael@0: void* wincx = mSocketInfo; michael@0: nsresult rv; michael@0: michael@0: // create caNameStrings michael@0: arena = PORT_NewArena(DER_DEFAULT_CHUNKSIZE); michael@0: if (!arena) { michael@0: goto loser; michael@0: } michael@0: michael@0: caNameStrings = (char**) PORT_ArenaAlloc(arena, michael@0: sizeof(char*) * (mCANames->nnames)); michael@0: if (!caNameStrings) { michael@0: goto loser; michael@0: } michael@0: michael@0: mRV = nsConvertCANamesToStrings(arena, caNameStrings, mCANames); michael@0: if (mRV != SECSuccess) { michael@0: goto loser; michael@0: } michael@0: michael@0: // get the preference michael@0: if (NS_FAILED(nsGetUserCertChoice(&certChoice))) { michael@0: goto loser; michael@0: } michael@0: michael@0: // find valid user cert and key pair michael@0: if (certChoice == AUTO) { michael@0: // automatically find the right cert michael@0: michael@0: // find all user certs that are valid and for SSL michael@0: certList = CERT_FindUserCertsByUsage(CERT_GetDefaultCertDB(), michael@0: certUsageSSLClient, false, michael@0: true, wincx); michael@0: if (!certList) { michael@0: goto noCert; michael@0: } michael@0: michael@0: // filter the list to those issued by CAs supported by the server michael@0: mRV = CERT_FilterCertListByCANames(certList.get(), mCANames->nnames, michael@0: caNameStrings, certUsageSSLClient); michael@0: if (mRV != SECSuccess) { michael@0: goto noCert; michael@0: } michael@0: michael@0: // make sure the list is not empty michael@0: node = CERT_LIST_HEAD(certList); michael@0: if (CERT_LIST_END(node, certList)) { michael@0: goto noCert; michael@0: } michael@0: michael@0: ScopedCERTCertificate low_prio_nonrep_cert; michael@0: michael@0: // loop through the list until we find a cert with a key michael@0: while (!CERT_LIST_END(node, certList)) { michael@0: // if the certificate has restriction and we do not satisfy it we do not michael@0: // use it michael@0: privKey = PK11_FindKeyByAnyCert(node->cert, wincx); michael@0: if (privKey) { michael@0: if (hasExplicitKeyUsageNonRepudiation(node->cert)) { michael@0: privKey = nullptr; michael@0: // Not a prefered cert michael@0: if (!low_prio_nonrep_cert) { // did not yet find a low prio cert michael@0: low_prio_nonrep_cert = CERT_DupCertificate(node->cert); michael@0: } michael@0: } else { michael@0: // this is a good cert to present michael@0: cert = CERT_DupCertificate(node->cert); michael@0: break; michael@0: } michael@0: } michael@0: keyError = PR_GetError(); michael@0: if (keyError == SEC_ERROR_BAD_PASSWORD) { michael@0: // problem with password: bail michael@0: goto loser; michael@0: } michael@0: michael@0: node = CERT_LIST_NEXT(node); michael@0: } michael@0: michael@0: if (!cert && low_prio_nonrep_cert) { michael@0: cert = low_prio_nonrep_cert.forget(); michael@0: privKey = PK11_FindKeyByAnyCert(cert.get(), wincx); michael@0: } michael@0: michael@0: if (!cert) { michael@0: goto noCert; michael@0: } michael@0: } else { // Not Auto => ask michael@0: // Get the SSL Certificate michael@0: michael@0: nsXPIDLCString hostname; michael@0: mSocketInfo->GetHostName(getter_Copies(hostname)); michael@0: michael@0: RefPtr cars = michael@0: mSocketInfo->SharedState().GetClientAuthRememberService(); michael@0: michael@0: bool hasRemembered = false; michael@0: nsCString rememberedDBKey; michael@0: if (cars) { michael@0: bool found; michael@0: rv = cars->HasRememberedDecision(hostname, mServerCert, michael@0: rememberedDBKey, &found); michael@0: if (NS_SUCCEEDED(rv) && found) { michael@0: hasRemembered = true; michael@0: } michael@0: } michael@0: michael@0: bool canceled = false; michael@0: michael@0: if (hasRemembered) { michael@0: if (rememberedDBKey.IsEmpty()) { michael@0: canceled = true; michael@0: } else { michael@0: nsCOMPtr certdb; michael@0: certdb = do_GetService(NS_X509CERTDB_CONTRACTID); michael@0: if (certdb) { michael@0: nsCOMPtr found_cert; michael@0: nsresult find_rv = michael@0: certdb->FindCertByDBKey(rememberedDBKey.get(), nullptr, michael@0: getter_AddRefs(found_cert)); michael@0: if (NS_SUCCEEDED(find_rv) && found_cert) { michael@0: nsNSSCertificate* obj_cert = michael@0: reinterpret_cast(found_cert.get()); michael@0: if (obj_cert) { michael@0: cert = obj_cert->GetCert(); michael@0: } michael@0: } michael@0: michael@0: if (!cert) { michael@0: hasRemembered = false; michael@0: } michael@0: } michael@0: } michael@0: } michael@0: michael@0: if (!hasRemembered) { michael@0: // user selects a cert to present michael@0: nsIClientAuthDialogs* dialogs = nullptr; michael@0: int32_t selectedIndex = -1; michael@0: char16_t** certNicknameList = nullptr; michael@0: char16_t** certDetailsList = nullptr; michael@0: michael@0: // find all user certs that are for SSL michael@0: // note that we are allowing expired certs in this list michael@0: certList = CERT_FindUserCertsByUsage(CERT_GetDefaultCertDB(), michael@0: certUsageSSLClient, false, michael@0: false, wincx); michael@0: if (!certList) { michael@0: goto noCert; michael@0: } michael@0: michael@0: if (mCANames->nnames != 0) { michael@0: // filter the list to those issued by CAs supported by the server michael@0: mRV = CERT_FilterCertListByCANames(certList.get(), michael@0: mCANames->nnames, michael@0: caNameStrings, michael@0: certUsageSSLClient); michael@0: if (mRV != SECSuccess) { michael@0: goto loser; michael@0: } michael@0: } michael@0: michael@0: if (CERT_LIST_END(CERT_LIST_HEAD(certList), certList)) { michael@0: // list is empty - no matching certs michael@0: goto noCert; michael@0: } michael@0: michael@0: // filter it further for hostname restriction michael@0: node = CERT_LIST_HEAD(certList.get()); michael@0: while (!CERT_LIST_END(node, certList.get())) { michael@0: ++NumberOfCerts; michael@0: node = CERT_LIST_NEXT(node); michael@0: } michael@0: if (CERT_LIST_END(CERT_LIST_HEAD(certList.get()), certList.get())) { michael@0: goto noCert; michael@0: } michael@0: michael@0: nicknames = getNSSCertNicknamesFromCertList(certList.get()); michael@0: michael@0: if (!nicknames) { michael@0: goto loser; michael@0: } michael@0: michael@0: NS_ASSERTION(nicknames->numnicknames == NumberOfCerts, "nicknames->numnicknames != NumberOfCerts"); michael@0: michael@0: // Get CN and O of the subject and O of the issuer michael@0: char* ccn = CERT_GetCommonName(&mServerCert->subject); michael@0: void* v = ccn; michael@0: voidCleaner ccnCleaner(v); michael@0: NS_ConvertUTF8toUTF16 cn(ccn); michael@0: michael@0: int32_t port; michael@0: mSocketInfo->GetPort(&port); michael@0: michael@0: nsString cn_host_port; michael@0: if (ccn && strcmp(ccn, hostname) == 0) { michael@0: cn_host_port.Append(cn); michael@0: cn_host_port.AppendLiteral(":"); michael@0: cn_host_port.AppendInt(port); michael@0: } else { michael@0: cn_host_port.Append(cn); michael@0: cn_host_port.AppendLiteral(" ("); michael@0: cn_host_port.AppendLiteral(":"); michael@0: cn_host_port.AppendInt(port); michael@0: cn_host_port.AppendLiteral(")"); michael@0: } michael@0: michael@0: char* corg = CERT_GetOrgName(&mServerCert->subject); michael@0: NS_ConvertUTF8toUTF16 org(corg); michael@0: if (corg) PORT_Free(corg); michael@0: michael@0: char* cissuer = CERT_GetOrgName(&mServerCert->issuer); michael@0: NS_ConvertUTF8toUTF16 issuer(cissuer); michael@0: if (cissuer) PORT_Free(cissuer); michael@0: michael@0: certNicknameList = michael@0: (char16_t**)nsMemory::Alloc(sizeof(char16_t*)* nicknames->numnicknames); michael@0: if (!certNicknameList) michael@0: goto loser; michael@0: certDetailsList = michael@0: (char16_t**)nsMemory::Alloc(sizeof(char16_t*)* nicknames->numnicknames); michael@0: if (!certDetailsList) { michael@0: nsMemory::Free(certNicknameList); michael@0: goto loser; michael@0: } michael@0: michael@0: int32_t CertsToUse; michael@0: for (CertsToUse = 0, node = CERT_LIST_HEAD(certList); michael@0: !CERT_LIST_END(node, certList) && CertsToUse < nicknames->numnicknames; michael@0: node = CERT_LIST_NEXT(node) michael@0: ) { michael@0: RefPtr tempCert(nsNSSCertificate::Create(node->cert)); michael@0: michael@0: if (!tempCert) michael@0: continue; michael@0: michael@0: NS_ConvertUTF8toUTF16 i_nickname(nicknames->nicknames[CertsToUse]); michael@0: nsAutoString nickWithSerial, details; michael@0: michael@0: if (NS_FAILED(tempCert->FormatUIStrings(i_nickname, nickWithSerial, details))) michael@0: continue; michael@0: michael@0: certNicknameList[CertsToUse] = ToNewUnicode(nickWithSerial); michael@0: if (!certNicknameList[CertsToUse]) michael@0: continue; michael@0: certDetailsList[CertsToUse] = ToNewUnicode(details); michael@0: if (!certDetailsList[CertsToUse]) { michael@0: nsMemory::Free(certNicknameList[CertsToUse]); michael@0: continue; michael@0: } michael@0: michael@0: ++CertsToUse; michael@0: } michael@0: michael@0: // Throw up the client auth dialog and get back the index of the selected cert michael@0: nsresult rv = getNSSDialogs((void**)&dialogs, michael@0: NS_GET_IID(nsIClientAuthDialogs), michael@0: NS_CLIENTAUTHDIALOGS_CONTRACTID); michael@0: michael@0: if (NS_FAILED(rv)) { michael@0: NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(CertsToUse, certNicknameList); michael@0: NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(CertsToUse, certDetailsList); michael@0: goto loser; michael@0: } michael@0: michael@0: { michael@0: nsPSMUITracker tracker; michael@0: if (tracker.isUIForbidden()) { michael@0: rv = NS_ERROR_NOT_AVAILABLE; michael@0: } else { michael@0: rv = dialogs->ChooseCertificate(mSocketInfo, cn_host_port.get(), michael@0: org.get(), issuer.get(), michael@0: (const char16_t**)certNicknameList, michael@0: (const char16_t**)certDetailsList, michael@0: CertsToUse, &selectedIndex, &canceled); michael@0: } michael@0: } michael@0: michael@0: NS_RELEASE(dialogs); michael@0: NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(CertsToUse, certNicknameList); michael@0: NS_FREE_XPCOM_ALLOCATED_POINTER_ARRAY(CertsToUse, certDetailsList); michael@0: michael@0: if (NS_FAILED(rv)) goto loser; michael@0: michael@0: // even if the user has canceled, we want to remember that, to avoid repeating prompts michael@0: bool wantRemember = false; michael@0: mSocketInfo->GetRememberClientAuthCertificate(&wantRemember); michael@0: michael@0: int i; michael@0: if (!canceled) michael@0: for (i = 0, node = CERT_LIST_HEAD(certList); michael@0: !CERT_LIST_END(node, certList); michael@0: ++i, node = CERT_LIST_NEXT(node)) { michael@0: michael@0: if (i == selectedIndex) { michael@0: cert = CERT_DupCertificate(node->cert); michael@0: break; michael@0: } michael@0: } michael@0: michael@0: if (cars && wantRemember) { michael@0: cars->RememberDecision(hostname, mServerCert, michael@0: canceled ? nullptr : cert.get()); michael@0: } michael@0: } michael@0: michael@0: if (canceled) { rv = NS_ERROR_NOT_AVAILABLE; goto loser; } michael@0: michael@0: if (!cert) { michael@0: goto loser; michael@0: } michael@0: michael@0: // go get the private key michael@0: privKey = PK11_FindKeyByAnyCert(cert.get(), wincx); michael@0: if (!privKey) { michael@0: keyError = PR_GetError(); michael@0: if (keyError == SEC_ERROR_BAD_PASSWORD) { michael@0: // problem with password: bail michael@0: goto loser; michael@0: } else { michael@0: goto noCert; michael@0: } michael@0: } michael@0: } michael@0: goto done; michael@0: michael@0: noCert: michael@0: loser: michael@0: if (mRV == SECSuccess) { michael@0: mRV = SECFailure; michael@0: } michael@0: done: michael@0: int error = PR_GetError(); michael@0: michael@0: if (arena) { michael@0: PORT_FreeArena(arena, false); michael@0: } michael@0: michael@0: *mPRetCert = cert.release(); michael@0: *mPRetKey = privKey.forget(); michael@0: michael@0: if (mRV == SECFailure) { michael@0: mErrorCodeToReport = error; michael@0: } michael@0: } michael@0: michael@0: static PRFileDesc* michael@0: nsSSLIOLayerImportFD(PRFileDesc* fd, michael@0: nsNSSSocketInfo* infoObject, michael@0: const char* host) michael@0: { michael@0: nsNSSShutDownPreventionLock locker; michael@0: PRFileDesc* sslSock = SSL_ImportFD(nullptr, fd); michael@0: if (!sslSock) { michael@0: NS_ASSERTION(false, "NSS: Error importing socket"); michael@0: return nullptr; michael@0: } michael@0: SSL_SetPKCS11PinArg(sslSock, (nsIInterfaceRequestor*) infoObject); michael@0: SSL_HandshakeCallback(sslSock, HandshakeCallback, infoObject); michael@0: SSL_SetCanFalseStartCallback(sslSock, CanFalseStartCallback, infoObject); michael@0: michael@0: // Disable this hook if we connect anonymously. See bug 466080. michael@0: uint32_t flags = 0; michael@0: infoObject->GetProviderFlags(&flags); michael@0: if (flags & nsISocketProvider::ANONYMOUS_CONNECT) { michael@0: SSL_GetClientAuthDataHook(sslSock, nullptr, infoObject); michael@0: } else { michael@0: SSL_GetClientAuthDataHook(sslSock, michael@0: (SSLGetClientAuthData) nsNSS_SSLGetClientAuthData, michael@0: infoObject); michael@0: } michael@0: if (SECSuccess != SSL_AuthCertificateHook(sslSock, AuthCertificateHook, michael@0: infoObject)) { michael@0: NS_NOTREACHED("failed to configure AuthCertificateHook"); michael@0: goto loser; michael@0: } michael@0: michael@0: if (SECSuccess != SSL_SetURL(sslSock, host)) { michael@0: NS_NOTREACHED("SSL_SetURL failed"); michael@0: goto loser; michael@0: } michael@0: michael@0: // This is an optimization to make sure the identity info dataset is parsed michael@0: // and loaded on a separate thread and can be overlapped with network latency. michael@0: EnsureServerVerificationInitialized(); michael@0: michael@0: return sslSock; michael@0: loser: michael@0: if (sslSock) { michael@0: PR_Close(sslSock); michael@0: } michael@0: return nullptr; michael@0: } michael@0: michael@0: static nsresult michael@0: nsSSLIOLayerSetOptions(PRFileDesc* fd, bool forSTARTTLS, michael@0: bool haveProxy, const char* host, int32_t port, michael@0: nsNSSSocketInfo* infoObject) michael@0: { michael@0: nsNSSShutDownPreventionLock locker; michael@0: if (forSTARTTLS || haveProxy) { michael@0: if (SECSuccess != SSL_OptionSet(fd, SSL_SECURITY, false)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: // Let's see if we're trying to connect to a site we know is michael@0: // TLS intolerant. michael@0: nsAutoCString key; michael@0: key = nsDependentCString(host) + NS_LITERAL_CSTRING(":") + nsPrintfCString("%d", port); michael@0: michael@0: SSLVersionRange range; michael@0: if (SSL_VersionRangeGet(fd, &range) != SECSuccess) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: uint16_t maxEnabledVersion = range.max; michael@0: michael@0: infoObject->SharedState().IOLayerHelpers() michael@0: .adjustForTLSIntolerance(infoObject->GetHostName(), infoObject->GetPort(), michael@0: range); michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, michael@0: ("[%p] nsSSLIOLayerSetOptions: using TLS version range (0x%04x,0x%04x)\n", michael@0: fd, static_cast(range.min), michael@0: static_cast(range.max))); michael@0: michael@0: if (SSL_VersionRangeSet(fd, &range) != SECSuccess) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: infoObject->SetTLSVersionRange(range); michael@0: michael@0: // when adjustForTLSIntolerance tweaks the maximum version downward, michael@0: // we tell the server using this SCSV so they can detect a downgrade attack michael@0: if (range.max < maxEnabledVersion) { michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, michael@0: ("[%p] nsSSLIOLayerSetOptions: enabling TLS_FALLBACK_SCSV\n", fd)); michael@0: if (SECSuccess != SSL_OptionSet(fd, SSL_ENABLE_FALLBACK_SCSV, true)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: bool enabled = infoObject->SharedState().IsOCSPStaplingEnabled(); michael@0: if (SECSuccess != SSL_OptionSet(fd, SSL_ENABLE_OCSP_STAPLING, enabled)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: if (SECSuccess != SSL_OptionSet(fd, SSL_HANDSHAKE_AS_CLIENT, true)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: nsSSLIOLayerHelpers& ioHelpers = infoObject->SharedState().IOLayerHelpers(); michael@0: if (ioHelpers.isRenegoUnrestrictedSite(nsDependentCString(host))) { michael@0: if (SECSuccess != SSL_OptionSet(fd, SSL_REQUIRE_SAFE_NEGOTIATION, false)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: if (SECSuccess != SSL_OptionSet(fd, SSL_ENABLE_RENEGOTIATION, SSL_RENEGOTIATE_UNRESTRICTED)) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: } michael@0: michael@0: // Set the Peer ID so that SSL proxy connections work properly and to michael@0: // separate anonymous and/or private browsing connections. michael@0: uint32_t flags = infoObject->GetProviderFlags(); michael@0: nsAutoCString peerId; michael@0: if (flags & nsISocketProvider::ANONYMOUS_CONNECT) { // See bug 466080 michael@0: peerId.Append("anon:"); michael@0: } michael@0: if (flags & nsISocketProvider::NO_PERMANENT_STORAGE) { michael@0: peerId.Append("private:"); michael@0: } michael@0: peerId.Append(host); michael@0: peerId.Append(':'); michael@0: peerId.AppendInt(port); michael@0: if (SECSuccess != SSL_SetSockPeerID(fd, peerId.get())) { michael@0: return NS_ERROR_FAILURE; michael@0: } michael@0: michael@0: return NS_OK; michael@0: } michael@0: michael@0: nsresult michael@0: nsSSLIOLayerAddToSocket(int32_t family, michael@0: const char* host, michael@0: int32_t port, michael@0: nsIProxyInfo* proxy, michael@0: PRFileDesc* fd, michael@0: nsISupports** info, michael@0: bool forSTARTTLS, michael@0: uint32_t providerFlags) michael@0: { michael@0: nsNSSShutDownPreventionLock locker; michael@0: PRFileDesc* layer = nullptr; michael@0: PRFileDesc* plaintextLayer = nullptr; michael@0: nsresult rv; michael@0: PRStatus stat; michael@0: michael@0: SharedSSLState* sharedState = michael@0: providerFlags & nsISocketProvider::NO_PERMANENT_STORAGE ? PrivateSSLState() : PublicSSLState(); michael@0: nsNSSSocketInfo* infoObject = new nsNSSSocketInfo(*sharedState, providerFlags); michael@0: if (!infoObject) return NS_ERROR_FAILURE; michael@0: michael@0: NS_ADDREF(infoObject); michael@0: infoObject->SetForSTARTTLS(forSTARTTLS); michael@0: infoObject->SetHostName(host); michael@0: infoObject->SetPort(port); michael@0: michael@0: bool haveProxy = false; michael@0: if (proxy) { michael@0: nsCString proxyHost; michael@0: proxy->GetHost(proxyHost); michael@0: haveProxy = !proxyHost.IsEmpty(); michael@0: } michael@0: michael@0: // A plaintext observer shim is inserted so we can observe some protocol michael@0: // details without modifying nss michael@0: plaintextLayer = PR_CreateIOLayerStub(nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity, michael@0: &nsSSLIOLayerHelpers::nsSSLPlaintextLayerMethods); michael@0: if (plaintextLayer) { michael@0: plaintextLayer->secret = (PRFilePrivate*) infoObject; michael@0: stat = PR_PushIOLayer(fd, PR_TOP_IO_LAYER, plaintextLayer); michael@0: if (stat == PR_FAILURE) { michael@0: plaintextLayer->dtor(plaintextLayer); michael@0: plaintextLayer = nullptr; michael@0: } michael@0: } michael@0: michael@0: PRFileDesc* sslSock = nsSSLIOLayerImportFD(fd, infoObject, host); michael@0: if (!sslSock) { michael@0: NS_ASSERTION(false, "NSS: Error importing socket"); michael@0: goto loser; michael@0: } michael@0: michael@0: infoObject->SetFileDescPtr(sslSock); michael@0: michael@0: rv = nsSSLIOLayerSetOptions(sslSock, forSTARTTLS, haveProxy, host, port, michael@0: infoObject); michael@0: michael@0: if (NS_FAILED(rv)) michael@0: goto loser; michael@0: michael@0: // Now, layer ourselves on top of the SSL socket... michael@0: layer = PR_CreateIOLayerStub(nsSSLIOLayerHelpers::nsSSLIOLayerIdentity, michael@0: &nsSSLIOLayerHelpers::nsSSLIOLayerMethods); michael@0: if (!layer) michael@0: goto loser; michael@0: michael@0: layer->secret = (PRFilePrivate*) infoObject; michael@0: stat = PR_PushIOLayer(sslSock, PR_GetLayersIdentity(sslSock), layer); michael@0: michael@0: if (stat == PR_FAILURE) { michael@0: goto loser; michael@0: } michael@0: michael@0: nsNSSShutDownList::trackSSLSocketCreate(); michael@0: michael@0: PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] Socket set up\n", (void*) sslSock)); michael@0: infoObject->QueryInterface(NS_GET_IID(nsISupports), (void**) (info)); michael@0: michael@0: // We are going use a clear connection first // michael@0: if (forSTARTTLS || haveProxy) { michael@0: infoObject->SetHandshakeNotPending(); michael@0: } michael@0: michael@0: infoObject->SharedState().NoteSocketCreated(); michael@0: michael@0: return NS_OK; michael@0: loser: michael@0: NS_IF_RELEASE(infoObject); michael@0: if (layer) { michael@0: layer->dtor(layer); michael@0: } michael@0: if (plaintextLayer) { michael@0: PR_PopIOLayer(fd, nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity); michael@0: plaintextLayer->dtor(plaintextLayer); michael@0: } michael@0: return NS_ERROR_FAILURE; michael@0: }