Bug 1075991 - Remember version intolerance reason code, r=keeler
authorMartin Thomson <martin.thomson@gmail.com>
Fri, 03 Oct 2014 11:01:24 -0700
changeset 208773 b1a9b31ab26e28c99b9583a31e5ea5c97f16aa68
parent 208772 3bea361c1f4014e09fdcee60db8b585b104acab9
child 208774 59ef591ec31895fff8d6ca429832e93e94462356
push id1
push userroot
push dateMon, 20 Oct 2014 17:29:22 +0000
reviewerskeeler
bugs1075991
milestone35.0a1
Bug 1075991 - Remember version intolerance reason code, r=keeler
security/manager/ssl/src/nsNSSIOLayer.cpp
security/manager/ssl/src/nsNSSIOLayer.h
security/manager/ssl/tests/gtest/TLSIntoleranceTest.cpp
--- a/security/manager/ssl/src/nsNSSIOLayer.cpp
+++ b/security/manager/ssl/src/nsNSSIOLayer.cpp
@@ -788,45 +788,49 @@ nsSSLIOLayerHelpers::rememberTolerantAtV
   MutexAutoLock lock(mutex);
 
   IntoleranceEntry entry;
   if (mTLSIntoleranceInfo.Get(key, &entry)) {
     entry.AssertInvariant();
     entry.tolerant = std::max(entry.tolerant, tolerant);
     if (entry.intolerant != 0 && entry.intolerant <= entry.tolerant) {
       entry.intolerant = entry.tolerant + 1;
+      entry.intoleranceReason = 0; // lose the reason
     }
   } else {
     entry.tolerant = tolerant;
     entry.intolerant = 0;
+    entry.intoleranceReason = 0;
   }
 
   entry.AssertInvariant();
 
   mTLSIntoleranceInfo.Put(key, entry);
 }
 
 // returns true if we should retry the handshake
 bool
 nsSSLIOLayerHelpers::rememberIntolerantAtVersion(const nsACString& hostName,
                                                  int16_t port,
                                                  uint16_t minVersion,
-                                                 uint16_t intolerant)
+                                                 uint16_t intolerant,
+                                                 PRErrorCode intoleranceReason)
 {
   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.intoleranceReason = 0;
       entry.AssertInvariant();
       mTLSIntoleranceInfo.Put(key, entry);
     }
 
     return false;
   }
 
   IntoleranceEntry entry;
@@ -840,16 +844,17 @@ nsSSLIOLayerHelpers::rememberIntolerantA
       // We already know that the server is intolerant at a lower version.
       return true;
     }
   } else {
     entry.tolerant = 0;
   }
 
   entry.intolerant = intolerant;
+  entry.intoleranceReason = intoleranceReason;
   entry.AssertInvariant();
   mTLSIntoleranceInfo.Put(key, entry);
 
   return true;
 }
 
 void
 nsSSLIOLayerHelpers::adjustForTLSIntolerance(const nsACString& hostName,
@@ -874,16 +879,36 @@ nsSSLIOLayerHelpers::adjustForTLSIntoler
     // We've tried connecting at a higher range but failed, so try at the
     // version we haven't tried yet, unless we have reached the minimum.
     if (range.min < entry.intolerant) {
       range.max = entry.intolerant - 1;
     }
   }
 }
 
+PRErrorCode
+nsSSLIOLayerHelpers::getIntoleranceReason(const nsACString& hostName,
+                                          int16_t port)
+{
+  IntoleranceEntry entry;
+
+  {
+    nsCString key;
+    getSiteKey(hostName, port, key);
+
+    MutexAutoLock lock(mutex);
+    if (!mTLSIntoleranceInfo.Get(key, &entry)) {
+      return 0;
+    }
+  }
+
+  entry.AssertInvariant();
+  return entry.intoleranceReason;
+}
+
 bool nsSSLIOLayerHelpers::nsSSLIOLayerInitialized = false;
 PRDescIdentity nsSSLIOLayerHelpers::nsSSLIOLayerIdentity;
 PRDescIdentity nsSSLIOLayerHelpers::nsSSLPlaintextLayerIdentity;
 PRIOMethods nsSSLIOLayerHelpers::nsSSLIOLayerMethods;
 PRIOMethods nsSSLIOLayerHelpers::nsSSLPlaintextLayerMethods;
 
 static PRStatus
 nsSSLIOLayerClose(PRFileDesc* fd)
@@ -1110,17 +1135,17 @@ retryDueToTLSIntolerance(PRErrorCode err
 
   // The difference between _PRE and _POST represents how often we avoided
   // TLS intolerance fallback due to remembered tolerance.
   Telemetry::Accumulate(pre, reason);
 
   if (!socketInfo->SharedState().IOLayerHelpers()
                  .rememberIntolerantAtVersion(socketInfo->GetHostName(),
                                               socketInfo->GetPort(),
-                                              range.min, range.max)) {
+                                              range.min, range.max, err)) {
     return false;
   }
 
   Telemetry::Accumulate(post, reason);
 
   return true;
 }
 
