bug 658222 - Enable TLS False Start (PSM) r=bsmith

--HG--
extra : rebase_source : ac9f77ba73a0a902f4b6aa8d52add7d52efb1b53
This commit is contained in:
Patrick McManus 2013-06-28 16:58:28 -04:00
parent 4bb2b60348
commit 9c44286c03
16 changed files with 588 additions and 79 deletions

View File

@ -12,8 +12,10 @@ pref("security.ssl.renego_unrestricted_hosts", "");
pref("security.ssl.treat_unsafe_negotiation_as_broken", false);
pref("security.ssl.require_safe_negotiation", false);
pref("security.ssl.warn_missing_rfc5746", 1);
pref("security.ssl.enable_false_start", false);
pref("security.ssl.enable_ocsp_stapling", true);
pref("security.ssl.enable_false_start", true);
pref("security.ssl.false_start.require-npn", true);
pref("security.ssl.false_start.require-forward-secrecy", false);
pref("security.ssl3.rsa_rc4_128_md5", true);
pref("security.ssl3.rsa_rc4_128_sha", true);

View File

@ -127,8 +127,8 @@ nsHttpConnection::PrintDiagnostics(nsCString &log)
{
log.AppendPrintf(" CanDirectlyActivate = %d\n", CanDirectlyActivate());
log.AppendPrintf(" npncomplete = %d setupNPNCalled = %d\n",
mNPNComplete, mSetupNPNCalled);
log.AppendPrintf(" npncomplete = %d setupSSLCalled = %d\n",
mNPNComplete, mSetupSSLCalled);
log.AppendPrintf(" spdyVersion = %d reportedSpdy = %d everspdy = %d\n",
mUsingSpdyVersion, mReportedSpdy, mEverUsedSpdy);

View File

@ -58,6 +58,11 @@ typedef uint8_t nsHttpVersion;
// group is currently blocking on some resources
#define NS_HTTP_LOAD_UNBLOCKED (1<<8)
// These flags allow a transaction to use TLS false start with
// weaker security profiles based on past history
#define NS_HTTP_ALLOW_RSA_FALSESTART (1<<9)
#define NS_HTTP_ALLOW_RC4_FALSESTART (1<<10)
//-----------------------------------------------------------------------------
// some default values
//-----------------------------------------------------------------------------

View File

@ -40,6 +40,14 @@
#include "NullHttpTransaction.h"
#include "mozilla/Attributes.h"
#include "mozilla/VisualEventTracer.h"
#include "nsISSLSocketControl.h"
#include "sslt.h"
#include "nsContentUtils.h"
#include "nsIPermissionManager.h"
#include "nsIPrincipal.h"
#include "nsIScriptSecurityManager.h"
#include "nsISSLStatus.h"
#include "nsISSLStatusProvider.h"
namespace mozilla { namespace net {
@ -391,6 +399,7 @@ nsHttpChannel::Connect()
return NS_ERROR_UNKNOWN_HOST;
// Consider opening a TCP connection right away
RetrieveSSLOptions();
SpeculativeConnect();
// Don't allow resuming when cache must be used
@ -527,8 +536,9 @@ nsHttpChannel::SpeculativeConnect()
mConnectionInfo->SetAnonymous((mLoadFlags & LOAD_ANONYMOUS) != 0);
mConnectionInfo->SetPrivate(mPrivateBrowsing);
gHttpHandler->SpeculativeConnect(mConnectionInfo,
callbacks);
gHttpHandler->SpeculativeConnect(
mConnectionInfo, callbacks,
mCaps & (NS_HTTP_ALLOW_RSA_FALSESTART | NS_HTTP_ALLOW_RC4_FALSESTART | NS_HTTP_DISALLOW_SPDY));
}
void
@ -693,6 +703,37 @@ nsHttpChannel::SetupTransactionLoadGroupInfo()
mTransaction->SetLoadGroupConnectionInfo(ci);
}
void
nsHttpChannel::RetrieveSSLOptions()
{
if (!IsHTTPS() || mPrivateBrowsing)
return;
nsIPrincipal *principal = GetPrincipal();
if (!principal)
return;
nsCOMPtr<nsIPermissionManager> permMgr =
do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
if (!permMgr)
return;
uint32_t perm;
nsresult rv = permMgr->TestPermissionFromPrincipal(principal,
"falsestart-rsa", &perm);
if (NS_SUCCEEDED(rv) && perm == nsIPermissionManager::ALLOW_ACTION) {
LOG(("nsHttpChannel::RetrieveSSLOptions [this=%p] "
"falsestart-rsa permission found\n", this));
mCaps |= NS_HTTP_ALLOW_RSA_FALSESTART;
}
rv = permMgr->TestPermissionFromPrincipal(principal, "falsestart-rc4", &perm);
if (NS_SUCCEEDED(rv) && perm == nsIPermissionManager::ALLOW_ACTION) {
LOG(("nsHttpChannel::RetrieveSSLOptions [this=%p] "
"falsestart-rc4 permission found\n", this));
mCaps |= NS_HTTP_ALLOW_RC4_FALSESTART;
}
}
nsresult
nsHttpChannel::SetupTransaction()
{
@ -1160,6 +1201,89 @@ nsHttpChannel::ProcessSTSHeader()
return NS_OK;
}
bool
nsHttpChannel::IsHTTPS()
{
bool isHttps;
if (NS_FAILED(mURI->SchemeIs("https", &isHttps)) || !isHttps)
return false;
return true;
}
void
nsHttpChannel::ProcessSSLInformation()
{
// If this is HTTPS, record any use of RSA so that Key Exchange Algorithm
// can be whitelisted for TLS False Start in future sessions. We could
// do the same for DH but its rarity doesn't justify the lookup.
// Also do the same for RC4 symmetric ciphers.
if (mCanceled || NS_FAILED(mStatus) || !mSecurityInfo ||
!IsHTTPS() || mPrivateBrowsing)
return;
nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(mSecurityInfo);
nsCOMPtr<nsISSLStatusProvider> statusProvider =
do_QueryInterface(mSecurityInfo);
if (!ssl || !statusProvider)
return;
nsCOMPtr<nsISSLStatus> sslstat;
statusProvider->GetSSLStatus(getter_AddRefs(sslstat));
if (!sslstat)
return;
// If certificate exceptions are being used don't record this information
// in the permission manager.
bool trustCheck;
if (NS_FAILED(sslstat->GetIsDomainMismatch(&trustCheck)) || trustCheck)
return;
if (NS_FAILED(sslstat->GetIsNotValidAtThisTime(&trustCheck)) || trustCheck)
return;
if (NS_FAILED(sslstat->GetIsUntrusted(&trustCheck)) || trustCheck)
return;
int16_t kea = ssl->GetKEAUsed();
int16_t symcipher = ssl->GetSymmetricCipherUsed();
nsIPrincipal *principal = GetPrincipal();
if (!principal)
return;
// set a permission manager flag that future transactions can
// use via RetrieveSSLOptions(()
nsCOMPtr<nsIPermissionManager> permMgr =
do_GetService(NS_PERMISSIONMANAGER_CONTRACTID);
if (!permMgr)
return;
// Allow this to stand for a week
int64_t expireTime = (PR_Now() / PR_USEC_PER_MSEC) +
(86400 * 7 * PR_MSEC_PER_SEC);
if (kea == ssl_kea_rsa) {
permMgr->AddFromPrincipal(principal, "falsestart-rsa",
nsIPermissionManager::ALLOW_ACTION,
nsIPermissionManager::EXPIRE_TIME,
expireTime);
LOG(("nsHttpChannel::ProcessSSLInformation [this=%p] "
"falsestart-rsa permission granted for this host\n", this));
} else {
permMgr->RemoveFromPrincipal(principal, "falsestart-rsa");
}
if (symcipher == ssl_calg_rc4) {
permMgr->AddFromPrincipal(principal, "falsestart-rc4",
nsIPermissionManager::ALLOW_ACTION,
nsIPermissionManager::EXPIRE_TIME,
expireTime);
LOG(("nsHttpChannel::ProcessSSLInformation [this=%p] "
"falsestart-rc4 permission granted for this host\n", this));
} else {
permMgr->RemoveFromPrincipal(principal, "falsestart-rc4");
}
}
nsresult
nsHttpChannel::ProcessResponse()
{
@ -1192,6 +1316,8 @@ nsHttpChannel::ProcessResponse()
MOZ_ASSERT(!mCachedContentIsValid);
ProcessSSLInformation();
// notify "http-on-examine-response" observers
gHttpHandler->OnExamineResponse(this);
@ -6037,6 +6163,30 @@ nsHttpChannel::ShouldSkipCache()
return true;
}
nsIPrincipal *
nsHttpChannel::GetPrincipal()
{
if (mPrincipal)
return mPrincipal;
nsIScriptSecurityManager *securityManager =
nsContentUtils::GetSecurityManager();
if (!securityManager)
return nullptr;
securityManager->GetChannelPrincipal(this, getter_AddRefs(mPrincipal));
if (!mPrincipal)
return nullptr;
// principals with unknown app ids do not work with the permission manager
if (mPrincipal->GetUnknownAppId())
mPrincipal = nullptr;
return mPrincipal;
}
NS_IMETHODIMP
nsHttpChannel::SetLoadGroup(nsILoadGroup *aLoadGroup)
{

View File

@ -34,6 +34,7 @@
#include "mozilla/Telemetry.h"
class nsAHttpConnection;
class nsIPrincipal;
namespace mozilla { namespace net {
@ -174,6 +175,9 @@ private:
nsresult ContinueProcessFallback(nsresult);
void HandleAsyncAbort();
nsresult EnsureAssocReq();
void ProcessSSLInformation();
bool IsHTTPS();
void RetrieveSSLOptions();
nsresult ContinueOnStartRequest1(nsresult);
nsresult ContinueOnStartRequest2(nsresult);
@ -373,6 +377,10 @@ protected:
private: // cache telemetry
bool mDidReval;
private:
nsIPrincipal *GetPrincipal();
nsCOMPtr<nsIPrincipal> mPrincipal;
};
} } // namespace mozilla::net

View File

@ -17,6 +17,7 @@
#include "nsISocketTransport.h"
#include "nsIServiceManager.h"
#include "nsISSLSocketControl.h"
#include "sslt.h"
#include "nsStringStream.h"
#include "netCore.h"
#include "nsNetCID.h"
@ -64,11 +65,12 @@ nsHttpConnection::nsHttpConnection()
, mRemainingConnectionUses(0xffffffff)
, mClassification(nsAHttpTransaction::CLASS_GENERAL)
, mNPNComplete(false)
, mSetupNPNCalled(false)
, mSetupSSLCalled(false)
, mUsingSpdyVersion(0)
, mPriority(nsISupportsPriority::PRIORITY_NORMAL)
, mReportedSpdy(false)
, mEverUsedSpdy(false)
, mTransactionCaps(0)
{
LOG(("Creating nsHttpConnection @%x\n", this));
@ -236,8 +238,6 @@ nsHttpConnection::StartSpdy(uint8_t spdyVersion)
bool
nsHttpConnection::EnsureNPNComplete()
{
// NPN is only used by SPDY right now.
//
// If for some reason the components to check on NPN aren't available,
// this function will just return true to continue on and disable SPDY
@ -308,6 +308,7 @@ nsHttpConnection::Activate(nsAHttpTransaction *trans, uint32_t caps, int32_t pri
LOG(("nsHttpConnection::Activate [this=%p trans=%x caps=%x]\n",
this, trans, caps));
mTransactionCaps = caps;
mPriority = pri;
if (mTransaction && mUsingSpdyVersion)
return AddTransaction(trans, pri);
@ -323,7 +324,7 @@ nsHttpConnection::Activate(nsAHttpTransaction *trans, uint32_t caps, int32_t pri
trans->GetSecurityCallbacks(getter_AddRefs(callbacks));
SetSecurityCallbacks(callbacks);
SetupNPN(caps); // only for spdy
SetupSSL(caps);
// take ownership of the transaction
mTransaction = trans;
@ -360,55 +361,70 @@ failed_activation:
}
void
nsHttpConnection::SetupNPN(uint32_t caps)
nsHttpConnection::SetupSSL(uint32_t caps)
{
if (mSetupNPNCalled) /* do only once */
LOG(("nsHttpConnection::SetupSSL %p caps=0x%X\n", this, caps));
if (mSetupSSLCalled) // do only once
return;
mSetupNPNCalled = true;
mSetupSSLCalled = true;
// Setup NPN Negotiation if necessary (only for SPDY)
if (!mNPNComplete) {
if (mNPNComplete)
return;
mNPNComplete = true;
// we flip this back to false if SetNPNList succeeds at the end
// of this function
mNPNComplete = true;
if (mConnInfo->UsingSSL()) {
LOG(("nsHttpConnection::SetupNPN Setting up "
"Next Protocol Negotiation"));
nsCOMPtr<nsISupports> securityInfo;
nsresult rv =
mSocketTransport->GetSecurityInfo(getter_AddRefs(securityInfo));
if (NS_FAILED(rv))
return;
if (!mConnInfo->UsingSSL())
return;
nsCOMPtr<nsISSLSocketControl> ssl =
do_QueryInterface(securityInfo, &rv);
if (NS_FAILED(rv))
return;
LOG(("nsHttpConnection::SetupSSL Setting up "
"Next Protocol Negotiation"));
nsCOMPtr<nsISupports> securityInfo;
nsresult rv =
mSocketTransport->GetSecurityInfo(getter_AddRefs(securityInfo));
if (NS_FAILED(rv))
return;
nsTArray<nsCString> protocolArray;
nsCOMPtr<nsISSLSocketControl> ssl = do_QueryInterface(securityInfo, &rv);
if (NS_FAILED(rv))
return;
// The first protocol is used as the fallback if none of the
// protocols supported overlap with the server's list.
// In the case of overlap, matching priority is driven by
// the order of the server's advertisement.
protocolArray.AppendElement(NS_LITERAL_CSTRING("http/1.1"));
if (caps & NS_HTTP_ALLOW_RSA_FALSESTART) {
LOG(("nsHttpConnection::SetupSSL %p "
">= RSA Key Exchange Expected\n", this));
ssl->SetKEAExpected(ssl_kea_rsa);
}
if (gHttpHandler->IsSpdyEnabled() &&
!(caps & NS_HTTP_DISALLOW_SPDY)) {
LOG(("nsHttpConnection::SetupNPN Allow SPDY NPN selection"));
if (gHttpHandler->SpdyInfo()->ProtocolEnabled(0))
protocolArray.AppendElement(
gHttpHandler->SpdyInfo()->VersionString[0]);
if (gHttpHandler->SpdyInfo()->ProtocolEnabled(1))
protocolArray.AppendElement(
gHttpHandler->SpdyInfo()->VersionString[1]);
}
if (caps & NS_HTTP_ALLOW_RC4_FALSESTART) {
LOG(("nsHttpConnection::SetupSSL %p "
">= RC4 Key Exchange Expected\n", this));
ssl->SetSymmetricCipherExpected(ssl_calg_rc4);
}
if (NS_SUCCEEDED(ssl->SetNPNList(protocolArray))) {
LOG(("nsHttpConnection::Init Setting up SPDY Negotiation OK"));
mNPNComplete = false;
}
}
nsTArray<nsCString> protocolArray;
// The first protocol is used as the fallback if none of the
// protocols supported overlap with the server's list.
// In the case of overlap, matching priority is driven by
// the order of the server's advertisement.
protocolArray.AppendElement(NS_LITERAL_CSTRING("http/1.1"));
if (gHttpHandler->IsSpdyEnabled() &&
!(caps & NS_HTTP_DISALLOW_SPDY)) {
LOG(("nsHttpConnection::SetupSSL Allow SPDY NPN selection"));
if (gHttpHandler->SpdyInfo()->ProtocolEnabled(0))
protocolArray.AppendElement(
gHttpHandler->SpdyInfo()->VersionString[0]);
if (gHttpHandler->SpdyInfo()->ProtocolEnabled(1))
protocolArray.AppendElement(
gHttpHandler->SpdyInfo()->VersionString[1]);
}
if (NS_SUCCEEDED(ssl->SetNPNList(protocolArray))) {
LOG(("nsHttpConnection::Init Setting up SPDY Negotiation OK"));
mNPNComplete = false;
}
}
@ -596,7 +612,7 @@ nsHttpConnection::IsAlive()
// SocketTransport::IsAlive can run the SSL state machine, so make sure
// the NPN options are set before that happens.
SetupNPN(0);
SetupSSL(mTransactionCaps);
bool alive;
nsresult rv = mSocketTransport->IsAlive(&alive);

View File

@ -158,6 +158,7 @@ public:
void SetSecurityCallbacks(nsIInterfaceRequestor* aCallbacks);
void PrintDiagnostics(nsCString &log);
void SetTransactionCaps(uint32_t aCaps) { mTransactionCaps = aCaps; }
private:
// called to cause the underlying socket to start speaking SSL
nsresult ProxyStartSSL();
@ -175,7 +176,7 @@ private:
// Makes certain the SSL handshake is complete and NPN negotiation
// has had a chance to happen
bool EnsureNPNComplete();
void SetupNPN(uint32_t caps);
void SetupSSL(uint32_t caps);
// Start the Spdy transaction handler when NPN indicates spdy/*
void StartSpdy(uint8_t versionLevel);
@ -240,7 +241,7 @@ private:
// SPDY related
bool mNPNComplete;
bool mSetupNPNCalled;
bool mSetupSSLCalled;
// version level in use, 0 if unused
uint8_t mUsingSpdyVersion;
@ -251,6 +252,9 @@ private:
// mUsingSpdyVersion is cleared when mSpdySession is freed, this is permanent
bool mEverUsedSpdy;
// The capabailities associated with the most recent transaction
uint32_t mTransactionCaps;
};
#endif // nsHttpConnection_h__

View File

@ -326,7 +326,8 @@ nsHttpConnectionMgr::DoShiftReloadConnectionCleanup(nsHttpConnectionInfo *aCI)
nsresult
nsHttpConnectionMgr::SpeculativeConnect(nsHttpConnectionInfo *ci,
nsIInterfaceRequestor *callbacks)
nsIInterfaceRequestor *callbacks,
uint32_t caps)
{
LOG(("nsHttpConnectionMgr::SpeculativeConnect [ci=%s]\n",
ci->HashKey().get()));
@ -336,7 +337,7 @@ nsHttpConnectionMgr::SpeculativeConnect(nsHttpConnectionInfo *ci,
nsCOMPtr<nsIInterfaceRequestor> wrappedCallbacks;
NS_NewInterfaceRequestorAggregation(callbacks, nullptr, getter_AddRefs(wrappedCallbacks));
uint32_t caps = ci->GetAnonymous() ? NS_HTTP_LOAD_ANONYMOUS : 0;
caps |= ci->GetAnonymous() ? NS_HTTP_LOAD_ANONYMOUS : 0;
nsRefPtr<NullHttpTransaction> trans =
new NullHttpTransaction(ci, wrappedCallbacks, caps);
@ -2814,6 +2815,10 @@ nsHalfOpenSocket::OnOutputStreamReady(nsIAsyncOutputStream *out)
LOG(("nsHalfOpenSocket::OnOutputStreamReady "
"Created new nshttpconnection %p\n", conn.get()));
// Some capabilities are needed before a transaciton actually gets
// scheduled (e.g. how to negotiate false start)
conn->SetTransactionCaps(mTransaction->Caps());
NetAddr peeraddr;
nsCOMPtr<nsIInterfaceRequestor> callbacks;
mTransaction->GetSecurityCallbacks(getter_AddRefs(callbacks));

View File

@ -113,7 +113,8 @@ public:
// connection manager, nor is the submitter obligated to actually submit a
// real transaction for this connectionInfo.
nsresult SpeculativeConnect(nsHttpConnectionInfo *,
nsIInterfaceRequestor *);
nsIInterfaceRequestor *,
uint32_t caps = 0);
// called when a connection is done processing a transaction. if the
// connection can be reused then it will be added to the idle list, else

View File

@ -181,9 +181,10 @@ public:
}
nsresult SpeculativeConnect(nsHttpConnectionInfo *ci,
nsIInterfaceRequestor *callbacks)
nsIInterfaceRequestor *callbacks,
uint32_t caps = 0)
{
return mConnMgr->SpeculativeConnect(ci, callbacks);
return mConnMgr->SpeculativeConnect(ci, callbacks, caps);
}
//

View File

@ -14,7 +14,7 @@ class nsCString;
%}
[ref] native nsCStringTArrayRef(nsTArray<nsCString>);
[scriptable, uuid(bb2bb490-3ba4-4254-b8f5-8b43c7b714ea)]
[scriptable, builtinclass, uuid(c5eb9af4-238c-4fc6-bdec-d5ab5e7dce68)]
interface nsISSLSocketControl : nsISupports {
attribute nsIInterfaceRequestor notificationCallbacks;
@ -52,6 +52,22 @@ interface nsISSLSocketControl : nsISupports {
in ACString hostname,
in long port);
/* The Key Exchange Algorithm and Symmetric Cipher
is used when determining whether or not to do false start.
After a handshake is complete it can be read from *Used,
before a handshake is started it may be set through *Expected.
The values correspond to the SSLKEAType and SSLCipherAlgorithm
enums in NSS or the *_UNKNOWN constant defined below.
*/
[infallible] readonly attribute short KEAUsed;
[infallible] attribute short KEAExpected;
[infallible] readonly attribute short SymmetricCipherUsed;
[infallible] attribute short SymmetricCipherExpected;
const short KEY_EXCHANGE_UNKNOWN = -1;
const short SYMMETRIC_CIPHER_UNKNOWN = -1;
/*
* The original flags from the socket provider.
*/

View File

@ -824,6 +824,186 @@ PK11PasswordPrompt(PK11SlotInfo* slot, PRBool retry, void* arg)
return runnable->mResult;
}
// call with shutdown prevention lock held
static void
PreliminaryHandshakeDone(PRFileDesc* fd)
{
nsNSSSocketInfo* infoObject = (nsNSSSocketInfo*) fd->higher->secret;
if (!infoObject)
return;
if (infoObject->IsPreliminaryHandshakeDone())
return;
infoObject->SetPreliminaryHandshakeDone();
infoObject->SetFirstServerHelloReceived();
// Get the NPN value.
SSLNextProtoState state;
unsigned char npnbuf[256];
unsigned int npnlen;
if (SSL_GetNextProto(fd, &state, npnbuf, &npnlen, 256) == SECSuccess) {
if (state == SSL_NEXT_PROTO_NEGOTIATED) {
infoObject->SetNegotiatedNPN(reinterpret_cast<char *>(npnbuf), npnlen);
}
else {
infoObject->SetNegotiatedNPN(nullptr, 0);
}
mozilla::Telemetry::Accumulate(Telemetry::SSL_NPN_TYPE, state);
}
else {
infoObject->SetNegotiatedNPN(nullptr, 0);
}
}
SECStatus
CanFalseStartCallback(PRFileDesc* fd, void* client_data, PRBool *canFalseStart)
{
*canFalseStart = false;
nsNSSShutDownPreventionLock locker;
nsNSSSocketInfo* infoObject = (nsNSSSocketInfo*) fd->higher->secret;
if (!infoObject) {
PR_SetError(PR_INVALID_STATE_ERROR, 0);
return SECFailure;
}
if (infoObject->isAlreadyShutDown()) {
MOZ_CRASH("SSL socket used after NSS shut down");
PR_SetError(PR_INVALID_STATE_ERROR, 0);
return SECFailure;
}
PreliminaryHandshakeDone(fd);
SSLChannelInfo channelInfo;
if (SSL_GetChannelInfo(fd, &channelInfo, sizeof(channelInfo)) != SECSuccess) {
return SECSuccess;
}
SSLCipherSuiteInfo cipherInfo;
if (SSL_GetCipherSuiteInfo(channelInfo.cipherSuite, &cipherInfo,
sizeof (cipherInfo)) != SECSuccess) {
PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("CanFalseStartCallback [%p] failed - "
" KEA %d\n", fd,
static_cast<int32_t>(cipherInfo.keaType)));
return SECSuccess;
}
if (channelInfo.protocolVersion < SSL_LIBRARY_VERSION_TLS_1_0) {
PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("CanFalseStartCallback [%p] failed - "
"SSL Version must be >= TLS1 %x\n", fd,
static_cast<int32_t>(channelInfo.protocolVersion)));
return SECSuccess;
}
// never do false start without one of these key exchange algorithms
if (cipherInfo.keaType != ssl_kea_rsa &&
cipherInfo.keaType != ssl_kea_dh &&
cipherInfo.keaType != ssl_kea_ecdh) {
PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("CanFalseStartCallback [%p] failed - "
"unsupported KEA %d\n", fd,
static_cast<int32_t>(cipherInfo.keaType)));
return SECSuccess;
}
// never do false start without at least 80 bits of key material. This should
// be redundant to an NSS precondition
if (cipherInfo.effectiveKeyBits < 80) {
MOZ_CRASH("NSS is not enforcing the precondition that the effective "
"key size must be >= 80 bits for false start");
PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("CanFalseStartCallback [%p] failed - "
"key too small %d\n", fd,
static_cast<int32_t>(cipherInfo.effectiveKeyBits)));
PR_SetError(PR_INVALID_STATE_ERROR, 0);
return SECFailure;
}
// XXX: An attacker can choose which protocols are advertised in the
// NPN extension. TODO(Bug 861311): We should restrict the ability
// of an attacker leverage this capability by restricting false start
// to the same protocol we previously saw for the server, after the
// first successful connection to the server.
// Enforce NPN to do false start if policy requires it. Do this as an
// indicator if server compatibility.
nsSSLIOLayerHelpers& helpers = infoObject->SharedState().IOLayerHelpers();
if (helpers.mFalseStartRequireNPN) {
nsAutoCString negotiatedNPN;
if (NS_FAILED(infoObject->GetNegotiatedNPN(negotiatedNPN)) ||
!negotiatedNPN.Length()) {
PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("CanFalseStartCallback [%p] failed - "
"NPN cannot be verified\n", fd));
return SECSuccess;
}
}
// If we're not using eliptical curve kea then make sure we've seen the
// same kea from this host in the past, to limit the potential for downgrade
// attacks.
if (cipherInfo.keaType != ssl_kea_ecdh) {
if (helpers.mFalseStartRequireForwardSecrecy) {
PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("CanFalseStartCallback [%p] failed - "
"KEA used is %d, but "
"require-forward-secrecy configured.\n",
fd, static_cast<int32_t>(cipherInfo.keaType)));
return SECSuccess;
}
int16_t expected = infoObject->GetKEAExpected();
if (cipherInfo.keaType != expected) {
PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("CanFalseStartCallback [%p] failed - "
"KEA used is %d, expected %d\n", fd,
static_cast<int32_t>(cipherInfo.keaType),
static_cast<int32_t>(expected)));
return SECSuccess;
}
// whitelist the expected key exchange algorithms that are
// acceptable for false start when seen before.
if (expected != ssl_kea_rsa && expected != ssl_kea_dh &&
expected != ssl_kea_ecdh) {
PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("CanFalseStartCallback [%p] failed - "
"KEA used, %d, "
"is not supported with False Start.\n",
fd, static_cast<int32_t>(expected)));
return SECSuccess;
}
}
// If we're not using AES then verify that this is the historically expected
// symmetrical cipher for this host, to limit potential for downgrade attacks.
if (cipherInfo.symCipher != ssl_calg_aes) {
int16_t expected = infoObject->GetSymmetricCipherExpected();
if (cipherInfo.symCipher != expected) {
PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("CanFalseStartCallback [%p] failed - "
"Symmetric cipher used is %d, expected %d\n",
fd, static_cast<int32_t>(cipherInfo.symCipher),
static_cast<int32_t>(expected)));
return SECSuccess;
}
// whitelist the expected ciphers that are
// acceptable for false start when seen before.
if ((expected != ssl_calg_rc4) && (expected != ssl_calg_3des) &&
(expected != ssl_calg_aes)) {
PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("CanFalseStartCallback [%p] failed - "
"Symmetric cipher used, %d, "
"is not supported with False Start.\n",
fd, static_cast<int32_t>(expected)));
return SECSuccess;
}
}
infoObject->NoteTimeUntilReady();
*canFalseStart = true;
PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("CanFalseStartCallback [%p] ok\n", fd));
return SECSuccess;
}
void HandshakeCallback(PRFileDesc* fd, void* client_data) {
nsNSSShutDownPreventionLock locker;
int32_t sslStatus;
@ -834,11 +1014,14 @@ void HandshakeCallback(PRFileDesc* fd, void* client_data) {
nsNSSSocketInfo* infoObject = (nsNSSSocketInfo*) fd->higher->secret;
// certificate validation sets FirstServerHelloReceived, so if that flag
// is absent at handshake time we have a resumed session.
// is absent at handshake time we have a resumed session. Check this before
// PreliminaryHandshakeDone() because that function also sets that flag.
bool isResumedSession = !(infoObject->GetFirstServerHelloReceived());
// This is the first callback on resumption handshakes
infoObject->SetFirstServerHelloReceived();
// Do the bookkeeping that needs to be done after the
// server's ServerHello...ServerHelloDone have been processed, but that doesn't
// need the handshake to be completed.
PreliminaryHandshakeDone(fd);
// If the handshake completed, then we know the site is TLS tolerant (if this
// was a TLS connection).
@ -930,21 +1113,6 @@ void HandshakeCallback(PRFileDesc* fd, void* client_data) {
status->mSecretKeyLength = encryptBits;
status->mCipherName.Assign(cipherName);
// Get the NPN value.
SSLNextProtoState state;
unsigned char npnbuf[256];
unsigned int npnlen;
if (SSL_GetNextProto(fd, &state, npnbuf, &npnlen, 256) == SECSuccess) {
if (state == SSL_NEXT_PROTO_NEGOTIATED)
infoObject->SetNegotiatedNPN(reinterpret_cast<char *>(npnbuf), npnlen);
else
infoObject->SetNegotiatedNPN(nullptr, 0);
mozilla::Telemetry::Accumulate(Telemetry::SSL_NPN_TYPE, state);
}
else
infoObject->SetNegotiatedNPN(nullptr, 0);
SSLChannelInfo channelInfo;
if (SSL_GetChannelInfo(fd, &channelInfo, sizeof(channelInfo)) == SECSuccess) {
// Get the protocol version for telemetry
@ -954,13 +1122,16 @@ void HandshakeCallback(PRFileDesc* fd, void* client_data) {
SSLCipherSuiteInfo cipherInfo;
if (SSL_GetCipherSuiteInfo(channelInfo.cipherSuite, &cipherInfo,
sizeof (cipherInfo)) == SECSuccess) {
sizeof (cipherInfo)) == SECSuccess) {
// keyExchange null=0, rsa=1, dh=2, fortezza=3, ecdh=4
Telemetry::Accumulate(Telemetry::SSL_KEY_EXCHANGE_ALGORITHM,
cipherInfo.keaType);
infoObject->SetKEAUsed(cipherInfo.keaType);
infoObject->SetSymmetricCipherUsed(cipherInfo.symCipher);
}
}
infoObject->NoteTimeUntilReady();
infoObject->SetHandshakeCompleted(isResumedSession);
PORT_Free(cipherName);

View File

@ -23,6 +23,8 @@ char*
PK11PasswordPrompt(PK11SlotInfo *slot, PRBool retry, void* arg);
void HandshakeCallback(PRFileDesc *fd, void *client_data);
SECStatus CanFalseStartCallback(PRFileDesc* fd, void* client_data,
PRBool *canFalseStart);
SECStatus RegisterMyOCSPAIAInfoCallback();
SECStatus UnregisterMyOCSPAIAInfoCallback();

View File

@ -83,12 +83,18 @@ nsNSSSocketInfo::nsNSSSocketInfo(SharedSSLState& aState, uint32_t providerFlags)
mHandshakeInProgress(false),
mAllowTLSIntoleranceTimeout(true),
mRememberClientAuthCertificate(false),
mPreliminaryHandshakeDone(false),
mHandshakeStartTime(0),
mFirstServerHelloReceived(false),
mNPNCompleted(false),
mHandshakeCompleted(false),
mJoined(false),
mSentClientCert(false),
mNotedTimeUntilReady(false),
mKEAUsed(nsISSLSocketControl::KEY_EXCHANGE_UNKNOWN),
mKEAExpected(nsISSLSocketControl::KEY_EXCHANGE_UNKNOWN),
mSymmetricCipherUsed(nsISSLSocketControl::SYMMETRIC_CIPHER_UNKNOWN),
mSymmetricCipherExpected(nsISSLSocketControl::SYMMETRIC_CIPHER_UNKNOWN),
mProviderFlags(providerFlags),
mSocketCreationTimestamp(TimeStamp::Now()),
mPlaintextBytesRead(0)
@ -106,6 +112,48 @@ nsNSSSocketInfo::GetProviderFlags(uint32_t* aProviderFlags)
return NS_OK;
}
NS_IMETHODIMP
nsNSSSocketInfo::GetKEAUsed(int16_t *aKea)
{
*aKea = mKEAUsed;
return NS_OK;
}
NS_IMETHODIMP
nsNSSSocketInfo::GetKEAExpected(int16_t *aKea)
{
*aKea = mKEAExpected;
return NS_OK;
}
NS_IMETHODIMP
nsNSSSocketInfo::SetKEAExpected(int16_t aKea)
{
mKEAExpected = aKea;
return NS_OK;
}
NS_IMETHODIMP
nsNSSSocketInfo::GetSymmetricCipherUsed(int16_t *aSymmetricCipher)
{
*aSymmetricCipher = mSymmetricCipherUsed;
return NS_OK;
}
NS_IMETHODIMP
nsNSSSocketInfo::GetSymmetricCipherExpected(int16_t *aSymmetricCipher)
{
*aSymmetricCipher = mSymmetricCipherExpected;
return NS_OK;
}
NS_IMETHODIMP
nsNSSSocketInfo::SetSymmetricCipherExpected(int16_t aSymmetricCipher)
{
mSymmetricCipherExpected = aSymmetricCipher;
return NS_OK;
}
nsresult
nsNSSSocketInfo::GetHandshakePending(bool *aHandshakePending)
{
@ -197,12 +245,27 @@ getSecureBrowserUI(nsIInterfaceRequestor * callbacks,
}
#endif
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());
PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
("[%p] nsNSSSocketInfo::NoteTimeUntilReady\n", mFd));
}
void
nsNSSSocketInfo::SetHandshakeCompleted(bool aResumedSession)
{
if (!mHandshakeCompleted) {
// This will include TCP and proxy tunnel wait time
Telemetry::AccumulateTimeDelta(Telemetry::SSL_TIME_UNTIL_READY,
Telemetry::AccumulateTimeDelta(Telemetry::SSL_TIME_UNTIL_HANDSHAKE_FINISHED,
mSocketCreationTimestamp, TimeStamp::Now());
// If the handshake is completed for the first time from just 1 callback
@ -220,6 +283,9 @@ nsNSSSocketInfo::SetHandshakeCompleted(bool aResumedSession)
}
mHandshakeCompleted = true;
PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
("[%p] nsNSSSocketInfo::SetHandshakeCompleted\n", (void*)mFd));
}
}
@ -1095,6 +1161,8 @@ nsSSLIOLayerHelpers::nsSSLIOLayerHelpers()
, mRenegoUnrestrictedSites(nullptr)
, mTreatUnsafeNegotiationAsBroken(false)
, mWarnLevelMissingRFC5746(1)
, mFalseStartRequireNPN(true)
, mFalseStartRequireForwardSecrecy(false)
{
}
@ -1285,6 +1353,12 @@ PrefObserver::Observe(nsISupports *aSubject, const char *aTopic,
int32_t warnLevel = 1;
Preferences::GetInt("security.ssl.warn_missing_rfc5746", &warnLevel);
mOwner->setWarnLevelMissingRFC5746(warnLevel);
} else if (prefName.Equals("security.ssl.false_start.require-npn")) {
Preferences::GetBool("security.ssl.false_start.require-npn",
&mOwner->mFalseStartRequireNPN);
} else if (prefName.Equals("security.ssl.false_start.require-forward-secrecy")) {
Preferences::GetBool("security.ssl.false_start.require-forward-secrecy",
&mOwner->mFalseStartRequireForwardSecrecy);
}
}
return NS_OK;
@ -1312,6 +1386,8 @@ nsSSLIOLayerHelpers::~nsSSLIOLayerHelpers()
Preferences::RemoveObserver(mPrefObserver, "security.ssl.renego_unrestricted_hosts");
Preferences::RemoveObserver(mPrefObserver, "security.ssl.treat_unsafe_negotiation_as_broken");
Preferences::RemoveObserver(mPrefObserver, "security.ssl.warn_missing_rfc5746");
Preferences::RemoveObserver(mPrefObserver, "security.ssl.false_start.require-npn");
Preferences::RemoveObserver(mPrefObserver, "security.ssl.false_start.require-forward-secrecy");
}
nsresult nsSSLIOLayerHelpers::Init()
@ -1386,6 +1462,11 @@ nsresult nsSSLIOLayerHelpers::Init()
Preferences::GetInt("security.ssl.warn_missing_rfc5746", &warnLevel);
setWarnLevelMissingRFC5746(warnLevel);
Preferences::GetBool("security.ssl.false_start.require-npn",
&mFalseStartRequireNPN);
Preferences::GetBool("security.ssl.false_start.require-forward-secrecy",
&mFalseStartRequireForwardSecrecy);
mPrefObserver = new PrefObserver(this);
Preferences::AddStrongObserver(mPrefObserver,
"security.ssl.renego_unrestricted_hosts");
@ -1393,7 +1474,10 @@ nsresult nsSSLIOLayerHelpers::Init()
"security.ssl.treat_unsafe_negotiation_as_broken");
Preferences::AddStrongObserver(mPrefObserver,
"security.ssl.warn_missing_rfc5746");
Preferences::AddStrongObserver(mPrefObserver,
"security.ssl.false_start.require-npn");
Preferences::AddStrongObserver(mPrefObserver,
"security.ssl.false_start.require-forward-secrecy");
return NS_OK;
}
@ -2479,6 +2563,7 @@ nsSSLIOLayerImportFD(PRFileDesc *fd,
}
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;

