Bug 839310: Expand TLS intolerance logic to work for versions beyond TLS 1.0, r=keeler, r=wtc
authorBrian Smith <brian@briansmith.org>
Fri, 11 Oct 2013 00:17:19 -0700
changeset 164605 5db1f111ac1972307ec97a6c4819cf5811bff503
parent 164604 3b75e10dac5d70c39d2c0f613873c191a4e1aa58
child 164606 7664c5abfdbd463ee71c1e1e561d216979f0f255
push id3066
push userakeybl@mozilla.com
push dateMon, 09 Dec 2013 19:58:46 +0000
treeherdermozilla-beta@a31a0dce83aa [default view] [failures only]
perfherder[talos] [build metrics] [platform microbench] (compared to previous push)
reviewerskeeler, wtc
bugs839310
milestone27.0a1
first release with
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
last release without
nightly linux32
nightly linux64
nightly mac
nightly win32
nightly win64
Bug 839310: Expand TLS intolerance logic to work for versions beyond TLS 1.0, r=keeler, r=wtc
security/manager/ssl/src/SSLServerCertVerification.cpp
security/manager/ssl/src/TransportSecurityInfo.h
security/manager/ssl/src/nsNSSCallbacks.cpp
security/manager/ssl/src/nsNSSIOLayer.cpp
security/manager/ssl/src/nsNSSIOLayer.h
security/manager/ssl/tests/gtest/Makefile.in
security/manager/ssl/tests/gtest/TLSIntoleranceTest.cpp
security/manager/ssl/tests/gtest/moz.build
security/manager/ssl/tests/moz.build
security/manager/ssl/tests/unit/moz.build
toolkit/library/Makefile.in
--- a/security/manager/ssl/src/SSLServerCertVerification.cpp
+++ b/security/manager/ssl/src/SSLServerCertVerification.cpp
@@ -294,33 +294,33 @@ CertErrorRunnable::CheckCertOverrides()
     return new SSLServerCertVerificationResult(mInfoObject,
                                                mDefaultErrorCodeToReport);
   }
 
   int32_t port;
   mInfoObject->GetPort(&port);
 
   nsCString hostWithPortString;
-  hostWithPortString.AppendASCII(mInfoObject->GetHostName());
+  hostWithPortString.AppendASCII(mInfoObject->GetHostNameRaw());
   hostWithPortString.AppendLiteral(":");
   hostWithPortString.AppendInt(port);
 
   uint32_t remaining_display_errors = mCollectedErrors;
 
   nsresult nsrv;
 
   // Enforce Strict-Transport-Security for hosts that are "STS" hosts:
   // connections must be dropped when there are any certificate errors
   // (STS Spec section 7.3).
   bool strictTransportSecurityEnabled = false;
   nsCOMPtr<nsISiteSecurityService> sss
     = do_GetService(NS_SSSERVICE_CONTRACTID, &nsrv);
   if (NS_SUCCEEDED(nsrv)) {
     nsrv = sss->IsSecureHost(nsISiteSecurityService::HEADER_HSTS,
-                             mInfoObject->GetHostName(),
+                             mInfoObject->GetHostNameRaw(),
                              mProviderFlags,
                              &strictTransportSecurityEnabled);
   }
   if (NS_FAILED(nsrv)) {
     return new SSLServerCertVerificationResult(mInfoObject,
                                                mDefaultErrorCodeToReport);
   }
 
@@ -408,17 +408,17 @@ CertErrorRunnable::CheckCertOverrides()
   SSLServerCertVerificationResult *result = 
     new SSLServerCertVerificationResult(mInfoObject, 
                                         errorCodeToReport,
                                         Telemetry::HistogramCount,
                                         -1,
                                         OverridableCertErrorMessage);
 
   LogInvalidCertError(mInfoObject,
-                      nsDependentCString(mInfoObject->GetHostName()),
+                      mInfoObject->GetHostName(),
                       hostWithPortString,
                       port,
                       result->mErrorCode,
                       result->mErrorMessageType,
                       mCert);
 
   return result;
 }
