summaryrefslogtreecommitdiffstats
path: root/security/manager/ssl/nsNSSIOLayer.cpp
diff options
context:
space:
mode:
Diffstat (limited to 'security/manager/ssl/nsNSSIOLayer.cpp')
-rw-r--r--security/manager/ssl/nsNSSIOLayer.cpp2731
1 files changed, 2731 insertions, 0 deletions
diff --git a/security/manager/ssl/nsNSSIOLayer.cpp b/security/manager/ssl/nsNSSIOLayer.cpp
new file mode 100644
index 000000000..8be215308
--- /dev/null
+++ b/security/manager/ssl/nsNSSIOLayer.cpp
@@ -0,0 +1,2731 @@
+/* -*- Mode: C++; tab-width: 2; indent-tabs-mode: nil; c-basic-offset: 2 -*-
+ *
+ * This Source Code Form is subject to the terms of the Mozilla Public
+ * License, v. 2.0. If a copy of the MPL was not distributed with this
+ * file, You can obtain one at http://mozilla.org/MPL/2.0/. */
+
+#include "nsNSSIOLayer.h"
+
+#include <algorithm>
+
+#include "NSSCertDBTrustDomain.h"
+#include "NSSErrorsService.h"
+#include "PSMRunnable.h"
+#include "SSLServerCertVerification.h"
+#include "ScopedNSSTypes.h"
+#include "SharedSSLState.h"
+#include "keyhi.h"
+#include "mozilla/Casting.h"
+#include "mozilla/DebugOnly.h"
+#include "mozilla/Logging.h"
+#include "mozilla/Move.h"
+#include "mozilla/Preferences.h"
+#include "mozilla/Telemetry.h"
+#include "nsArray.h"
+#include "nsArrayUtils.h"
+#include "nsCharSeparatedTokenizer.h"
+#include "nsClientAuthRemember.h"
+#include "nsContentUtils.h"
+#include "nsIClientAuthDialogs.h"
+#include "nsIConsoleService.h"
+#include "nsIPrefService.h"
+#include "nsISocketProvider.h"
+#include "nsIWebProgressListener.h"
+#include "nsNSSCertHelper.h"
+#include "nsNSSComponent.h"
+#include "nsPrintfCString.h"
+#include "nsServiceManagerUtils.h"
+#include "pkix/pkixtypes.h"
+#include "prmem.h"
+#include "prnetdb.h"
+#include "secder.h"
+#include "secerr.h"
+#include "ssl.h"
+#include "sslerr.h"
+#include "sslproto.h"
+
+using namespace mozilla;
+using namespace mozilla::psm;
+
+//#define DEBUG_SSL_VERBOSE //Enable this define to get minimal
+ //reports when doing SSL read/write
+
+//#define DUMP_BUFFER //Enable this define along with
+ //DEBUG_SSL_VERBOSE to dump SSL
+ //read/write buffer to a log.
+ //Uses PR_LOG except on Mac where
+ //we always write out to our own
+ //file.
+
+namespace {
+
+#define MAX_ALPN_LENGTH 255
+
+void
+getSiteKey(const nsACString& hostName, uint16_t port,
+ /*out*/ nsCSubstring& key)
+{
+ key = hostName;
+ key.AppendASCII(":");
+ key.AppendInt(port);
+}
+
+// Historically, we have required that the server negotiate ALPN or NPN in
+// order to false start, as a compatibility hack to work around
+// implementations that just stop responding during false start. However, now
+// false start is resricted to modern crypto (TLS 1.2 and AEAD cipher suites)
+// so it is less likely that requring NPN or ALPN is still necessary.
+static const bool FALSE_START_REQUIRE_NPN_DEFAULT = false;
+
+} // unnamed namespace
+
+extern LazyLogModule gPIPNSSLog;
+
+nsNSSSocketInfo::nsNSSSocketInfo(SharedSSLState& aState, uint32_t providerFlags)
+ : mFd(nullptr),
+ mCertVerificationState(before_cert_verification),
+ mSharedState(aState),
+ mForSTARTTLS(false),
+ mHandshakePending(true),
+ mRememberClientAuthCertificate(false),
+ mPreliminaryHandshakeDone(false),
+ mNPNCompleted(false),
+ mEarlyDataAccepted(false),
+ mFalseStartCallbackCalled(false),
+ mFalseStarted(false),
+ mIsFullHandshake(false),
+ mHandshakeCompleted(false),
+ mJoined(false),
+ mSentClientCert(false),
+ mNotedTimeUntilReady(false),
+ mFailedVerification(false),
+ mKEAUsed(nsISSLSocketControl::KEY_EXCHANGE_UNKNOWN),
+ mKEAKeyBits(0),
+ mSSLVersionUsed(nsISSLSocketControl::SSL_VERSION_UNKNOWN),
+ mMACAlgorithmUsed(nsISSLSocketControl::SSL_MAC_UNKNOWN),
+ mBypassAuthentication(false),
+ mProviderFlags(providerFlags),
+ mSocketCreationTimestamp(TimeStamp::Now()),
+ mPlaintextBytesRead(0),
+ mClientCert(nullptr)
+{
+ mTLSVersionRange.min = 0;
+ mTLSVersionRange.max = 0;
+}
+
+nsNSSSocketInfo::~nsNSSSocketInfo()
+{
+}
+
+NS_IMPL_ISUPPORTS_INHERITED(nsNSSSocketInfo, TransportSecurityInfo,
+ nsISSLSocketControl,
+ nsIClientAuthUserDecision)
+
+NS_IMETHODIMP
+nsNSSSocketInfo::GetProviderFlags(uint32_t* aProviderFlags)
+{
+ *aProviderFlags = mProviderFlags;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNSSSocketInfo::GetKEAUsed(int16_t* aKea)
+{
+ *aKea = mKEAUsed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNSSSocketInfo::GetKEAKeyBits(uint32_t* aKeyBits)
+{
+ *aKeyBits = mKEAKeyBits;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNSSSocketInfo::GetSSLVersionUsed(int16_t* aSSLVersionUsed)
+{
+ *aSSLVersionUsed = mSSLVersionUsed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNSSSocketInfo::GetSSLVersionOffered(int16_t* aSSLVersionOffered)
+{
+ *aSSLVersionOffered = mTLSVersionRange.max;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNSSSocketInfo::GetMACAlgorithmUsed(int16_t* aMac)
+{
+ *aMac = mMACAlgorithmUsed;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNSSSocketInfo::GetClientCert(nsIX509Cert** aClientCert)
+{
+ NS_ENSURE_ARG_POINTER(aClientCert);
+ *aClientCert = mClientCert;
+ NS_IF_ADDREF(*aClientCert);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNSSSocketInfo::SetClientCert(nsIX509Cert* aClientCert)
+{
+ mClientCert = aClientCert;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNSSSocketInfo::GetBypassAuthentication(bool* arg)
+{
+ *arg = mBypassAuthentication;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNSSSocketInfo::GetFailedVerification(bool* arg)
+{
+ *arg = mFailedVerification;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNSSSocketInfo::GetRememberClientAuthCertificate(bool* aRemember)
+{
+ NS_ENSURE_ARG_POINTER(aRemember);
+ *aRemember = mRememberClientAuthCertificate;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNSSSocketInfo::SetRememberClientAuthCertificate(bool aRemember)
+{
+ mRememberClientAuthCertificate = aRemember;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNSSSocketInfo::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks)
+{
+ *aCallbacks = mCallbacks;
+ NS_IF_ADDREF(*aCallbacks);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNSSSocketInfo::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks)
+{
+ if (!aCallbacks) {
+ mCallbacks = nullptr;
+ return NS_OK;
+ }
+
+ mCallbacks = aCallbacks;
+
+ return NS_OK;
+}
+
+void
+nsNSSSocketInfo::NoteTimeUntilReady()
+{
+ if (mNotedTimeUntilReady)
+ return;
+
+ mNotedTimeUntilReady = true;
+
+ // This will include TCP and proxy tunnel wait time
+ Telemetry::AccumulateTimeDelta(Telemetry::SSL_TIME_UNTIL_READY,
+ mSocketCreationTimestamp, TimeStamp::Now());
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("[%p] nsNSSSocketInfo::NoteTimeUntilReady\n", mFd));
+}
+
+void
+nsNSSSocketInfo::SetHandshakeCompleted()
+{
+ if (!mHandshakeCompleted) {
+ enum HandshakeType {
+ Resumption = 1,
+ FalseStarted = 2,
+ ChoseNotToFalseStart = 3,
+ NotAllowedToFalseStart = 4,
+ };
+
+ HandshakeType handshakeType = !IsFullHandshake() ? Resumption
+ : mFalseStarted ? FalseStarted
+ : mFalseStartCallbackCalled ? ChoseNotToFalseStart
+ : NotAllowedToFalseStart;
+
+ // This will include TCP and proxy tunnel wait time
+ Telemetry::AccumulateTimeDelta(Telemetry::SSL_TIME_UNTIL_HANDSHAKE_FINISHED,
+ mSocketCreationTimestamp, TimeStamp::Now());
+
+ // If the handshake is completed for the first time from just 1 callback
+ // that means that TLS session resumption must have been used.
+ Telemetry::Accumulate(Telemetry::SSL_RESUMED_SESSION,
+ handshakeType == Resumption);
+ Telemetry::Accumulate(Telemetry::SSL_HANDSHAKE_TYPE, handshakeType);
+ }
+
+
+ // Remove the plain text layer as it is not needed anymore.
+ // The plain text layer is not always present - so its not a fatal error
+ // if it cannot be removed
+ PRFileDesc* poppedPlaintext =
+ PR_GetIdentitiesLayer(mFd, nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity);
+ if (poppedPlaintext) {
+ PR_PopIOLayer(mFd, nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity);
+ poppedPlaintext->dtor(poppedPlaintext);
+ }
+
+ mHandshakeCompleted = true;
+
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("[%p] nsNSSSocketInfo::SetHandshakeCompleted\n", (void*) mFd));
+
+ mIsFullHandshake = false; // reset for next handshake on this connection
+}
+
+void
+nsNSSSocketInfo::SetNegotiatedNPN(const char* value, uint32_t length)
+{
+ if (!value) {
+ mNegotiatedNPN.Truncate();
+ } else {
+ mNegotiatedNPN.Assign(value, length);
+ }
+ mNPNCompleted = true;
+}
+
+NS_IMETHODIMP
+nsNSSSocketInfo::GetNegotiatedNPN(nsACString& aNegotiatedNPN)
+{
+ if (!mNPNCompleted)
+ return NS_ERROR_NOT_CONNECTED;
+
+ aNegotiatedNPN = mNegotiatedNPN;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNSSSocketInfo::GetAlpnEarlySelection(nsACString& aAlpnSelected)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown() || isPK11LoggedOut()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ SSLNextProtoState alpnState;
+ unsigned char chosenAlpn[MAX_ALPN_LENGTH];
+ unsigned int chosenAlpnLen;
+ SECStatus rv = SSL_GetNextProto(mFd, &alpnState, chosenAlpn, &chosenAlpnLen,
+ AssertedCast<unsigned int>(ArrayLength(chosenAlpn)));
+
+ if (rv != SECSuccess || alpnState != SSL_NEXT_PROTO_EARLY_VALUE ||
+ chosenAlpnLen == 0) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+
+ aAlpnSelected.Assign(BitwiseCast<char*, unsigned char*>(chosenAlpn),
+ chosenAlpnLen);
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNSSSocketInfo::GetEarlyDataAccepted(bool* aAccepted)
+{
+ *aAccepted = mEarlyDataAccepted;
+ return NS_OK;
+}
+
+void
+nsNSSSocketInfo::SetEarlyDataAccepted(bool aAccepted)
+{
+ mEarlyDataAccepted = aAccepted;
+}
+
+NS_IMETHODIMP
+nsNSSSocketInfo::DriveHandshake()
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown() || isPK11LoggedOut()) {
+ return NS_ERROR_NOT_AVAILABLE;
+ }
+ if (!mFd) {
+ return NS_ERROR_FAILURE;
+ }
+ PRErrorCode errorCode = GetErrorCode();
+ if (errorCode) {
+ return GetXPCOMFromNSSError(errorCode);
+ }
+
+ SECStatus rv = SSL_ForceHandshake(mFd);
+
+ if (rv != SECSuccess) {
+ errorCode = PR_GetError();
+ if (errorCode == PR_WOULD_BLOCK_ERROR) {
+ return NS_BASE_STREAM_WOULD_BLOCK;
+ }
+
+ SetCanceled(errorCode, PlainErrorMessage);
+ return GetXPCOMFromNSSError(errorCode);
+ }
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNSSSocketInfo::IsAcceptableForHost(const nsACString& hostname, bool* _retval)
+{
+ NS_ENSURE_ARG(_retval);
+
+ *_retval = false;
+
+ // If this is the same hostname then the certicate status does not
+ // need to be considered. They are joinable.
+ if (hostname.Equals(GetHostName())) {
+ *_retval = true;
+ return NS_OK;
+ }
+
+ // Before checking the server certificate we need to make sure the
+ // handshake has completed.
+ if (!mHandshakeCompleted || !SSLStatus() || !SSLStatus()->HasServerCert()) {
+ return NS_OK;
+ }
+
+ // If the cert has error bits (e.g. it is untrusted) then do not join.
+ // The value of mHaveCertErrorBits is only reliable because we know that
+ // the handshake completed.
+ if (SSLStatus()->mHaveCertErrorBits)
+ return NS_OK;
+
+ // If the connection is using client certificates then do not join
+ // because the user decides on whether to send client certs to hosts on a
+ // per-domain basis.
+ if (mSentClientCert)
+ return NS_OK;
+
+ // Ensure that the server certificate covers the hostname that would
+ // like to join this connection
+
+ UniqueCERTCertificate nssCert;
+
+ nsCOMPtr<nsIX509Cert> cert;
+ if (NS_FAILED(SSLStatus()->GetServerCert(getter_AddRefs(cert)))) {
+ return NS_OK;
+ }
+ if (cert) {
+ nssCert.reset(cert->GetCert());
+ }
+
+ if (!nssCert) {
+ return NS_OK;
+ }
+
+ // Attempt to verify the joinee's certificate using the joining hostname.
+ // This ensures that any hostname-specific verification logic (e.g. key
+ // pinning) is satisfied by the joinee's certificate chain.
+ // This verification only uses local information; since we're on the network
+ // thread, we would be blocking on ourselves if we attempted any network i/o.
+ // TODO(bug 1056935): The certificate chain built by this verification may be
+ // different than the certificate chain originally built during the joined
+ // connection's TLS handshake. Consequently, we may report a wrong and/or
+ // misleading certificate chain for HTTP transactions coalesced onto this
+ // connection. This may become problematic in the future. For example,
+ // if/when we begin relying on intermediate certificates being stored in the
+ // securityInfo of a cached HTTPS response, that cached certificate chain may
+ // actually be the wrong chain. We should consider having JoinConnection
+ // return the certificate chain built here, so that the calling Necko code
+ // can associate the correct certificate chain with the HTTP transactions it
+ // is trying to join onto this connection.
+ RefPtr<SharedCertVerifier> certVerifier(GetDefaultCertVerifier());
+ if (!certVerifier) {
+ return NS_OK;
+ }
+ nsAutoCString hostnameFlat(PromiseFlatCString(hostname));
+ CertVerifier::Flags flags = CertVerifier::FLAG_LOCAL_ONLY;
+ UniqueCERTCertList unusedBuiltChain;
+ mozilla::pkix::Result result =
+ certVerifier->VerifySSLServerCert(nssCert,
+ nullptr, // stapledOCSPResponse
+ nullptr, // sctsFromTLSExtension
+ mozilla::pkix::Now(),
+ nullptr, // pinarg
+ hostnameFlat.get(),
+ unusedBuiltChain,
+ false, // save intermediates
+ flags);
+ if (result != mozilla::pkix::Success) {
+ return NS_OK;
+ }
+
+ // All tests pass
+ *_retval = true;
+ return NS_OK;
+}
+
+NS_IMETHODIMP
+nsNSSSocketInfo::JoinConnection(const nsACString& npnProtocol,
+ const nsACString& hostname,
+ int32_t port,
+ bool* _retval)
+{
+ *_retval = false;
+
+ // Different ports may not be joined together
+ if (port != GetPort())
+ return NS_OK;
+
+ // Make sure NPN has been completed and matches requested npnProtocol
+ if (!mNPNCompleted || !mNegotiatedNPN.Equals(npnProtocol))
+ return NS_OK;
+
+ if (mBypassAuthentication) {
+ // An unauthenticated connection does not know whether or not it
+ // is acceptable for a particular hostname
+ return NS_OK;
+ }
+
+ IsAcceptableForHost(hostname, _retval);
+
+ if (*_retval) {
+ // All tests pass - this is joinable
+ mJoined = true;
+ }
+ return NS_OK;
+}
+
+bool
+nsNSSSocketInfo::GetForSTARTTLS()
+{
+ return mForSTARTTLS;
+}
+
+void
+nsNSSSocketInfo::SetForSTARTTLS(bool aForSTARTTLS)
+{
+ mForSTARTTLS = aForSTARTTLS;
+}
+
+NS_IMETHODIMP
+nsNSSSocketInfo::ProxyStartSSL()
+{
+ return ActivateSSL();
+}
+
+NS_IMETHODIMP
+nsNSSSocketInfo::StartTLS()
+{
+ return ActivateSSL();
+}
+
+NS_IMETHODIMP
+nsNSSSocketInfo::SetNPNList(nsTArray<nsCString>& protocolArray)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown())
+ return NS_ERROR_NOT_AVAILABLE;
+ if (!mFd)
+ return NS_ERROR_FAILURE;
+
+ // the npn list is a concatenated list of 8 bit byte strings.
+ nsCString npnList;
+
+ for (uint32_t index = 0; index < protocolArray.Length(); ++index) {
+ if (protocolArray[index].IsEmpty() ||
+ protocolArray[index].Length() > 255)
+ return NS_ERROR_ILLEGAL_VALUE;
+
+ npnList.Append(protocolArray[index].Length());
+ npnList.Append(protocolArray[index]);
+ }
+
+ if (SSL_SetNextProtoNego(
+ mFd,
+ BitwiseCast<const unsigned char*, const char*>(npnList.get()),
+ npnList.Length()) != SECSuccess)
+ return NS_ERROR_FAILURE;
+
+ return NS_OK;
+}
+
+nsresult
+nsNSSSocketInfo::ActivateSSL()
+{
+ nsNSSShutDownPreventionLock locker;
+ if (isAlreadyShutDown())
+ return NS_ERROR_NOT_AVAILABLE;
+
+ if (SECSuccess != SSL_OptionSet(mFd, SSL_SECURITY, true))
+ return NS_ERROR_FAILURE;
+ if (SECSuccess != SSL_ResetHandshake(mFd, false))
+ return NS_ERROR_FAILURE;
+
+ mHandshakePending = true;
+
+ return NS_OK;
+}
+
+nsresult
+nsNSSSocketInfo::GetFileDescPtr(PRFileDesc** aFilePtr)
+{
+ *aFilePtr = mFd;
+ return NS_OK;
+}
+
+nsresult
+nsNSSSocketInfo::SetFileDescPtr(PRFileDesc* aFilePtr)
+{
+ mFd = aFilePtr;
+ return NS_OK;
+}
+
+void
+nsNSSSocketInfo::SetCertVerificationWaiting()
+{
+ // mCertVerificationState may be before_cert_verification for the first
+ // handshake on the connection, or after_cert_verification for subsequent
+ // renegotiation handshakes.
+ NS_ASSERTION(mCertVerificationState != waiting_for_cert_verification,
+ "Invalid state transition to waiting_for_cert_verification");
+ mCertVerificationState = waiting_for_cert_verification;
+}
+
+// Be careful that SetCertVerificationResult does NOT get called while we are
+// processing a SSL callback function, because SSL_AuthCertificateComplete will
+// attempt to acquire locks that are already held by libssl when it calls
+// callbacks.
+void
+nsNSSSocketInfo::SetCertVerificationResult(PRErrorCode errorCode,
+ SSLErrorMessageType errorMessageType)
+{
+ NS_ASSERTION(mCertVerificationState == waiting_for_cert_verification,
+ "Invalid state transition to cert_verification_finished");
+
+ if (mFd) {
+ SECStatus rv = SSL_AuthCertificateComplete(mFd, errorCode);
+ // Only replace errorCode if there was originally no error
+ if (rv != SECSuccess && errorCode == 0) {
+ errorCode = PR_GetError();
+ errorMessageType = PlainErrorMessage;
+ if (errorCode == 0) {
+ NS_ERROR("SSL_AuthCertificateComplete didn't set error code");
+ errorCode = PR_INVALID_STATE_ERROR;
+ }
+ }
+ }
+
+ if (errorCode) {
+ mFailedVerification = true;
+ SetCanceled(errorCode, errorMessageType);
+ }
+
+ if (mPlaintextBytesRead && !errorCode) {
+ Telemetry::Accumulate(Telemetry::SSL_BYTES_BEFORE_CERT_CALLBACK,
+ AssertedCast<uint32_t>(mPlaintextBytesRead));
+ }
+
+ mCertVerificationState = after_cert_verification;
+}
+
+SharedSSLState&
+nsNSSSocketInfo::SharedState()
+{
+ return mSharedState;
+}
+
+void nsSSLIOLayerHelpers::Cleanup()
+{
+ MutexAutoLock lock(mutex);
+ mTLSIntoleranceInfo.Clear();
+ mInsecureFallbackSites.Clear();
+}
+
+static void
+nsHandleSSLError(nsNSSSocketInfo* socketInfo,
+ ::mozilla::psm::SSLErrorMessageType errtype,
+ PRErrorCode err)
+{
+ if (!NS_IsMainThread()) {
+ NS_ERROR("nsHandleSSLError called off the main thread");
+ return;
+ }
+
+ // SetCanceled is only called by the main thread or the socket transport
+ // thread. Whenever this function is called on the main thread, the SSL
+ // thread is blocked on it. So, no mutex is necessary for
+ // SetCanceled()/GetError*().
+ if (socketInfo->GetErrorCode()) {
+ // If the socket has been flagged as canceled,
+ // the code who did was responsible for setting the error code.
+ return;
+ }
+
+ // We must cancel first, which sets the error code.
+ socketInfo->SetCanceled(err, PlainErrorMessage);
+ nsXPIDLString errorString;
+ socketInfo->GetErrorLogMessage(err, errtype, errorString);
+
+ if (!errorString.IsEmpty()) {
+ nsContentUtils::LogSimpleConsoleError(errorString, "SSL");
+ }
+}
+
+namespace {
+
+enum Operation { reading, writing, not_reading_or_writing };
+
+int32_t checkHandshake(int32_t bytesTransfered, bool wasReading,
+ PRFileDesc* ssl_layer_fd,
+ nsNSSSocketInfo* socketInfo);
+
+nsNSSSocketInfo*
+getSocketInfoIfRunning(PRFileDesc* fd, Operation op,
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+{
+ if (!fd || !fd->lower || !fd->secret ||
+ fd->identity != nsSSLIOLayerHelpers::nsSSLIOLayerIdentity) {
+ NS_ERROR("bad file descriptor passed to getSocketInfoIfRunning");
+ PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0);
+ return nullptr;
+ }
+
+ nsNSSSocketInfo* socketInfo = (nsNSSSocketInfo*) fd->secret;
+
+ if (socketInfo->isAlreadyShutDown() || socketInfo->isPK11LoggedOut()) {
+ PR_SetError(PR_SOCKET_SHUTDOWN_ERROR, 0);
+ return nullptr;
+ }
+
+ if (socketInfo->GetErrorCode()) {
+ PRErrorCode err = socketInfo->GetErrorCode();
+ PR_SetError(err, 0);
+ if (op == reading || op == writing) {
+ // We must do TLS intolerance checks for reads and writes, for timeouts
+ // in particular.
+ (void) checkHandshake(-1, op == reading, fd, socketInfo);
+ }
+
+ // If we get here, it is probably because cert verification failed and this
+ // is the first I/O attempt since that failure.
+ return nullptr;
+ }
+
+ return socketInfo;
+}
+
+} // namespace
+
+static PRStatus
+nsSSLIOLayerConnect(PRFileDesc* fd, const PRNetAddr* addr,
+ PRIntervalTime timeout)
+{
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("[%p] connecting SSL socket\n",
+ (void*) fd));
+ nsNSSShutDownPreventionLock locker;
+ if (!getSocketInfoIfRunning(fd, not_reading_or_writing, locker))
+ return PR_FAILURE;
+
+ PRStatus status = fd->lower->methods->connect(fd->lower, addr, timeout);
+ if (status != PR_SUCCESS) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Error, ("[%p] Lower layer connect error: %d\n",
+ (void*) fd, PR_GetError()));
+ return status;
+ }
+
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("[%p] Connect\n", (void*) fd));
+ return status;
+}
+
+void
+nsSSLIOLayerHelpers::rememberTolerantAtVersion(const nsACString& hostName,
+ int16_t port, uint16_t tolerant)
+{
+ nsCString key;
+ getSiteKey(hostName, port, key);
+
+ MutexAutoLock lock(mutex);
+
+ IntoleranceEntry entry;
+ if (mTLSIntoleranceInfo.Get(key, &entry)) {
+ entry.AssertInvariant();
+ entry.tolerant = std::max(entry.tolerant, tolerant);
+ if (entry.intolerant != 0 && entry.intolerant <= entry.tolerant) {
+ entry.intolerant = entry.tolerant + 1;
+ entry.intoleranceReason = 0; // lose the reason
+ }
+ if (entry.strongCipherStatus == StrongCipherStatusUnknown) {
+ entry.strongCipherStatus = StrongCiphersWorked;
+ }
+ } else {
+ entry.tolerant = tolerant;
+ entry.intolerant = 0;
+ entry.intoleranceReason = 0;
+ entry.strongCipherStatus = StrongCiphersWorked;
+ }
+
+ entry.AssertInvariant();
+
+ mTLSIntoleranceInfo.Put(key, entry);
+}
+
+void
+nsSSLIOLayerHelpers::forgetIntolerance(const nsACString& hostName,
+ int16_t port)
+{
+ nsCString key;
+ getSiteKey(hostName, port, key);
+
+ MutexAutoLock lock(mutex);
+
+ IntoleranceEntry entry;
+ if (mTLSIntoleranceInfo.Get(key, &entry)) {
+ entry.AssertInvariant();
+
+ entry.intolerant = 0;
+ entry.intoleranceReason = 0;
+ if (entry.strongCipherStatus != StrongCiphersWorked) {
+ entry.strongCipherStatus = StrongCipherStatusUnknown;
+ }
+
+ entry.AssertInvariant();
+ mTLSIntoleranceInfo.Put(key, entry);
+ }
+}
+
+bool
+nsSSLIOLayerHelpers::fallbackLimitReached(const nsACString& hostName,
+ uint16_t intolerant)
+{
+ if (isInsecureFallbackSite(hostName)) {
+ return intolerant <= SSL_LIBRARY_VERSION_TLS_1_0;
+ }
+ return intolerant <= mVersionFallbackLimit;
+}
+
+// returns true if we should retry the handshake
+bool
+nsSSLIOLayerHelpers::rememberIntolerantAtVersion(const nsACString& hostName,
+ int16_t port,
+ uint16_t minVersion,
+ uint16_t intolerant,
+ PRErrorCode intoleranceReason)
+{
+ if (intolerant <= minVersion || fallbackLimitReached(hostName, intolerant)) {
+ // We can't fall back any further. Assume that intolerance isn't the issue.
+ forgetIntolerance(hostName, port);
+ return false;
+ }
+
+ nsCString key;
+ getSiteKey(hostName, port, key);
+
+ MutexAutoLock lock(mutex);
+
+ IntoleranceEntry entry;
+ if (mTLSIntoleranceInfo.Get(key, &entry)) {
+ entry.AssertInvariant();
+ if (intolerant <= entry.tolerant) {
+ // We already know the server is tolerant at an equal or higher version.
+ return false;
+ }
+ if ((entry.intolerant != 0 && intolerant >= entry.intolerant)) {
+ // We already know that the server is intolerant at a lower version.
+ return true;
+ }
+ } else {
+ entry.tolerant = 0;
+ entry.strongCipherStatus = StrongCipherStatusUnknown;
+ }
+
+ entry.intolerant = intolerant;
+ entry.intoleranceReason = intoleranceReason;
+ entry.AssertInvariant();
+ mTLSIntoleranceInfo.Put(key, entry);
+
+ return true;
+}
+
+// returns true if we should retry the handshake
+bool
+nsSSLIOLayerHelpers::rememberStrongCiphersFailed(const nsACString& hostName,
+ int16_t port,
+ PRErrorCode intoleranceReason)
+{
+ nsCString key;
+ getSiteKey(hostName, port, key);
+
+ MutexAutoLock lock(mutex);
+
+ IntoleranceEntry entry;
+ if (mTLSIntoleranceInfo.Get(key, &entry)) {
+ entry.AssertInvariant();
+ if (entry.strongCipherStatus != StrongCipherStatusUnknown) {
+ // We already know if the server supports a strong cipher.
+ return false;
+ }
+ } else {
+ entry.tolerant = 0;
+ entry.intolerant = 0;
+ entry.intoleranceReason = intoleranceReason;
+ }
+
+ entry.strongCipherStatus = StrongCiphersFailed;
+ entry.AssertInvariant();
+ mTLSIntoleranceInfo.Put(key, entry);
+
+ return true;
+}
+
+void
+nsSSLIOLayerHelpers::adjustForTLSIntolerance(const nsACString& hostName,
+ int16_t port,
+ /*in/out*/ SSLVersionRange& range,
+ /*out*/ StrongCipherStatus& strongCipherStatus)
+{
+ IntoleranceEntry entry;
+
+ {
+ nsCString key;
+ getSiteKey(hostName, port, key);
+
+ MutexAutoLock lock(mutex);
+ if (!mTLSIntoleranceInfo.Get(key, &entry)) {
+ return;
+ }
+ }
+
+ entry.AssertInvariant();
+
+ if (entry.intolerant != 0) {
+ // We've tried connecting at a higher range but failed, so try at the
+ // version we haven't tried yet, unless we have reached the minimum.
+ if (range.min < entry.intolerant) {
+ range.max = entry.intolerant - 1;
+ }
+ }
+ strongCipherStatus = entry.strongCipherStatus;
+}
+
+PRErrorCode
+nsSSLIOLayerHelpers::getIntoleranceReason(const nsACString& hostName,
+ int16_t port)
+{
+ IntoleranceEntry entry;
+
+ {
+ nsCString key;
+ getSiteKey(hostName, port, key);
+
+ MutexAutoLock lock(mutex);
+ if (!mTLSIntoleranceInfo.Get(key, &entry)) {
+ return 0;
+ }
+ }
+
+ entry.AssertInvariant();
+ return entry.intoleranceReason;
+}
+
+bool nsSSLIOLayerHelpers::nsSSLIOLayerInitialized = false;
+PRDescIdentity nsSSLIOLayerHelpers::nsSSLIOLayerIdentity;
+PRDescIdentity nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity;
+PRIOMethods nsSSLIOLayerHelpers::nsSSLIOLayerMethods;
+PRIOMethods nsSSLIOLayerHelpers::nsSSLPlaintextLayerMethods;
+
+static PRStatus
+nsSSLIOLayerClose(PRFileDesc* fd)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (!fd)
+ return PR_FAILURE;
+
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("[%p] Shutting down socket\n",
+ (void*) fd));
+
+ nsNSSSocketInfo* socketInfo = (nsNSSSocketInfo*) fd->secret;
+ NS_ASSERTION(socketInfo,"nsNSSSocketInfo was null for an fd");
+
+ return socketInfo->CloseSocketAndDestroy(locker);
+}
+
+PRStatus
+nsNSSSocketInfo::CloseSocketAndDestroy(
+ const nsNSSShutDownPreventionLock& /*proofOfLock*/)
+{
+ PRFileDesc* popped = PR_PopIOLayer(mFd, PR_TOP_IO_LAYER);
+ NS_ASSERTION(popped &&
+ popped->identity == nsSSLIOLayerHelpers::nsSSLIOLayerIdentity,
+ "SSL Layer not on top of stack");
+
+ // The plain text layer is not always present - so its not a fatal error
+ // if it cannot be removed
+ PRFileDesc* poppedPlaintext =
+ PR_GetIdentitiesLayer(mFd, nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity);
+ if (poppedPlaintext) {
+ PR_PopIOLayer(mFd, nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity);
+ poppedPlaintext->dtor(poppedPlaintext);
+ }
+
+ PRStatus status = mFd->methods->close(mFd);
+
+ // the nsNSSSocketInfo instance can out-live the connection, so we need some
+ // indication that the connection has been closed. mFd == nullptr is that
+ // indication. This is needed, for example, when the connection is closed
+ // before we have finished validating the server's certificate.
+ mFd = nullptr;
+
+ if (status != PR_SUCCESS) return status;
+
+ popped->identity = PR_INVALID_IO_LAYER;
+ NS_RELEASE_THIS();
+ popped->dtor(popped);
+
+ return PR_SUCCESS;
+}
+
+#if defined(DEBUG_SSL_VERBOSE) && defined(DUMP_BUFFER)
+// Dumps a (potentially binary) buffer using SSM_DEBUG. (We could have used
+// the version in ssltrace.c, but that's specifically tailored to SSLTRACE.)
+#define DUMPBUF_LINESIZE 24
+static void
+nsDumpBuffer(unsigned char* buf, int len)
+{
+ char hexbuf[DUMPBUF_LINESIZE*3+1];
+ char chrbuf[DUMPBUF_LINESIZE+1];
+ static const char* hex = "0123456789abcdef";
+ int i = 0;
+ int l = 0;
+ char ch;
+ char* c;
+ char* h;
+ if (len == 0)
+ return;
+ hexbuf[DUMPBUF_LINESIZE*3] = '\0';
+ chrbuf[DUMPBUF_LINESIZE] = '\0';
+ (void) memset(hexbuf, 0x20, DUMPBUF_LINESIZE*3);
+ (void) memset(chrbuf, 0x20, DUMPBUF_LINESIZE);
+ h = hexbuf;
+ c = chrbuf;
+
+ while (i < len) {
+ ch = buf[i];
+
+ if (l == DUMPBUF_LINESIZE) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("%s%s\n", hexbuf, chrbuf));
+ (void) memset(hexbuf, 0x20, DUMPBUF_LINESIZE*3);
+ (void) memset(chrbuf, 0x20, DUMPBUF_LINESIZE);
+ h = hexbuf;
+ c = chrbuf;
+ l = 0;
+ }
+
+ // Convert a character to hex.
+ *h++ = hex[(ch >> 4) & 0xf];
+ *h++ = hex[ch & 0xf];
+ h++;
+
+ // Put the character (if it's printable) into the character buffer.
+ if ((ch >= 0x20) && (ch <= 0x7e)) {
+ *c++ = ch;
+ } else {
+ *c++ = '.';
+ }
+ i++; l++;
+ }
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("%s%s\n", hexbuf, chrbuf));
+}
+
+#define DEBUG_DUMP_BUFFER(buf,len) nsDumpBuffer(buf,len)
+#else
+#define DEBUG_DUMP_BUFFER(buf,len)
+#endif
+
+class SSLErrorRunnable : public SyncRunnableBase
+{
+ public:
+ SSLErrorRunnable(nsNSSSocketInfo* infoObject,
+ ::mozilla::psm::SSLErrorMessageType errtype,
+ PRErrorCode errorCode)
+ : mInfoObject(infoObject)
+ , mErrType(errtype)
+ , mErrorCode(errorCode)
+ {
+ }
+
+ virtual void RunOnTargetThread()
+ {
+ nsHandleSSLError(mInfoObject, mErrType, mErrorCode);
+ }
+
+ RefPtr<nsNSSSocketInfo> mInfoObject;
+ ::mozilla::psm::SSLErrorMessageType mErrType;
+ const PRErrorCode mErrorCode;
+};
+
+namespace {
+
+uint32_t tlsIntoleranceTelemetryBucket(PRErrorCode err)
+{
+ // returns a numeric code for where we track various errors in telemetry
+ // only errors that cause version fallback are tracked,
+ // so this is also used to determine which errors can cause version fallback
+ switch (err) {
+ case SSL_ERROR_BAD_MAC_ALERT: return 1;
+ case SSL_ERROR_BAD_MAC_READ: return 2;
+ case SSL_ERROR_HANDSHAKE_FAILURE_ALERT: return 3;
+ case SSL_ERROR_HANDSHAKE_UNEXPECTED_ALERT: return 4;
+ case SSL_ERROR_ILLEGAL_PARAMETER_ALERT: return 6;
+ case SSL_ERROR_NO_CYPHER_OVERLAP: return 7;
+ case SSL_ERROR_UNSUPPORTED_VERSION: return 10;
+ case SSL_ERROR_PROTOCOL_VERSION_ALERT: return 11;
+ case SSL_ERROR_BAD_HANDSHAKE_HASH_VALUE: return 13;
+ case SSL_ERROR_DECODE_ERROR_ALERT: return 14;
+ case PR_CONNECT_RESET_ERROR: return 16;
+ case PR_END_OF_FILE_ERROR: return 17;
+ case SSL_ERROR_INTERNAL_ERROR_ALERT: return 18;
+ default: return 0;
+ }
+}
+
+bool
+retryDueToTLSIntolerance(PRErrorCode err, nsNSSSocketInfo* socketInfo)
+{
+ // This function is supposed to decide which error codes should
+ // be used to conclude server is TLS intolerant.
+ // Note this only happens during the initial SSL handshake.
+
+ SSLVersionRange range = socketInfo->GetTLSVersionRange();
+ nsSSLIOLayerHelpers& helpers = socketInfo->SharedState().IOLayerHelpers();
+
+ if (err == SSL_ERROR_UNSUPPORTED_VERSION &&
+ range.min == SSL_LIBRARY_VERSION_TLS_1_0) {
+ socketInfo->SetSecurityState(nsIWebProgressListener::STATE_IS_INSECURE |
+ nsIWebProgressListener::STATE_USES_SSL_3);
+ }
+
+ // NSS will return SSL_ERROR_RX_MALFORMED_SERVER_HELLO if anti-downgrade
+ // detected the downgrade.
+ if (err == SSL_ERROR_INAPPROPRIATE_FALLBACK_ALERT ||
+ err == SSL_ERROR_RX_MALFORMED_SERVER_HELLO) {
+ // This is a clear signal that we've fallen back too many versions. Treat
+ // this as a hard failure, but forget any intolerance so that later attempts
+ // don't use this version (i.e., range.max) and trigger the error again.
+
+ // First, track the original cause of the version fallback. This uses the
+ // same buckets as the telemetry below, except that bucket 0 will include
+ // all cases where there wasn't an original reason.
+ PRErrorCode originalReason =
+ helpers.getIntoleranceReason(socketInfo->GetHostName(),
+ socketInfo->GetPort());
+ Telemetry::Accumulate(Telemetry::SSL_VERSION_FALLBACK_INAPPROPRIATE,
+ tlsIntoleranceTelemetryBucket(originalReason));
+
+ helpers.forgetIntolerance(socketInfo->GetHostName(),
+ socketInfo->GetPort());
+
+ return false;
+ }
+
+ // Disallow PR_CONNECT_RESET_ERROR if fallback limit reached.
+ bool fallbackLimitReached =
+ helpers.fallbackLimitReached(socketInfo->GetHostName(), range.max);
+ if (err == PR_CONNECT_RESET_ERROR && fallbackLimitReached) {
+ return false;
+ }
+
+ if ((err == SSL_ERROR_NO_CYPHER_OVERLAP || err == PR_END_OF_FILE_ERROR ||
+ err == PR_CONNECT_RESET_ERROR) &&
+ nsNSSComponent::AreAnyWeakCiphersEnabled()) {
+ if (helpers.isInsecureFallbackSite(socketInfo->GetHostName()) ||
+ helpers.mUnrestrictedRC4Fallback) {
+ if (helpers.rememberStrongCiphersFailed(socketInfo->GetHostName(),
+ socketInfo->GetPort(), err)) {
+ Telemetry::Accumulate(Telemetry::SSL_WEAK_CIPHERS_FALLBACK,
+ tlsIntoleranceTelemetryBucket(err));
+ return true;
+ }
+ Telemetry::Accumulate(Telemetry::SSL_WEAK_CIPHERS_FALLBACK, 0);
+ }
+ }
+
+ // When not using a proxy we'll see a connection reset error.
+ // When using a proxy, we'll see an end of file error.
+
+ // Don't allow STARTTLS connections to fall back on connection resets or
+ // EOF.
+ if ((err == PR_CONNECT_RESET_ERROR || err == PR_END_OF_FILE_ERROR)
+ && socketInfo->GetForSTARTTLS()) {
+ return false;
+ }
+
+ uint32_t reason = tlsIntoleranceTelemetryBucket(err);
+ if (reason == 0) {
+ return false;
+ }
+
+ Telemetry::ID pre;
+ Telemetry::ID post;
+ switch (range.max) {
+ case SSL_LIBRARY_VERSION_TLS_1_3:
+ pre = Telemetry::SSL_TLS13_INTOLERANCE_REASON_PRE;
+ post = Telemetry::SSL_TLS13_INTOLERANCE_REASON_POST;
+ break;
+ case SSL_LIBRARY_VERSION_TLS_1_2:
+ pre = Telemetry::SSL_TLS12_INTOLERANCE_REASON_PRE;
+ post = Telemetry::SSL_TLS12_INTOLERANCE_REASON_POST;
+ break;
+ case SSL_LIBRARY_VERSION_TLS_1_1:
+ pre = Telemetry::SSL_TLS11_INTOLERANCE_REASON_PRE;
+ post = Telemetry::SSL_TLS11_INTOLERANCE_REASON_POST;
+ break;
+ case SSL_LIBRARY_VERSION_TLS_1_0:
+ pre = Telemetry::SSL_TLS10_INTOLERANCE_REASON_PRE;
+ post = Telemetry::SSL_TLS10_INTOLERANCE_REASON_POST;
+ break;
+ default:
+ MOZ_CRASH("impossible TLS version");
+ return false;
+ }
+
+ // The difference between _PRE and _POST represents how often we avoided
+ // TLS intolerance fallback due to remembered tolerance.
+ Telemetry::Accumulate(pre, reason);
+
+ if (!helpers.rememberIntolerantAtVersion(socketInfo->GetHostName(),
+ socketInfo->GetPort(),
+ range.min, range.max, err)) {
+ return false;
+ }
+
+ Telemetry::Accumulate(post, reason);
+
+ return true;
+}
+
+// Ensure that we haven't added too many errors to fit.
+static_assert((SSL_ERROR_END_OF_LIST - SSL_ERROR_BASE) <= 256,
+ "too many SSL errors");
+static_assert((SEC_ERROR_END_OF_LIST - SEC_ERROR_BASE) <= 256,
+ "too many SEC errors");
+static_assert((PR_MAX_ERROR - PR_NSPR_ERROR_BASE) <= 128,
+ "too many NSPR errors");
+static_assert((mozilla::pkix::ERROR_BASE - mozilla::pkix::END_OF_LIST) < 31,
+ "too many moz::pkix errors");
+
+static void
+reportHandshakeResult(int32_t bytesTransferred, bool wasReading, PRErrorCode err)
+{
+ uint32_t bucket;
+
+ // A negative bytesTransferred or a 0 read are errors.
+ if (bytesTransferred > 0) {
+ bucket = 0;
+ } else if ((bytesTransferred == 0) && !wasReading) {
+ // PR_Write() is defined to never return 0, but let's make sure.
+ // https://developer.mozilla.org/en-US/docs/Mozilla/Projects/NSPR/Reference/PR_Write.
+ MOZ_ASSERT(false);
+ bucket = 671;
+ } else if (IS_SSL_ERROR(err)) {
+ bucket = err - SSL_ERROR_BASE;
+ MOZ_ASSERT(bucket > 0); // SSL_ERROR_EXPORT_ONLY_SERVER isn't used.
+ } else if (IS_SEC_ERROR(err)) {
+ bucket = (err - SEC_ERROR_BASE) + 256;
+ } else if ((err >= PR_NSPR_ERROR_BASE) && (err < PR_MAX_ERROR)) {
+ bucket = (err - PR_NSPR_ERROR_BASE) + 512;
+ } else if ((err >= mozilla::pkix::ERROR_BASE) &&
+ (err < mozilla::pkix::ERROR_LIMIT)) {
+ bucket = (err - mozilla::pkix::ERROR_BASE) + 640;
+ } else {
+ bucket = 671;
+ }
+
+ Telemetry::Accumulate(Telemetry::SSL_HANDSHAKE_RESULT, bucket);
+}
+
+int32_t
+checkHandshake(int32_t bytesTransfered, bool wasReading,
+ PRFileDesc* ssl_layer_fd, nsNSSSocketInfo* socketInfo)
+{
+ const PRErrorCode originalError = PR_GetError();
+ PRErrorCode err = originalError;
+
+ // This is where we work around all of those SSL servers that don't
+ // conform to the SSL spec and shutdown a connection when we request
+ // SSL v3.1 (aka TLS). The spec says the client says what version
+ // of the protocol we're willing to perform, in our case SSL v3.1
+ // In its response, the server says which version it wants to perform.
+ // Many servers out there only know how to do v3.0. Next, we're supposed
+ // to send back the version of the protocol we requested (ie v3.1). At
+ // this point many servers's implementations are broken and they shut
+ // down the connection when they don't see the version they sent back.
+ // This is supposed to prevent a man in the middle from forcing one
+ // side to dumb down to a lower level of the protocol. Unfortunately,
+ // there are enough broken servers out there that such a gross work-around
+ // is necessary. :(
+
+ // Do NOT assume TLS intolerance on a closed connection after bad cert ui was shown.
+ // Simply retry.
+ // This depends on the fact that Cert UI will not be shown again,
+ // should the user override the bad cert.
+
+ bool handleHandshakeResultNow = socketInfo->IsHandshakePending();
+
+ bool wantRetry = false;
+
+ if (0 > bytesTransfered) {
+ if (handleHandshakeResultNow) {
+ if (PR_WOULD_BLOCK_ERROR == err) {
+ PR_SetError(err, 0);
+ return bytesTransfered;
+ }
+
+ wantRetry = retryDueToTLSIntolerance(err, socketInfo);
+ }
+
+ // This is the common place where we trigger non-cert-errors on a SSL
+ // socket. This might be reached at any time of the connection.
+ //
+ // The socketInfo->GetErrorCode() check is here to ensure we don't try to
+ // do the synchronous dispatch to the main thread unnecessarily after we've
+ // already handled a certificate error. (SSLErrorRunnable calls
+ // nsHandleSSLError, which has logic to avoid replacing the error message,
+ // so without the !socketInfo->GetErrorCode(), it would just be an
+ // expensive no-op.)
+ if (!wantRetry && mozilla::psm::IsNSSErrorCode(err) &&
+ !socketInfo->GetErrorCode()) {
+ RefPtr<SyncRunnableBase> runnable(new SSLErrorRunnable(socketInfo,
+ PlainErrorMessage,
+ err));
+ (void) runnable->DispatchToMainThreadAndWait();
+ }
+ } else if (wasReading && 0 == bytesTransfered) {
+ // zero bytes on reading, socket closed
+ if (handleHandshakeResultNow) {
+ wantRetry = retryDueToTLSIntolerance(PR_END_OF_FILE_ERROR, socketInfo);
+ }
+ }
+
+ if (wantRetry) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("[%p] checkHandshake: will retry with lower max TLS version\n",
+ ssl_layer_fd));
+ // We want to cause the network layer to retry the connection.
+ err = PR_CONNECT_RESET_ERROR;
+ if (wasReading)
+ bytesTransfered = -1;
+ }
+
+ // TLS intolerant servers only cause the first transfer to fail, so let's
+ // set the HandshakePending attribute to false so that we don't try the logic
+ // above again in a subsequent transfer.
+ if (handleHandshakeResultNow) {
+ // Report the result once for each handshake. Note that this does not
+ // get handshakes which are cancelled before any reads or writes
+ // happen.
+ reportHandshakeResult(bytesTransfered, wasReading, originalError);
+ socketInfo->SetHandshakeNotPending();
+ }
+
+ if (bytesTransfered < 0) {
+ // Remember that we encountered an error so that getSocketInfoIfRunning
+ // will correctly cause us to fail if another part of Gecko
+ // (erroneously) calls an I/O function (PR_Send/PR_Recv/etc.) again on
+ // this socket. Note that we use the original error because if we use
+ // PR_CONNECT_RESET_ERROR, we'll repeated try to reconnect.
+ if (originalError != PR_WOULD_BLOCK_ERROR && !socketInfo->GetErrorCode()) {
+ socketInfo->SetCanceled(originalError, PlainErrorMessage);
+ }
+ PR_SetError(err, 0);
+ }
+
+ return bytesTransfered;
+}
+
+} // namespace
+
+static int16_t
+nsSSLIOLayerPoll(PRFileDesc* fd, int16_t in_flags, int16_t* out_flags)
+{
+ nsNSSShutDownPreventionLock locker;
+
+ if (!out_flags) {
+ NS_WARNING("nsSSLIOLayerPoll called with null out_flags");
+ return 0;
+ }
+
+ *out_flags = 0;
+
+ nsNSSSocketInfo* socketInfo =
+ getSocketInfoIfRunning(fd, not_reading_or_writing, locker);
+
+ if (!socketInfo) {
+ // If we get here, it is probably because certificate validation failed
+ // and this is the first I/O operation after the failure.
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("[%p] polling SSL socket right after certificate verification failed "
+ "or NSS shutdown or SDR logout %d\n",
+ fd, (int) in_flags));
+
+ NS_ASSERTION(in_flags & PR_POLL_EXCEPT,
+ "caller did not poll for EXCEPT (canceled)");
+ // Since this poll method cannot return errors, we want the caller to call
+ // PR_Send/PR_Recv right away to get the error, so we tell that we are
+ // ready for whatever I/O they are asking for. (See getSocketInfoIfRunning).
+ *out_flags = in_flags | PR_POLL_EXCEPT; // see also bug 480619
+ return in_flags;
+ }
+
+ MOZ_LOG(gPIPNSSLog, LogLevel::Verbose,
+ (socketInfo->IsWaitingForCertVerification()
+ ? "[%p] polling SSL socket during certificate verification using lower %d\n"
+ : "[%p] poll SSL socket using lower %d\n",
+ fd, (int) in_flags));
+
+ // We want the handshake to continue during certificate validation, so we
+ // don't need to do anything special here. libssl automatically blocks when
+ // it reaches any point that would be unsafe to send/receive something before
+ // cert validation is complete.
+ int16_t result = fd->lower->methods->poll(fd->lower, in_flags, out_flags);
+ MOZ_LOG(gPIPNSSLog, LogLevel::Verbose,
+ ("[%p] poll SSL socket returned %d\n", (void*) fd, (int) result));
+ return result;
+}
+
+nsSSLIOLayerHelpers::nsSSLIOLayerHelpers()
+ : mTreatUnsafeNegotiationAsBroken(false)
+ , mTLSIntoleranceInfo()
+ , mFalseStartRequireNPN(false)
+ , mUnrestrictedRC4Fallback(false)
+ , mVersionFallbackLimit(SSL_LIBRARY_VERSION_TLS_1_0)
+ , mutex("nsSSLIOLayerHelpers.mutex")
+{
+}
+
+static int
+_PSM_InvalidInt(void)
+{
+ MOZ_ASSERT_UNREACHABLE("I/O method is invalid");
+ PR_SetError(PR_INVALID_METHOD_ERROR, 0);
+ return -1;
+}
+
+static int64_t
+_PSM_InvalidInt64(void)
+{
+ MOZ_ASSERT_UNREACHABLE("I/O method is invalid");
+ PR_SetError(PR_INVALID_METHOD_ERROR, 0);
+ return -1;
+}
+
+static PRStatus
+_PSM_InvalidStatus(void)
+{
+ MOZ_ASSERT_UNREACHABLE("I/O method is invalid");
+ PR_SetError(PR_INVALID_METHOD_ERROR, 0);
+ return PR_FAILURE;
+}
+
+static PRFileDesc*
+_PSM_InvalidDesc(void)
+{
+ MOZ_ASSERT_UNREACHABLE("I/O method is invalid");
+ PR_SetError(PR_INVALID_METHOD_ERROR, 0);
+ return nullptr;
+}
+
+static PRStatus
+PSMGetsockname(PRFileDesc* fd, PRNetAddr* addr)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (!getSocketInfoIfRunning(fd, not_reading_or_writing, locker))
+ return PR_FAILURE;
+
+ return fd->lower->methods->getsockname(fd->lower, addr);
+}
+
+static PRStatus
+PSMGetpeername(PRFileDesc* fd, PRNetAddr* addr)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (!getSocketInfoIfRunning(fd, not_reading_or_writing, locker))
+ return PR_FAILURE;
+
+ return fd->lower->methods->getpeername(fd->lower, addr);
+}
+
+static PRStatus
+PSMGetsocketoption(PRFileDesc* fd, PRSocketOptionData* data)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (!getSocketInfoIfRunning(fd, not_reading_or_writing, locker))
+ return PR_FAILURE;
+
+ return fd->lower->methods->getsocketoption(fd, data);
+}
+
+static PRStatus
+PSMSetsocketoption(PRFileDesc* fd, const PRSocketOptionData* data)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (!getSocketInfoIfRunning(fd, not_reading_or_writing, locker))
+ return PR_FAILURE;
+
+ return fd->lower->methods->setsocketoption(fd, data);
+}
+
+static int32_t
+PSMRecv(PRFileDesc* fd, void* buf, int32_t amount, int flags,
+ PRIntervalTime timeout)
+{
+ nsNSSShutDownPreventionLock locker;
+ nsNSSSocketInfo* socketInfo = getSocketInfoIfRunning(fd, reading, locker);
+ if (!socketInfo)
+ return -1;
+
+ if (flags != PR_MSG_PEEK && flags != 0) {
+ PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
+ return -1;
+ }
+
+ int32_t bytesRead = fd->lower->methods->recv(fd->lower, buf, amount, flags,
+ timeout);
+
+ MOZ_LOG(gPIPNSSLog, LogLevel::Verbose,
+ ("[%p] read %d bytes\n", (void*) fd, bytesRead));
+
+#ifdef DEBUG_SSL_VERBOSE
+ DEBUG_DUMP_BUFFER((unsigned char*) buf, bytesRead);
+#endif
+
+ return checkHandshake(bytesRead, true, fd, socketInfo);
+}
+
+static int32_t
+PSMSend(PRFileDesc* fd, const void* buf, int32_t amount, int flags,
+ PRIntervalTime timeout)
+{
+ nsNSSShutDownPreventionLock locker;
+ nsNSSSocketInfo* socketInfo = getSocketInfoIfRunning(fd, writing, locker);
+ if (!socketInfo)
+ return -1;
+
+ if (flags != 0) {
+ PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
+ return -1;
+ }
+
+#ifdef DEBUG_SSL_VERBOSE
+ DEBUG_DUMP_BUFFER((unsigned char*) buf, amount);
+#endif
+
+ int32_t bytesWritten = fd->lower->methods->send(fd->lower, buf, amount,
+ flags, timeout);
+
+ MOZ_LOG(gPIPNSSLog, LogLevel::Verbose,
+ ("[%p] wrote %d bytes\n", fd, bytesWritten));
+
+ return checkHandshake(bytesWritten, false, fd, socketInfo);
+}
+
+static PRStatus
+PSMBind(PRFileDesc* fd, const PRNetAddr *addr)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (!getSocketInfoIfRunning(fd, not_reading_or_writing, locker))
+ return PR_FAILURE;
+
+ return fd->lower->methods->bind(fd->lower, addr);
+}
+
+static int32_t
+nsSSLIOLayerRead(PRFileDesc* fd, void* buf, int32_t amount)
+{
+ return PSMRecv(fd, buf, amount, 0, PR_INTERVAL_NO_TIMEOUT);
+}
+
+static int32_t
+nsSSLIOLayerWrite(PRFileDesc* fd, const void* buf, int32_t amount)
+{
+ return PSMSend(fd, buf, amount, 0, PR_INTERVAL_NO_TIMEOUT);
+}
+
+static PRStatus
+PSMConnectcontinue(PRFileDesc* fd, int16_t out_flags)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (!getSocketInfoIfRunning(fd, not_reading_or_writing, locker)) {
+ return PR_FAILURE;
+ }
+
+ return fd->lower->methods->connectcontinue(fd, out_flags);
+}
+
+static int
+PSMAvailable(void)
+{
+ // This is called through PR_Available(), but is not implemented in PSM
+ PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
+ return -1;
+}
+
+static int64_t
+PSMAvailable64(void)
+{
+ // This is called through PR_Available(), but is not implemented in PSM
+ PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0);
+ return -1;
+}
+
+namespace {
+
+class PrefObserver : public nsIObserver {
+public:
+ NS_DECL_THREADSAFE_ISUPPORTS
+ NS_DECL_NSIOBSERVER
+ explicit PrefObserver(nsSSLIOLayerHelpers* aOwner) : mOwner(aOwner) {}
+
+protected:
+ virtual ~PrefObserver() {}
+private:
+ nsSSLIOLayerHelpers* mOwner;
+};
+
+} // unnamed namespace
+
+NS_IMPL_ISUPPORTS(PrefObserver, nsIObserver)
+
+NS_IMETHODIMP
+PrefObserver::Observe(nsISupports* aSubject, const char* aTopic,
+ const char16_t* someData)
+{
+ if (nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) {
+ NS_ConvertUTF16toUTF8 prefName(someData);
+
+ if (prefName.EqualsLiteral("security.ssl.treat_unsafe_negotiation_as_broken")) {
+ bool enabled;
+ Preferences::GetBool("security.ssl.treat_unsafe_negotiation_as_broken", &enabled);
+ mOwner->setTreatUnsafeNegotiationAsBroken(enabled);
+ } else if (prefName.EqualsLiteral("security.ssl.false_start.require-npn")) {
+ mOwner->mFalseStartRequireNPN =
+ Preferences::GetBool("security.ssl.false_start.require-npn",
+ FALSE_START_REQUIRE_NPN_DEFAULT);
+ } else if (prefName.EqualsLiteral("security.tls.version.fallback-limit")) {
+ mOwner->loadVersionFallbackLimit();
+ } else if (prefName.EqualsLiteral("security.tls.insecure_fallback_hosts")) {
+ // Changes to the whitelist on the public side will update the pref.
+ // Don't propagate the changes to the private side.
+ if (mOwner->isPublic()) {
+ mOwner->initInsecureFallbackSites();
+ }
+ } else if (prefName.EqualsLiteral("security.tls.unrestricted_rc4_fallback")) {
+ mOwner->mUnrestrictedRC4Fallback =
+ Preferences::GetBool("security.tls.unrestricted_rc4_fallback", false);
+ }
+ }
+ return NS_OK;
+}
+
+static int32_t
+PlaintextRecv(PRFileDesc* fd, void* buf, int32_t amount, int flags,
+ PRIntervalTime timeout)
+{
+ // The shutdownlocker is not needed here because it will already be
+ // held higher in the stack
+ nsNSSSocketInfo* socketInfo = nullptr;
+
+ int32_t bytesRead = fd->lower->methods->recv(fd->lower, buf, amount, flags,
+ timeout);
+ if (fd->identity == nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity)
+ socketInfo = (nsNSSSocketInfo*) fd->secret;
+
+ if ((bytesRead > 0) && socketInfo)
+ socketInfo->AddPlaintextBytesRead(bytesRead);
+ return bytesRead;
+}
+
+nsSSLIOLayerHelpers::~nsSSLIOLayerHelpers()
+{
+ // mPrefObserver will only be set if this->Init was called. The GTest tests
+ // do not call Init.
+ if (mPrefObserver) {
+ Preferences::RemoveObserver(mPrefObserver,
+ "security.ssl.treat_unsafe_negotiation_as_broken");
+ Preferences::RemoveObserver(mPrefObserver,
+ "security.ssl.false_start.require-npn");
+ Preferences::RemoveObserver(mPrefObserver,
+ "security.tls.version.fallback-limit");
+ Preferences::RemoveObserver(mPrefObserver,
+ "security.tls.insecure_fallback_hosts");
+ Preferences::RemoveObserver(mPrefObserver,
+ "security.tls.unrestricted_rc4_fallback");
+ }
+}
+
+nsresult
+nsSSLIOLayerHelpers::Init()
+{
+ if (!nsSSLIOLayerInitialized) {
+ nsSSLIOLayerInitialized = true;
+ nsSSLIOLayerIdentity = PR_GetUniqueIdentity("NSS layer");
+ nsSSLIOLayerMethods = *PR_GetDefaultIOMethods();
+
+ nsSSLIOLayerMethods.available = (PRAvailableFN) PSMAvailable;
+ nsSSLIOLayerMethods.available64 = (PRAvailable64FN) PSMAvailable64;
+ nsSSLIOLayerMethods.fsync = (PRFsyncFN) _PSM_InvalidStatus;
+ nsSSLIOLayerMethods.seek = (PRSeekFN) _PSM_InvalidInt;
+ nsSSLIOLayerMethods.seek64 = (PRSeek64FN) _PSM_InvalidInt64;
+ nsSSLIOLayerMethods.fileInfo = (PRFileInfoFN) _PSM_InvalidStatus;
+ nsSSLIOLayerMethods.fileInfo64 = (PRFileInfo64FN) _PSM_InvalidStatus;
+ nsSSLIOLayerMethods.writev = (PRWritevFN) _PSM_InvalidInt;
+ nsSSLIOLayerMethods.accept = (PRAcceptFN) _PSM_InvalidDesc;
+ nsSSLIOLayerMethods.listen = (PRListenFN) _PSM_InvalidStatus;
+ nsSSLIOLayerMethods.shutdown = (PRShutdownFN) _PSM_InvalidStatus;
+ nsSSLIOLayerMethods.recvfrom = (PRRecvfromFN) _PSM_InvalidInt;
+ nsSSLIOLayerMethods.sendto = (PRSendtoFN) _PSM_InvalidInt;
+ nsSSLIOLayerMethods.acceptread = (PRAcceptreadFN) _PSM_InvalidInt;
+ nsSSLIOLayerMethods.transmitfile = (PRTransmitfileFN) _PSM_InvalidInt;
+ nsSSLIOLayerMethods.sendfile = (PRSendfileFN) _PSM_InvalidInt;
+
+ nsSSLIOLayerMethods.getsockname = PSMGetsockname;
+ nsSSLIOLayerMethods.getpeername = PSMGetpeername;
+ nsSSLIOLayerMethods.getsocketoption = PSMGetsocketoption;
+ nsSSLIOLayerMethods.setsocketoption = PSMSetsocketoption;
+ nsSSLIOLayerMethods.recv = PSMRecv;
+ nsSSLIOLayerMethods.send = PSMSend;
+ nsSSLIOLayerMethods.connectcontinue = PSMConnectcontinue;
+ nsSSLIOLayerMethods.bind = PSMBind;
+
+ nsSSLIOLayerMethods.connect = nsSSLIOLayerConnect;
+ nsSSLIOLayerMethods.close = nsSSLIOLayerClose;
+ nsSSLIOLayerMethods.write = nsSSLIOLayerWrite;
+ nsSSLIOLayerMethods.read = nsSSLIOLayerRead;
+ nsSSLIOLayerMethods.poll = nsSSLIOLayerPoll;
+
+ nsSSLPlaintextLayerIdentity = PR_GetUniqueIdentity("Plaintxext PSM layer");
+ nsSSLPlaintextLayerMethods = *PR_GetDefaultIOMethods();
+ nsSSLPlaintextLayerMethods.recv = PlaintextRecv;
+ }
+
+ bool enabled = false;
+ Preferences::GetBool("security.ssl.treat_unsafe_negotiation_as_broken", &enabled);
+ setTreatUnsafeNegotiationAsBroken(enabled);
+
+ mFalseStartRequireNPN =
+ Preferences::GetBool("security.ssl.false_start.require-npn",
+ FALSE_START_REQUIRE_NPN_DEFAULT);
+ loadVersionFallbackLimit();
+ initInsecureFallbackSites();
+ mUnrestrictedRC4Fallback =
+ Preferences::GetBool("security.tls.unrestricted_rc4_fallback", false);
+
+ mPrefObserver = new PrefObserver(this);
+ Preferences::AddStrongObserver(mPrefObserver,
+ "security.ssl.treat_unsafe_negotiation_as_broken");
+ Preferences::AddStrongObserver(mPrefObserver,
+ "security.ssl.false_start.require-npn");
+ Preferences::AddStrongObserver(mPrefObserver,
+ "security.tls.version.fallback-limit");
+ Preferences::AddStrongObserver(mPrefObserver,
+ "security.tls.insecure_fallback_hosts");
+ Preferences::AddStrongObserver(mPrefObserver,
+ "security.tls.unrestricted_rc4_fallback");
+ return NS_OK;
+}
+
+void
+nsSSLIOLayerHelpers::loadVersionFallbackLimit()
+{
+ // see nsNSSComponent::setEnabledTLSVersions for pref handling rules
+ uint32_t limit = Preferences::GetUint("security.tls.version.fallback-limit",
+ 3); // 3 = TLS 1.2
+ SSLVersionRange defaults = { SSL_LIBRARY_VERSION_TLS_1_2,
+ SSL_LIBRARY_VERSION_TLS_1_2 };
+ SSLVersionRange filledInRange;
+ nsNSSComponent::FillTLSVersionRange(filledInRange, limit, limit, defaults);
+ if (filledInRange.max < SSL_LIBRARY_VERSION_TLS_1_2) {
+ filledInRange.max = SSL_LIBRARY_VERSION_TLS_1_2;
+ }
+
+ mVersionFallbackLimit = filledInRange.max;
+}
+
+void
+nsSSLIOLayerHelpers::clearStoredData()
+{
+ MutexAutoLock lock(mutex);
+ mInsecureFallbackSites.Clear();
+ mTLSIntoleranceInfo.Clear();
+}
+
+void
+nsSSLIOLayerHelpers::setInsecureFallbackSites(const nsCString& str)
+{
+ MutexAutoLock lock(mutex);
+
+ mInsecureFallbackSites.Clear();
+
+ if (str.IsEmpty()) {
+ return;
+ }
+
+ nsCCharSeparatedTokenizer toker(str, ',');
+
+ while (toker.hasMoreTokens()) {
+ const nsCSubstring& host = toker.nextToken();
+ if (!host.IsEmpty()) {
+ mInsecureFallbackSites.PutEntry(host);
+ }
+ }
+}
+
+void
+nsSSLIOLayerHelpers::initInsecureFallbackSites()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCString insecureFallbackHosts;
+ Preferences::GetCString("security.tls.insecure_fallback_hosts",
+ &insecureFallbackHosts);
+ setInsecureFallbackSites(insecureFallbackHosts);
+}
+
+bool
+nsSSLIOLayerHelpers::isPublic() const
+{
+ return this == &PublicSSLState()->IOLayerHelpers();
+}
+
+void
+nsSSLIOLayerHelpers::addInsecureFallbackSite(const nsCString& hostname,
+ bool temporary)
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ {
+ MutexAutoLock lock(mutex);
+ if (mInsecureFallbackSites.Contains(hostname)) {
+ return;
+ }
+ mInsecureFallbackSites.PutEntry(hostname);
+ }
+ if (!isPublic() || temporary) {
+ return;
+ }
+ nsCString value;
+ Preferences::GetCString("security.tls.insecure_fallback_hosts", &value);
+ if (!value.IsEmpty()) {
+ value.Append(',');
+ }
+ value.Append(hostname);
+ Preferences::SetCString("security.tls.insecure_fallback_hosts", value);
+}
+
+class FallbackPrefRemover final : public Runnable
+{
+public:
+ explicit FallbackPrefRemover(const nsACString& aHost)
+ : mHost(aHost)
+ {}
+ NS_IMETHOD Run() override;
+private:
+ nsCString mHost;
+};
+
+NS_IMETHODIMP
+FallbackPrefRemover::Run()
+{
+ MOZ_ASSERT(NS_IsMainThread());
+ nsCString oldValue;
+ Preferences::GetCString("security.tls.insecure_fallback_hosts", &oldValue);
+ nsCCharSeparatedTokenizer toker(oldValue, ',');
+ nsCString newValue;
+ while (toker.hasMoreTokens()) {
+ const nsCSubstring& host = toker.nextToken();
+ if (host.Equals(mHost)) {
+ continue;
+ }
+ if (!newValue.IsEmpty()) {
+ newValue.Append(',');
+ }
+ newValue.Append(host);
+ }
+ Preferences::SetCString("security.tls.insecure_fallback_hosts", newValue);
+ return NS_OK;
+}
+
+void
+nsSSLIOLayerHelpers::removeInsecureFallbackSite(const nsACString& hostname,
+ uint16_t port)
+{
+ forgetIntolerance(hostname, port);
+ {
+ MutexAutoLock lock(mutex);
+ if (!mInsecureFallbackSites.Contains(hostname)) {
+ return;
+ }
+ mInsecureFallbackSites.RemoveEntry(hostname);
+ }
+ if (!isPublic()) {
+ return;
+ }
+ RefPtr<Runnable> runnable = new FallbackPrefRemover(hostname);
+ if (NS_IsMainThread()) {
+ runnable->Run();
+ } else {
+ NS_DispatchToMainThread(runnable);
+ }
+}
+
+bool
+nsSSLIOLayerHelpers::isInsecureFallbackSite(const nsACString& hostname)
+{
+ MutexAutoLock lock(mutex);
+ return mInsecureFallbackSites.Contains(hostname);
+}
+
+void
+nsSSLIOLayerHelpers::setTreatUnsafeNegotiationAsBroken(bool broken)
+{
+ MutexAutoLock lock(mutex);
+ mTreatUnsafeNegotiationAsBroken = broken;
+}
+
+bool
+nsSSLIOLayerHelpers::treatUnsafeNegotiationAsBroken()
+{
+ MutexAutoLock lock(mutex);
+ return mTreatUnsafeNegotiationAsBroken;
+}
+
+nsresult
+nsSSLIOLayerNewSocket(int32_t family,
+ const char* host,
+ int32_t port,
+ nsIProxyInfo *proxy,
+ const NeckoOriginAttributes& originAttributes,
+ PRFileDesc** fd,
+ nsISupports** info,
+ bool forSTARTTLS,
+ uint32_t flags)
+{
+
+ PRFileDesc* sock = PR_OpenTCPSocket(family);
+ if (!sock) return NS_ERROR_OUT_OF_MEMORY;
+
+ nsresult rv = nsSSLIOLayerAddToSocket(family, host, port, proxy,
+ originAttributes, sock, info,
+ forSTARTTLS, flags);
+ if (NS_FAILED(rv)) {
+ PR_Close(sock);
+ return rv;
+ }
+
+ *fd = sock;
+ return NS_OK;
+}
+
+// Creates CA names strings from (CERTDistNames* caNames)
+//
+// - arena: arena to allocate strings on
+// - caNameStrings: filled with CA names strings on return
+// - caNames: CERTDistNames to extract strings from
+// - return: SECSuccess if successful; error code otherwise
+//
+// Note: copied in its entirety from Nova code
+static SECStatus
+nsConvertCANamesToStrings(const UniquePLArenaPool& arena, char** caNameStrings,
+ CERTDistNames* caNames)
+{
+ MOZ_ASSERT(arena.get());
+ MOZ_ASSERT(caNameStrings);
+ MOZ_ASSERT(caNames);
+ if (!arena.get() || !caNameStrings || !caNames) {
+ PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
+ return SECFailure;
+ }
+
+ SECItem* dername;
+ SECStatus rv;
+ int headerlen;
+ uint32_t contentlen;
+ SECItem newitem;
+ int n;
+ char* namestring;
+
+ for (n = 0; n < caNames->nnames; n++) {
+ newitem.data = nullptr;
+ dername = &caNames->names[n];
+
+ rv = DER_Lengths(dername, &headerlen, &contentlen);
+
+ if (rv != SECSuccess) {
+ goto loser;
+ }
+
+ if (headerlen + contentlen != dername->len) {
+ // This must be from an enterprise 2.x server, which sent
+ // incorrectly formatted der without the outer wrapper of type and
+ // length. Fix it up by adding the top level header.
+ if (dername->len <= 127) {
+ newitem.data = (unsigned char*) PR_Malloc(dername->len + 2);
+ if (!newitem.data) {
+ goto loser;
+ }
+ newitem.data[0] = (unsigned char) 0x30;
+ newitem.data[1] = (unsigned char) dername->len;
+ (void) memcpy(&newitem.data[2], dername->data, dername->len);
+ } else if (dername->len <= 255) {
+ newitem.data = (unsigned char*) PR_Malloc(dername->len + 3);
+ if (!newitem.data) {
+ goto loser;
+ }
+ newitem.data[0] = (unsigned char) 0x30;
+ newitem.data[1] = (unsigned char) 0x81;
+ newitem.data[2] = (unsigned char) dername->len;
+ (void) memcpy(&newitem.data[3], dername->data, dername->len);
+ } else {
+ // greater than 256, better be less than 64k
+ newitem.data = (unsigned char*) PR_Malloc(dername->len + 4);
+ if (!newitem.data) {
+ goto loser;
+ }
+ newitem.data[0] = (unsigned char) 0x30;
+ newitem.data[1] = (unsigned char) 0x82;
+ newitem.data[2] = (unsigned char) ((dername->len >> 8) & 0xff);
+ newitem.data[3] = (unsigned char) (dername->len & 0xff);
+ memcpy(&newitem.data[4], dername->data, dername->len);
+ }
+ dername = &newitem;
+ }
+
+ namestring = CERT_DerNameToAscii(dername);
+ if (!namestring) {
+ // XXX - keep going until we fail to convert the name
+ caNameStrings[n] = const_cast<char*>("");
+ } else {
+ caNameStrings[n] = PORT_ArenaStrdup(arena.get(), namestring);
+ PR_Free(namestring);
+ if (!caNameStrings[n]) {
+ goto loser;
+ }
+ }
+
+ if (newitem.data) {
+ PR_Free(newitem.data);
+ }
+ }
+
+ return SECSuccess;
+loser:
+ if (newitem.data) {
+ PR_Free(newitem.data);
+ }
+ return SECFailure;
+}
+
+// Possible behaviors for choosing a cert for client auth.
+enum class UserCertChoice {
+ // Ask the user to choose a cert.
+ Ask = 0,
+ // Automatically choose a cert.
+ Auto = 1,
+};
+
+// Returns the most appropriate user cert choice based on the value of the
+// security.default_personal_cert preference.
+UserCertChoice
+nsGetUserCertChoice()
+{
+ nsAutoCString value;
+ nsresult rv = Preferences::GetCString("security.default_personal_cert", &value);
+ if (NS_FAILED(rv)) {
+ return UserCertChoice::Ask;
+ }
+
+ // There are three cases for what the preference could be set to:
+ // 1. "Select Automatically" -> Auto.
+ // 2. "Ask Every Time" -> Ask.
+ // 3. Something else -> Ask. This might be a nickname from a migrated cert,
+ // but we no longer support this case.
+ return value.EqualsLiteral("Select Automatically") ? UserCertChoice::Auto
+ : UserCertChoice::Ask;
+}
+
+static bool
+hasExplicitKeyUsageNonRepudiation(CERTCertificate* cert)
+{
+ // There is no extension, v1 or v2 certificate
+ if (!cert->extensions)
+ return false;
+
+ SECStatus srv;
+ SECItem keyUsageItem;
+ keyUsageItem.data = nullptr;
+
+ srv = CERT_FindKeyUsageExtension(cert, &keyUsageItem);
+ if (srv == SECFailure)
+ return false;
+
+ unsigned char keyUsage = keyUsageItem.data[0];
+ PORT_Free (keyUsageItem.data);
+
+ return !!(keyUsage & KU_NON_REPUDIATION);
+}
+
+class ClientAuthDataRunnable : public SyncRunnableBase
+{
+public:
+ ClientAuthDataRunnable(CERTDistNames* caNames,
+ CERTCertificate** pRetCert,
+ SECKEYPrivateKey** pRetKey,
+ nsNSSSocketInfo* info,
+ const UniqueCERTCertificate& serverCert)
+ : mRV(SECFailure)
+ , mErrorCodeToReport(SEC_ERROR_NO_MEMORY)
+ , mPRetCert(pRetCert)
+ , mPRetKey(pRetKey)
+ , mCANames(caNames)
+ , mSocketInfo(info)
+ , mServerCert(serverCert.get())
+ {
+ }
+
+ SECStatus mRV; // out
+ PRErrorCode mErrorCodeToReport; // out
+ CERTCertificate** const mPRetCert; // in/out
+ SECKEYPrivateKey** const mPRetKey; // in/out
+protected:
+ virtual void RunOnTargetThread();
+private:
+ CERTDistNames* const mCANames; // in
+ nsNSSSocketInfo* const mSocketInfo; // in
+ CERTCertificate* const mServerCert; // in
+};
+
+// This callback function is used to pull client certificate
+// information upon server request
+//
+// - arg: SSL data connection
+// - socket: SSL socket we're dealing with
+// - caNames: list of CA names
+// - pRetCert: returns a pointer to a pointer to a valid certificate if
+// successful; otherwise nullptr
+// - pRetKey: returns a pointer to a pointer to the corresponding key if
+// successful; otherwise nullptr
+SECStatus
+nsNSS_SSLGetClientAuthData(void* arg, PRFileDesc* socket,
+ CERTDistNames* caNames, CERTCertificate** pRetCert,
+ SECKEYPrivateKey** pRetKey)
+{
+ nsNSSShutDownPreventionLock locker;
+
+ if (!socket || !caNames || !pRetCert || !pRetKey) {
+ PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
+ return SECFailure;
+ }
+
+ RefPtr<nsNSSSocketInfo> info(
+ BitwiseCast<nsNSSSocketInfo*, PRFilePrivate*>(socket->higher->secret));
+
+ UniqueCERTCertificate serverCert(SSL_PeerCertificate(socket));
+ if (!serverCert) {
+ NS_NOTREACHED("Missing server certificate should have been detected during "
+ "server cert authentication.");
+ PR_SetError(SSL_ERROR_NO_CERTIFICATE, 0);
+ return SECFailure;
+ }
+
+ if (info->GetJoined()) {
+ // We refuse to send a client certificate when there are multiple hostnames
+ // joined on this connection, because we only show the user one hostname
+ // (mHostName) in the client certificate UI.
+
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("[%p] Not returning client cert due to previous join\n", socket));
+ *pRetCert = nullptr;
+ *pRetKey = nullptr;
+ return SECSuccess;
+ }
+
+ // XXX: This should be done asynchronously; see bug 696976
+ RefPtr<ClientAuthDataRunnable> runnable(
+ new ClientAuthDataRunnable(caNames, pRetCert, pRetKey, info, serverCert));
+ nsresult rv = runnable->DispatchToMainThreadAndWait();
+ if (NS_FAILED(rv)) {
+ PR_SetError(SEC_ERROR_NO_MEMORY, 0);
+ return SECFailure;
+ }
+
+ if (runnable->mRV != SECSuccess) {
+ PR_SetError(runnable->mErrorCodeToReport, 0);
+ } else if (*runnable->mPRetCert || *runnable->mPRetKey) {
+ // Make joinConnection prohibit joining after we've sent a client cert
+ info->SetSentClientCert();
+ }
+
+ return runnable->mRV;
+}
+
+void
+ClientAuthDataRunnable::RunOnTargetThread()
+{
+ // We check the value of a pref in this runnable, so this runnable should only
+ // be run on the main thread.
+ MOZ_ASSERT(NS_IsMainThread());
+
+ UniquePLArenaPool arena;
+ char** caNameStrings;
+ UniqueCERTCertificate cert;
+ UniqueSECKEYPrivateKey privKey;
+ void* wincx = mSocketInfo;
+ nsresult rv;
+
+ nsCOMPtr<nsIX509Cert> socketClientCert;
+ mSocketInfo->GetClientCert(getter_AddRefs(socketClientCert));
+
+ // If a client cert preference was set on the socket info, use that and skip
+ // the client cert UI and/or search of the user's past cert decisions.
+ if (socketClientCert) {
+ cert.reset(socketClientCert->GetCert());
+ if (!cert) {
+ goto loser;
+ }
+
+ // Get the private key
+ privKey.reset(PK11_FindKeyByAnyCert(cert.get(), wincx));
+ if (!privKey) {
+ goto loser;
+ }
+
+ *mPRetCert = cert.release();
+ *mPRetKey = privKey.release();
+ mRV = SECSuccess;
+ return;
+ }
+
+ // create caNameStrings
+ arena.reset(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
+ if (!arena) {
+ goto loser;
+ }
+
+ caNameStrings = static_cast<char**>(
+ PORT_ArenaAlloc(arena.get(), sizeof(char*) * mCANames->nnames));
+ if (!caNameStrings) {
+ goto loser;
+ }
+
+ mRV = nsConvertCANamesToStrings(arena, caNameStrings, mCANames);
+ if (mRV != SECSuccess) {
+ goto loser;
+ }
+
+ // find valid user cert and key pair
+ if (nsGetUserCertChoice() == UserCertChoice::Auto) {
+ // automatically find the right cert
+
+ // find all user certs that are valid and for SSL
+ UniqueCERTCertList certList(
+ CERT_FindUserCertsByUsage(CERT_GetDefaultCertDB(), certUsageSSLClient,
+ false, true, wincx));
+ if (!certList) {
+ goto loser;
+ }
+
+ // filter the list to those issued by CAs supported by the server
+ mRV = CERT_FilterCertListByCANames(certList.get(), mCANames->nnames,
+ caNameStrings, certUsageSSLClient);
+ if (mRV != SECSuccess) {
+ goto loser;
+ }
+
+ // make sure the list is not empty
+ if (CERT_LIST_END(CERT_LIST_HEAD(certList), certList)) {
+ goto loser;
+ }
+
+ UniqueCERTCertificate lowPrioNonrepCert;
+
+ // loop through the list until we find a cert with a key
+ for (CERTCertListNode* node = CERT_LIST_HEAD(certList);
+ !CERT_LIST_END(node, certList);
+ node = CERT_LIST_NEXT(node)) {
+ // if the certificate has restriction and we do not satisfy it we do not
+ // use it
+ privKey.reset(PK11_FindKeyByAnyCert(node->cert, wincx));
+ if (privKey) {
+ if (hasExplicitKeyUsageNonRepudiation(node->cert)) {
+ privKey = nullptr;
+ // Not a preferred cert
+ if (!lowPrioNonrepCert) { // did not yet find a low prio cert
+ lowPrioNonrepCert.reset(CERT_DupCertificate(node->cert));
+ }
+ } else {
+ // this is a good cert to present
+ cert.reset(CERT_DupCertificate(node->cert));
+ break;
+ }
+ }
+ if (PR_GetError() == SEC_ERROR_BAD_PASSWORD) {
+ // problem with password: bail
+ goto loser;
+ }
+ }
+
+ if (!cert && lowPrioNonrepCert) {
+ cert = Move(lowPrioNonrepCert);
+ privKey.reset(PK11_FindKeyByAnyCert(cert.get(), wincx));
+ }
+
+ if (!cert) {
+ goto loser;
+ }
+ } else { // Not Auto => ask
+ // Get the SSL Certificate
+
+ nsXPIDLCString hostname;
+ mSocketInfo->GetHostName(getter_Copies(hostname));
+
+ RefPtr<nsClientAuthRememberService> cars =
+ mSocketInfo->SharedState().GetClientAuthRememberService();
+
+ bool hasRemembered = false;
+ nsCString rememberedDBKey;
+ if (cars) {
+ bool found;
+ rv = cars->HasRememberedDecision(hostname,
+ mSocketInfo->GetOriginAttributes(),
+ mServerCert, rememberedDBKey, &found);
+ if (NS_SUCCEEDED(rv) && found) {
+ hasRemembered = true;
+ }
+ }
+
+ if (hasRemembered && !rememberedDBKey.IsEmpty()) {
+ nsCOMPtr<nsIX509CertDB> certdb = do_GetService(NS_X509CERTDB_CONTRACTID);
+ if (certdb) {
+ nsCOMPtr<nsIX509Cert> foundCert;
+ rv = certdb->FindCertByDBKey(rememberedDBKey.get(),
+ getter_AddRefs(foundCert));
+ if (NS_SUCCEEDED(rv) && foundCert) {
+ nsNSSCertificate* objCert =
+ BitwiseCast<nsNSSCertificate*, nsIX509Cert*>(foundCert.get());
+ if (objCert) {
+ cert.reset(objCert->GetCert());
+ }
+ }
+
+ if (!cert) {
+ hasRemembered = false;
+ }
+ }
+ }
+
+ if (!hasRemembered) {
+ // user selects a cert to present
+ nsCOMPtr<nsIClientAuthDialogs> dialogs;
+
+ // find all user certs that are for SSL
+ // note that we are allowing expired certs in this list
+ UniqueCERTCertList certList(
+ CERT_FindUserCertsByUsage(CERT_GetDefaultCertDB(), certUsageSSLClient,
+ false, false, wincx));
+ if (!certList) {
+ goto loser;
+ }
+
+ if (mCANames->nnames != 0) {
+ // filter the list to those issued by CAs supported by the server
+ mRV = CERT_FilterCertListByCANames(certList.get(),
+ mCANames->nnames,
+ caNameStrings,
+ certUsageSSLClient);
+ if (mRV != SECSuccess) {
+ goto loser;
+ }
+ }
+
+ if (CERT_LIST_END(CERT_LIST_HEAD(certList), certList)) {
+ // list is empty - no matching certs
+ goto loser;
+ }
+
+ int32_t port;
+ mSocketInfo->GetPort(&port);
+
+ UniquePORTString corg(CERT_GetOrgName(&mServerCert->subject));
+ nsAutoCString org(corg.get());
+
+ UniquePORTString cissuer(CERT_GetOrgName(&mServerCert->issuer));
+ nsAutoCString issuer(cissuer.get());
+
+ nsCOMPtr<nsIMutableArray> certArray = nsArrayBase::Create();
+ if (!certArray) {
+ goto loser;
+ }
+
+ for (CERTCertListNode* node = CERT_LIST_HEAD(certList);
+ !CERT_LIST_END(node, certList);
+ node = CERT_LIST_NEXT(node)) {
+ nsCOMPtr<nsIX509Cert> tempCert = nsNSSCertificate::Create(node->cert);
+ if (!tempCert) {
+ goto loser;
+ }
+
+ rv = certArray->AppendElement(tempCert, false);
+ if (NS_FAILED(rv)) {
+ goto loser;
+ }
+ }
+
+ // Throw up the client auth dialog and get back the index of the selected cert
+ rv = getNSSDialogs(getter_AddRefs(dialogs),
+ NS_GET_IID(nsIClientAuthDialogs),
+ NS_CLIENTAUTHDIALOGS_CONTRACTID);
+
+ if (NS_FAILED(rv)) {
+ goto loser;
+ }
+
+ uint32_t selectedIndex = 0;
+ bool certChosen = false;
+ rv = dialogs->ChooseCertificate(mSocketInfo, hostname, port, org, issuer,
+ certArray, &selectedIndex, &certChosen);
+ if (NS_FAILED(rv)) {
+ goto loser;
+ }
+
+ // even if the user has canceled, we want to remember that, to avoid repeating prompts
+ bool wantRemember = false;
+ mSocketInfo->GetRememberClientAuthCertificate(&wantRemember);
+
+ if (certChosen) {
+ nsCOMPtr<nsIX509Cert> selectedCert = do_QueryElementAt(certArray,
+ selectedIndex);
+ if (!selectedCert) {
+ goto loser;
+ }
+ cert.reset(selectedCert->GetCert());
+ }
+
+ if (cars && wantRemember) {
+ cars->RememberDecision(hostname, mSocketInfo->GetOriginAttributes(),
+ mServerCert, certChosen ? cert.get() : nullptr);
+ }
+ }
+
+ if (!cert) {
+ goto loser;
+ }
+
+ // go get the private key
+ privKey.reset(PK11_FindKeyByAnyCert(cert.get(), wincx));
+ if (!privKey) {
+ goto loser;
+ }
+ }
+ goto done;
+
+loser:
+ if (mRV == SECSuccess) {
+ mRV = SECFailure;
+ }
+done:
+ int error = PR_GetError();
+
+ *mPRetCert = cert.release();
+ *mPRetKey = privKey.release();
+
+ if (mRV == SECFailure) {
+ mErrorCodeToReport = error;
+ }
+}
+
+static PRFileDesc*
+nsSSLIOLayerImportFD(PRFileDesc* fd,
+ nsNSSSocketInfo* infoObject,
+ const char* host)
+{
+ nsNSSShutDownPreventionLock locker;
+ PRFileDesc* sslSock = SSL_ImportFD(nullptr, fd);
+ if (!sslSock) {
+ NS_ASSERTION(false, "NSS: Error importing socket");
+ return nullptr;
+ }
+ SSL_SetPKCS11PinArg(sslSock, (nsIInterfaceRequestor*) infoObject);
+ SSL_HandshakeCallback(sslSock, HandshakeCallback, infoObject);
+ SSL_SetCanFalseStartCallback(sslSock, CanFalseStartCallback, infoObject);
+
+ // Disable this hook if we connect anonymously. See bug 466080.
+ uint32_t flags = 0;
+ infoObject->GetProviderFlags(&flags);
+ if (flags & nsISocketProvider::ANONYMOUS_CONNECT) {
+ SSL_GetClientAuthDataHook(sslSock, nullptr, infoObject);
+ } else {
+ SSL_GetClientAuthDataHook(sslSock,
+ (SSLGetClientAuthData) nsNSS_SSLGetClientAuthData,
+ infoObject);
+ }
+ if (flags & nsISocketProvider::MITM_OK) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("[%p] nsSSLIOLayerImportFD: bypass authentication flag\n", fd));
+ infoObject->SetBypassAuthentication(true);
+ }
+ if (SECSuccess != SSL_AuthCertificateHook(sslSock, AuthCertificateHook,
+ infoObject)) {
+ NS_NOTREACHED("failed to configure AuthCertificateHook");
+ goto loser;
+ }
+
+ if (SECSuccess != SSL_SetURL(sslSock, host)) {
+ NS_NOTREACHED("SSL_SetURL failed");
+ goto loser;
+ }
+
+ return sslSock;
+loser:
+ if (sslSock) {
+ PR_Close(sslSock);
+ }
+ return nullptr;
+}
+
+static const SSLSignatureScheme sEnabledSignatureSchemes[] = {
+ ssl_sig_ecdsa_secp256r1_sha256,
+ ssl_sig_ecdsa_secp384r1_sha384,
+ ssl_sig_ecdsa_secp521r1_sha512,
+ ssl_sig_rsa_pss_sha256,
+ ssl_sig_rsa_pss_sha384,
+ ssl_sig_rsa_pss_sha512,
+ ssl_sig_rsa_pkcs1_sha256,
+ ssl_sig_rsa_pkcs1_sha384,
+ ssl_sig_rsa_pkcs1_sha512,
+ ssl_sig_ecdsa_sha1,
+ ssl_sig_rsa_pkcs1_sha1,
+};
+
+static nsresult
+nsSSLIOLayerSetOptions(PRFileDesc* fd, bool forSTARTTLS,
+ bool haveProxy, const char* host, int32_t port,
+ nsNSSSocketInfo* infoObject)
+{
+ nsNSSShutDownPreventionLock locker;
+ if (forSTARTTLS || haveProxy) {
+ if (SECSuccess != SSL_OptionSet(fd, SSL_SECURITY, false)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ SSLVersionRange range;
+ if (SSL_VersionRangeGet(fd, &range) != SECSuccess) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if ((infoObject->GetProviderFlags() & nsISocketProvider::BE_CONSERVATIVE) &&
+ (range.max > SSL_LIBRARY_VERSION_TLS_1_2)) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("[%p] nsSSLIOLayerSetOptions: range.max limited to 1.2 due to BE_CONSERVATIVE flag\n",
+ fd));
+ range.max = SSL_LIBRARY_VERSION_TLS_1_2;
+ }
+
+ uint16_t maxEnabledVersion = range.max;
+ StrongCipherStatus strongCiphersStatus = StrongCipherStatusUnknown;
+ infoObject->SharedState().IOLayerHelpers()
+ .adjustForTLSIntolerance(infoObject->GetHostName(), infoObject->GetPort(),
+ range, strongCiphersStatus);
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("[%p] nsSSLIOLayerSetOptions: using TLS version range (0x%04x,0x%04x)%s\n",
+ fd, static_cast<unsigned int>(range.min),
+ static_cast<unsigned int>(range.max),
+ strongCiphersStatus == StrongCiphersFailed ? " with weak ciphers" : ""));
+
+ if (SSL_VersionRangeSet(fd, &range) != SECSuccess) {
+ return NS_ERROR_FAILURE;
+ }
+ infoObject->SetTLSVersionRange(range);
+
+ if (strongCiphersStatus == StrongCiphersFailed) {
+ nsNSSComponent::UseWeakCiphersOnSocket(fd);
+ }
+
+ // when adjustForTLSIntolerance tweaks the maximum version downward,
+ // we tell the server using this SCSV so they can detect a downgrade attack
+ if (range.max < maxEnabledVersion) {
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
+ ("[%p] nsSSLIOLayerSetOptions: enabling TLS_FALLBACK_SCSV\n", fd));
+ // Some servers will choke if we send the fallback SCSV with TLS 1.2.
+ if (range.max < SSL_LIBRARY_VERSION_TLS_1_2) {
+ if (SECSuccess != SSL_OptionSet(fd, SSL_ENABLE_FALLBACK_SCSV, true)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+ // tell NSS the max enabled version to make anti-downgrade effective
+ if (SECSuccess != SSL_SetDowngradeCheckVersion(fd, maxEnabledVersion)) {
+ return NS_ERROR_FAILURE;
+ }
+ }
+
+ // Include a modest set of named groups.
+ const SSLNamedGroup namedGroups[] = {
+ ssl_grp_ec_curve25519, ssl_grp_ec_secp256r1, ssl_grp_ec_secp384r1,
+ ssl_grp_ec_secp521r1, ssl_grp_ffdhe_2048, ssl_grp_ffdhe_3072
+ };
+ if (SECSuccess != SSL_NamedGroupConfig(fd, namedGroups,
+ mozilla::ArrayLength(namedGroups))) {
+ return NS_ERROR_FAILURE;
+ }
+ // This ensures that we send key shares for X25519 and P-256 in TLS 1.3, so
+ // that servers are less likely to use HelloRetryRequest.
+ if (SECSuccess != SSL_SendAdditionalKeyShares(fd, 1)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (SECSuccess != SSL_SignatureSchemePrefSet(fd, sEnabledSignatureSchemes,
+ mozilla::ArrayLength(sEnabledSignatureSchemes))) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool enabled = infoObject->SharedState().IsOCSPStaplingEnabled();
+ if (SECSuccess != SSL_OptionSet(fd, SSL_ENABLE_OCSP_STAPLING, enabled)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ bool sctsEnabled = infoObject->SharedState().IsSignedCertTimestampsEnabled();
+ if (SECSuccess != SSL_OptionSet(fd, SSL_ENABLE_SIGNED_CERT_TIMESTAMPS,
+ sctsEnabled)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ if (SECSuccess != SSL_OptionSet(fd, SSL_HANDSHAKE_AS_CLIENT, true)) {
+ return NS_ERROR_FAILURE;
+ }
+
+ // Set the Peer ID so that SSL proxy connections work properly and to
+ // separate anonymous and/or private browsing connections.
+ uint32_t flags = infoObject->GetProviderFlags();
+ nsAutoCString peerId;
+ if (flags & nsISocketProvider::ANONYMOUS_CONNECT) { // See bug 466080
+ peerId.AppendLiteral("anon:");
+ }
+ if (flags & nsISocketProvider::NO_PERMANENT_STORAGE) {
+ peerId.AppendLiteral("private:");
+ }
+ if (flags & nsISocketProvider::MITM_OK) {
+ peerId.AppendLiteral("bypassAuth:");
+ }
+ if (flags & nsISocketProvider::BE_CONSERVATIVE) {
+ peerId.AppendLiteral("beConservative:");
+ }
+ peerId.Append(host);
+ peerId.Append(':');
+ peerId.AppendInt(port);
+ nsAutoCString suffix;
+ infoObject->GetOriginAttributes().CreateSuffix(suffix);
+ peerId.Append(suffix);
+ if (SECSuccess != SSL_SetSockPeerID(fd, peerId.get())) {
+ return NS_ERROR_FAILURE;
+ }
+
+ return NS_OK;
+}
+
+nsresult
+nsSSLIOLayerAddToSocket(int32_t family,
+ const char* host,
+ int32_t port,
+ nsIProxyInfo* proxy,
+ const NeckoOriginAttributes& originAttributes,
+ PRFileDesc* fd,
+ nsISupports** info,
+ bool forSTARTTLS,
+ uint32_t providerFlags)
+{
+ nsNSSShutDownPreventionLock locker;
+ PRFileDesc* layer = nullptr;
+ PRFileDesc* plaintextLayer = nullptr;
+ nsresult rv;
+ PRStatus stat;
+
+ SharedSSLState* sharedState =
+ providerFlags & nsISocketProvider::NO_PERMANENT_STORAGE ? PrivateSSLState() : PublicSSLState();
+ nsNSSSocketInfo* infoObject = new nsNSSSocketInfo(*sharedState, providerFlags);
+ if (!infoObject) return NS_ERROR_FAILURE;
+
+ NS_ADDREF(infoObject);
+ infoObject->SetForSTARTTLS(forSTARTTLS);
+ infoObject->SetHostName(host);
+ infoObject->SetPort(port);
+ infoObject->SetOriginAttributes(originAttributes);
+
+ bool haveProxy = false;
+ if (proxy) {
+ nsCString proxyHost;
+ proxy->GetHost(proxyHost);
+ haveProxy = !proxyHost.IsEmpty();
+ }
+
+ // A plaintext observer shim is inserted so we can observe some protocol
+ // details without modifying nss
+ plaintextLayer = PR_CreateIOLayerStub(nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity,
+ &nsSSLIOLayerHelpers::nsSSLPlaintextLayerMethods);
+ if (plaintextLayer) {
+ plaintextLayer->secret = (PRFilePrivate*) infoObject;
+ stat = PR_PushIOLayer(fd, PR_TOP_IO_LAYER, plaintextLayer);
+ if (stat == PR_FAILURE) {
+ plaintextLayer->dtor(plaintextLayer);
+ plaintextLayer = nullptr;
+ }
+ }
+
+ PRFileDesc* sslSock = nsSSLIOLayerImportFD(fd, infoObject, host);
+ if (!sslSock) {
+ NS_ASSERTION(false, "NSS: Error importing socket");
+ goto loser;
+ }
+
+ infoObject->SetFileDescPtr(sslSock);
+
+ rv = nsSSLIOLayerSetOptions(sslSock, forSTARTTLS, haveProxy, host, port,
+ infoObject);
+
+ if (NS_FAILED(rv))
+ goto loser;
+
+ // Now, layer ourselves on top of the SSL socket...
+ layer = PR_CreateIOLayerStub(nsSSLIOLayerHelpers::nsSSLIOLayerIdentity,
+ &nsSSLIOLayerHelpers::nsSSLIOLayerMethods);
+ if (!layer)
+ goto loser;
+
+ layer->secret = (PRFilePrivate*) infoObject;
+ stat = PR_PushIOLayer(sslSock, PR_GetLayersIdentity(sslSock), layer);
+
+ if (stat == PR_FAILURE) {
+ goto loser;
+ }
+
+ MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("[%p] Socket set up\n", (void*) sslSock));
+ infoObject->QueryInterface(NS_GET_IID(nsISupports), (void**) (info));
+
+ // We are going use a clear connection first //
+ if (forSTARTTLS || haveProxy) {
+ infoObject->SetHandshakeNotPending();
+ }
+
+ infoObject->SharedState().NoteSocketCreated();
+
+ return NS_OK;
+ loser:
+ NS_IF_RELEASE(infoObject);
+ if (layer) {
+ layer->dtor(layer);
+ }
+ if (plaintextLayer) {
+ PR_PopIOLayer(fd, nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity);
+ plaintextLayer->dtor(plaintextLayer);
+ }
+ return NS_ERROR_FAILURE;
+}