View File

@ -61,6 +61,7 @@ public:
void SetNegotiatedNPN(const char *value, uint32_t length);
void SetHandshakeCompleted(bool aResumedSession);
void NoteTimeUntilReady();
bool GetJoined() { return mJoined; }
void SetSentClientCert() { mSentClientCert = true; }
@ -93,6 +94,30 @@ public:
void SetTLSEnabled(bool enabled) { mTLSEnabled = enabled; }
void AddPlaintextBytesRead(uint64_t val) { mPlaintextBytesRead += val; }
bool IsPreliminaryHandshakeDone() const { return mPreliminaryHandshakeDone; }
void SetPreliminaryHandshakeDone() { mPreliminaryHandshakeDone = true; }
void SetKEAUsed(PRUint16 kea) { mKEAUsed = kea; }
inline int16_t GetKEAExpected() // infallible in nsISSLSocketControl
{
int16_t result;
mozilla::DebugOnly<nsresult> rv = GetKEAExpected(&result);
MOZ_ASSERT(NS_SUCCEEDED(rv));
return result;
}
void SetSymmetricCipherUsed(PRUint16 symmetricCipher)
{
mSymmetricCipherUsed = symmetricCipher;
}
inline int16_t GetSymmetricCipherExpected() // infallible in nsISSLSocketControl
{
int16_t result;
mozilla::DebugOnly<nsresult> rv = GetSymmetricCipherExpected(&result);
MOZ_ASSERT(NS_SUCCEEDED(rv));
return result;
}
private:
PRFileDesc* mFd;
@ -107,6 +132,7 @@ private:
bool mHandshakeInProgress;
bool mAllowTLSIntoleranceTimeout;
bool mRememberClientAuthCertificate;
bool mPreliminaryHandshakeDone; // after false start items are complete
PRIntervalTime mHandshakeStartTime;
bool mFirstServerHelloReceived;
@ -117,6 +143,14 @@ private:
bool mHandshakeCompleted;
bool mJoined;
bool mSentClientCert;
bool mNotedTimeUntilReady;
// mKEA* and mSymmetricCipher* are used in false start detetermination
// values are from nsISSLSocketControl
PRInt16 mKEAUsed;
PRInt16 mKEAExpected;
PRInt16 mSymmetricCipherUsed;
PRInt16 mSymmetricCipherExpected;
uint32_t mProviderFlags;
mozilla::TimeStamp mSocketCreationTimestamp;
@ -164,6 +198,9 @@ public:
bool isRenegoUnrestrictedSite(const nsCString &str);
void clearStoredData();
bool mFalseStartRequireNPN;
bool mFalseStartRequireForwardSecrecy;
private:
nsCOMPtr<nsIObserver> mPrefObserver;
};

View File

@ -843,6 +843,12 @@
"extended_statistics_ok": true,
"description": "ms of SSL wait time including TCP and proxy tunneling"
},
"SSL_TIME_UNTIL_HANDSHAKE_FINISHED": {
"kind": "exponential",
"high": "60000",
"n_buckets": 200,
"description": "ms of SSL wait time for full handshake including TCP and proxy tunneling"
},
"SSL_BYTES_BEFORE_CERT_CALLBACK": {
"kind": "exponential",
"high": "32000",