@@ -505,17 +505,17 @@ CreateCertErrorRunnable(PRErrorCode defa
   uint32_t collected_errors = 0;
 
   if (infoObject->IsCertIssuerBlacklisted()) {
     collected_errors |= nsICertOverrideService::ERROR_UNTRUSTED;
     errorCodeTrust = defaultErrorCodeToReport;
   }
 
   // Check the name field against the desired hostname.
-  if (CERT_VerifyCertName(cert, infoObject->GetHostName()) != SECSuccess) {
+  if (CERT_VerifyCertName(cert, infoObject->GetHostNameRaw()) != SECSuccess) {
     collected_errors |= nsICertOverrideService::ERROR_MISMATCH;
     errorCodeMismatch = SSL_ERROR_BAD_CERT_DOMAIN;
   }
 
   CERTVerifyLogNode *i_node;
   for (i_node = verify_log->head; i_node; i_node = i_node->next)
   {
     switch (i_node->error)
@@ -878,17 +878,17 @@ AuthCertificate(TransportSecurityInfo * 
                                                infoObject);
     if (rv != SECSuccess) {
       return rv;
     }
   }
 
   CERTCertList *verifyCertChain = nullptr;
   SECOidTag evOidPolicy;
-  rv = PSM_SSL_PKIX_AuthCertificate(cert, infoObject, infoObject->GetHostName(),
+  rv = PSM_SSL_PKIX_AuthCertificate(cert, infoObject, infoObject->GetHostNameRaw(),
                                     &verifyCertChain, &evOidPolicy);
 
   // We want to remember the CA certs in the temp db, so that the application can find the
   // complete chain at any time it might need it.
   // But we keep only those CA certs in the temp db, that we didn't already know.
 
   RefPtr<nsSSLStatus> status(infoObject->SSLStatus());
   RefPtr<nsNSSCertificate> nsc;
--- a/security/manager/ssl/src/TransportSecurityInfo.h
+++ b/security/manager/ssl/src/TransportSecurityInfo.h
@@ -44,19 +44,19 @@ public:
   NS_DECL_NSISSLSTATUSPROVIDER
   NS_DECL_NSIASSOCIATEDCONTENTSECURITY
   NS_DECL_NSISERIALIZABLE
   NS_DECL_NSICLASSINFO
 
   nsresult SetSecurityState(uint32_t aState);
   nsresult SetShortSecurityDescription(const PRUnichar *aText);
 
-  const char * GetHostName() const {
-    return mHostName.get();
-  }
+  const nsACString & GetHostName() const { return mHostName; }
+  const char * GetHostNameRaw() const { return mHostName.get(); }
+
   nsresult GetHostName(char **aHostName);
   nsresult SetHostName(const char *aHostName);
 
   int32_t GetPort() const { return mPort; }
   nsresult GetPort(int32_t *aPort);
   nsresult SetPort(int32_t aPort);
 
   PRErrorCode GetErrorCode() const;
--- a/security/manager/ssl/src/nsNSSCallbacks.cpp
+++ b/security/manager/ssl/src/nsNSSCallbacks.cpp
@@ -1027,26 +1027,35 @@ void HandshakeCallback(PRFileDesc* fd, v
   SECStatus rv;
 
   nsNSSSocketInfo* infoObject = (nsNSSSocketInfo*) fd->higher->secret;
 
   // certificate validation sets FirstServerHelloReceived, so if that flag
   // 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());
-
   // 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).
-  nsSSLIOLayerHelpers& ioLayerHelpers = infoObject->SharedState().IOLayerHelpers();
-  ioLayerHelpers.rememberTolerantSite(infoObject);
+  nsSSLIOLayerHelpers& ioLayerHelpers
+    = infoObject->SharedState().IOLayerHelpers();
+
+  SSLVersionRange versions(infoObject->GetTLSVersionRange());
+
+  PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
+         ("[%p] HandshakeCallback: succeeded using TLS version range (0x%04x,0x%04x)\n",
+          fd, static_cast<unsigned int>(versions.min),
+              static_cast<unsigned int>(versions.max)));
+
+  // If the handshake completed, then we know the site is TLS tolerant
+  ioLayerHelpers.rememberTolerantAtVersion(infoObject->GetHostName(),
+                                           infoObject->GetPort(),
+                                           versions.max);
 
   PRBool siteSupportsSafeRenego;
   rv = SSL_HandshakeNegotiatedExtension(fd, ssl_renegotiation_info_xtn,
                                         &siteSupportsSafeRenego);
   MOZ_ASSERT(rv == SECSuccess);
   if (rv != SECSuccess) {
     siteSupportsSafeRenego = false;
   }
--- a/security/manager/ssl/src/nsNSSIOLayer.cpp
+++ b/security/manager/ssl/src/nsNSSIOLayer.cpp
@@ -59,32 +59,45 @@ using namespace mozilla::psm;
                        //file.
 
 namespace {
 
 NSSCleanupAutoPtrClass(void, PR_FREEIF)
 
 static NS_DEFINE_CID(kNSSComponentCID, NS_NSSCOMPONENT_CID);
 
+void
+getSiteKey(const nsACString & hostName, uint16_t port,
+           /*out*/ nsCSubstring & key)
+{
+  key = hostName;
+  key.AppendASCII(":");
+  key.AppendInt(port);
+}
+
+void
+getSiteKey(const nsNSSSocketInfo & socketInfo, /*out*/ nsCSubstring & key)
+{
+  getSiteKey(socketInfo.GetHostName(), socketInfo.GetPort(), key);
+}
+
 /* SSM_UserCertChoice: enum for cert choice info */
 typedef enum {ASK, AUTO} SSM_UserCertChoice;
 
 } // unnamed namespace
 
 #ifdef PR_LOGGING
 extern PRLogModuleInfo* gPIPNSSLog;
 #endif
 
 nsNSSSocketInfo::nsNSSSocketInfo(SharedSSLState& aState, uint32_t providerFlags)
   : mFd(nullptr),
     mCertVerificationState(before_cert_verification),
     mSharedState(aState),
     mForSTARTTLS(false),
-    mSSL3Enabled(false),
-    mTLSEnabled(false),
     mHandshakePending(true),
     mHasCleartextPhase(false),
     mHandshakeInProgress(false),
     mAllowTLSIntoleranceTimeout(true),
     mRememberClientAuthCertificate(false),
     mPreliminaryHandshakeDone(false),
     mHandshakeStartTime(0),
     mFirstServerHelloReceived(false),
@@ -96,16 +109,18 @@ nsNSSSocketInfo::nsNSSSocketInfo(SharedS
     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)
 {
+  mTLSVersionRange.min = 0;
+  mTLSVersionRange.max = 0;
 }
 
 NS_IMPL_ISUPPORTS_INHERITED2(nsNSSSocketInfo, TransportSecurityInfo,
                              nsISSLSocketControl,
                              nsIClientAuthUserDecision)
 
 NS_IMETHODIMP
 nsNSSSocketInfo::GetProviderFlags(uint32_t* aProviderFlags)
@@ -324,17 +339,17 @@ nsNSSSocketInfo::JoinConnection(const ns
     return NS_OK;
 
   // Make sure NPN has been completed and matches requested npnProtocol
   if (!mNPNCompleted || !mNegotiatedNPN.Equals(npnProtocol))
     return NS_OK;
 
   // If this is the same hostname then the certicate status does not
   // need to be considered. They are joinable.
-  if (GetHostName() && hostname.Equals(GetHostName())) {
+  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()->mServerCert)
     return NS_OK;
@@ -589,35 +604,22 @@ bool nsNSSSocketInfo::HandshakeTimeout()
 
   PRIntervalTime now = PR_IntervalNow();
   bool result = (now - mHandshakeStartTime) > handshakeTimeoutInterval;
   return result;
 }
 
 void nsSSLIOLayerHelpers::Cleanup()
 {
-  if (mTLSIntolerantSites) {
-    delete mTLSIntolerantSites;
-    mTLSIntolerantSites = nullptr;
-  }
-
-  if (mTLSTolerantSites) {
-    delete mTLSTolerantSites;
-    mTLSTolerantSites = nullptr;
-  }
+  mTLSIntoleranceInfo.Clear();
 
   if (mRenegoUnrestrictedSites) {
     delete mRenegoUnrestrictedSites;
     mRenegoUnrestrictedSites = nullptr;
   }
-
-  if (mutex) {
-    delete mutex;
-    mutex = nullptr;
-  }
 }
 
 static void
 nsHandleSSLError(nsNSSSocketInfo *socketInfo, 
                  ::mozilla::psm::SSLErrorMessageType errtype, 
                  PRErrorCode err)
 {
   if (!NS_IsMainThread()) {
@@ -636,33 +638,28 @@ nsHandleSSLError(nsNSSSocketInfo *socket
   }
 
   nsresult rv;
   NS_DEFINE_CID(nssComponentCID, NS_NSSCOMPONENT_CID);
   nsCOMPtr<nsINSSComponent> nssComponent(do_GetService(nssComponentCID, &rv));
   if (NS_FAILED(rv))
     return;
 
-  nsXPIDLCString hostName;
-  socketInfo->GetHostName(getter_Copies(hostName));
-
-  int32_t port;
-  socketInfo->GetPort(&port);
-
   // Try to get a nsISSLErrorListener implementation from the socket consumer.
   nsCOMPtr<nsIInterfaceRequestor> cb;
   socketInfo->GetNotificationCallbacks(getter_AddRefs(cb));
   if (cb) {
     nsCOMPtr<nsISSLErrorListener> sel = do_GetInterface(cb);
     if (sel) {
       nsIInterfaceRequestor *csi = static_cast<nsIInterfaceRequestor*>(socketInfo);
-      nsCString hostWithPortString = hostName;
-      hostWithPortString.AppendLiteral(":");
-      hostWithPortString.AppendInt(port);
-    
+
+      nsCString hostWithPortString;
+      getSiteKey(socketInfo->GetHostName(), socketInfo->GetPort(),
+                 hostWithPortString);
+
       bool suppressMessage = false; // obsolete, ignored
       rv = sel->NotifySSLError(csi, err, hostWithPortString, &suppressMessage);
     }
   }
   
   // We must cancel first, which sets the error code.
   socketInfo->SetCanceled(err, PlainErrorMessage);
   nsXPIDLString errorString;
@@ -734,68 +731,114 @@ nsSSLIOLayerConnect(PRFileDesc* fd, cons
     return status;
   }
 
   PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] Connect\n", (void*)fd));
   return status;
 }
 
 void
-nsSSLIOLayerHelpers::getSiteKey(nsNSSSocketInfo *socketInfo, nsCSubstring &key)
+nsSSLIOLayerHelpers::rememberTolerantAtVersion(const nsACString & hostName,
+                                               int16_t port, uint16_t tolerant)
 {
-  int32_t port;
-  socketInfo->GetPort(&port);
-
-  nsXPIDLCString host;
-  socketInfo->GetHostName(getter_Copies(host));
-
-  key = host + NS_LITERAL_CSTRING(":") + nsPrintfCString("%d", port);
+  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;
+    }
+  } else {
+    entry.tolerant = tolerant;
+    entry.intolerant = 0;
+  }
+
+  entry.AssertInvariant();
+
+  mTLSIntoleranceInfo.Put(key, entry);
 }
 
-// Call this function to report a site that is possibly TLS intolerant.
-// This function will return true, if the given socket is currently using TLS,
-// and it's allowed to retry. Retrying only makes sense if an older
-// protocol is enabled.
+// returns true if we should retry the handshake
 bool
-nsSSLIOLayerHelpers::rememberPossibleTLSProblemSite(nsNSSSocketInfo *socketInfo)
+nsSSLIOLayerHelpers::rememberIntolerantAtVersion(const nsACString & hostName,
+                                                 int16_t port,
+                                                 uint16_t minVersion,
+                                                 uint16_t intolerant)
 {
-  nsAutoCString key;
-  getSiteKey(socketInfo, key);
-
-  if (!socketInfo->IsTLSEnabled()) {
-    // We did not offer TLS but failed with an intolerant error using
-    // a different protocol. To give TLS a try on next connection attempt again
-    // drop this site from the list of intolerant sites. TLS failure might be 
-    // caused only by a traffic congestion while the server is TLS tolerant.
-    removeIntolerantSite(key);
+  nsCString key;
+  getSiteKey(hostName, port, key);
+
+  MutexAutoLock lock(mutex);
+
+  if (intolerant <= minVersion) {
+    // We can't fall back any further. Assume that intolerance isn't the issue.
+    IntoleranceEntry entry;
+    if (mTLSIntoleranceInfo.Get(key, &entry)) {
+      entry.AssertInvariant();
+      entry.intolerant = 0;
+      entry.AssertInvariant();
+      mTLSIntoleranceInfo.Put(key, entry);
+    }
+
     return false;
   }
 
-  if (socketInfo->IsSSL3Enabled()) {
-    // Add this site to the list of TLS intolerant sites.
-    addIntolerantSite(key);
+  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;
   }
-  else {
-    return false; // doesn't make sense to retry
-  }
-  
-  return socketInfo->IsTLSEnabled();
+
+  entry.intolerant = intolerant;
+  entry.AssertInvariant();
+  mTLSIntoleranceInfo.Put(key, entry);
+
+  return true;
 }
 
 void
-nsSSLIOLayerHelpers::rememberTolerantSite(nsNSSSocketInfo *socketInfo)
+nsSSLIOLayerHelpers::adjustForTLSIntolerance(const nsACString & hostName,
+                                             int16_t port,
+                                             /*in/out*/ SSLVersionRange & range)
 {
-  if (!socketInfo->IsTLSEnabled())
-    return;
-
-  nsAutoCString key;
-  getSiteKey(socketInfo, key);
-
-  MutexAutoLock lock(*mutex);
-  nsSSLIOLayerHelpers::mTLSTolerantSites->PutEntry(key);
+  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;
+    }
+  }
 }
 
 bool nsSSLIOLayerHelpers::nsSSLIOLayerInitialized = false;
 PRDescIdentity nsSSLIOLayerHelpers::nsSSLIOLayerIdentity;
 PRDescIdentity nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity;
 PRIOMethods nsSSLIOLayerHelpers::nsSSLIOLayerMethods;
 PRIOMethods nsSSLIOLayerHelpers::nsSSLPlaintextLayerMethods;
 
