зеркало из https://github.com/mozilla/gecko-dev.git
2648 строки
80 KiB
C++
2648 строки
80 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 "mozilla/Telemetry.h"
|
|
#include "nsArrayUtils.h"
|
|
#include "nsCharSeparatedTokenizer.h"
|
|
#include "nsClientAuthRemember.h"
|
|
#include "nsContentUtils.h"
|
|
#include "nsIClientAuthDialogs.h"
|
|
#include "nsIConsoleService.h"
|
|
#include "nsIPrefService.h"
|
|
#include "nsISocketProvider.h"
|
|
#include "nsIWebProgressListener.h"
|
|
#include "nsNSSCertHelper.h"
|
|
#include "nsNSSComponent.h"
|
|
#include "nsPrintfCString.h"
|
|
#include "nsServiceManagerUtils.h"
|
|
#include "pkix/pkixtypes.h"
|
|
#include "prmem.h"
|
|
#include "prnetdb.h"
|
|
#include "secder.h"
|
|
#include "secerr.h"
|
|
#include "ssl.h"
|
|
#include "sslerr.h"
|
|
#include "sslproto.h"
|
|
|
|
using namespace mozilla;
|
|
using namespace mozilla::psm;
|
|
|
|
//#define DEBUG_SSL_VERBOSE //Enable this define to get minimal
|
|
//reports when doing SSL read/write
|
|
|
|
//#define DUMP_BUFFER //Enable this define along with
|
|
//DEBUG_SSL_VERBOSE to dump SSL
|
|
//read/write buffer to a log.
|
|
//Uses PR_LOG except on Mac where
|
|
//we always write out to our own
|
|
//file.
|
|
|
|
namespace {
|
|
|
|
#define MAX_ALPN_LENGTH 255
|
|
|
|
void
|
|
getSiteKey(const nsACString& hostName, uint16_t port,
|
|
/*out*/ nsCSubstring& key)
|
|
{
|
|
key = hostName;
|
|
key.AppendASCII(":");
|
|
key.AppendInt(port);
|
|
}
|
|
|
|
// Historically, we have required that the server negotiate ALPN or NPN in
|
|
// order to false start, as a compatibility hack to work around
|
|
// implementations that just stop responding during false start. However, now
|
|
// false start is resricted to modern crypto (TLS 1.2 and AEAD cipher suites)
|
|
// so it is less likely that requring NPN or ALPN is still necessary.
|
|
static const bool FALSE_START_REQUIRE_NPN_DEFAULT = false;
|
|
|
|
} // unnamed namespace
|
|
|
|
extern LazyLogModule gPIPNSSLog;
|
|
|
|
nsNSSSocketInfo::nsNSSSocketInfo(SharedSSLState& aState, uint32_t providerFlags)
|
|
: mFd(nullptr),
|
|
mCertVerificationState(before_cert_verification),
|
|
mSharedState(aState),
|
|
mForSTARTTLS(false),
|
|
mHandshakePending(true),
|
|
mRememberClientAuthCertificate(false),
|
|
mPreliminaryHandshakeDone(false),
|
|
mNPNCompleted(false),
|
|
mEarlyDataAccepted(false),
|
|
mFalseStartCallbackCalled(false),
|
|
mFalseStarted(false),
|
|
mIsFullHandshake(false),
|
|
mHandshakeCompleted(false),
|
|
mJoined(false),
|
|
mSentClientCert(false),
|
|
mNotedTimeUntilReady(false),
|
|
mFailedVerification(false),
|
|
mKEAUsed(nsISSLSocketControl::KEY_EXCHANGE_UNKNOWN),
|
|
mKEAKeyBits(0),
|
|
mSSLVersionUsed(nsISSLSocketControl::SSL_VERSION_UNKNOWN),
|
|
mMACAlgorithmUsed(nsISSLSocketControl::SSL_MAC_UNKNOWN),
|
|
mBypassAuthentication(false),
|
|
mProviderFlags(providerFlags),
|
|
mSocketCreationTimestamp(TimeStamp::Now()),
|
|
mPlaintextBytesRead(0),
|
|
mClientCert(nullptr)
|
|
{
|
|
mTLSVersionRange.min = 0;
|
|
mTLSVersionRange.max = 0;
|
|
}
|
|
|
|
nsNSSSocketInfo::~nsNSSSocketInfo()
|
|
{
|
|
}
|
|
|
|
NS_IMPL_ISUPPORTS_INHERITED(nsNSSSocketInfo, TransportSecurityInfo,
|
|
nsISSLSocketControl,
|
|
nsIClientAuthUserDecision)
|
|
|
|
NS_IMETHODIMP
|
|
nsNSSSocketInfo::GetProviderFlags(uint32_t* aProviderFlags)
|
|
{
|
|
*aProviderFlags = mProviderFlags;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNSSSocketInfo::GetKEAUsed(int16_t* aKea)
|
|
{
|
|
*aKea = mKEAUsed;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNSSSocketInfo::GetKEAKeyBits(uint32_t* aKeyBits)
|
|
{
|
|
*aKeyBits = mKEAKeyBits;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNSSSocketInfo::GetSSLVersionUsed(int16_t* aSSLVersionUsed)
|
|
{
|
|
*aSSLVersionUsed = mSSLVersionUsed;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNSSSocketInfo::GetSSLVersionOffered(int16_t* aSSLVersionOffered)
|
|
{
|
|
*aSSLVersionOffered = mTLSVersionRange.max;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNSSSocketInfo::GetMACAlgorithmUsed(int16_t* aMac)
|
|
{
|
|
*aMac = mMACAlgorithmUsed;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNSSSocketInfo::GetClientCert(nsIX509Cert** aClientCert)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aClientCert);
|
|
*aClientCert = mClientCert;
|
|
NS_IF_ADDREF(*aClientCert);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNSSSocketInfo::SetClientCert(nsIX509Cert* aClientCert)
|
|
{
|
|
mClientCert = aClientCert;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNSSSocketInfo::GetBypassAuthentication(bool* arg)
|
|
{
|
|
*arg = mBypassAuthentication;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNSSSocketInfo::GetFailedVerification(bool* arg)
|
|
{
|
|
*arg = mFailedVerification;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNSSSocketInfo::GetRememberClientAuthCertificate(bool* aRemember)
|
|
{
|
|
NS_ENSURE_ARG_POINTER(aRemember);
|
|
*aRemember = mRememberClientAuthCertificate;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNSSSocketInfo::SetRememberClientAuthCertificate(bool aRemember)
|
|
{
|
|
mRememberClientAuthCertificate = aRemember;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNSSSocketInfo::GetNotificationCallbacks(nsIInterfaceRequestor** aCallbacks)
|
|
{
|
|
*aCallbacks = mCallbacks;
|
|
NS_IF_ADDREF(*aCallbacks);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNSSSocketInfo::SetNotificationCallbacks(nsIInterfaceRequestor* aCallbacks)
|
|
{
|
|
if (!aCallbacks) {
|
|
mCallbacks = nullptr;
|
|
return NS_OK;
|
|
}
|
|
|
|
mCallbacks = aCallbacks;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
nsNSSSocketInfo::NoteTimeUntilReady()
|
|
{
|
|
if (mNotedTimeUntilReady)
|
|
return;
|
|
|
|
mNotedTimeUntilReady = true;
|
|
|
|
// This will include TCP and proxy tunnel wait time
|
|
Telemetry::AccumulateTimeDelta(Telemetry::SSL_TIME_UNTIL_READY,
|
|
mSocketCreationTimestamp, TimeStamp::Now());
|
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
|
("[%p] nsNSSSocketInfo::NoteTimeUntilReady\n", mFd));
|
|
}
|
|
|
|
void
|
|
nsNSSSocketInfo::SetHandshakeCompleted()
|
|
{
|
|
if (!mHandshakeCompleted) {
|
|
enum HandshakeType {
|
|
Resumption = 1,
|
|
FalseStarted = 2,
|
|
ChoseNotToFalseStart = 3,
|
|
NotAllowedToFalseStart = 4,
|
|
};
|
|
|
|
HandshakeType handshakeType = !IsFullHandshake() ? Resumption
|
|
: mFalseStarted ? FalseStarted
|
|
: mFalseStartCallbackCalled ? ChoseNotToFalseStart
|
|
: NotAllowedToFalseStart;
|
|
|
|
// This will include TCP and proxy tunnel wait time
|
|
Telemetry::AccumulateTimeDelta(Telemetry::SSL_TIME_UNTIL_HANDSHAKE_FINISHED,
|
|
mSocketCreationTimestamp, TimeStamp::Now());
|
|
|
|
// If the handshake is completed for the first time from just 1 callback
|
|
// that means that TLS session resumption must have been used.
|
|
Telemetry::Accumulate(Telemetry::SSL_RESUMED_SESSION,
|
|
handshakeType == Resumption);
|
|
Telemetry::Accumulate(Telemetry::SSL_HANDSHAKE_TYPE, handshakeType);
|
|
}
|
|
|
|
|
|
// Remove the plain text layer as it is not needed anymore.
|
|
// The plain text layer is not always present - so its not a fatal error
|
|
// if it cannot be removed
|
|
PRFileDesc* poppedPlaintext =
|
|
PR_GetIdentitiesLayer(mFd, nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity);
|
|
if (poppedPlaintext) {
|
|
PR_PopIOLayer(mFd, nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity);
|
|
poppedPlaintext->dtor(poppedPlaintext);
|
|
}
|
|
|
|
mHandshakeCompleted = true;
|
|
|
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
|
("[%p] nsNSSSocketInfo::SetHandshakeCompleted\n", (void*) mFd));
|
|
|
|
mIsFullHandshake = false; // reset for next handshake on this connection
|
|
}
|
|
|
|
void
|
|
nsNSSSocketInfo::SetNegotiatedNPN(const char* value, uint32_t length)
|
|
{
|
|
if (!value) {
|
|
mNegotiatedNPN.Truncate();
|
|
} else {
|
|
mNegotiatedNPN.Assign(value, length);
|
|
}
|
|
mNPNCompleted = true;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNSSSocketInfo::GetNegotiatedNPN(nsACString& aNegotiatedNPN)
|
|
{
|
|
if (!mNPNCompleted)
|
|
return NS_ERROR_NOT_CONNECTED;
|
|
|
|
aNegotiatedNPN = mNegotiatedNPN;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNSSSocketInfo::GetAlpnEarlySelection(nsACString& aAlpnSelected)
|
|
{
|
|
nsNSSShutDownPreventionLock locker;
|
|
if (isAlreadyShutDown() || isPK11LoggedOut()) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
SSLNextProtoState alpnState;
|
|
unsigned char chosenAlpn[MAX_ALPN_LENGTH];
|
|
unsigned int chosenAlpnLen;
|
|
SECStatus rv = SSL_GetNextProto(mFd, &alpnState, chosenAlpn, &chosenAlpnLen,
|
|
AssertedCast<unsigned int>(ArrayLength(chosenAlpn)));
|
|
|
|
if (rv != SECSuccess || alpnState != SSL_NEXT_PROTO_EARLY_VALUE ||
|
|
chosenAlpnLen == 0) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
|
|
aAlpnSelected.Assign(BitwiseCast<char*, unsigned char*>(chosenAlpn),
|
|
chosenAlpnLen);
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNSSSocketInfo::GetEarlyDataAccepted(bool* aAccepted)
|
|
{
|
|
*aAccepted = mEarlyDataAccepted;
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
nsNSSSocketInfo::SetEarlyDataAccepted(bool aAccepted)
|
|
{
|
|
mEarlyDataAccepted = aAccepted;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNSSSocketInfo::DriveHandshake()
|
|
{
|
|
nsNSSShutDownPreventionLock locker;
|
|
if (isAlreadyShutDown() || isPK11LoggedOut()) {
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
}
|
|
if (!mFd) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
PRErrorCode errorCode = GetErrorCode();
|
|
if (errorCode) {
|
|
return GetXPCOMFromNSSError(errorCode);
|
|
}
|
|
|
|
SECStatus rv = SSL_ForceHandshake(mFd);
|
|
|
|
if (rv != SECSuccess) {
|
|
errorCode = PR_GetError();
|
|
if (errorCode == PR_WOULD_BLOCK_ERROR) {
|
|
return NS_BASE_STREAM_WOULD_BLOCK;
|
|
}
|
|
|
|
SetCanceled(errorCode, PlainErrorMessage);
|
|
return GetXPCOMFromNSSError(errorCode);
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNSSSocketInfo::IsAcceptableForHost(const nsACString& hostname, bool* _retval)
|
|
{
|
|
// 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;
|
|
SECStatus rv = certVerifier->VerifySSLServerCert(nssCert,
|
|
nullptr, // stapledOCSPResponse
|
|
nullptr, // sctsFromTLSExtension
|
|
mozilla::pkix::Now(),
|
|
nullptr, // pinarg
|
|
hostnameFlat.get(),
|
|
unusedBuiltChain,
|
|
false, // save intermediates
|
|
flags);
|
|
if (rv != SECSuccess) {
|
|
return NS_OK;
|
|
}
|
|
|
|
// All tests pass
|
|
*_retval = true;
|
|
return NS_OK;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNSSSocketInfo::JoinConnection(const nsACString& npnProtocol,
|
|
const nsACString& hostname,
|
|
int32_t port,
|
|
bool* _retval)
|
|
{
|
|
*_retval = false;
|
|
|
|
// Different ports may not be joined together
|
|
if (port != GetPort())
|
|
return NS_OK;
|
|
|
|
// Make sure NPN has been completed and matches requested npnProtocol
|
|
if (!mNPNCompleted || !mNegotiatedNPN.Equals(npnProtocol))
|
|
return NS_OK;
|
|
|
|
if (mBypassAuthentication) {
|
|
// An unauthenticated connection does not know whether or not it
|
|
// is acceptable for a particular hostname
|
|
return NS_OK;
|
|
}
|
|
|
|
IsAcceptableForHost(hostname, _retval);
|
|
|
|
if (*_retval) {
|
|
// All tests pass - this is joinable
|
|
mJoined = true;
|
|
}
|
|
return NS_OK;
|
|
}
|
|
|
|
bool
|
|
nsNSSSocketInfo::GetForSTARTTLS()
|
|
{
|
|
return mForSTARTTLS;
|
|
}
|
|
|
|
void
|
|
nsNSSSocketInfo::SetForSTARTTLS(bool aForSTARTTLS)
|
|
{
|
|
mForSTARTTLS = aForSTARTTLS;
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNSSSocketInfo::ProxyStartSSL()
|
|
{
|
|
return ActivateSSL();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNSSSocketInfo::StartTLS()
|
|
{
|
|
return ActivateSSL();
|
|
}
|
|
|
|
NS_IMETHODIMP
|
|
nsNSSSocketInfo::SetNPNList(nsTArray<nsCString>& protocolArray)
|
|
{
|
|
nsNSSShutDownPreventionLock locker;
|
|
if (isAlreadyShutDown())
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
if (!mFd)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
// the npn list is a concatenated list of 8 bit byte strings.
|
|
nsCString npnList;
|
|
|
|
for (uint32_t index = 0; index < protocolArray.Length(); ++index) {
|
|
if (protocolArray[index].IsEmpty() ||
|
|
protocolArray[index].Length() > 255)
|
|
return NS_ERROR_ILLEGAL_VALUE;
|
|
|
|
npnList.Append(protocolArray[index].Length());
|
|
npnList.Append(protocolArray[index]);
|
|
}
|
|
|
|
if (SSL_SetNextProtoNego(
|
|
mFd,
|
|
BitwiseCast<const unsigned char*, const char*>(npnList.get()),
|
|
npnList.Length()) != SECSuccess)
|
|
return NS_ERROR_FAILURE;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsNSSSocketInfo::ActivateSSL()
|
|
{
|
|
nsNSSShutDownPreventionLock locker;
|
|
if (isAlreadyShutDown())
|
|
return NS_ERROR_NOT_AVAILABLE;
|
|
|
|
if (SECSuccess != SSL_OptionSet(mFd, SSL_SECURITY, true))
|
|
return NS_ERROR_FAILURE;
|
|
if (SECSuccess != SSL_ResetHandshake(mFd, false))
|
|
return NS_ERROR_FAILURE;
|
|
|
|
mHandshakePending = true;
|
|
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsNSSSocketInfo::GetFileDescPtr(PRFileDesc** aFilePtr)
|
|
{
|
|
*aFilePtr = mFd;
|
|
return NS_OK;
|
|
}
|
|
|
|
nsresult
|
|
nsNSSSocketInfo::SetFileDescPtr(PRFileDesc* aFilePtr)
|
|
{
|
|
mFd = aFilePtr;
|
|
return NS_OK;
|
|
}
|
|
|
|
void
|
|
nsNSSSocketInfo::SetCertVerificationWaiting()
|
|
{
|
|
// mCertVerificationState may be before_cert_verification for the first
|
|
// handshake on the connection, or after_cert_verification for subsequent
|
|
// renegotiation handshakes.
|
|
NS_ASSERTION(mCertVerificationState != waiting_for_cert_verification,
|
|
"Invalid state transition to waiting_for_cert_verification");
|
|
mCertVerificationState = waiting_for_cert_verification;
|
|
}
|
|
|
|
// Be careful that SetCertVerificationResult does NOT get called while we are
|
|
// processing a SSL callback function, because SSL_AuthCertificateComplete will
|
|
// attempt to acquire locks that are already held by libssl when it calls
|
|
// callbacks.
|
|
void
|
|
nsNSSSocketInfo::SetCertVerificationResult(PRErrorCode errorCode,
|
|
SSLErrorMessageType errorMessageType)
|
|
{
|
|
NS_ASSERTION(mCertVerificationState == waiting_for_cert_verification,
|
|
"Invalid state transition to cert_verification_finished");
|
|
|
|
if (mFd) {
|
|
SECStatus rv = SSL_AuthCertificateComplete(mFd, errorCode);
|
|
// Only replace errorCode if there was originally no error
|
|
if (rv != SECSuccess && errorCode == 0) {
|
|
errorCode = PR_GetError();
|
|
errorMessageType = PlainErrorMessage;
|
|
if (errorCode == 0) {
|
|
NS_ERROR("SSL_AuthCertificateComplete didn't set error code");
|
|
errorCode = PR_INVALID_STATE_ERROR;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (errorCode) {
|
|
mFailedVerification = true;
|
|
SetCanceled(errorCode, errorMessageType);
|
|
}
|
|
|
|
if (mPlaintextBytesRead && !errorCode) {
|
|
Telemetry::Accumulate(Telemetry::SSL_BYTES_BEFORE_CERT_CALLBACK,
|
|
AssertedCast<uint32_t>(mPlaintextBytesRead));
|
|
}
|
|
|
|
mCertVerificationState = after_cert_verification;
|
|
}
|
|
|
|
SharedSSLState&
|
|
nsNSSSocketInfo::SharedState()
|
|
{
|
|
return mSharedState;
|
|
}
|
|
|
|
void nsSSLIOLayerHelpers::Cleanup()
|
|
{
|
|
MutexAutoLock lock(mutex);
|
|
mTLSIntoleranceInfo.Clear();
|
|
mInsecureFallbackSites.Clear();
|
|
}
|
|
|
|
static void
|
|
nsHandleSSLError(nsNSSSocketInfo* socketInfo,
|
|
::mozilla::psm::SSLErrorMessageType errtype,
|
|
PRErrorCode err)
|
|
{
|
|
if (!NS_IsMainThread()) {
|
|
NS_ERROR("nsHandleSSLError called off the main thread");
|
|
return;
|
|
}
|
|
|
|
// SetCanceled is only called by the main thread or the socket transport
|
|
// thread. Whenever this function is called on the main thread, the SSL
|
|
// thread is blocked on it. So, no mutex is necessary for
|
|
// SetCanceled()/GetError*().
|
|
if (socketInfo->GetErrorCode()) {
|
|
// If the socket has been flagged as canceled,
|
|
// the code who did was responsible for setting the error code.
|
|
return;
|
|
}
|
|
|
|
// We must cancel first, which sets the error code.
|
|
socketInfo->SetCanceled(err, PlainErrorMessage);
|
|
nsXPIDLString errorString;
|
|
socketInfo->GetErrorLogMessage(err, errtype, errorString);
|
|
|
|
if (!errorString.IsEmpty()) {
|
|
nsContentUtils::LogSimpleConsoleError(errorString, "SSL");
|
|
}
|
|
}
|
|
|
|
namespace {
|
|
|
|
enum Operation { reading, writing, not_reading_or_writing };
|
|
|
|
int32_t checkHandshake(int32_t bytesTransfered, bool wasReading,
|
|
PRFileDesc* ssl_layer_fd,
|
|
nsNSSSocketInfo* socketInfo);
|
|
|
|
nsNSSSocketInfo*
|
|
getSocketInfoIfRunning(PRFileDesc* fd, Operation op,
|
|
const nsNSSShutDownPreventionLock& /*proofOfLock*/)
|
|
{
|
|
if (!fd || !fd->lower || !fd->secret ||
|
|
fd->identity != nsSSLIOLayerHelpers::nsSSLIOLayerIdentity) {
|
|
NS_ERROR("bad file descriptor passed to getSocketInfoIfRunning");
|
|
PR_SetError(PR_BAD_DESCRIPTOR_ERROR, 0);
|
|
return nullptr;
|
|
}
|
|
|
|
nsNSSSocketInfo* socketInfo = (nsNSSSocketInfo*) fd->secret;
|
|
|
|
if (socketInfo->isAlreadyShutDown() || socketInfo->isPK11LoggedOut()) {
|
|
PR_SetError(PR_SOCKET_SHUTDOWN_ERROR, 0);
|
|
return nullptr;
|
|
}
|
|
|
|
if (socketInfo->GetErrorCode()) {
|
|
PRErrorCode err = socketInfo->GetErrorCode();
|
|
PR_SetError(err, 0);
|
|
if (op == reading || op == writing) {
|
|
// We must do TLS intolerance checks for reads and writes, for timeouts
|
|
// in particular.
|
|
(void) checkHandshake(-1, op == reading, fd, socketInfo);
|
|
}
|
|
|
|
// If we get here, it is probably because cert verification failed and this
|
|
// is the first I/O attempt since that failure.
|
|
return nullptr;
|
|
}
|
|
|
|
return socketInfo;
|
|
}
|
|
|
|
} // namespace
|
|
|
|
static PRStatus
|
|
nsSSLIOLayerConnect(PRFileDesc* fd, const PRNetAddr* addr,
|
|
PRIntervalTime timeout)
|
|
{
|
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("[%p] connecting SSL socket\n",
|
|
(void*) fd));
|
|
nsNSSShutDownPreventionLock locker;
|
|
if (!getSocketInfoIfRunning(fd, not_reading_or_writing, locker))
|
|
return PR_FAILURE;
|
|
|
|
PRStatus status = fd->lower->methods->connect(fd->lower, addr, timeout);
|
|
if (status != PR_SUCCESS) {
|
|
MOZ_LOG(gPIPNSSLog, LogLevel::Error, ("[%p] Lower layer connect error: %d\n",
|
|
(void*) fd, PR_GetError()));
|
|
return status;
|
|
}
|
|
|
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("[%p] Connect\n", (void*) fd));
|
|
return status;
|
|
}
|
|
|
|
void
|
|
nsSSLIOLayerHelpers::rememberTolerantAtVersion(const nsACString& hostName,
|
|
int16_t port, uint16_t tolerant)
|
|
{
|
|
nsCString key;
|
|
getSiteKey(hostName, port, key);
|
|
|
|
MutexAutoLock lock(mutex);
|
|
|
|
IntoleranceEntry entry;
|
|
if (mTLSIntoleranceInfo.Get(key, &entry)) {
|
|
entry.AssertInvariant();
|
|
entry.tolerant = std::max(entry.tolerant, tolerant);
|
|
if (entry.intolerant != 0 && entry.intolerant <= entry.tolerant) {
|
|
entry.intolerant = entry.tolerant + 1;
|
|
entry.intoleranceReason = 0; // lose the reason
|
|
}
|
|
if (entry.strongCipherStatus == StrongCipherStatusUnknown) {
|
|
entry.strongCipherStatus = StrongCiphersWorked;
|
|
}
|
|
} else {
|
|
entry.tolerant = tolerant;
|
|
entry.intolerant = 0;
|
|
entry.intoleranceReason = 0;
|
|
entry.strongCipherStatus = StrongCiphersWorked;
|
|
}
|
|
|
|
entry.AssertInvariant();
|
|
|
|
mTLSIntoleranceInfo.Put(key, entry);
|
|
}
|
|
|
|
void
|
|
nsSSLIOLayerHelpers::forgetIntolerance(const nsACString& hostName,
|
|
int16_t port)
|
|
{
|
|
nsCString key;
|
|
getSiteKey(hostName, port, key);
|
|
|
|
MutexAutoLock lock(mutex);
|
|
|
|
IntoleranceEntry entry;
|
|
if (mTLSIntoleranceInfo.Get(key, &entry)) {
|
|
entry.AssertInvariant();
|
|
|
|
entry.intolerant = 0;
|
|
entry.intoleranceReason = 0;
|
|
if (entry.strongCipherStatus != StrongCiphersWorked) {
|
|
entry.strongCipherStatus = StrongCipherStatusUnknown;
|
|
}
|
|
|
|
entry.AssertInvariant();
|
|
mTLSIntoleranceInfo.Put(key, entry);
|
|
}
|
|
}
|
|
|
|
bool
|
|
nsSSLIOLayerHelpers::fallbackLimitReached(const nsACString& hostName,
|
|
uint16_t intolerant)
|
|
{
|
|
if (isInsecureFallbackSite(hostName)) {
|
|
return intolerant <= SSL_LIBRARY_VERSION_TLS_1_0;
|
|
}
|
|
return intolerant <= mVersionFallbackLimit;
|
|
}
|
|
|
|
// returns true if we should retry the handshake
|
|
bool
|
|
nsSSLIOLayerHelpers::rememberIntolerantAtVersion(const nsACString& hostName,
|
|
int16_t port,
|
|
uint16_t minVersion,
|
|
uint16_t intolerant,
|
|
PRErrorCode intoleranceReason)
|
|
{
|
|
if (intolerant <= minVersion || fallbackLimitReached(hostName, intolerant)) {
|
|
// We can't fall back any further. Assume that intolerance isn't the issue.
|
|
forgetIntolerance(hostName, port);
|
|
return false;
|
|
}
|
|
|
|
nsCString key;
|
|
getSiteKey(hostName, port, key);
|
|
|
|
MutexAutoLock lock(mutex);
|
|
|
|
IntoleranceEntry entry;
|
|
if (mTLSIntoleranceInfo.Get(key, &entry)) {
|
|
entry.AssertInvariant();
|
|
if (intolerant <= entry.tolerant) {
|
|
// We already know the server is tolerant at an equal or higher version.
|
|
return false;
|
|
}
|
|
if ((entry.intolerant != 0 && intolerant >= entry.intolerant)) {
|
|
// We already know that the server is intolerant at a lower version.
|
|
return true;
|
|
}
|
|
} else {
|
|
entry.tolerant = 0;
|
|
entry.strongCipherStatus = StrongCipherStatusUnknown;
|
|
}
|
|
|
|
entry.intolerant = intolerant;
|
|
entry.intoleranceReason = intoleranceReason;
|
|
entry.AssertInvariant();
|
|
mTLSIntoleranceInfo.Put(key, entry);
|
|
|
|
return true;
|
|
}
|
|
|
|
// returns true if we should retry the handshake
|
|
bool
|
|
nsSSLIOLayerHelpers::rememberStrongCiphersFailed(const nsACString& hostName,
|
|
int16_t port,
|
|
PRErrorCode intoleranceReason)
|
|
{
|
|
nsCString key;
|
|
getSiteKey(hostName, port, key);
|
|
|
|
MutexAutoLock lock(mutex);
|
|
|
|
IntoleranceEntry entry;
|
|
if (mTLSIntoleranceInfo.Get(key, &entry)) {
|
|
entry.AssertInvariant();
|
|
if (entry.strongCipherStatus != StrongCipherStatusUnknown) {
|
|
// We already know if the server supports a strong cipher.
|
|
return false;
|
|
}
|
|
} else {
|
|
entry.tolerant = 0;
|
|
entry.intolerant = 0;
|
|
entry.intoleranceReason = intoleranceReason;
|
|
}
|
|
|
|
entry.strongCipherStatus = StrongCiphersFailed;
|
|
entry.AssertInvariant();
|
|
mTLSIntoleranceInfo.Put(key, entry);
|
|
|
|
return true;
|
|
}
|
|
|
|
void
|
|
nsSSLIOLayerHelpers::adjustForTLSIntolerance(const nsACString& hostName,
|
|
int16_t port,
|
|
/*in/out*/ SSLVersionRange& range,
|
|
/*out*/ StrongCipherStatus& strongCipherStatus)
|
|
{
|
|
IntoleranceEntry entry;
|
|
|
|
{
|
|
nsCString key;
|
|
getSiteKey(hostName, port, key);
|
|
|
|
MutexAutoLock lock(mutex);
|
|
if (!mTLSIntoleranceInfo.Get(key, &entry)) {
|
|
return;
|
|
}
|
|
}
|
|
|
|
entry.AssertInvariant();
|
|
|
|
if (entry.intolerant != 0) {
|
|
// We've tried connecting at a higher range but failed, so try at the
|
|
// version we haven't tried yet, unless we have reached the minimum.
|
|
if (range.min < entry.intolerant) {
|
|
range.max = entry.intolerant - 1;
|
|
}
|
|
}
|
|
strongCipherStatus = entry.strongCipherStatus;
|
|
}
|
|
|
|
PRErrorCode
|
|
nsSSLIOLayerHelpers::getIntoleranceReason(const nsACString& hostName,
|
|
int16_t port)
|
|
{
|
|
IntoleranceEntry entry;
|
|
|
|
{
|
|
nsCString key;
|
|
getSiteKey(hostName, port, key);
|
|
|
|
MutexAutoLock lock(mutex);
|
|
if (!mTLSIntoleranceInfo.Get(key, &entry)) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
entry.AssertInvariant();
|
|
return entry.intoleranceReason;
|
|
}
|
|
|
|
bool nsSSLIOLayerHelpers::nsSSLIOLayerInitialized = false;
|
|
PRDescIdentity nsSSLIOLayerHelpers::nsSSLIOLayerIdentity;
|
|
PRDescIdentity nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity;
|
|
PRIOMethods nsSSLIOLayerHelpers::nsSSLIOLayerMethods;
|
|
PRIOMethods nsSSLIOLayerHelpers::nsSSLPlaintextLayerMethods;
|
|
|
|
static PRStatus
|
|
nsSSLIOLayerClose(PRFileDesc* fd)
|
|
{
|
|
nsNSSShutDownPreventionLock locker;
|
|
if (!fd)
|
|
return PR_FAILURE;
|
|
|
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("[%p] Shutting down socket\n",
|
|
(void*) fd));
|
|
|
|
nsNSSSocketInfo* socketInfo = (nsNSSSocketInfo*) fd->secret;
|
|
NS_ASSERTION(socketInfo,"nsNSSSocketInfo was null for an fd");
|
|
|
|
return socketInfo->CloseSocketAndDestroy(locker);
|
|
}
|
|
|
|
PRStatus
|
|
nsNSSSocketInfo::CloseSocketAndDestroy(
|
|
const nsNSSShutDownPreventionLock& /*proofOfLock*/)
|
|
{
|
|
PRFileDesc* popped = PR_PopIOLayer(mFd, PR_TOP_IO_LAYER);
|
|
NS_ASSERTION(popped &&
|
|
popped->identity == nsSSLIOLayerHelpers::nsSSLIOLayerIdentity,
|
|
"SSL Layer not on top of stack");
|
|
|
|
// The plain text layer is not always present - so its not a fatal error
|
|
// if it cannot be removed
|
|
PRFileDesc* poppedPlaintext =
|
|
PR_GetIdentitiesLayer(mFd, nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity);
|
|
if (poppedPlaintext) {
|
|
PR_PopIOLayer(mFd, nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity);
|
|
poppedPlaintext->dtor(poppedPlaintext);
|
|
}
|
|
|
|
PRStatus status = mFd->methods->close(mFd);
|
|
|
|
// the nsNSSSocketInfo instance can out-live the connection, so we need some
|
|
// indication that the connection has been closed. mFd == nullptr is that
|
|
// indication. This is needed, for example, when the connection is closed
|
|
// before we have finished validating the server's certificate.
|
|
mFd = nullptr;
|
|
|
|
if (status != PR_SUCCESS) return status;
|
|
|
|
popped->identity = PR_INVALID_IO_LAYER;
|
|
NS_RELEASE_THIS();
|
|
popped->dtor(popped);
|
|
|
|
return PR_SUCCESS;
|
|
}
|
|
|
|
#if defined(DEBUG_SSL_VERBOSE) && defined(DUMP_BUFFER)
|
|
// Dumps a (potentially binary) buffer using SSM_DEBUG. (We could have used
|
|
// the version in ssltrace.c, but that's specifically tailored to SSLTRACE.)
|
|
#define DUMPBUF_LINESIZE 24
|
|
static void
|
|
nsDumpBuffer(unsigned char* buf, int len)
|
|
{
|
|
char hexbuf[DUMPBUF_LINESIZE*3+1];
|
|
char chrbuf[DUMPBUF_LINESIZE+1];
|
|
static const char* hex = "0123456789abcdef";
|
|
int i = 0;
|
|
int l = 0;
|
|
char ch;
|
|
char* c;
|
|
char* h;
|
|
if (len == 0)
|
|
return;
|
|
hexbuf[DUMPBUF_LINESIZE*3] = '\0';
|
|
chrbuf[DUMPBUF_LINESIZE] = '\0';
|
|
(void) memset(hexbuf, 0x20, DUMPBUF_LINESIZE*3);
|
|
(void) memset(chrbuf, 0x20, DUMPBUF_LINESIZE);
|
|
h = hexbuf;
|
|
c = chrbuf;
|
|
|
|
while (i < len) {
|
|
ch = buf[i];
|
|
|
|
if (l == DUMPBUF_LINESIZE) {
|
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("%s%s\n", hexbuf, chrbuf));
|
|
(void) memset(hexbuf, 0x20, DUMPBUF_LINESIZE*3);
|
|
(void) memset(chrbuf, 0x20, DUMPBUF_LINESIZE);
|
|
h = hexbuf;
|
|
c = chrbuf;
|
|
l = 0;
|
|
}
|
|
|
|
// Convert a character to hex.
|
|
*h++ = hex[(ch >> 4) & 0xf];
|
|
*h++ = hex[ch & 0xf];
|
|
h++;
|
|
|
|
// Put the character (if it's printable) into the character buffer.
|
|
if ((ch >= 0x20) && (ch <= 0x7e)) {
|
|
*c++ = ch;
|
|
} else {
|
|
*c++ = '.';
|
|
}
|
|
i++; l++;
|
|
}
|
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug, ("%s%s\n", hexbuf, chrbuf));
|
|
}
|
|
|
|
#define DEBUG_DUMP_BUFFER(buf,len) nsDumpBuffer(buf,len)
|
|
#else
|
|
#define DEBUG_DUMP_BUFFER(buf,len)
|
|
#endif
|
|
|
|
class SSLErrorRunnable : public SyncRunnableBase
|
|
{
|
|
public:
|
|
SSLErrorRunnable(nsNSSSocketInfo* infoObject,
|
|
::mozilla::psm::SSLErrorMessageType errtype,
|
|
PRErrorCode errorCode)
|
|
: mInfoObject(infoObject)
|
|
, mErrType(errtype)
|
|
, mErrorCode(errorCode)
|
|
{
|
|
}
|
|
|
|
virtual void RunOnTargetThread()
|
|
{
|
|
nsHandleSSLError(mInfoObject, mErrType, mErrorCode);
|
|
}
|
|
|
|
RefPtr<nsNSSSocketInfo> mInfoObject;
|
|
::mozilla::psm::SSLErrorMessageType mErrType;
|
|
const PRErrorCode mErrorCode;
|
|
};
|
|
|
|
namespace {
|
|
|
|
uint32_t tlsIntoleranceTelemetryBucket(PRErrorCode err)
|
|
{
|
|
// returns a numeric code for where we track various errors in telemetry
|
|
// only errors that cause version fallback are tracked,
|
|
// so this is also used to determine which errors can cause version fallback
|
|
switch (err) {
|
|
case SSL_ERROR_BAD_MAC_ALERT: return 1;
|
|
case SSL_ERROR_BAD_MAC_READ: return 2;
|
|
case SSL_ERROR_HANDSHAKE_FAILURE_ALERT: return 3;
|
|
case SSL_ERROR_HANDSHAKE_UNEXPECTED_ALERT: return 4;
|
|
case SSL_ERROR_ILLEGAL_PARAMETER_ALERT: return 6;
|
|
case SSL_ERROR_NO_CYPHER_OVERLAP: return 7;
|
|
case SSL_ERROR_UNSUPPORTED_VERSION: return 10;
|
|
case SSL_ERROR_PROTOCOL_VERSION_ALERT: return 11;
|
|
case SSL_ERROR_BAD_HANDSHAKE_HASH_VALUE: return 13;
|
|
case SSL_ERROR_DECODE_ERROR_ALERT: return 14;
|
|
case PR_CONNECT_RESET_ERROR: return 16;
|
|
case PR_END_OF_FILE_ERROR: return 17;
|
|
case SSL_ERROR_INTERNAL_ERROR_ALERT: return 18;
|
|
default: return 0;
|
|
}
|
|
}
|
|
|
|
bool
|
|
retryDueToTLSIntolerance(PRErrorCode err, nsNSSSocketInfo* socketInfo)
|
|
{
|
|
// This function is supposed to decide which error codes should
|
|
// be used to conclude server is TLS intolerant.
|
|
// Note this only happens during the initial SSL handshake.
|
|
|
|
SSLVersionRange range = socketInfo->GetTLSVersionRange();
|
|
nsSSLIOLayerHelpers& helpers = socketInfo->SharedState().IOLayerHelpers();
|
|
|
|
if (err == SSL_ERROR_UNSUPPORTED_VERSION &&
|
|
range.min == SSL_LIBRARY_VERSION_TLS_1_0) {
|
|
socketInfo->SetSecurityState(nsIWebProgressListener::STATE_IS_INSECURE |
|
|
nsIWebProgressListener::STATE_USES_SSL_3);
|
|
}
|
|
|
|
// NSS will return SSL_ERROR_RX_MALFORMED_SERVER_HELLO if anti-downgrade
|
|
// detected the downgrade.
|
|
if (err == SSL_ERROR_INAPPROPRIATE_FALLBACK_ALERT ||
|
|
err == SSL_ERROR_RX_MALFORMED_SERVER_HELLO) {
|
|
// This is a clear signal that we've fallen back too many versions. Treat
|
|
// this as a hard failure, but forget any intolerance so that later attempts
|
|
// don't use this version (i.e., range.max) and trigger the error again.
|
|
|
|
// First, track the original cause of the version fallback. This uses the
|
|
// same buckets as the telemetry below, except that bucket 0 will include
|
|
// all cases where there wasn't an original reason.
|
|
PRErrorCode originalReason =
|
|
helpers.getIntoleranceReason(socketInfo->GetHostName(),
|
|
socketInfo->GetPort());
|
|
Telemetry::Accumulate(Telemetry::SSL_VERSION_FALLBACK_INAPPROPRIATE,
|
|
tlsIntoleranceTelemetryBucket(originalReason));
|
|
|
|
helpers.forgetIntolerance(socketInfo->GetHostName(),
|
|
socketInfo->GetPort());
|
|
|
|
return false;
|
|
}
|
|
|
|
// Disallow PR_CONNECT_RESET_ERROR if fallback limit reached.
|
|
bool fallbackLimitReached =
|
|
helpers.fallbackLimitReached(socketInfo->GetHostName(), range.max);
|
|
if (err == PR_CONNECT_RESET_ERROR && fallbackLimitReached) {
|
|
return false;
|
|
}
|
|
|
|
if ((err == SSL_ERROR_NO_CYPHER_OVERLAP || err == PR_END_OF_FILE_ERROR ||
|
|
err == PR_CONNECT_RESET_ERROR) &&
|
|
nsNSSComponent::AreAnyWeakCiphersEnabled()) {
|
|
if (helpers.isInsecureFallbackSite(socketInfo->GetHostName()) ||
|
|
helpers.mUnrestrictedRC4Fallback) {
|
|
if (helpers.rememberStrongCiphersFailed(socketInfo->GetHostName(),
|
|
socketInfo->GetPort(), err)) {
|
|
Telemetry::Accumulate(Telemetry::SSL_WEAK_CIPHERS_FALLBACK,
|
|
tlsIntoleranceTelemetryBucket(err));
|
|
return true;
|
|
}
|
|
Telemetry::Accumulate(Telemetry::SSL_WEAK_CIPHERS_FALLBACK, 0);
|
|
}
|
|
}
|
|
|
|
// When not using a proxy we'll see a connection reset error.
|
|
// When using a proxy, we'll see an end of file error.
|
|
|
|
// Don't allow STARTTLS connections to fall back on connection resets or
|
|
// EOF.
|
|
if ((err == PR_CONNECT_RESET_ERROR || err == PR_END_OF_FILE_ERROR)
|
|
&& socketInfo->GetForSTARTTLS()) {
|
|
return false;
|
|
}
|
|
|
|
uint32_t reason = tlsIntoleranceTelemetryBucket(err);
|
|
if (reason == 0) {
|
|
return false;
|
|
}
|
|
|
|
Telemetry::ID pre;
|
|
Telemetry::ID post;
|
|
switch (range.max) {
|
|
case SSL_LIBRARY_VERSION_TLS_1_3:
|
|
pre = Telemetry::SSL_TLS13_INTOLERANCE_REASON_PRE;
|
|
post = Telemetry::SSL_TLS13_INTOLERANCE_REASON_POST;
|
|
break;
|
|
case SSL_LIBRARY_VERSION_TLS_1_2:
|
|
pre = Telemetry::SSL_TLS12_INTOLERANCE_REASON_PRE;
|
|
post = Telemetry::SSL_TLS12_INTOLERANCE_REASON_POST;
|
|
break;
|
|
case SSL_LIBRARY_VERSION_TLS_1_1:
|
|
pre = Telemetry::SSL_TLS11_INTOLERANCE_REASON_PRE;
|
|
post = Telemetry::SSL_TLS11_INTOLERANCE_REASON_POST;
|
|
break;
|
|
case SSL_LIBRARY_VERSION_TLS_1_0:
|
|
pre = Telemetry::SSL_TLS10_INTOLERANCE_REASON_PRE;
|
|
post = Telemetry::SSL_TLS10_INTOLERANCE_REASON_POST;
|
|
break;
|
|
default:
|
|
MOZ_CRASH("impossible TLS version");
|
|
return false;
|
|
}
|
|
|
|
// The difference between _PRE and _POST represents how often we avoided
|
|
// TLS intolerance fallback due to remembered tolerance.
|
|
Telemetry::Accumulate(pre, reason);
|
|
|
|
if (!helpers.rememberIntolerantAtVersion(socketInfo->GetHostName(),
|
|
socketInfo->GetPort(),
|
|
range.min, range.max, err)) {
|
|
return false;
|
|
}
|
|
|
|
Telemetry::Accumulate(post, reason);
|
|
|
|
return true;
|
|
}
|
|
|
|
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,
|
|
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,
|
|
sock, info, forSTARTTLS, flags);
|
|
if (NS_FAILED(rv)) {
|
|
PR_Close(sock);
|
|
return rv;
|
|
}
|
|
|
|
*fd = sock;
|
|
return NS_OK;
|
|
}
|
|
|
|
// Creates CA names strings from (CERTDistNames* caNames)
|
|
//
|
|
// - arena: arena to allocate strings on
|
|
// - caNameStrings: filled with CA names strings on return
|
|
// - caNames: CERTDistNames to extract strings from
|
|
// - return: SECSuccess if successful; error code otherwise
|
|
//
|
|
// Note: copied in its entirety from Nova code
|
|
static SECStatus
|
|
nsConvertCANamesToStrings(const UniquePLArenaPool& arena, char** caNameStrings,
|
|
CERTDistNames* caNames)
|
|
{
|
|
MOZ_ASSERT(arena.get());
|
|
MOZ_ASSERT(caNameStrings);
|
|
MOZ_ASSERT(caNames);
|
|
if (!arena.get() || !caNameStrings || !caNames) {
|
|
PR_SetError(SEC_ERROR_INVALID_ARGS, 0);
|
|
return SECFailure;
|
|
}
|
|
|
|
SECItem* dername;
|
|
SECStatus rv;
|
|
int headerlen;
|
|
uint32_t contentlen;
|
|
SECItem newitem;
|
|
int n;
|
|
char* namestring;
|
|
|
|
for (n = 0; n < caNames->nnames; n++) {
|
|
newitem.data = nullptr;
|
|
dername = &caNames->names[n];
|
|
|
|
rv = DER_Lengths(dername, &headerlen, &contentlen);
|
|
|
|
if (rv != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
|
|
if (headerlen + contentlen != dername->len) {
|
|
// This must be from an enterprise 2.x server, which sent
|
|
// incorrectly formatted der without the outer wrapper of type and
|
|
// length. Fix it up by adding the top level header.
|
|
if (dername->len <= 127) {
|
|
newitem.data = (unsigned char*) PR_Malloc(dername->len + 2);
|
|
if (!newitem.data) {
|
|
goto loser;
|
|
}
|
|
newitem.data[0] = (unsigned char) 0x30;
|
|
newitem.data[1] = (unsigned char) dername->len;
|
|
(void) memcpy(&newitem.data[2], dername->data, dername->len);
|
|
} else if (dername->len <= 255) {
|
|
newitem.data = (unsigned char*) PR_Malloc(dername->len + 3);
|
|
if (!newitem.data) {
|
|
goto loser;
|
|
}
|
|
newitem.data[0] = (unsigned char) 0x30;
|
|
newitem.data[1] = (unsigned char) 0x81;
|
|
newitem.data[2] = (unsigned char) dername->len;
|
|
(void) memcpy(&newitem.data[3], dername->data, dername->len);
|
|
} else {
|
|
// greater than 256, better be less than 64k
|
|
newitem.data = (unsigned char*) PR_Malloc(dername->len + 4);
|
|
if (!newitem.data) {
|
|
goto loser;
|
|
}
|
|
newitem.data[0] = (unsigned char) 0x30;
|
|
newitem.data[1] = (unsigned char) 0x82;
|
|
newitem.data[2] = (unsigned char) ((dername->len >> 8) & 0xff);
|
|
newitem.data[3] = (unsigned char) (dername->len & 0xff);
|
|
memcpy(&newitem.data[4], dername->data, dername->len);
|
|
}
|
|
dername = &newitem;
|
|
}
|
|
|
|
namestring = CERT_DerNameToAscii(dername);
|
|
if (!namestring) {
|
|
// XXX - keep going until we fail to convert the name
|
|
caNameStrings[n] = const_cast<char*>("");
|
|
} else {
|
|
caNameStrings[n] = PORT_ArenaStrdup(arena.get(), namestring);
|
|
PR_Free(namestring);
|
|
if (!caNameStrings[n]) {
|
|
goto loser;
|
|
}
|
|
}
|
|
|
|
if (newitem.data) {
|
|
PR_Free(newitem.data);
|
|
}
|
|
}
|
|
|
|
return SECSuccess;
|
|
loser:
|
|
if (newitem.data) {
|
|
PR_Free(newitem.data);
|
|
}
|
|
return SECFailure;
|
|
}
|
|
|
|
// Possible behaviors for choosing a cert for client auth.
|
|
enum class UserCertChoice {
|
|
// Ask the user to choose a cert.
|
|
Ask = 0,
|
|
// Automatically choose a cert.
|
|
Auto = 1,
|
|
};
|
|
|
|
// Returns the most appropriate user cert choice based on the value of the
|
|
// security.default_personal_cert preference.
|
|
UserCertChoice
|
|
nsGetUserCertChoice()
|
|
{
|
|
nsAutoCString value;
|
|
nsresult rv = Preferences::GetCString("security.default_personal_cert", &value);
|
|
if (NS_FAILED(rv)) {
|
|
return UserCertChoice::Ask;
|
|
}
|
|
|
|
// There are three cases for what the preference could be set to:
|
|
// 1. "Select Automatically" -> Auto.
|
|
// 2. "Ask Every Time" -> Ask.
|
|
// 3. Something else -> Ask. This might be a nickname from a migrated cert,
|
|
// but we no longer support this case.
|
|
return value.EqualsLiteral("Select Automatically") ? UserCertChoice::Auto
|
|
: UserCertChoice::Ask;
|
|
}
|
|
|
|
static bool
|
|
hasExplicitKeyUsageNonRepudiation(CERTCertificate* cert)
|
|
{
|
|
// There is no extension, v1 or v2 certificate
|
|
if (!cert->extensions)
|
|
return false;
|
|
|
|
SECStatus srv;
|
|
SECItem keyUsageItem;
|
|
keyUsageItem.data = nullptr;
|
|
|
|
srv = CERT_FindKeyUsageExtension(cert, &keyUsageItem);
|
|
if (srv == SECFailure)
|
|
return false;
|
|
|
|
unsigned char keyUsage = keyUsageItem.data[0];
|
|
PORT_Free (keyUsageItem.data);
|
|
|
|
return !!(keyUsage & KU_NON_REPUDIATION);
|
|
}
|
|
|
|
class ClientAuthDataRunnable : public SyncRunnableBase
|
|
{
|
|
public:
|
|
ClientAuthDataRunnable(CERTDistNames* caNames,
|
|
CERTCertificate** pRetCert,
|
|
SECKEYPrivateKey** pRetKey,
|
|
nsNSSSocketInfo* info,
|
|
const UniqueCERTCertificate& serverCert)
|
|
: mRV(SECFailure)
|
|
, mErrorCodeToReport(SEC_ERROR_NO_MEMORY)
|
|
, mPRetCert(pRetCert)
|
|
, mPRetKey(pRetKey)
|
|
, mCANames(caNames)
|
|
, mSocketInfo(info)
|
|
, mServerCert(serverCert.get())
|
|
{
|
|
}
|
|
|
|
SECStatus mRV; // out
|
|
PRErrorCode mErrorCodeToReport; // out
|
|
CERTCertificate** const mPRetCert; // in/out
|
|
SECKEYPrivateKey** const mPRetKey; // in/out
|
|
protected:
|
|
virtual void RunOnTargetThread();
|
|
private:
|
|
CERTDistNames* const mCANames; // in
|
|
nsNSSSocketInfo* const mSocketInfo; // in
|
|
CERTCertificate* const mServerCert; // in
|
|
};
|
|
|
|
// This callback function is used to pull client certificate
|
|
// information upon server request
|
|
//
|
|
// - arg: SSL data connection
|
|
// - socket: SSL socket we're dealing with
|
|
// - caNames: list of CA names
|
|
// - pRetCert: returns a pointer to a pointer to a valid certificate if
|
|
// successful; otherwise nullptr
|
|
// - pRetKey: returns a pointer to a pointer to the corresponding key if
|
|
// successful; otherwise nullptr
|
|
SECStatus
|
|
nsNSS_SSLGetClientAuthData(void* arg, PRFileDesc* socket,
|
|
CERTDistNames* caNames, CERTCertificate** pRetCert,
|
|
SECKEYPrivateKey** pRetKey)
|
|
{
|
|
nsNSSShutDownPreventionLock locker;
|
|
|
|
if (!socket || !caNames || !pRetCert || !pRetKey) {
|
|
PR_SetError(PR_INVALID_ARGUMENT_ERROR, 0);
|
|
return SECFailure;
|
|
}
|
|
|
|
RefPtr<nsNSSSocketInfo> info(
|
|
BitwiseCast<nsNSSSocketInfo*, PRFilePrivate*>(socket->higher->secret));
|
|
|
|
UniqueCERTCertificate serverCert(SSL_PeerCertificate(socket));
|
|
if (!serverCert) {
|
|
NS_NOTREACHED("Missing server certificate should have been detected during "
|
|
"server cert authentication.");
|
|
PR_SetError(SSL_ERROR_NO_CERTIFICATE, 0);
|
|
return SECFailure;
|
|
}
|
|
|
|
if (info->GetJoined()) {
|
|
// We refuse to send a client certificate when there are multiple hostnames
|
|
// joined on this connection, because we only show the user one hostname
|
|
// (mHostName) in the client certificate UI.
|
|
|
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
|
("[%p] Not returning client cert due to previous join\n", socket));
|
|
*pRetCert = nullptr;
|
|
*pRetKey = nullptr;
|
|
return SECSuccess;
|
|
}
|
|
|
|
// XXX: This should be done asynchronously; see bug 696976
|
|
RefPtr<ClientAuthDataRunnable> runnable(
|
|
new ClientAuthDataRunnable(caNames, pRetCert, pRetKey, info, serverCert));
|
|
nsresult rv = runnable->DispatchToMainThreadAndWait();
|
|
if (NS_FAILED(rv)) {
|
|
PR_SetError(SEC_ERROR_NO_MEMORY, 0);
|
|
return SECFailure;
|
|
}
|
|
|
|
if (runnable->mRV != SECSuccess) {
|
|
PR_SetError(runnable->mErrorCodeToReport, 0);
|
|
} else if (*runnable->mPRetCert || *runnable->mPRetKey) {
|
|
// Make joinConnection prohibit joining after we've sent a client cert
|
|
info->SetSentClientCert();
|
|
}
|
|
|
|
return runnable->mRV;
|
|
}
|
|
|
|
void
|
|
ClientAuthDataRunnable::RunOnTargetThread()
|
|
{
|
|
// We check the value of a pref in this runnable, so this runnable should only
|
|
// be run on the main thread.
|
|
MOZ_ASSERT(NS_IsMainThread());
|
|
|
|
UniquePLArenaPool arena;
|
|
char** caNameStrings;
|
|
UniqueCERTCertificate cert;
|
|
UniqueSECKEYPrivateKey privKey;
|
|
void* wincx = mSocketInfo;
|
|
nsresult rv;
|
|
|
|
nsCOMPtr<nsIX509Cert> socketClientCert;
|
|
mSocketInfo->GetClientCert(getter_AddRefs(socketClientCert));
|
|
|
|
// If a client cert preference was set on the socket info, use that and skip
|
|
// the client cert UI and/or search of the user's past cert decisions.
|
|
if (socketClientCert) {
|
|
cert.reset(socketClientCert->GetCert());
|
|
if (!cert) {
|
|
goto loser;
|
|
}
|
|
|
|
// Get the private key
|
|
privKey.reset(PK11_FindKeyByAnyCert(cert.get(), wincx));
|
|
if (!privKey) {
|
|
goto loser;
|
|
}
|
|
|
|
*mPRetCert = cert.release();
|
|
*mPRetKey = privKey.release();
|
|
mRV = SECSuccess;
|
|
return;
|
|
}
|
|
|
|
// create caNameStrings
|
|
arena.reset(PORT_NewArena(DER_DEFAULT_CHUNKSIZE));
|
|
if (!arena) {
|
|
goto loser;
|
|
}
|
|
|
|
caNameStrings = static_cast<char**>(
|
|
PORT_ArenaAlloc(arena.get(), sizeof(char*) * mCANames->nnames));
|
|
if (!caNameStrings) {
|
|
goto loser;
|
|
}
|
|
|
|
mRV = nsConvertCANamesToStrings(arena, caNameStrings, mCANames);
|
|
if (mRV != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
|
|
// find valid user cert and key pair
|
|
if (nsGetUserCertChoice() == UserCertChoice::Auto) {
|
|
// automatically find the right cert
|
|
|
|
// find all user certs that are valid and for SSL
|
|
UniqueCERTCertList certList(
|
|
CERT_FindUserCertsByUsage(CERT_GetDefaultCertDB(), certUsageSSLClient,
|
|
false, true, wincx));
|
|
if (!certList) {
|
|
goto loser;
|
|
}
|
|
|
|
// filter the list to those issued by CAs supported by the server
|
|
mRV = CERT_FilterCertListByCANames(certList.get(), mCANames->nnames,
|
|
caNameStrings, certUsageSSLClient);
|
|
if (mRV != SECSuccess) {
|
|
goto loser;
|
|
}
|
|
|
|
// make sure the list is not empty
|
|
if (CERT_LIST_END(CERT_LIST_HEAD(certList), certList)) {
|
|
goto loser;
|
|
}
|
|
|
|
UniqueCERTCertificate lowPrioNonrepCert;
|
|
|
|
// loop through the list until we find a cert with a key
|
|
for (CERTCertListNode* node = CERT_LIST_HEAD(certList);
|
|
!CERT_LIST_END(node, certList);
|
|
node = CERT_LIST_NEXT(node)) {
|
|
// if the certificate has restriction and we do not satisfy it we do not
|
|
// use it
|
|
privKey.reset(PK11_FindKeyByAnyCert(node->cert, wincx));
|
|
if (privKey) {
|
|
if (hasExplicitKeyUsageNonRepudiation(node->cert)) {
|
|
privKey = nullptr;
|
|
// Not a preferred cert
|
|
if (!lowPrioNonrepCert) { // did not yet find a low prio cert
|
|
lowPrioNonrepCert.reset(CERT_DupCertificate(node->cert));
|
|
}
|
|
} else {
|
|
// this is a good cert to present
|
|
cert.reset(CERT_DupCertificate(node->cert));
|
|
break;
|
|
}
|
|
}
|
|
if (PR_GetError() == SEC_ERROR_BAD_PASSWORD) {
|
|
// problem with password: bail
|
|
goto loser;
|
|
}
|
|
}
|
|
|
|
if (!cert && lowPrioNonrepCert) {
|
|
cert = Move(lowPrioNonrepCert);
|
|
privKey.reset(PK11_FindKeyByAnyCert(cert.get(), wincx));
|
|
}
|
|
|
|
if (!cert) {
|
|
goto loser;
|
|
}
|
|
} else { // Not Auto => ask
|
|
// Get the SSL Certificate
|
|
|
|
nsXPIDLCString hostname;
|
|
mSocketInfo->GetHostName(getter_Copies(hostname));
|
|
|
|
RefPtr<nsClientAuthRememberService> cars =
|
|
mSocketInfo->SharedState().GetClientAuthRememberService();
|
|
|
|
bool hasRemembered = false;
|
|
nsCString rememberedDBKey;
|
|
if (cars) {
|
|
bool found;
|
|
rv = cars->HasRememberedDecision(hostname, 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, 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;
|
|
}
|
|
|
|
// This is an optimization to make sure the identity info dataset is parsed
|
|
// and loaded on a separate thread and can be overlapped with network latency.
|
|
EnsureServerVerificationInitialized();
|
|
|
|
return sslSock;
|
|
loser:
|
|
if (sslSock) {
|
|
PR_Close(sslSock);
|
|
}
|
|
return nullptr;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
uint16_t maxEnabledVersion = range.max;
|
|
StrongCipherStatus strongCiphersStatus = StrongCipherStatusUnknown;
|
|
infoObject->SharedState().IOLayerHelpers()
|
|
.adjustForTLSIntolerance(infoObject->GetHostName(), infoObject->GetPort(),
|
|
range, strongCiphersStatus);
|
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
|
("[%p] nsSSLIOLayerSetOptions: using TLS version range (0x%04x,0x%04x)%s\n",
|
|
fd, static_cast<unsigned int>(range.min),
|
|
static_cast<unsigned int>(range.max),
|
|
strongCiphersStatus == StrongCiphersFailed ? " with weak ciphers" : ""));
|
|
|
|
if (SSL_VersionRangeSet(fd, &range) != SECSuccess) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
infoObject->SetTLSVersionRange(range);
|
|
|
|
if (strongCiphersStatus == StrongCiphersFailed) {
|
|
nsNSSComponent::UseWeakCiphersOnSocket(fd);
|
|
}
|
|
|
|
// when adjustForTLSIntolerance tweaks the maximum version downward,
|
|
// we tell the server using this SCSV so they can detect a downgrade attack
|
|
if (range.max < maxEnabledVersion) {
|
|
MOZ_LOG(gPIPNSSLog, LogLevel::Debug,
|
|
("[%p] nsSSLIOLayerSetOptions: enabling TLS_FALLBACK_SCSV\n", fd));
|
|
// Some servers will choke if we send the fallback SCSV with TLS 1.2.
|
|
if (range.max < SSL_LIBRARY_VERSION_TLS_1_2) {
|
|
if (SECSuccess != SSL_OptionSet(fd, SSL_ENABLE_FALLBACK_SCSV, true)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
}
|
|
// tell NSS the max enabled version to make anti-downgrade effective
|
|
if (SECSuccess != SSL_SetDowngradeCheckVersion(fd, maxEnabledVersion)) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
}
|
|
|
|
// Include a modest set of named groups.
|
|
const SSLNamedGroup namedGroups[] = {
|
|
ssl_grp_ec_curve25519, ssl_grp_ec_secp256r1, ssl_grp_ec_secp384r1,
|
|
ssl_grp_ec_secp521r1, ssl_grp_ffdhe_2048, ssl_grp_ffdhe_3072
|
|
};
|
|
if (SECSuccess != SSL_NamedGroupConfig(fd, namedGroups,
|
|
mozilla::ArrayLength(namedGroups))) {
|
|
return NS_ERROR_FAILURE;
|
|
}
|
|
// This ensures that we send key shares for X25519 and P-256 in TLS 1.3, so
|
|
// that servers are less likely to use HelloRetryRequest.
|
|
if (SECSuccess != SSL_SendAdditionalKeyShares(fd, 2)) {
|
|
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:");
|
|
}
|
|
peerId.Append(host);
|
|
peerId.Append(':');
|
|
peerId.AppendInt(port);
|
|
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,
|
|
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);
|
|
|
|
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;
|
|
}
|