/* -*- 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 #include #include #include "NSSCertDBTrustDomain.h" #include "NSSErrorsService.h" #include "PSMIPCCommon.h" #include "PSMRunnable.h" #include "SSLServerCertVerification.h" #include "ScopedNSSTypes.h" #include "SharedSSLState.h" #include "cert_storage/src/cert_storage.h" #include "keyhi.h" #include "mozilla/Base64.h" #include "mozilla/Casting.h" #include "mozilla/DebugOnly.h" #include "mozilla/Logging.h" #include "mozilla/Preferences.h" #include "mozilla/Telemetry.h" #include "mozilla/net/SSLTokensCache.h" #include "mozilla/net/SocketProcessChild.h" #include "mozpkix/pkixnss.h" #include "mozpkix/pkixtypes.h" #include "mozpkix/pkixutil.h" #include "nsArray.h" #include "nsArrayUtils.h" #include "nsCRT.h" #include "nsCharSeparatedTokenizer.h" #include "nsClientAuthRemember.h" #include "nsContentUtils.h" #include "nsIClientAuthDialogs.h" #include "nsISocketProvider.h" #include "nsIWebProgressListener.h" #include "nsNSSCertHelper.h" #include "nsNSSComponent.h" #include "nsNSSHelper.h" #include "nsPrintfCString.h" #include "nsServiceManagerUtils.h" #include "prmem.h" #include "prnetdb.h" #include "secder.h" #include "secerr.h" #include "ssl.h" #include "sslerr.h" #include "sslexp.h" #include "sslproto.h" 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 { // The NSSSocketInfo tls flags are meant to be opaque to most calling // applications but provide a mechanism for direct TLS manipulation when // experimenting with new features in the scope of a single socket. They do not // create a persistent ABI. // // Use of these flags creates a new 'sharedSSLState' so existing states for // intolerance are not carried to sockets that use these flags (and intolerance // they discover does not impact other normal sockets not using the flags.) // // Their current definitions are: // // bits 0-2 (mask 0x07) specify the max tls version // 0 means no override 1->4 are 1.0, 1.1, 1.2, 1.3, 4->7 unused // bits 3-5 (mask 0x38) specify the tls fallback limit // 0 means no override, values 1->4 match prefs // bit 6 (mask 0x40) was used to specify compat mode. Temporarily reserved. enum { kTLSProviderFlagMaxVersion10 = 0x01, kTLSProviderFlagMaxVersion11 = 0x02, kTLSProviderFlagMaxVersion12 = 0x03, kTLSProviderFlagMaxVersion13 = 0x04, }; static uint32_t getTLSProviderFlagMaxVersion(uint32_t flags) { return (flags & 0x07); } static uint32_t getTLSProviderFlagFallbackLimit(uint32_t flags) { return (flags & 0x38) >> 3; } #define MAX_ALPN_LENGTH 255 void getSiteKey(const nsACString& hostName, uint16_t port, /*out*/ nsACString& key) { key = hostName; key.AppendLiteral(":"); key.AppendInt(port); } } // unnamed namespace extern LazyLogModule gPIPNSSLog; nsNSSSocketInfo::nsNSSSocketInfo(SharedSSLState& aState, uint32_t providerFlags, uint32_t providerTlsFlags) : CommonSocketControl(providerFlags), mFd(nullptr), mCertVerificationState(before_cert_verification), mSharedState(aState), mForSTARTTLS(false), mHandshakePending(true), mPreliminaryHandshakeDone(false), mEarlyDataAccepted(false), mDenyClientCert(false), mFalseStartCallbackCalled(false), mFalseStarted(false), mIsFullHandshake(false), mNotedTimeUntilReady(false), mIsShortWritePending(false), mShortWritePendingByte(0), mShortWriteOriginalAmount(-1), mKEAUsed(nsISSLSocketControl::KEY_EXCHANGE_UNKNOWN), mKEAKeyBits(0), mMACAlgorithmUsed(nsISSLSocketControl::SSL_MAC_UNKNOWN), mProviderTlsFlags(providerTlsFlags), mSocketCreationTimestamp(TimeStamp::Now()), mPlaintextBytesRead(0), mClientCert(nullptr) { mTLSVersionRange.min = 0; mTLSVersionRange.max = 0; } nsNSSSocketInfo::~nsNSSSocketInfo() = default; NS_IMPL_ISUPPORTS_INHERITED(nsNSSSocketInfo, TransportSecurityInfo, nsISSLSocketControl) NS_IMETHODIMP nsNSSSocketInfo::GetProviderTlsFlags(uint32_t* aProviderTlsFlags) { *aProviderTlsFlags = mProviderTlsFlags; 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::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; } void nsNSSSocketInfo::NoteTimeUntilReady() { MutexAutoLock lock(mMutex); 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; MutexAutoLock lock(mMutex); // This will include TCP and proxy tunnel wait time Telemetry::AccumulateTimeDelta( Telemetry::SSL_TIME_UNTIL_HANDSHAKE_FINISHED_KEYED_BY_KA, mKeaGroup, 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 plaintext layer as it is not needed anymore. // The plaintext layer is not always present - so it's not a fatal error if it // cannot be removed. // Note that PR_PopIOLayer may modify its stack, so a pointer returned by // PR_GetIdentitiesLayer may not point to what we think it points to after // calling PR_PopIOLayer. We must operate on the pointer returned by // PR_PopIOLayer. if (PR_GetIdentitiesLayer(mFd, nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity)) { PRFileDesc* 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) { MutexAutoLock lock(mMutex); if (!value) { mNegotiatedNPN.Truncate(); } else { mNegotiatedNPN.Assign(value, length); } mNPNCompleted = true; } NS_IMETHODIMP nsNSSSocketInfo::GetAlpnEarlySelection(nsACString& aAlpnSelected) { aAlpnSelected.Truncate(); SSLPreliminaryChannelInfo info; SECStatus rv = SSL_GetPreliminaryChannelInfo(mFd, &info, sizeof(info)); if (rv != SECSuccess || !info.canSendEarlyData) { return NS_ERROR_NOT_AVAILABLE; } SSLNextProtoState alpnState; unsigned char chosenAlpn[MAX_ALPN_LENGTH]; unsigned int chosenAlpnLen; rv = SSL_GetNextProto(mFd, &alpnState, chosenAlpn, &chosenAlpnLen, AssertedCast(ArrayLength(chosenAlpn))); if (rv != SECSuccess) { return NS_ERROR_NOT_AVAILABLE; } if (alpnState == SSL_NEXT_PROTO_EARLY_VALUE) { aAlpnSelected.Assign(BitwiseCast(chosenAlpn), chosenAlpnLen); } return NS_OK; } NS_IMETHODIMP nsNSSSocketInfo::GetEarlyDataAccepted(bool* aAccepted) { *aAccepted = mEarlyDataAccepted; return NS_OK; } void nsNSSSocketInfo::SetEarlyDataAccepted(bool aAccepted) { mEarlyDataAccepted = aAccepted; } bool nsNSSSocketInfo::GetDenyClientCert() { return mDenyClientCert; } void nsNSSSocketInfo::SetDenyClientCert(bool aDenyClientCert) { mDenyClientCert = aDenyClientCert; } NS_IMETHODIMP nsNSSSocketInfo::DriveHandshake() { if (!mFd) { return NS_ERROR_FAILURE; } if (IsCanceled()) { PRErrorCode errorCode = GetErrorCode(); MOZ_DIAGNOSTIC_ASSERT(errorCode, "handshake cancelled without error code"); return GetXPCOMFromNSSError(errorCode); } SECStatus rv = SSL_ForceHandshake(mFd); if (rv != SECSuccess) { PRErrorCode errorCode = PR_GetError(); MOZ_DIAGNOSTIC_ASSERT(errorCode, "handshake failed without error code"); if (errorCode == PR_WOULD_BLOCK_ERROR) { return NS_BASE_STREAM_WOULD_BLOCK; } SetCanceled(errorCode); return GetXPCOMFromNSSError(errorCode); } 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& protocolArray) { 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(npnList.get()), npnList.Length()) != SECSuccess) return NS_ERROR_FAILURE; return NS_OK; } nsresult nsNSSSocketInfo::ActivateSSL() { 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 SetResumptionTokenFromExternalCache(); } 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. MOZ_ASSERT(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) { MOZ_ASSERT(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(); if (errorCode == 0) { NS_ERROR("SSL_AuthCertificateComplete didn't set error code"); errorCode = PR_INVALID_STATE_ERROR; } } } if (errorCode) { mFailedVerification = true; SetCanceled(errorCode); } if (mPlaintextBytesRead && !errorCode) { Telemetry::Accumulate(Telemetry::SSL_BYTES_BEFORE_CERT_CALLBACK, AssertedCast(mPlaintextBytesRead)); } mCertVerificationState = after_cert_verification; } SharedSSLState& nsNSSSocketInfo::SharedState() { return mSharedState; } void nsNSSSocketInfo::SetSharedOwningReference(SharedSSLState* aRef) { mOwningSharedRef = aRef; } void nsSSLIOLayerHelpers::Cleanup() { MutexAutoLock lock(mutex); mTLSIntoleranceInfo.Clear(); mInsecureFallbackSites.Clear(); } 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) { 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->IsCanceled()) { 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)); if (!getSocketInfoIfRunning(fd, not_reading_or_writing)) 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 } } else { entry.tolerant = tolerant; entry.intolerant = 0; entry.intoleranceReason = 0; } 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; 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.intolerant = intolerant; entry.intoleranceReason = intoleranceReason; entry.AssertInvariant(); mTLSIntoleranceInfo.Put(key, entry); return true; } void nsSSLIOLayerHelpers::adjustForTLSIntolerance( const nsACString& hostName, int16_t port, /*in/out*/ SSLVersionRange& range) { 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; } } } 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) { if (!fd) return PR_FAILURE; MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("[%p] Shutting down socket\n", (void*)fd)); nsNSSSocketInfo* socketInfo = (nsNSSSocketInfo*)fd->secret; MOZ_ASSERT(socketInfo, "nsNSSSocketInfo was null for an fd"); return socketInfo->CloseSocketAndDestroy(); } PRStatus nsNSSSocketInfo::CloseSocketAndDestroy() { PRFileDesc* popped = PR_PopIOLayer(mFd, PR_TOP_IO_LAYER); MOZ_ASSERT( popped && popped->identity == nsSSLIOLayerHelpers::nsSSLIOLayerIdentity, "SSL Layer not on top of stack"); // The plaintext layer is not always present - so it's not a fatal error if it // cannot be removed. // Note that PR_PopIOLayer may modify its stack, so a pointer returned by // PR_GetIdentitiesLayer may not point to what we think it points to after // calling PR_PopIOLayer. We must operate on the pointer returned by // PR_PopIOLayer. if (PR_GetIdentitiesLayer(mFd, nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity)) { PRFileDesc* poppedPlaintext = PR_PopIOLayer(mFd, nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity); poppedPlaintext->dtor(poppedPlaintext); } // We need to clear the callback to make sure the ssl layer cannot call the // callback after mFD is nulled. if (StaticPrefs::network_ssl_tokens_cache_enabled()) { SSL_SetResumptionTokenCallback(mFd, nullptr, nullptr); } 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; } NS_IMETHODIMP nsNSSSocketInfo::GetEsniTxt(nsACString& aEsniTxt) { aEsniTxt = mEsniTxt; return NS_OK; } NS_IMETHODIMP nsNSSSocketInfo::SetEsniTxt(const nsACString& aEsniTxt) { mEsniTxt = aEsniTxt; if (mEsniTxt.Length()) { nsAutoCString esniBin; if (NS_OK != Base64Decode(mEsniTxt, esniBin)) { MOZ_LOG(gPIPNSSLog, LogLevel::Error, ("[%p] Invalid ESNIKeys record. Couldn't base64 decode\n", (void*)mFd)); return NS_OK; } if (SECSuccess != SSL_EnableESNI(mFd, reinterpret_cast(esniBin.get()), esniBin.Length(), nullptr)) { MOZ_LOG(gPIPNSSLog, LogLevel::Error, ("[%p] Invalid ESNIKeys record %s\n", (void*)mFd, PR_ErrorToName(PR_GetError()))); return NS_OK; } } return NS_OK; } NS_IMETHODIMP nsNSSSocketInfo::GetEchConfig(nsACString& aEchConfig) { aEchConfig = mEchConfig; return NS_OK; } NS_IMETHODIMP nsNSSSocketInfo::SetEchConfig(const nsACString& aEchConfig) { mEchConfig = aEchConfig; if (mEchConfig.Length()) { if (SECSuccess != SSL_SetClientEchConfigs( mFd, reinterpret_cast(aEchConfig.BeginReading()), aEchConfig.Length())) { MOZ_LOG(gPIPNSSLog, LogLevel::Error, ("[%p] Invalid EchConfig record %s\n", (void*)mFd, PR_ErrorToName(PR_GetError()))); return NS_OK; } } return NS_OK; } NS_IMETHODIMP nsNSSSocketInfo::GetRetryEchConfig(nsACString& aEchConfig) { if (!mFd) { return NS_ERROR_FAILURE; } ScopedAutoSECItem retryConfigItem; SECStatus rv = SSL_GetEchRetryConfigs(mFd, &retryConfigItem); if (rv != SECSuccess) { return NS_ERROR_FAILURE; } aEchConfig = nsCString(reinterpret_cast(retryConfigItem.data), retryConfigItem.len); return NS_OK; } NS_IMETHODIMP nsNSSSocketInfo::GetPeerId(nsACString& aResult) { if (!mPeerId.IsEmpty()) { aResult.Assign(mPeerId); return NS_OK; } if (mProviderFlags & nsISocketProvider::ANONYMOUS_CONNECT) { // See bug 466080 mPeerId.AppendLiteral("anon:"); } if (mProviderFlags & nsISocketProvider::NO_PERMANENT_STORAGE) { mPeerId.AppendLiteral("private:"); } if (mProviderFlags & nsISocketProvider::BE_CONSERVATIVE) { mPeerId.AppendLiteral("beConservative:"); } mPeerId.AppendPrintf("tlsflags0x%08x:", mProviderTlsFlags); mPeerId.Append(GetHostName()); mPeerId.Append(':'); mPeerId.AppendInt(GetPort()); nsAutoCString suffix; GetOriginAttributes().CreateSuffix(suffix); mPeerId.Append(suffix); aResult.Assign(mPeerId); return NS_OK; } nsresult nsNSSSocketInfo::SetResumptionTokenFromExternalCache() { if (!StaticPrefs::network_ssl_tokens_cache_enabled()) { return NS_OK; } if (!mFd) { return NS_ERROR_FAILURE; } // If SSL_NO_CACHE option was set, we must not use the cache PRIntn val; if (SSL_OptionGet(mFd, SSL_NO_CACHE, &val) != SECSuccess) { return NS_ERROR_FAILURE; } if (val != 0) { return NS_OK; } nsTArray token; nsAutoCString peerId; nsresult rv = GetPeerId(peerId); if (NS_FAILED(rv)) { return rv; } rv = mozilla::net::SSLTokensCache::Get(peerId, token); if (NS_FAILED(rv)) { if (rv == NS_ERROR_NOT_AVAILABLE) { // It's ok if we can't find the token. return NS_OK; } return rv; } SECStatus srv = SSL_SetResumptionToken(mFd, token.Elements(), token.Length()); if (srv == SECFailure) { PRErrorCode error = PR_GetError(); mozilla::net::SSLTokensCache::Remove(peerId); MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("Setting token failed with NSS error %d [id=%s]", error, PromiseFlatCString(peerId).get())); // We don't consider SSL_ERROR_BAD_RESUMPTION_TOKEN_ERROR as a hard error, // since this error means this token is just expired or can't be decoded // correctly. if (error == SSL_ERROR_BAD_RESUMPTION_TOKEN_ERROR) { return NS_OK; } return NS_ERROR_FAILURE; } return NS_OK; } #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 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; } // 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::HistogramID pre; Telemetry::HistogramID 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. // // IsCanceled() is backed by an atomic boolean. It will only ever go from // false to true, so we will never erroneously not call SetCanceled here. We // could in theory overwrite a previously-set error code, but we'll always // have some sort of error. if (!wantRetry && mozilla::psm::IsNSSErrorCode(err) && !socketInfo->IsCanceled()) { socketInfo->SetCanceled(err); } } 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->IsCanceled()) { socketInfo->SetCanceled(originalError); } PR_SetError(err, 0); } return bytesTransfered; } } // namespace static int16_t nsSSLIOLayerPoll(PRFileDesc* fd, int16_t in_flags, int16_t* out_flags) { if (!out_flags) { NS_WARNING("nsSSLIOLayerPoll called with null out_flags"); return 0; } *out_flags = 0; nsNSSSocketInfo* socketInfo = getSocketInfoIfRunning(fd, not_reading_or_writing); 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)); MOZ_ASSERT(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(uint32_t aTlsFlags) : mTreatUnsafeNegotiationAsBroken(false), mTLSIntoleranceInfo(), mVersionFallbackLimit(SSL_LIBRARY_VERSION_TLS_1_0), mutex("nsSSLIOLayerHelpers.mutex"), mTlsFlags(aTlsFlags) {} // PSMAvailable and PSMAvailable64 are reachable, but they're unimplemented in // PSM, so we set an error and return -1. static int32_t PSMAvailable(PRFileDesc*) { PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0); return -1; } static int64_t PSMAvailable64(PRFileDesc*) { PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0); return -1; } static PRStatus PSMGetsockname(PRFileDesc* fd, PRNetAddr* addr) { if (!getSocketInfoIfRunning(fd, not_reading_or_writing)) return PR_FAILURE; return fd->lower->methods->getsockname(fd->lower, addr); } static PRStatus PSMGetpeername(PRFileDesc* fd, PRNetAddr* addr) { if (!getSocketInfoIfRunning(fd, not_reading_or_writing)) return PR_FAILURE; return fd->lower->methods->getpeername(fd->lower, addr); } static PRStatus PSMGetsocketoption(PRFileDesc* fd, PRSocketOptionData* data) { if (!getSocketInfoIfRunning(fd, not_reading_or_writing)) return PR_FAILURE; return fd->lower->methods->getsocketoption(fd, data); } static PRStatus PSMSetsocketoption(PRFileDesc* fd, const PRSocketOptionData* data) { if (!getSocketInfoIfRunning(fd, not_reading_or_writing)) 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) { nsNSSSocketInfo* socketInfo = getSocketInfoIfRunning(fd, reading); 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) { nsNSSSocketInfo* socketInfo = getSocketInfoIfRunning(fd, writing); 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 if (socketInfo->IsShortWritePending() && amount > 0) { // We got "SSL short write" last time, try to flush the pending byte. #ifdef DEBUG socketInfo->CheckShortWrittenBuffer(static_cast(buf), amount); #endif buf = socketInfo->GetShortWritePendingByteRef(); amount = 1; MOZ_LOG(gPIPNSSLog, LogLevel::Verbose, ("[%p] pushing 1 byte after SSL short write", fd)); } int32_t bytesWritten = fd->lower->methods->send(fd->lower, buf, amount, flags, timeout); // NSS indicates that it can't write all requested data (due to network // congestion, for example) by returning either one less than the amount // of data requested or 16383, if the requested amount is greater than // 16384. We refer to this as a "short write". If we simply returned // the amount that NSS did write, the layer above us would then call // PSMSend with a very small amount of data (often 1). This is inefficient // and can lead to alternating between sending large packets and very small // packets. To prevent this, we alert the layer calling us that the operation // would block and that it should be retried later, with the same data. // When it does, we tell NSS to write the remaining byte it didn't write // in the previous call. We then return the total number of bytes written, // which is the number that caused the short write plus the additional byte // we just wrote out. // The 16384 value is based on libssl's maximum buffer size: // MAX_FRAGMENT_LENGTH - 1 // // It's in a private header, though, filed bug 1394822 to expose it. static const int32_t kShortWrite16k = 16383; if ((amount > 1 && bytesWritten == (amount - 1)) || (amount > kShortWrite16k && bytesWritten == kShortWrite16k)) { // This is indication of an "SSL short write", block to force retry. socketInfo->SetShortWritePending( bytesWritten + 1, // The amount to return after the flush *(static_cast(buf) + bytesWritten)); MOZ_LOG( gPIPNSSLog, LogLevel::Verbose, ("[%p] indicated SSL short write for %d bytes (written just %d bytes)", fd, amount, bytesWritten)); bytesWritten = -1; PR_SetError(PR_WOULD_BLOCK_ERROR, 0); #ifdef DEBUG socketInfo->RememberShortWrittenBuffer( static_cast(buf)); #endif } else if (socketInfo->IsShortWritePending() && bytesWritten == 1) { // We have now flushed all pending data in the SSL socket // after the indicated short write. Tell the upper layer // it has sent all its data now. MOZ_LOG(gPIPNSSLog, LogLevel::Verbose, ("[%p] finished SSL short write", fd)); bytesWritten = socketInfo->ResetShortWritePending(); } 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) { if (!getSocketInfoIfRunning(fd, not_reading_or_writing)) 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) { if (!getSocketInfoIfRunning(fd, not_reading_or_writing)) { return PR_FAILURE; } return fd->lower->methods->connectcontinue(fd, out_flags); } namespace { class PrefObserver : public nsIObserver { public: NS_DECL_THREADSAFE_ISUPPORTS NS_DECL_NSIOBSERVER explicit PrefObserver(nsSSLIOLayerHelpers* aOwner) : mOwner(aOwner) {} protected: virtual ~PrefObserver() = default; 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.tls.version.fallback-limit")) { mOwner->loadVersionFallbackLimit(); } else if (prefName.EqualsLiteral("security.tls.insecure_fallback_hosts")) { // Changes to the allowlist on the public side will update the pref. // Don't propagate the changes to the private side. if (mOwner->isPublic()) { mOwner->initInsecureFallbackSites(); } } } 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.tls.version.fallback-limit"); Preferences::RemoveObserver(mPrefObserver, "security.tls.insecure_fallback_hosts"); } } template static R InvalidPRIOMethod(Args...) { MOZ_ASSERT_UNREACHABLE("I/O method is invalid"); PR_SetError(PR_NOT_IMPLEMENTED_ERROR, 0); return return_value; } nsresult nsSSLIOLayerHelpers::Init() { if (!nsSSLIOLayerInitialized) { MOZ_ASSERT(NS_IsMainThread()); nsSSLIOLayerInitialized = true; nsSSLIOLayerIdentity = PR_GetUniqueIdentity("NSS layer"); nsSSLIOLayerMethods = *PR_GetDefaultIOMethods(); nsSSLIOLayerMethods.fsync = InvalidPRIOMethod; nsSSLIOLayerMethods.seek = InvalidPRIOMethod; nsSSLIOLayerMethods.seek64 = InvalidPRIOMethod; nsSSLIOLayerMethods.fileInfo = InvalidPRIOMethod; nsSSLIOLayerMethods.fileInfo64 = InvalidPRIOMethod; nsSSLIOLayerMethods.writev = InvalidPRIOMethod; nsSSLIOLayerMethods.accept = InvalidPRIOMethod; nsSSLIOLayerMethods.listen = InvalidPRIOMethod; nsSSLIOLayerMethods.shutdown = InvalidPRIOMethod; nsSSLIOLayerMethods.recvfrom = InvalidPRIOMethod; nsSSLIOLayerMethods.sendto = InvalidPRIOMethod; nsSSLIOLayerMethods.acceptread = InvalidPRIOMethod; nsSSLIOLayerMethods.transmitfile = InvalidPRIOMethod; nsSSLIOLayerMethods.sendfile = InvalidPRIOMethod; nsSSLIOLayerMethods.available = PSMAvailable; nsSSLIOLayerMethods.available64 = PSMAvailable64; 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; } loadVersionFallbackLimit(); // non main thread helpers will need to use defaults if (NS_IsMainThread()) { bool enabled = false; Preferences::GetBool("security.ssl.treat_unsafe_negotiation_as_broken", &enabled); setTreatUnsafeNegotiationAsBroken(enabled); initInsecureFallbackSites(); mPrefObserver = new PrefObserver(this); Preferences::AddStrongObserver( mPrefObserver, "security.ssl.treat_unsafe_negotiation_as_broken"); Preferences::AddStrongObserver(mPrefObserver, "security.tls.version.fallback-limit"); Preferences::AddStrongObserver(mPrefObserver, "security.tls.insecure_fallback_hosts"); } else { MOZ_ASSERT(mTlsFlags, "Only per socket version can ignore prefs"); } return NS_OK; } void nsSSLIOLayerHelpers::loadVersionFallbackLimit() { // see nsNSSComponent::SetEnabledTLSVersions for pref handling rules uint32_t limit = 3; // TLS 1.2 if (NS_IsMainThread()) { limit = Preferences::GetUint("security.tls.version.fallback-limit", 3); // 3 = TLS 1.2 } // set fallback limit if it is set in the tls flags uint32_t tlsFlagsFallbackLimit = getTLSProviderFlagFallbackLimit(mTlsFlags); if (tlsFlagsFallbackLimit) { limit = tlsFlagsFallbackLimit; MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("loadVersionFallbackLimit overriden by tlsFlags %d\n", limit)); } 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() { MOZ_ASSERT(NS_IsMainThread()); initInsecureFallbackSites(); MutexAutoLock lock(mutex); mTLSIntoleranceInfo.Clear(); } void nsSSLIOLayerHelpers::setInsecureFallbackSites(const nsCString& str) { MutexAutoLock lock(mutex); mInsecureFallbackSites.Clear(); for (const nsACString& host : nsCCharSeparatedTokenizer(str, ',').ToRange()) { if (!host.IsEmpty()) { mInsecureFallbackSites.PutEntry(host); } } } void nsSSLIOLayerHelpers::initInsecureFallbackSites() { MOZ_ASSERT(NS_IsMainThread()); nsAutoCString insecureFallbackHosts; Preferences::GetCString("security.tls.insecure_fallback_hosts", insecureFallbackHosts); setInsecureFallbackSites(insecureFallbackHosts); } bool nsSSLIOLayerHelpers::isPublic() const { return this == &PublicSSLState()->IOLayerHelpers(); } class FallbackPrefRemover final : public Runnable { public: explicit FallbackPrefRemover(const nsACString& aHost) : mozilla::Runnable("FallbackPrefRemover"), mHost(aHost) {} NS_IMETHOD Run() override; private: nsCString mHost; }; NS_IMETHODIMP FallbackPrefRemover::Run() { MOZ_ASSERT(NS_IsMainThread()); nsAutoCString oldValue; Preferences::GetCString("security.tls.insecure_fallback_hosts", oldValue); nsCString newValue; for (const nsACString& host : nsCCharSeparatedTokenizer(oldValue, ',').ToRange()) { 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 = 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 OriginAttributes& originAttributes, PRFileDesc** fd, nsISupports** info, bool forSTARTTLS, uint32_t flags, uint32_t tlsFlags) { 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, tlsFlags); if (NS_FAILED(rv)) { PR_Close(sock); return rv; } *fd = sock; return NS_OK; } // 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); } ClientAuthInfo::ClientAuthInfo(const nsACString& hostName, const OriginAttributes& originAttributes, int32_t port, uint32_t providerFlags, uint32_t providerTlsFlags, nsIX509Cert* clientCert) : mHostName(hostName), mOriginAttributes(originAttributes), mPort(port), mProviderFlags(providerFlags), mProviderTlsFlags(providerTlsFlags), mClientCert(clientCert) {} ClientAuthInfo::ClientAuthInfo(ClientAuthInfo&& aOther) noexcept : mHostName(std::move(aOther.mHostName)), mOriginAttributes(std::move(aOther.mOriginAttributes)), mPort(aOther.mPort), mProviderFlags(aOther.mProviderFlags), mProviderTlsFlags(aOther.mProviderTlsFlags), mClientCert(std::move(aOther.mClientCert)) {} const nsACString& ClientAuthInfo::HostName() const { return mHostName; } const OriginAttributes& ClientAuthInfo::OriginAttributesRef() const { return mOriginAttributes; } int32_t ClientAuthInfo::Port() const { return mPort; } uint32_t ClientAuthInfo::ProviderFlags() const { return mProviderFlags; } uint32_t ClientAuthInfo::ProviderTlsFlags() const { return mProviderTlsFlags; } already_AddRefed ClientAuthInfo::GetClientCert() const { nsCOMPtr cert = mClientCert; return cert.forget(); } class ClientAuthDataRunnable : public SyncRunnableBase { public: ClientAuthDataRunnable(ClientAuthInfo&& info, const UniqueCERTCertificate& serverCert, nsTArray>&& collectedCANames) : mInfo(std::move(info)), mServerCert(serverCert.get()), mCollectedCANames(std::move(collectedCANames)), mSelectedCertificate(nullptr), mSelectedKey(nullptr) {} virtual mozilla::pkix::Result BuildChainForCertificate( CERTCertificate* cert, UniqueCERTCertList& builtChain); // Take the selected certificate. Will be null if none was selected or if an // error prevented selecting one. UniqueCERTCertificate TakeSelectedCertificate() { return std::move(mSelectedCertificate); } // Take the private key for the selected certificate. Will be null if no // certificate was selected or an error prevented selecting one or getting // the corresponding key. UniqueSECKEYPrivateKey TakeSelectedKey() { return std::move(mSelectedKey); } protected: virtual void RunOnTargetThread() override; ClientAuthInfo mInfo; CERTCertificate* const mServerCert; nsTArray> mCollectedCANames; nsTArray> mEnterpriseCertificates; UniqueCERTCertificate mSelectedCertificate; UniqueSECKEYPrivateKey mSelectedKey; }; class RemoteClientAuthDataRunnable : public ClientAuthDataRunnable { public: RemoteClientAuthDataRunnable(ClientAuthInfo&& info, const UniqueCERTCertificate& serverCert, nsTArray>&& collectedCANames) : ClientAuthDataRunnable(std::move(info), serverCert, std::move(collectedCANames)) {} virtual mozilla::pkix::Result BuildChainForCertificate( CERTCertificate* cert, UniqueCERTCertList& builtChain) override; protected: virtual void RunOnTargetThread() override; CopyableTArray mBuiltChain; }; nsTArray> CollectCANames(CERTDistNames* caNames) { MOZ_ASSERT(caNames); nsTArray> collectedCANames; if (!caNames) { return collectedCANames; } for (int i = 0; i < caNames->nnames; i++) { nsTArray caName; caName.AppendElements(caNames->names[i].data, caNames->names[i].len); collectedCANames.AppendElement(std::move(caName)); } return collectedCANames; } // 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 to use as a hint for selecting potential client // certificates (may be empty) // - 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) { if (!socket || !caNames || !pRetCert || !pRetKey) { PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0); return SECFailure; } *pRetCert = nullptr; *pRetKey = nullptr; Telemetry::ScalarAdd(Telemetry::ScalarID::SECURITY_CLIENT_CERT, u"requested"_ns, 1); RefPtr info( BitwiseCast(socket->higher->secret)); UniqueCERTCertificate serverCert(SSL_PeerCertificate(socket)); if (!serverCert) { MOZ_ASSERT_UNREACHABLE( "Missing server cert should have been detected during server cert " "auth."); PR_SetError(SSL_ERROR_NO_CERTIFICATE, 0); return SECFailure; } if (info->GetDenyClientCert()) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("[%p] Not returning client cert due to denyClientCert attribute\n", socket)); return SECSuccess; } 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)); return SECSuccess; } nsCOMPtr socketClientCert; info->GetClientCert(getter_AddRefs(socketClientCert)); ClientAuthInfo authInfo(info->GetHostName(), info->GetOriginAttributes(), info->GetPort(), info->GetProviderFlags(), info->GetProviderTlsFlags(), socketClientCert); nsTArray> collectedCANames(CollectCANames(caNames)); UniqueCERTCertificate selectedCertificate; UniqueSECKEYPrivateKey selectedKey; UniqueCERTCertList builtChain; SECStatus status = DoGetClientAuthData( std::move(authInfo), serverCert, std::move(collectedCANames), selectedCertificate, selectedKey, builtChain); if (status != SECSuccess) { return status; } if (selectedCertificate && selectedKey) { if (builtChain) { info->SetClientCertChain(std::move(builtChain)); } else { MOZ_LOG( gPIPNSSLog, LogLevel::Debug, ("[%p] couldn't determine chain for selected client cert", socket)); } *pRetCert = selectedCertificate.release(); *pRetKey = selectedKey.release(); // Make joinConnection prohibit joining after we've sent a client cert info->SetSentClientCert(); Telemetry::ScalarAdd(Telemetry::ScalarID::SECURITY_CLIENT_CERT, u"sent"_ns, 1); if (info->GetSSLVersionUsed() == nsISSLSocketControl::TLS_VERSION_1_3) { Telemetry::Accumulate(Telemetry::TLS_1_3_CLIENT_AUTH_USES_PHA, info->IsHandshakeCompleted()); } } return SECSuccess; } SECStatus DoGetClientAuthData(ClientAuthInfo&& info, const UniqueCERTCertificate& serverCert, nsTArray>&& collectedCANames, UniqueCERTCertificate& outCert, UniqueSECKEYPrivateKey& outKey, UniqueCERTCertList& outBuiltChain) { // XXX: This should be done asynchronously; see bug 696976 RefPtr runnable = XRE_IsSocketProcess() ? new RemoteClientAuthDataRunnable(std::move(info), serverCert, std::move(collectedCANames)) : new ClientAuthDataRunnable(std::move(info), serverCert, std::move(collectedCANames)); nsresult rv = runnable->DispatchToMainThreadAndWait(); if (NS_FAILED(rv)) { PR_SetError(SEC_ERROR_NO_MEMORY, 0); return SECFailure; } outCert = runnable->TakeSelectedCertificate(); outKey = runnable->TakeSelectedKey(); if (outCert && outKey) { mozilla::pkix::Result result = runnable->BuildChainForCertificate(outCert.get(), outBuiltChain); if (result != Success) { outBuiltChain.reset(nullptr); } } return SECSuccess; } // This TrustDomain only exists to facilitate the mozilla::pkix path building // algorithm. It considers any certificate with an issuer distinguished name in // the set of given CA names to be a trust anchor. It does essentially no // validation or verification (in particular, the signature checking function // always returns "Success"). class ClientAuthCertNonverifyingTrustDomain final : public TrustDomain { public: ClientAuthCertNonverifyingTrustDomain( nsTArray>& collectedCANames, nsTArray>& thirdPartyCertificates) : mCollectedCANames(collectedCANames), mCertStorage(do_GetService(NS_CERT_STORAGE_CID)), mThirdPartyCertificates(thirdPartyCertificates) {} virtual mozilla::pkix::Result GetCertTrust( EndEntityOrCA endEntityOrCA, const CertPolicyId& policy, Input candidateCertDER, /*out*/ TrustLevel& trustLevel) override; virtual mozilla::pkix::Result FindIssuer(Input encodedIssuerName, IssuerChecker& checker, Time time) override; virtual mozilla::pkix::Result CheckRevocation( EndEntityOrCA endEntityOrCA, const CertID& certID, Time time, Duration validityDuration, /*optional*/ const Input* stapledOCSPresponse, /*optional*/ const Input* aiaExtension, /*optional*/ const Input* sctExtension) override { return Success; } virtual mozilla::pkix::Result IsChainValid( const DERArray& certChain, Time time, const CertPolicyId& requiredPolicy) override; virtual mozilla::pkix::Result CheckSignatureDigestAlgorithm( DigestAlgorithm digestAlg, EndEntityOrCA endEntityOrCA, Time notBefore) override { return Success; } virtual mozilla::pkix::Result CheckRSAPublicKeyModulusSizeInBits( EndEntityOrCA endEntityOrCA, unsigned int modulusSizeInBits) override { return Success; } virtual mozilla::pkix::Result VerifyRSAPKCS1SignedDigest( const SignedDigest& signedDigest, Input subjectPublicKeyInfo) override { return Success; } virtual mozilla::pkix::Result CheckECDSACurveIsAcceptable( EndEntityOrCA endEntityOrCA, NamedCurve curve) override { return Success; } virtual mozilla::pkix::Result VerifyECDSASignedDigest( const SignedDigest& signedDigest, Input subjectPublicKeyInfo) override { return Success; } virtual mozilla::pkix::Result CheckValidityIsAcceptable( Time notBefore, Time notAfter, EndEntityOrCA endEntityOrCA, KeyPurposeId keyPurpose) override { return Success; } virtual mozilla::pkix::Result NetscapeStepUpMatchesServerAuth( Time notBefore, /*out*/ bool& matches) override { matches = true; return Success; } virtual void NoteAuxiliaryExtension(AuxiliaryExtension extension, Input extensionData) override {} virtual mozilla::pkix::Result DigestBuf(Input item, DigestAlgorithm digestAlg, /*out*/ uint8_t* digestBuf, size_t digestBufLen) override { return DigestBufNSS(item, digestAlg, digestBuf, digestBufLen); } UniqueCERTCertList TakeBuiltChain() { return std::move(mBuiltChain); } private: nsTArray>& mCollectedCANames; // non-owning nsCOMPtr mCertStorage; nsTArray>& mThirdPartyCertificates; // non-owning UniqueCERTCertList mBuiltChain; }; mozilla::pkix::Result ClientAuthCertNonverifyingTrustDomain::GetCertTrust( EndEntityOrCA endEntityOrCA, const CertPolicyId& policy, Input candidateCertDER, /*out*/ TrustLevel& trustLevel) { // If the server did not specify any CA names, all client certificates are // acceptable. if (mCollectedCANames.Length() == 0) { trustLevel = TrustLevel::TrustAnchor; return Success; } BackCert cert(candidateCertDER, endEntityOrCA, nullptr); mozilla::pkix::Result rv = cert.Init(); if (rv != Success) { return rv; } // If this certificate's issuer distinguished name is in the set of acceptable // CA names, we say this is a trust anchor so that the client certificate // issued from this certificate will be presented as an option for the user. // We also check the certificate's subject distinguished name to account for // the case where client certificates that have the id-kp-OCSPSigning EKU // can't be trust anchors according to mozilla::pkix, and thus we may be // looking directly at the issuer. Input issuer(cert.GetIssuer()); Input subject(cert.GetSubject()); for (const auto& caName : mCollectedCANames) { Input caNameInput; rv = caNameInput.Init(caName.Elements(), caName.Length()); if (rv != Success) { continue; // probably too big } if (InputsAreEqual(issuer, caNameInput) || InputsAreEqual(subject, caNameInput)) { trustLevel = TrustLevel::TrustAnchor; return Success; } } trustLevel = TrustLevel::InheritsTrust; return Success; } // In theory this implementation should only need to consider intermediate // certificates, since in theory it should only need to look at the issuer // distinguished name of each certificate to determine if the client // certificate is considered acceptable to the server. // However, because we need to account for client certificates with the // id-kp-OCSPSigning EKU, and because mozilla::pkix doesn't allow such // certificates to be trust anchors, we need to consider the issuers of such // certificates directly. These issuers could be roots, so we have to consider // roots here. mozilla::pkix::Result ClientAuthCertNonverifyingTrustDomain::FindIssuer( Input encodedIssuerName, IssuerChecker& checker, Time time) { // First try all relevant certificates known to Gecko, which avoids calling // CERT_CreateSubjectCertList, because that can be expensive. Vector geckoCandidates; if (!mCertStorage) { return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE; } nsTArray subject; subject.AppendElements(encodedIssuerName.UnsafeGetData(), encodedIssuerName.GetLength()); nsTArray> certs; nsresult rv = mCertStorage->FindCertsBySubject(subject, certs); if (NS_FAILED(rv)) { return mozilla::pkix::Result::FATAL_ERROR_LIBRARY_FAILURE; } for (auto& cert : certs) { Input certDER; mozilla::pkix::Result rv = certDER.Init(cert.Elements(), cert.Length()); if (rv != Success) { continue; // probably too big } if (!geckoCandidates.append(certDER)) { return mozilla::pkix::Result::FATAL_ERROR_NO_MEMORY; } } for (const auto& thirdPartyCertificate : mThirdPartyCertificates) { Input thirdPartyCertificateInput; mozilla::pkix::Result rv = thirdPartyCertificateInput.Init( thirdPartyCertificate.Elements(), thirdPartyCertificate.Length()); if (rv != Success) { continue; // probably too big } if (!geckoCandidates.append(thirdPartyCertificateInput)) { return mozilla::pkix::Result::FATAL_ERROR_NO_MEMORY; } } bool keepGoing = true; for (Input candidate : geckoCandidates) { mozilla::pkix::Result rv = checker.Check(candidate, nullptr, keepGoing); if (rv != Success) { return rv; } if (!keepGoing) { return Success; } } SECItem encodedIssuerNameItem = UnsafeMapInputToSECItem(encodedIssuerName); // NSS seems not to differentiate between "no potential issuers found" and // "there was an error trying to retrieve the potential issuers." We assume // there was no error if CERT_CreateSubjectCertList returns nullptr. UniqueCERTCertList candidates(CERT_CreateSubjectCertList( nullptr, CERT_GetDefaultCertDB(), &encodedIssuerNameItem, 0, false)); Vector nssCandidates; if (candidates) { for (CERTCertListNode* n = CERT_LIST_HEAD(candidates); !CERT_LIST_END(n, candidates); n = CERT_LIST_NEXT(n)) { Input certDER; mozilla::pkix::Result rv = certDER.Init(n->cert->derCert.data, n->cert->derCert.len); if (rv != Success) { continue; // probably too big } if (!nssCandidates.append(certDER)) { return mozilla::pkix::Result::FATAL_ERROR_NO_MEMORY; } } } for (Input candidate : nssCandidates) { mozilla::pkix::Result rv = checker.Check(candidate, nullptr, keepGoing); if (rv != Success) { return rv; } if (!keepGoing) { return Success; } } return Success; } mozilla::pkix::Result ClientAuthCertNonverifyingTrustDomain::IsChainValid( const DERArray& certChain, Time, const CertPolicyId&) { if (ConstructCERTCertListFromReversedDERArray(certChain, mBuiltChain) != SECSuccess) { return MapPRErrorCodeToResult(PR_GetError()); } return Success; } mozilla::pkix::Result ClientAuthDataRunnable::BuildChainForCertificate( CERTCertificate* cert, UniqueCERTCertList& builtChain) { ClientAuthCertNonverifyingTrustDomain trustDomain(mCollectedCANames, mEnterpriseCertificates); Input certDER; mozilla::pkix::Result result = certDER.Init(cert->derCert.data, cert->derCert.len); if (result != Success) { return result; } // Client certificates shouldn't be CAs, but for interoperability reasons we // attempt to build a path with each certificate as an end entity and then as // a CA if that fails. const EndEntityOrCA kEndEntityOrCAParams[] = {EndEntityOrCA::MustBeEndEntity, EndEntityOrCA::MustBeCA}; // mozilla::pkix rejects certificates with id-kp-OCSPSigning unless it is // specifically required. A client certificate should never have this EKU. // Unfortunately, there are some client certificates in private PKIs that // have this EKU. For interoperability, we attempt to work around this // restriction in mozilla::pkix by first building the certificate chain with // no particular EKU required and then again with id-kp-OCSPSigning required // if that fails. const KeyPurposeId kKeyPurposeIdParams[] = {KeyPurposeId::anyExtendedKeyUsage, KeyPurposeId::id_kp_OCSPSigning}; for (const auto& endEntityOrCAParam : kEndEntityOrCAParams) { for (const auto& keyPurposeIdParam : kKeyPurposeIdParams) { mozilla::pkix::Result result = BuildCertChain(trustDomain, certDER, Now(), endEntityOrCAParam, KeyUsage::noParticularKeyUsageRequired, keyPurposeIdParam, CertPolicyId::anyPolicy, nullptr); if (result == Success) { builtChain = trustDomain.TakeBuiltChain(); return Success; } MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("client cert non-validation returned %d for '%s'", static_cast(result), cert->subjectName)); } } return mozilla::pkix::Result::ERROR_UNKNOWN_ISSUER; } 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()); nsCOMPtr component(do_GetService(PSM_COMPONENT_CONTRACTID)); if (NS_WARN_IF(!component)) { return; } nsresult rv = component->GetEnterpriseIntermediates(mEnterpriseCertificates); if (NS_WARN_IF(NS_FAILED(rv))) { return; } nsTArray> enterpriseRoots; rv = component->GetEnterpriseRoots(enterpriseRoots); if (NS_WARN_IF(NS_FAILED(rv))) { return; } mEnterpriseCertificates.AppendElements(std::move(enterpriseRoots)); if (NS_WARN_IF(NS_FAILED(CheckForSmartCardChanges()))) { return; } // 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. nsCOMPtr socketClientCert = mInfo.GetClientCert(); if (socketClientCert) { mSelectedCertificate.reset(socketClientCert->GetCert()); if (NS_WARN_IF(!mSelectedCertificate)) { return; } mSelectedKey.reset( PK11_FindKeyByAnyCert(mSelectedCertificate.get(), nullptr)); return; } UniqueCERTCertList certList(FindClientCertificatesWithPrivateKeys()); if (!certList) { return; } CERTCertListNode* n = CERT_LIST_HEAD(certList); while (!CERT_LIST_END(n, certList)) { UniqueCERTCertList unusedBuiltChain; mozilla::pkix::Result result = BuildChainForCertificate(n->cert, unusedBuiltChain); if (result != Success) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("removing cert '%s'", n->cert->subjectName)); CERTCertListNode* toRemove = n; n = CERT_LIST_NEXT(n); CERT_RemoveCertListNode(toRemove); continue; } MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("keeping cert '%s'\n", n->cert->subjectName)); n = CERT_LIST_NEXT(n); } if (CERT_LIST_EMPTY(certList)) { MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("no client certificates available after filtering by CA")); return; } // find valid user cert and key pair if (nsGetUserCertChoice() == UserCertChoice::Auto) { // automatically find the right cert 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)) { UniqueSECKEYPrivateKey tmpKey(PK11_FindKeyByAnyCert(node->cert, nullptr)); if (tmpKey) { if (hasExplicitKeyUsageNonRepudiation(node->cert)) { // 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 mSelectedCertificate.reset(CERT_DupCertificate(node->cert)); mSelectedKey = std::move(tmpKey); return; } } if (PR_GetError() == SEC_ERROR_BAD_PASSWORD) { // problem with password: bail break; } } if (lowPrioNonrepCert) { mSelectedCertificate = std::move(lowPrioNonrepCert); mSelectedKey.reset( PK11_FindKeyByAnyCert(mSelectedCertificate.get(), nullptr)); } return; } // Not Auto => ask // Get the SSL Certificate const nsACString& hostname = mInfo.HostName(); nsCOMPtr cars = nullptr; if (mInfo.ProviderTlsFlags() == 0) { cars = do_GetService(NS_CLIENTAUTHREMEMBERSERVICE_CONTRACTID); } if (cars) { nsCString rememberedDBKey; bool found; nsresult rv = cars->HasRememberedDecision(hostname, mInfo.OriginAttributesRef(), mServerCert, rememberedDBKey, &found); if (NS_WARN_IF(NS_FAILED(rv))) { return; } if (found) { // An empty dbKey indicates that the user chose not to use a certificate // and chose to remember this decision if (rememberedDBKey.IsEmpty()) { return; } nsCOMPtr certdb = do_GetService(NS_X509CERTDB_CONTRACTID); if (NS_WARN_IF(!certdb)) { return; } nsCOMPtr foundCert; nsresult rv = certdb->FindCertByDBKey(rememberedDBKey, getter_AddRefs(foundCert)); if (NS_WARN_IF(NS_FAILED(rv))) { return; } if (foundCert) { nsNSSCertificate* objCert = BitwiseCast(foundCert.get()); if (NS_WARN_IF(!objCert)) { return; } mSelectedCertificate.reset(objCert->GetCert()); if (NS_WARN_IF(!mSelectedCertificate)) { return; } mSelectedKey.reset( PK11_FindKeyByAnyCert(mSelectedCertificate.get(), nullptr)); return; } } } // ask the user to select a certificate nsCOMPtr dialogs; UniquePORTString corg(CERT_GetOrgName(&mServerCert->subject)); nsAutoCString org(corg.get()); UniquePORTString cissuer(CERT_GetOrgName(&mServerCert->issuer)); nsAutoCString issuer(cissuer.get()); nsCOMPtr certArray = nsArrayBase::Create(); if (NS_WARN_IF(!certArray)) { return; } for (CERTCertListNode* node = CERT_LIST_HEAD(certList); !CERT_LIST_END(node, certList); node = CERT_LIST_NEXT(node)) { nsCOMPtr tempCert = nsNSSCertificate::Create(node->cert); if (NS_WARN_IF(!tempCert)) { return; } nsresult rv = certArray->AppendElement(tempCert); if (NS_WARN_IF(NS_FAILED(rv))) { return; } } // 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_WARN_IF(NS_FAILED(rv))) { return; } uint32_t selectedIndex = 0; bool certChosen = false; // even if the user has canceled, we want to remember that, to avoid // repeating prompts bool wantRemember = false; rv = dialogs->ChooseCertificate(hostname, mInfo.Port(), org, issuer, certArray, &selectedIndex, &wantRemember, &certChosen); if (NS_WARN_IF(NS_FAILED(rv))) { return; } if (certChosen) { nsCOMPtr selectedCert = do_QueryElementAt(certArray, selectedIndex); if (NS_WARN_IF(!selectedCert)) { return; } mSelectedCertificate.reset(selectedCert->GetCert()); if (NS_WARN_IF(!mSelectedCertificate)) { return; } mSelectedKey.reset( PK11_FindKeyByAnyCert(mSelectedCertificate.get(), nullptr)); } if (cars && wantRemember) { rv = cars->RememberDecision( hostname, mInfo.OriginAttributesRef(), mServerCert, certChosen ? mSelectedCertificate.get() : nullptr); Unused << NS_WARN_IF(NS_FAILED(rv)); } } mozilla::pkix::Result RemoteClientAuthDataRunnable::BuildChainForCertificate( CERTCertificate*, UniqueCERTCertList& builtChain) { builtChain.reset(CERT_NewCertList()); if (!builtChain) { return mozilla::pkix::Result::FATAL_ERROR_NO_MEMORY; } for (auto& certBytes : mBuiltChain) { SECItem certDER = {siBuffer, certBytes.data().Elements(), static_cast(certBytes.data().Length())}; UniqueCERTCertificate cert(CERT_NewTempCertificate( CERT_GetDefaultCertDB(), &certDER, nullptr, false, true)); if (!cert) { return mozilla::pkix::Result::ERROR_BAD_DER; } if (CERT_AddCertToListTail(builtChain.get(), cert.get()) != SECSuccess) { return mozilla::pkix::Result::FATAL_ERROR_NO_MEMORY; } Unused << cert.release(); } return Success; } void RemoteClientAuthDataRunnable::RunOnTargetThread() { MOZ_ASSERT(NS_IsMainThread()); const ByteArray serverCertSerialized = CopyableTArray{ mServerCert->derCert.data, mServerCert->derCert.len}; // Note that client cert is NULL in socket process until bug 1632809 is done. Maybe clientCertSerialized; nsCOMPtr socketClientCert = mInfo.GetClientCert(); if (socketClientCert) { nsTArray certBytes; if (NS_FAILED(socketClientCert->GetRawDER(certBytes))) { return; } clientCertSerialized.emplace(std::move(certBytes)); } nsTArray collectedCANames; for (auto& name : mCollectedCANames) { collectedCANames.AppendElement(std::move(name)); } bool succeeded = false; ByteArray cert; ByteArray key; mozilla::net::SocketProcessChild::GetSingleton()->SendGetTLSClientCert( nsCString(mInfo.HostName()), mInfo.OriginAttributesRef(), mInfo.Port(), mInfo.ProviderFlags(), mInfo.ProviderTlsFlags(), serverCertSerialized, clientCertSerialized, collectedCANames, &succeeded, &cert, &key, &mBuiltChain); if (!succeeded) { return; } DeserializeClientCertAndKey(cert, key, mSelectedCertificate, mSelectedKey); } static PRFileDesc* nsSSLIOLayerImportFD(PRFileDesc* fd, nsNSSSocketInfo* infoObject, const char* host, bool haveHTTPSProxy) { PRFileDesc* sslSock = SSL_ImportFD(nullptr, fd); if (!sslSock) { MOZ_ASSERT_UNREACHABLE("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); // Provide the client cert to HTTPS proxy no matter if it is anonymous. if (flags & nsISocketProvider::ANONYMOUS_CONNECT && !haveHTTPSProxy) { SSL_GetClientAuthDataHook(sslSock, nullptr, infoObject); } else { SSL_GetClientAuthDataHook( sslSock, (SSLGetClientAuthData)nsNSS_SSLGetClientAuthData, infoObject); } if (SECSuccess != SSL_AuthCertificateHook(sslSock, AuthCertificateHook, infoObject)) { MOZ_ASSERT_UNREACHABLE("Failed to configure AuthCertificateHook"); goto loser; } if (SECSuccess != SSL_SetURL(sslSock, host)) { MOZ_ASSERT_UNREACHABLE("SSL_SetURL failed"); goto loser; } return sslSock; loser: if (sslSock) { PR_Close(sslSock); } return nullptr; } // Please change getSignatureName in nsNSSCallbacks.cpp when changing the list // here. See NOTE at SSL_SignatureSchemePrefSet call site. 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) { 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; } // Set TLS 1.3 compat mode. if (SECSuccess != SSL_OptionSet(fd, SSL_ENABLE_TLS13_COMPAT_MODE, PR_TRUE)) { MOZ_LOG(gPIPNSSLog, LogLevel::Error, ("[%p] nsSSLIOLayerSetOptions: Setting compat mode failed\n", fd)); } // setting TLS max version uint32_t versionFlags = getTLSProviderFlagMaxVersion(infoObject->GetProviderTlsFlags()); if (versionFlags) { MOZ_LOG( gPIPNSSLog, LogLevel::Debug, ("[%p] nsSSLIOLayerSetOptions: version flags %d\n", fd, versionFlags)); if (versionFlags == kTLSProviderFlagMaxVersion10) { range.max = SSL_LIBRARY_VERSION_TLS_1_0; } else if (versionFlags == kTLSProviderFlagMaxVersion11) { range.max = SSL_LIBRARY_VERSION_TLS_1_1; } else if (versionFlags == kTLSProviderFlagMaxVersion12) { range.max = SSL_LIBRARY_VERSION_TLS_1_2; } else if (versionFlags == kTLSProviderFlagMaxVersion13) { range.max = SSL_LIBRARY_VERSION_TLS_1_3; } else { MOZ_LOG(gPIPNSSLog, LogLevel::Error, ("[%p] nsSSLIOLayerSetOptions: unknown version flags %d\n", fd, versionFlags)); } } 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; infoObject->SharedState().IOLayerHelpers().adjustForTLSIntolerance( infoObject->GetHostName(), infoObject->GetPort(), range); MOZ_LOG( gPIPNSSLog, LogLevel::Debug, ("[%p] nsSSLIOLayerSetOptions: using TLS version range (0x%04x,0x%04x)\n", fd, static_cast(range.min), static_cast(range.max))); // If the user has set their minimum version to something higher than what // we've now set the maximum to, this will result in an inconsistent version // range unless we fix it up. This will override their preference, but we only // do this for sites critical to the operation of the browser (e.g. update // servers) and telemetry experiments. if (range.min > range.max) { range.min = range.max; } if (SSL_VersionRangeSet(fd, &range) != SECSuccess) { return NS_ERROR_FAILURE; } infoObject->SetTLSVersionRange(range); // 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. // Please change getKeaGroupName in nsNSSCallbacks.cpp when changing the list // here. 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; } // NOTE: Should this list ever include ssl_sig_rsa_pss_pss_sha* (or should // it become possible to enable this scheme via a pref), it is required // to test that a Delegated Credential containing a small-modulus RSA-PSS SPKI // is properly rejected. NSS will not advertise PKCS1 or RSAE schemes (which // the |ssl_sig_rsa_pss_*| defines alias, meaning we will not currently accept // any RSA DC. 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; } #ifdef __arm__ unsigned int enabledCiphers = 0; std::vector ciphers(SSL_GetNumImplementedCiphers()); // Returns only the enabled (reflecting prefs) ciphers, ordered // by their occurence in // https://hg.mozilla.org/projects/nss/file/a75ea4cdacd95282c6c245ebb849c25e84ccd908/lib/ssl/ssl3con.c#l87 if (SSL_CipherSuiteOrderGet(fd, ciphers.data(), &enabledCiphers) != SECSuccess) { return NS_ERROR_FAILURE; } // On ARM, prefer (TLS_CHACHA20_POLY1305_SHA256) over AES. However, // it may be disabled. If enabled, it will either be element [0] or [1]*. // If [0], we're done. If [1], swap it with [0] (TLS_AES_128_GCM_SHA256). // * (assuming the compile-time order remains unchanged) if (enabledCiphers > 1) { if (ciphers[0] != TLS_CHACHA20_POLY1305_SHA256 && ciphers[1] == TLS_CHACHA20_POLY1305_SHA256) { std::swap(ciphers[0], ciphers[1]); if (SSL_CipherSuiteOrderSet(fd, ciphers.data(), enabledCiphers) != SECSuccess) { return NS_ERROR_FAILURE; } } } #endif // Set the Peer ID so that SSL proxy connections work properly and to // separate anonymous and/or private browsing connections. nsAutoCString peerId; infoObject->GetPeerId(peerId); if (SECSuccess != SSL_SetSockPeerID(fd, peerId.get())) { return NS_ERROR_FAILURE; } uint32_t flags = infoObject->GetProviderFlags(); if (flags & nsISocketProvider::NO_PERMANENT_STORAGE) { if (SECSuccess != SSL_OptionSet(fd, SSL_ENABLE_SESSION_TICKETS, false) || SECSuccess != SSL_OptionSet(fd, SSL_NO_CACHE, true)) { return NS_ERROR_FAILURE; } } return NS_OK; } SECStatus StoreResumptionToken(PRFileDesc* fd, const PRUint8* resumptionToken, unsigned int len, void* ctx) { PRIntn val; if (SSL_OptionGet(fd, SSL_ENABLE_SESSION_TICKETS, &val) != SECSuccess || val == 0) { return SECFailure; } nsNSSSocketInfo* infoObject = (nsNSSSocketInfo*)ctx; if (!infoObject) { return SECFailure; } nsAutoCString peerId; infoObject->GetPeerId(peerId); if (NS_FAILED( net::SSLTokensCache::Put(peerId, resumptionToken, len, infoObject))) { return SECFailure; } return SECSuccess; } nsresult nsSSLIOLayerAddToSocket(int32_t family, const char* host, int32_t port, nsIProxyInfo* proxy, const OriginAttributes& originAttributes, PRFileDesc* fd, nsISupports** info, bool forSTARTTLS, uint32_t providerFlags, uint32_t providerTlsFlags) { PRFileDesc* layer = nullptr; PRFileDesc* plaintextLayer = nullptr; nsresult rv; PRStatus stat; SharedSSLState* sharedState = nullptr; RefPtr allocatedState; if (providerTlsFlags) { allocatedState = new SharedSSLState(providerTlsFlags); sharedState = allocatedState.get(); } else { bool isPrivate = providerFlags & nsISocketProvider::NO_PERMANENT_STORAGE || originAttributes.mPrivateBrowsingId != OriginAttributes().mPrivateBrowsingId; sharedState = isPrivate ? PrivateSSLState() : PublicSSLState(); } nsNSSSocketInfo* infoObject = new nsNSSSocketInfo(*sharedState, providerFlags, providerTlsFlags); if (!infoObject) return NS_ERROR_FAILURE; NS_ADDREF(infoObject); infoObject->SetForSTARTTLS(forSTARTTLS); infoObject->SetHostName(host); infoObject->SetPort(port); infoObject->SetOriginAttributes(originAttributes); if (allocatedState) { infoObject->SetSharedOwningReference(allocatedState); } bool haveProxy = false; bool haveHTTPSProxy = false; if (proxy) { nsAutoCString proxyHost; proxy->GetHost(proxyHost); haveProxy = !proxyHost.IsEmpty(); nsAutoCString type; haveHTTPSProxy = haveProxy && NS_SUCCEEDED(proxy->GetType(type)) && type.EqualsLiteral("https"); } // 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, haveHTTPSProxy); if (!sslSock) { MOZ_ASSERT_UNREACHABLE("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(); if (StaticPrefs::network_ssl_tokens_cache_enabled()) { rv = infoObject->SetResumptionTokenFromExternalCache(); if (NS_FAILED(rv)) { return rv; } SSL_SetResumptionTokenCallback(sslSock, &StoreResumptionToken, infoObject); } return NS_OK; loser: NS_IF_RELEASE(infoObject); if (layer) { layer->dtor(layer); } if (plaintextLayer) { // Note that PR_*IOLayer operations may modify the stack of fds, so a // previously-valid pointer may no longer point to what we think it points // to after calling PR_PopIOLayer. We must operate on the pointer returned // by PR_PopIOLayer. plaintextLayer = PR_PopIOLayer(fd, nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity); plaintextLayer->dtor(plaintextLayer); } return NS_ERROR_FAILURE; }