@@ -1032,18 +1075,21 @@ int32_t checkHandshake(int32_t bytesTran
       if (PR_WOULD_BLOCK_ERROR == err) {
         socketInfo->SetHandshakeInProgress(true);
         return bytesTransfered;
       }
 
       if (!wantRetry // no decision yet
           && isTLSIntoleranceError(err, socketInfo->GetHasCleartextPhase()))
       {
-        nsSSLIOLayerHelpers& helpers = socketInfo->SharedState().IOLayerHelpers();
-        wantRetry = helpers.rememberPossibleTLSProblemSite(socketInfo);
+        SSLVersionRange range = socketInfo->GetTLSVersionRange();
+        wantRetry = socketInfo->SharedState().IOLayerHelpers()
+                      .rememberIntolerantAtVersion(socketInfo->GetHostName(),
+                                                   socketInfo->GetPort(),
+                                                   range.min, range.max);
       }
     }
     
     // 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
@@ -1061,23 +1107,29 @@ int32_t checkHandshake(int32_t bytesTran
   }
   else if (wasReading && 0 == bytesTransfered) // zero bytes on reading, socket closed
   {
     if (handleHandshakeResultNow)
     {
       if (!wantRetry // no decision yet
           && !socketInfo->GetHasCleartextPhase()) // mirror PR_CONNECT_RESET_ERROR treament
       {
-        nsSSLIOLayerHelpers& helpers = socketInfo->SharedState().IOLayerHelpers();
-        wantRetry = helpers.rememberPossibleTLSProblemSite(socketInfo);
+        SSLVersionRange range = socketInfo->GetTLSVersionRange();
+        wantRetry = socketInfo->SharedState().IOLayerHelpers()
+                        .rememberIntolerantAtVersion(socketInfo->GetHostName(),
+                                                     socketInfo->GetPort(),
+                                                     range.min, range.max);
       }
     }
   }
 
   if (wantRetry) {
+    PR_LOG(gPIPNSSLog, PR_LOG_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.
     PR_SetError(PR_CONNECT_RESET_ERROR, 0);
     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
@@ -1148,22 +1200,21 @@ nsSSLIOLayerPoll(PRFileDesc * fd, int16_
   // cert validation is complete.
   int16_t result = fd->lower->methods->poll(fd->lower, in_flags, out_flags);
   PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, ("[%p] poll SSL socket returned %d\n",
                                     (void*)fd, (int) result));
   return result;
 }
 
 nsSSLIOLayerHelpers::nsSSLIOLayerHelpers()
-: mutex(nullptr)
-, mTLSIntolerantSites(nullptr)
-, mTLSTolerantSites(nullptr)
+: mutex("nsSSLIOLayerHelpers.mutex")
 , mRenegoUnrestrictedSites(nullptr)
 , mTreatUnsafeNegotiationAsBroken(false)
 , mWarnLevelMissingRFC5746(1)
+, mTLSIntoleranceInfo(16)
 , mFalseStartRequireNPN(true)
 , mFalseStartRequireForwardSecrecy(false)
 {
 }
 
 static int _PSM_InvalidInt(void)
 {
     PR_ASSERT(!"I/O method is invalid");
@@ -1376,21 +1427,30 @@ static int32_t PlaintextRecv(PRFileDesc 
 
   if ((bytesRead > 0) && socketInfo)
     socketInfo->AddPlaintextBytesRead(bytesRead);
   return bytesRead;
 }
 
 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");
+  // mPrefObserver will only be set if this->Init was called. The GTest tests
+  // do not call Init.
+  if (mPrefObserver) {
+    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()
 {
   if (!nsSSLIOLayerInitialized) {
     nsSSLIOLayerInitialized = true;
     nsSSLIOLayerIdentity = PR_GetUniqueIdentity("NSS layer");
     nsSSLIOLayerMethods  = *PR_GetDefaultIOMethods();
@@ -1427,26 +1487,17 @@ nsresult nsSSLIOLayerHelpers::Init()
     nsSSLIOLayerMethods.read = nsSSLIOLayerRead;
     nsSSLIOLayerMethods.poll = nsSSLIOLayerPoll;
 
     nsSSLPlaintextLayerIdentity = PR_GetUniqueIdentity("Plaintxext PSM layer");
     nsSSLPlaintextLayerMethods  = *PR_GetDefaultIOMethods();
     nsSSLPlaintextLayerMethods.recv = PlaintextRecv;
   }
 
-  mutex = new Mutex("nsSSLIOLayerHelpers.mutex");
-
-  mTLSIntolerantSites = new nsTHashtable<nsCStringHashKey>(1);
-
-  // Initialize the tolerant site hashtable to 16 items at the start seems
-  // reasonable as most servers are TLS tolerant. We just want to lower 
-  // the rate of hashtable array reallocation.
-  mTLSTolerantSites = new nsTHashtable<nsCStringHashKey>(16);
-
-  mRenegoUnrestrictedSites = new nsTHashtable<nsCStringHashKey>(1);
+  mRenegoUnrestrictedSites = new nsTHashtable<nsCStringHashKey>(16);
 
   nsCString unrestricted_hosts;
   Preferences::GetCString("security.ssl.renego_unrestricted_hosts", &unrestricted_hosts);
   if (!unrestricted_hosts.IsEmpty()) {
     setRenegoUnrestrictedSites(unrestricted_hosts);
   }
 
   bool enabled = false;
@@ -1474,90 +1525,69 @@ nsresult nsSSLIOLayerHelpers::Init()
   Preferences::AddStrongObserver(mPrefObserver,
                                  "security.ssl.false_start.require-forward-secrecy");
   return NS_OK;
 }
 
 void nsSSLIOLayerHelpers::clearStoredData()
 {
   mRenegoUnrestrictedSites->Clear();
-  mTLSTolerantSites->Clear();
-  mTLSIntolerantSites->Clear();
-}
-
-void nsSSLIOLayerHelpers::addIntolerantSite(const nsCString &str)
-{
-  MutexAutoLock lock(*mutex);
-  // Remember intolerant site only if it is not known as tolerant
-  if (!mTLSTolerantSites->Contains(str))
-    mTLSIntolerantSites->PutEntry(str);
-}
-
-void nsSSLIOLayerHelpers::removeIntolerantSite(const nsCString &str)
-{
-  MutexAutoLock lock(*mutex);
-  mTLSIntolerantSites->RemoveEntry(str);
-}
-
-bool nsSSLIOLayerHelpers::isKnownAsIntolerantSite(const nsCString &str)
-{
-  MutexAutoLock lock(*mutex);
-  return mTLSIntolerantSites->Contains(str);
+  mTLSIntoleranceInfo.Clear();
 }
 
 void nsSSLIOLayerHelpers::setRenegoUnrestrictedSites(const nsCString &str)
 {
-  MutexAutoLock lock(*mutex);
+  MutexAutoLock lock(mutex);
   
   if (mRenegoUnrestrictedSites) {
     delete mRenegoUnrestrictedSites;
     mRenegoUnrestrictedSites = nullptr;
   }
 
-  mRenegoUnrestrictedSites = new nsTHashtable<nsCStringHashKey>(1);
+  mRenegoUnrestrictedSites = new nsTHashtable<nsCStringHashKey>();
   if (!mRenegoUnrestrictedSites)
     return;
   
   nsCCharSeparatedTokenizer toker(str, ',');
 
   while (toker.hasMoreTokens()) {
     const nsCSubstring &host = toker.nextToken();
     if (!host.IsEmpty()) {
       mRenegoUnrestrictedSites->PutEntry(host);
     }
   }
 }
 
 bool nsSSLIOLayerHelpers::isRenegoUnrestrictedSite(const nsCString &str)
 {
-  MutexAutoLock lock(*mutex);
+  MutexAutoLock lock(mutex);
   return mRenegoUnrestrictedSites->Contains(str);
 }
 
 void nsSSLIOLayerHelpers::setTreatUnsafeNegotiationAsBroken(bool broken)
 {
-  MutexAutoLock lock(*mutex);
+  MutexAutoLock lock(mutex);
   mTreatUnsafeNegotiationAsBroken = broken;
 }
 
 bool nsSSLIOLayerHelpers::treatUnsafeNegotiationAsBroken()
 {
-  MutexAutoLock lock(*mutex);
+  MutexAutoLock lock(mutex);
   return mTreatUnsafeNegotiationAsBroken;
 }
 
 void nsSSLIOLayerHelpers::setWarnLevelMissingRFC5746(int32_t level)
 {
-  MutexAutoLock lock(*mutex);
+  MutexAutoLock lock(mutex);
   mWarnLevelMissingRFC5746 = level;
 }
 
 int32_t nsSSLIOLayerHelpers::getWarnLevelMissingRFC5746()
 {
-  MutexAutoLock lock(*mutex);
+  MutexAutoLock lock(mutex);
   return mWarnLevelMissingRFC5746;
 }
 
 nsresult
 nsSSLIOLayerNewSocket(int32_t family,
                       const char *host,
                       int32_t port,
                       const char *proxyHost,
@@ -2604,41 +2634,41 @@ nsSSLIOLayerSetOptions(PRFileDesc *fd, b
     infoObject->SetHasCleartextPhase(true);
   }
 
   // Let's see if we're trying to connect to a site we know is
   // TLS intolerant.
   nsAutoCString key;
   key = nsDependentCString(host) + NS_LITERAL_CSTRING(":") + nsPrintfCString("%d", port);
 
-  if (infoObject->SharedState().IOLayerHelpers().isKnownAsIntolerantSite(key)) {
-    if (SECSuccess != SSL_OptionSet(fd, SSL_ENABLE_TLS, false))
-      return NS_ERROR_FAILURE;
-
-    infoObject->SetAllowTLSIntoleranceTimeout(false);
-      
-    // We assume that protocols that use the STARTTLS mechanism should support
-    // modern hellos. For other protocols, if we suspect a site 
-    // does not support TLS, let's also use V2 hellos.
-    // One advantage of this approach, if a site only supports the older
-    // hellos, it is more likely that we will get a reasonable error code
-    // on our single retry attempt.
-  }
-
-  PRBool enabled;
-  if (SECSuccess != SSL_OptionGet(fd, SSL_ENABLE_SSL3, &enabled)) {
+  SSLVersionRange range;
+  if (SSL_VersionRangeGet(fd, &range) != SECSuccess) {
     return NS_ERROR_FAILURE;
   }
-  infoObject->SetSSL3Enabled(enabled);
-  if (SECSuccess != SSL_OptionGet(fd, SSL_ENABLE_TLS, &enabled)) {
+
+  infoObject->SharedState().IOLayerHelpers()
+    .adjustForTLSIntolerance(infoObject->GetHostName(), infoObject->GetPort(),
+                             range);
+  PR_LOG(gPIPNSSLog, PR_LOG_DEBUG,
+         ("[%p] nsSSLIOLayerSetOptions: using TLS version range (0x%04x,0x%04x)\n",
+          fd, static_cast<unsigned int>(range.min),
+              static_cast<unsigned int>(range.max)));
+
+  if (SSL_VersionRangeSet(fd, &range) != SECSuccess) {
     return NS_ERROR_FAILURE;
   }
-  infoObject->SetTLSEnabled(enabled);
-
-  enabled = infoObject->SharedState().IsOCSPStaplingEnabled();
+  infoObject->SetTLSVersionRange(range);
+
+  // If min == max, then we don't need the intolerance timeout since we have no
+  // lower version to fall back to.
+  if (range.min == range.max) {
+    infoObject->SetAllowTLSIntoleranceTimeout(false);
+  }
+
+  bool enabled = infoObject->SharedState().IsOCSPStaplingEnabled();
   if (SECSuccess != SSL_OptionSet(fd, SSL_ENABLE_OCSP_STAPLING, enabled)) {
     return NS_ERROR_FAILURE;
   }
 
   if (SECSuccess != SSL_OptionSet(fd, SSL_HANDSHAKE_AS_CLIENT, true)) {
     return NS_ERROR_FAILURE;
   }
 
--- a/security/manager/ssl/src/nsNSSIOLayer.h
+++ b/security/manager/ssl/src/nsNSSIOLayer.h
@@ -9,16 +9,17 @@
 
 #include "TransportSecurityInfo.h"
 #include "nsISSLSocketControl.h"
 #include "nsIClientAuthDialogs.h"
 #include "nsNSSCertificate.h"
 #include "nsDataHashtable.h"
 #include "nsTHashtable.h"
 #include "mozilla/TimeStamp.h"
+#include "sslt.h"
 
 namespace mozilla {
 namespace psm {
 class SharedSSLState;
 }
 }
 
 class nsIObserver;
@@ -51,16 +52,19 @@ public:
   void SetHandshakeInProgress(bool aIsIn);
   bool GetHandshakeInProgress() { return mHandshakeInProgress; }
   void SetFirstServerHelloReceived() { mFirstServerHelloReceived = true; }
   bool GetFirstServerHelloReceived() { return mFirstServerHelloReceived; }
   bool HandshakeTimeout();
 
   void SetAllowTLSIntoleranceTimeout(bool aAllow);
 
+  void SetTLSVersionRange(SSLVersionRange range) { mTLSVersionRange = range; }
+  SSLVersionRange GetTLSVersionRange() const { return mTLSVersionRange; };
+
   PRStatus CloseSocketAndDestroy(
                 const nsNSSShutDownPreventionLock & proofOfLock);
   
   void SetNegotiatedNPN(const char *value, uint32_t length);
   void SetHandshakeCompleted(bool aResumedSession);
   void NoteTimeUntilReady();
 
   bool GetJoined() { return mJoined; }
@@ -82,22 +86,16 @@ public:
   void SetCertVerificationResult(PRErrorCode errorCode,
               ::mozilla::psm::SSLErrorMessageType errorMessageType);
   
   // for logging only
   PRBool IsWaitingForCertVerification() const
   {
     return mCertVerificationState == waiting_for_cert_verification;
   }
-
-  bool IsSSL3Enabled() const { return mSSL3Enabled; }
-  void SetSSL3Enabled(bool enabled) { mSSL3Enabled = enabled; }
-  bool IsTLSEnabled() const { return mTLSEnabled; }
-  void SetTLSEnabled(bool enabled) { mTLSEnabled = enabled; }
-
   void AddPlaintextBytesRead(uint64_t val) { mPlaintextBytesRead += val; }
 
   bool IsPreliminaryHandshakeDone() const { return mPreliminaryHandshakeDone; }
   void SetPreliminaryHandshakeDone() { mPreliminaryHandshakeDone = true; }
 
   void SetKEAUsed(uint16_t kea) { mKEAUsed = kea; }
   inline int16_t GetKEAExpected() // infallible in nsISSLSocketControl
   {
@@ -120,18 +118,17 @@ public:
 
 private:
   PRFileDesc* mFd;
 
   CertVerificationState mCertVerificationState;
 
   mozilla::psm::SharedSSLState& mSharedState;
   bool mForSTARTTLS;
-  bool mSSL3Enabled;
-  bool mTLSEnabled;
+  SSLVersionRange mTLSVersionRange;
   bool mHandshakePending;
   bool mHasCleartextPhase;
   bool mHandshakeInProgress;
   bool mAllowTLSIntoleranceTimeout;
   bool mRememberClientAuthCertificate;
   bool mPreliminaryHandshakeDone; // after false start items are complete
   PRIntervalTime mHandshakeStartTime;
   bool mFirstServerHelloReceived;
@@ -167,46 +164,53 @@ public:
   void Cleanup();
 
   static bool nsSSLIOLayerInitialized;
   static PRDescIdentity nsSSLIOLayerIdentity;
   static PRDescIdentity nsSSLPlaintextLayerIdentity;
   static PRIOMethods nsSSLIOLayerMethods;
   static PRIOMethods nsSSLPlaintextLayerMethods;
 
-  mozilla::Mutex *mutex;
-  nsTHashtable<nsCStringHashKey> *mTLSIntolerantSites;
-  nsTHashtable<nsCStringHashKey> *mTLSTolerantSites;
-
   nsTHashtable<nsCStringHashKey> *mRenegoUnrestrictedSites;
   bool mTreatUnsafeNegotiationAsBroken;
   int32_t mWarnLevelMissingRFC5746;
 
   void setTreatUnsafeNegotiationAsBroken(bool broken);
   bool treatUnsafeNegotiationAsBroken();
-
   void setWarnLevelMissingRFC5746(int32_t level);
   int32_t getWarnLevelMissingRFC5746();
 
-  static void getSiteKey(nsNSSSocketInfo *socketInfo, nsCSubstring &key);
-  bool rememberPossibleTLSProblemSite(nsNSSSocketInfo *socketInfo);
-  void rememberTolerantSite(nsNSSSocketInfo *socketInfo);
+private:
+  struct IntoleranceEntry
+  {
+    uint16_t tolerant;
+    uint16_t intolerant;
 
-  void addIntolerantSite(const nsCString &str);
-  void removeIntolerantSite(const nsCString &str);
-  bool isKnownAsIntolerantSite(const nsCString &str);
+    void AssertInvariant() const
+    {
+      MOZ_ASSERT(intolerant == 0 || tolerant < intolerant);
+    }
+  };
+  nsDataHashtable<nsCStringHashKey, IntoleranceEntry> mTLSIntoleranceInfo;
+public:
+  void rememberTolerantAtVersion(const nsACString & hostname, int16_t port,
+                                 uint16_t tolerant);
+  bool rememberIntolerantAtVersion(const nsACString & hostname, int16_t port,
+                                   uint16_t intolerant, uint16_t minVersion);
+  void adjustForTLSIntolerance(const nsACString & hostname, int16_t port,
+                               /*in/out*/ SSLVersionRange & range);
 
   void setRenegoUnrestrictedSites(const nsCString &str);
   bool isRenegoUnrestrictedSite(const nsCString &str);
-
   void clearStoredData();
 
   bool mFalseStartRequireNPN;
   bool mFalseStartRequireForwardSecrecy;
 private:
+  mozilla::Mutex mutex;
   nsCOMPtr<nsIObserver> mPrefObserver;
 };
 
 nsresult nsSSLIOLayerNewSocket(int32_t family,
                                const char *host,
                                int32_t port,
                                const char *proxyHost,
                                int32_t proxyPort,
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/gtest/Makefile.in
@@ -0,0 +1,16 @@
+#! gmake
+# 
+# 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 $(DEPTH)/config/autoconf.mk
+
+EXPORT_LIBRARY = 1
+
+LOCAL_INCLUDES = \
+  -I$(topsrcdir)/security/manager/ssl/src \
+  $(NULL)
+
+include $(topsrcdir)/config/rules.mk
+include $(topsrcdir)/ipc/chromium/chromium-config.mk
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/gtest/TLSIntoleranceTest.cpp
@@ -0,0 +1,145 @@
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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 "sslproto.h"
+
+#include "gtest/gtest.h"
+
+NS_NAMED_LITERAL_CSTRING(HOST, "example.org");
+const int16_t PORT = 443;
+
+class TLSIntoleranceTest : public ::testing::Test
+{
+protected:
+  nsSSLIOLayerHelpers helpers;
+};
+
+TEST_F(TLSIntoleranceTest, Test_1_2_through_3_0)
+{
+  // No adjustment made when there is no entry for the site.
+  {
+    SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0,
+                              SSL_LIBRARY_VERSION_TLS_1_2 };
+    helpers.adjustForTLSIntolerance(HOST, PORT, range);
+    ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.min);
+    ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_2, range.max);
+
+    ASSERT_TRUE(helpers.rememberIntolerantAtVersion(HOST, PORT,
+                                                    range.min, range.max));
+  }
+
+  {
+    SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0,
+                              SSL_LIBRARY_VERSION_TLS_1_2 };
+    helpers.adjustForTLSIntolerance(HOST, PORT, range);
+    ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.min);
+    ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_1, range.max);
+
+    ASSERT_TRUE(helpers.rememberIntolerantAtVersion(HOST, PORT,
+                                                    range.min, range.max));
+  }
+
+  {
+    SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0,
+                              SSL_LIBRARY_VERSION_TLS_1_2 };
+    helpers.adjustForTLSIntolerance(HOST, PORT, range);
+    ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.min);
+    ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_0, range.max);
+
+    ASSERT_TRUE(helpers.rememberIntolerantAtVersion(HOST, PORT,
+                                                    range.min, range.max));
+  }
+
+  {
+    SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0,
+                              SSL_LIBRARY_VERSION_TLS_1_2 };
+
+    helpers.adjustForTLSIntolerance(HOST, PORT, range);
+    ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.min);
+    ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.max);
+
+    // false because we reached the floor set by range.min
+    ASSERT_FALSE(helpers.rememberIntolerantAtVersion(HOST, PORT,
+                                                     range.min, range.max));
+  }
+
+  {
+    SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0,
+                              SSL_LIBRARY_VERSION_TLS_1_2 };
+    helpers.adjustForTLSIntolerance(HOST, PORT, range);
+    ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.min);
+    // When rememberIntolerantAtVersion returns false, it also resets the
+    // intolerance information for the server.
+    ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_2, range.max);
+  }
+}
+
+TEST_F(TLSIntoleranceTest, Test_Tolerant_Overrides_Intolerant_1)
+{
+  ASSERT_TRUE(helpers.rememberIntolerantAtVersion(HOST, PORT,
+                                                  SSL_LIBRARY_VERSION_3_0,
+                                                  SSL_LIBRARY_VERSION_TLS_1_0));
+  helpers.rememberTolerantAtVersion(HOST, PORT, SSL_LIBRARY_VERSION_TLS_1_0);
+  SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0,
+                            SSL_LIBRARY_VERSION_TLS_1_2 };
+  helpers.adjustForTLSIntolerance(HOST, PORT, range);
+  ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.min);
+  ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_0, range.max);
+}
+
+TEST_F(TLSIntoleranceTest, Test_Tolerant_Overrides_Intolerant_2)
+{
+  ASSERT_TRUE(helpers.rememberIntolerantAtVersion(HOST, PORT,
+                                                  SSL_LIBRARY_VERSION_3_0,
+                                                  SSL_LIBRARY_VERSION_TLS_1_0));
+  helpers.rememberTolerantAtVersion(HOST, PORT, SSL_LIBRARY_VERSION_TLS_1_1);
+  SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0,
+                            SSL_LIBRARY_VERSION_TLS_1_2 };
+  helpers.adjustForTLSIntolerance(HOST, PORT, range);
+  ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.min);
+  ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_1, range.max);
+}
+
+TEST_F(TLSIntoleranceTest, Test_Intolerant_Does_Not_Override_Tolerant)
+{
+  // No adjustment made when there is no entry for the site.
+  helpers.rememberTolerantAtVersion(HOST, PORT, SSL_LIBRARY_VERSION_TLS_1_0);
+  // false because we reached the floor set by rememberTolerantAtVersion.
+  ASSERT_FALSE(helpers.rememberIntolerantAtVersion(HOST, PORT,
+                                                   SSL_LIBRARY_VERSION_3_0,
+                                                   SSL_LIBRARY_VERSION_TLS_1_0));
+  SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0,
+                            SSL_LIBRARY_VERSION_TLS_1_2 };
+  helpers.adjustForTLSIntolerance(HOST, PORT, range);
+  ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.min);
+  ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_2, range.max);
+}
+
+TEST_F(TLSIntoleranceTest, Test_Port_Is_Relevant)
+{
+  helpers.rememberTolerantAtVersion(HOST, 1, SSL_LIBRARY_VERSION_TLS_1_2);
+  ASSERT_FALSE(helpers.rememberIntolerantAtVersion(HOST, 1,
+                                                   SSL_LIBRARY_VERSION_3_0,
+                                                   SSL_LIBRARY_VERSION_TLS_1_2));
+  ASSERT_TRUE(helpers.rememberIntolerantAtVersion(HOST, 2,
+                                                  SSL_LIBRARY_VERSION_3_0,
+                                                  SSL_LIBRARY_VERSION_TLS_1_2));
+
+  {
+    SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0,
+                              SSL_LIBRARY_VERSION_TLS_1_2 };
+    helpers.adjustForTLSIntolerance(HOST, 1, range);
+    ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_2, range.max);
+  }
+
+  {
+    SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0,
+                              SSL_LIBRARY_VERSION_TLS_1_2 };
+    helpers.adjustForTLSIntolerance(HOST, 2, range);
+    ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_1, range.max);
+  }
+}
new file mode 100644
--- /dev/null
+++ b/security/manager/ssl/tests/gtest/moz.build
@@ -0,0 +1,15 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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/.
+
+MODULE = 'ssltest'
+
+LIBRARY_NAME = 'ssltest'
+
+LIBXUL_LIBRARY = True
+
+GTEST_CPP_SOURCES += [
+	'TLSIntoleranceTest.cpp',
+]
--- a/security/manager/ssl/tests/moz.build
+++ b/security/manager/ssl/tests/moz.build
@@ -1,12 +1,16 @@
 # -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
 # vim: set filetype=python:
 # 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/.
 
 DIRS += ['unit']
