2542 lines
76 KiB
C++
2542 lines
76 KiB
C++
|
/* -*- 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 "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;
|
||
|
|
||
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
||
|
("[%p] nsNSSSocketInfo::NoteTimeUntilReady\n", mFd));
|
||
|
}
|
||
|
|
||
|
void
|
||
|
nsNSSSocketInfo::SetHandshakeCompleted()
|
||
|
{
|
||
|
// 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);
|
||
|
}
|
||
|
|
||
|
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 {
|
||
|
|
||
|
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.
|
||
|
|
||
|
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)) {
|
||
|
return true;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
// 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;
|
||
|
}
|
||
|
|
||
|
if (!helpers.rememberIntolerantAtVersion(socketInfo->GetHostName(),
|
||
|
socketInfo->GetPort(),
|
||
|
range.min, range.max, err)) {
|
||
|
return false;
|
||
|
}
|
||
|
|
||
|
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");
|
||
|
|
||
|
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) {
|
||
|
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;
|
||
|
int n;
|
||
|
char* namestring;
|
||
|
|
||
|
for (n = 0; n < caNames->nnames; n++) {
|
||
|
dername = &caNames->names[n];
|
||
|
|
||
|
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]) {
|
||
|
return SECFailure;
|
||
|
}
|
||
|
}
|
||
|
}
|
||
|
|
||
|
return SECSuccess;
|
||
|
}
|
||
|
|
||
|
// 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;
|
||
|
}
|
||
|
|
||
|
// Please change getSignatureName in nsNSSCallbacks.cpp when changing the list
|
||
|
// here.
|
||
|
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.
|
||
|
// 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;
|
||
|
}
|
||
|
|
||
|
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;
|
||
|
}
|