--- a/security/manager/ssl/src/nsNSSIOLayer.h
+++ b/security/manager/ssl/src/nsNSSIOLayer.h
@@ -179,30 +179,33 @@ public:
   void setWarnLevelMissingRFC5746(int32_t level);
   int32_t getWarnLevelMissingRFC5746();
 
 private:
   struct IntoleranceEntry
   {
     uint16_t tolerant;
     uint16_t intolerant;
+    PRErrorCode intoleranceReason;
 
     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);
+                                   uint16_t intolerant, uint16_t minVersion,
+                                   PRErrorCode intoleranceReason);
   void adjustForTLSIntolerance(const nsACString& hostname, int16_t port,
                                /*in/out*/ SSLVersionRange& range);
+  PRErrorCode getIntoleranceReason(const nsACString& hostname, int16_t port);
 
   void setRenegoUnrestrictedSites(const nsCString& str);
   bool isRenegoUnrestrictedSite(const nsCString& str);
   void clearStoredData();
 
   bool mFalseStartRequireNPN;
   bool mFalseStartRequireForwardSecrecy;
 private:
--- a/security/manager/ssl/tests/gtest/TLSIntoleranceTest.cpp
+++ b/security/manager/ssl/tests/gtest/TLSIntoleranceTest.cpp
@@ -1,16 +1,17 @@
 /* -*- 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 "sslerr.h"
 
 #include "gtest/gtest.h"
 
 NS_NAMED_LITERAL_CSTRING(HOST, "example.org");
 const int16_t PORT = 443;
 
 class TLSIntoleranceTest : public ::testing::Test
 {
@@ -24,52 +25,52 @@ TEST_F(TLSIntoleranceTest, Test_1_2_thro
   {
     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));
+                                                    range.min, range.max, 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_1, range.max);
 
     ASSERT_TRUE(helpers.rememberIntolerantAtVersion(HOST, PORT,
-                                                    range.min, range.max));
+                                                    range.min, range.max, 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);
 
     ASSERT_TRUE(helpers.rememberIntolerantAtVersion(HOST, PORT,
-                                                    range.min, range.max));
+                                                    range.min, range.max, 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_3_0, range.max);
 
     // false because we reached the floor set by range.min
     ASSERT_FALSE(helpers.rememberIntolerantAtVersion(HOST, PORT,
-                                                     range.min, range.max));
+                                                     range.min, range.max, 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);
     // When rememberIntolerantAtVersion returns false, it also resets the
@@ -77,69 +78,108 @@ TEST_F(TLSIntoleranceTest, Test_1_2_thro
     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));
+                                                  SSL_LIBRARY_VERSION_TLS_1_0,
+                                                  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));
+                                                  SSL_LIBRARY_VERSION_TLS_1_0,
+                                                  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));
+                                                   SSL_LIBRARY_VERSION_TLS_1_0,
+                                                   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));
+                                                   SSL_LIBRARY_VERSION_TLS_1_2,
+                                                   0));
   ASSERT_TRUE(helpers.rememberIntolerantAtVersion(HOST, 2,
                                                   SSL_LIBRARY_VERSION_3_0,
-                                                  SSL_LIBRARY_VERSION_TLS_1_2));
+                                                  SSL_LIBRARY_VERSION_TLS_1_2,
+                                                  0));
 
   {
     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);
   }
 }
+
+TEST_F(TLSIntoleranceTest, Test_Intolerance_Reason_Initial)
+{
+  ASSERT_EQ(0, helpers.getIntoleranceReason(HOST, 1));
+
+  helpers.rememberTolerantAtVersion(HOST, 2, SSL_LIBRARY_VERSION_TLS_1_2);
+  ASSERT_EQ(0, helpers.getIntoleranceReason(HOST, 2));
+}
+
+TEST_F(TLSIntoleranceTest, Test_Intolerance_Reason_Stored)
+{
+  helpers.rememberIntolerantAtVersion(HOST, 1, SSL_LIBRARY_VERSION_3_0,
+                                      SSL_LIBRARY_VERSION_TLS_1_2,
+                                      SSL_ERROR_BAD_SERVER);
+  ASSERT_EQ(SSL_ERROR_BAD_SERVER, helpers.getIntoleranceReason(HOST, 1));
+
+  helpers.rememberIntolerantAtVersion(HOST, 1, SSL_LIBRARY_VERSION_3_0,
+                                      SSL_LIBRARY_VERSION_TLS_1_1,
+                                      SSL_ERROR_BAD_MAC_READ);
+  ASSERT_EQ(SSL_ERROR_BAD_MAC_READ, helpers.getIntoleranceReason(HOST, 1));
+}
+
+TEST_F(TLSIntoleranceTest, Test_Intolerance_Reason_Cleared)
+{
+  ASSERT_EQ(0, helpers.getIntoleranceReason(HOST, 1));
+
+  helpers.rememberIntolerantAtVersion(HOST, 1, SSL_LIBRARY_VERSION_3_0,
+                                      SSL_LIBRARY_VERSION_TLS_1_2,
+                                      SSL_ERROR_HANDSHAKE_UNEXPECTED_ALERT);
+  ASSERT_EQ(SSL_ERROR_HANDSHAKE_UNEXPECTED_ALERT, helpers.getIntoleranceReason(HOST, 1));
+
+  helpers.rememberTolerantAtVersion(HOST, 1, SSL_LIBRARY_VERSION_TLS_1_2);
+  ASSERT_EQ(0, helpers.getIntoleranceReason(HOST, 1));
+}