-TEST_DIRS += ['mochitest']
+
+TEST_DIRS += [
+	'gtest',
+	'mochitest',
+]
 
 MODULE = 'pipnss'
 
 XPCSHELL_TESTS_MANIFESTS += ['unit/xpcshell.ini']
--- a/security/manager/ssl/tests/unit/moz.build
+++ b/security/manager/ssl/tests/unit/moz.build
@@ -2,9 +2,8 @@
 # vim: set filetype=python:
 # 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/.
 
 DIRS += ['tlsserver']
 
 MODULE = 'pipnss'
-
--- a/toolkit/library/Makefile.in
+++ b/toolkit/library/Makefile.in
@@ -688,13 +688,14 @@ gtestxul: $(GTEST_LIB)
 	$(MAKE) libs SHARED_LIBRARY_NAME=gtest/$(EFFECTIVE_LIB_PREFIX)$(LIBRARY_NAME) FINAL_TARGET=$(FINAL_TARGET)/gtest SDK_LIBRARY= IMPORT_LIB_DEST=$(IMPORT_LIB_DEST)/gtest LINK_GTEST=true
 endif
 
 ifdef LINK_GTEST
 
 COMPONENT_LIBS += \
   gtest \
   gfxtest \
+  ssltest \
   $(NULL)
 endif
 
 $(FINAL_TARGET)/dependentlibs.list: dependentlibs.py $(SHARED_LIBRARY) $(wildcard $(if $(wildcard $(FINAL_TARGET)/dependentlibs.list),$(addprefix $(FINAL_TARGET)/,$(shell cat $(FINAL_TARGET)/dependentlibs.list))))
 	$(PYTHON) $< $(SHARED_LIBRARY) -L $(FINAL_TARGET) $(if $(TOOLCHAIN_PREFIX),$(addprefix -p ,$(TOOLCHAIN_PREFIX))) > $@