diff --git a/security/manager/ssl/src/nsNSSComponent.cpp b/security/manager/ssl/src/nsNSSComponent.cpp index 772959da9c1..f5a2a5ad762 100644 --- a/security/manager/ssl/src/nsNSSComponent.cpp +++ b/security/manager/ssl/src/nsNSSComponent.cpp @@ -624,6 +624,7 @@ typedef struct { const char* pref; long id; bool enabledByDefault; + bool weak; } CipherPref; // Update the switch statement in HandshakeCallback in nsNSSCallbacks.cpp when @@ -667,9 +668,9 @@ static const CipherPref sCipherPrefs[] = { TLS_DHE_DSS_WITH_AES_256_CBC_SHA, false }, // deprecated (DSS) { "security.ssl3.ecdhe_rsa_rc4_128_sha", - TLS_ECDHE_RSA_WITH_RC4_128_SHA, true }, // deprecated (RC4) + TLS_ECDHE_RSA_WITH_RC4_128_SHA, true, true }, // deprecated (RC4) { "security.ssl3.ecdhe_ecdsa_rc4_128_sha", - TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, true }, // deprecated (RC4) + TLS_ECDHE_ECDSA_WITH_RC4_128_SHA, true, true }, // deprecated (RC4) { "security.ssl3.rsa_aes_128_sha", TLS_RSA_WITH_AES_128_CBC_SHA, true }, // deprecated (RSA key exchange) @@ -683,15 +684,40 @@ static const CipherPref sCipherPrefs[] = { TLS_RSA_WITH_3DES_EDE_CBC_SHA, true }, // deprecated (RSA key exchange, 3DES) { "security.ssl3.rsa_rc4_128_sha", - TLS_RSA_WITH_RC4_128_SHA, true }, // deprecated (RSA key exchange, RC4) + TLS_RSA_WITH_RC4_128_SHA, true, true }, // deprecated (RSA key exchange, RC4) { "security.ssl3.rsa_rc4_128_md5", - TLS_RSA_WITH_RC4_128_MD5, true }, // deprecated (RSA key exchange, RC4, HMAC-MD5) + TLS_RSA_WITH_RC4_128_MD5, true, true }, // deprecated (RSA key exchange, RC4, HMAC-MD5) // All the rest are disabled by default { nullptr, 0 } // end marker }; +// Bit flags indicating what weak ciphers are enabled. +// The bit index will correspond to the index in sCipherPrefs. +// Wrtten by the main thread, read from any threads. +static Atomic sEnabledWeakCiphers; +static_assert(MOZ_ARRAY_LENGTH(sCipherPrefs) - 1 <= sizeof(uint32_t) * CHAR_BIT, + "too many cipher suites"); + +/*static*/ bool +nsNSSComponent::AreAnyWeakCiphersEnabled() +{ + return !!sEnabledWeakCiphers; +} + +/*static*/ void +nsNSSComponent::UseWeakCiphersOnSocket(PRFileDesc* fd) +{ + const uint32_t enabledWeakCiphers = sEnabledWeakCiphers; + const CipherPref* const cp = sCipherPrefs; + for (size_t i = 0; cp[i].pref; ++i) { + if (enabledWeakCiphers & ((uint32_t)1 << i)) { + SSL_CipherPrefSet(fd, cp[i].id, true); + } + } +} + static const int32_t OCSP_ENABLED_DEFAULT = 1; static const bool REQUIRE_SAFE_NEGOTIATION_DEFAULT = false; static const bool ALLOW_UNRESTRICTED_RENEGO_DEFAULT = false; @@ -773,12 +799,27 @@ CipherSuiteChangeObserver::Observe(nsISupports* aSubject, if (nsCRT::strcmp(aTopic, NS_PREFBRANCH_PREFCHANGE_TOPIC_ID) == 0) { NS_ConvertUTF16toUTF8 prefName(someData); // Look through the cipher table and set according to pref setting - for (const CipherPref* cp = sCipherPrefs; cp->pref; ++cp) { - if (prefName.Equals(cp->pref)) { - bool cipherEnabled = Preferences::GetBool(cp->pref, - cp->enabledByDefault); - SSL_CipherPrefSetDefault(cp->id, cipherEnabled); - SSL_ClearSessionCache(); + const CipherPref* const cp = sCipherPrefs; + for (size_t i = 0; cp[i].pref; ++i) { + if (prefName.Equals(cp[i].pref)) { + bool cipherEnabled = Preferences::GetBool(cp[i].pref, + cp[i].enabledByDefault); + if (cp[i].weak) { + // Weak ciphers will not be used by default even if they + // are enabled in prefs. They are only used on specific + // sockets as a part of a fallback mechanism. + // Only the main thread will change sEnabledWeakCiphers. + uint32_t enabledWeakCiphers = sEnabledWeakCiphers; + if (cipherEnabled) { + enabledWeakCiphers |= ((uint32_t)1 << i); + } else { + enabledWeakCiphers &= ~((uint32_t)1 << i); + } + sEnabledWeakCiphers = enabledWeakCiphers; + } else { + SSL_CipherPrefSetDefault(cp[i].id, cipherEnabled); + SSL_ClearSessionCache(); + } break; } } @@ -1646,10 +1687,22 @@ InitializeCipherSuite() } // Now only set SSL/TLS ciphers we knew about at compile time - for (const CipherPref* cp = sCipherPrefs; cp->pref; ++cp) { - bool cipherEnabled = Preferences::GetBool(cp->pref, cp->enabledByDefault); - SSL_CipherPrefSetDefault(cp->id, cipherEnabled); + uint32_t enabledWeakCiphers = 0; + const CipherPref* const cp = sCipherPrefs; + for (size_t i = 0; cp[i].pref; ++i) { + bool cipherEnabled = Preferences::GetBool(cp[i].pref, + cp[i].enabledByDefault); + if (cp[i].weak) { + // Weak ciphers are not used by default. See the comment + // in CipherSuiteChangeObserver::Observe for details. + if (cipherEnabled) { + enabledWeakCiphers |= ((uint32_t)1 << i); + } + } else { + SSL_CipherPrefSetDefault(cp[i].id, cipherEnabled); + } } + sEnabledWeakCiphers = enabledWeakCiphers; // Enable ciphers for PKCS#12 SEC_PKCS12EnableCipher(PKCS12_RC4_40, 1); diff --git a/security/manager/ssl/src/nsNSSComponent.h b/security/manager/ssl/src/nsNSSComponent.h index 23a0f39016c..29f046c4202 100644 --- a/security/manager/ssl/src/nsNSSComponent.h +++ b/security/manager/ssl/src/nsNSSComponent.h @@ -153,6 +153,10 @@ public: ::mozilla::TemporaryRef GetDefaultCertVerifier() MOZ_OVERRIDE; + // The following two methods are thread-safe. + static bool AreAnyWeakCiphersEnabled(); + static void UseWeakCiphersOnSocket(PRFileDesc* fd); + protected: virtual ~nsNSSComponent(); diff --git a/security/manager/ssl/src/nsNSSIOLayer.cpp b/security/manager/ssl/src/nsNSSIOLayer.cpp index 9fe96042e70..96f0ed6e21c 100644 --- a/security/manager/ssl/src/nsNSSIOLayer.cpp +++ b/security/manager/ssl/src/nsNSSIOLayer.cpp @@ -855,10 +855,14 @@ nsSSLIOLayerHelpers::rememberTolerantAtVersion(const nsACString& hostName, 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(); @@ -880,6 +884,9 @@ void nsSSLIOLayerHelpers::forgetIntolerance(const nsACString& hostName, entry.intolerant = 0; entry.intoleranceReason = 0; + if (entry.strongCipherStatus != StrongCiphersWorked) { + entry.strongCipherStatus = StrongCipherStatusUnknown; + } entry.AssertInvariant(); mTLSIntoleranceInfo.Put(key, entry); @@ -918,6 +925,7 @@ nsSSLIOLayerHelpers::rememberIntolerantAtVersion(const nsACString& hostName, } } else { entry.tolerant = 0; + entry.strongCipherStatus = StrongCipherStatusUnknown; } entry.intolerant = intolerant; @@ -928,10 +936,41 @@ nsSSLIOLayerHelpers::rememberIntolerantAtVersion(const nsACString& hostName, return true; } +// returns true if we should retry the handshake +bool +nsSSLIOLayerHelpers::rememberStrongCiphersFailed(const nsACString& hostName, + int16_t port) +{ + 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 = SSL_ERROR_NO_CYPHER_OVERLAP; + } + + entry.strongCipherStatus = StrongCiphersFailed; + entry.AssertInvariant(); + mTLSIntoleranceInfo.Put(key, entry); + + return true; +} + void nsSSLIOLayerHelpers::adjustForTLSIntolerance(const nsACString& hostName, int16_t port, - /*in/out*/ SSLVersionRange& range) + /*in/out*/ SSLVersionRange& range, + /*out*/ StrongCipherStatus& strongCipherStatus) { IntoleranceEntry entry; @@ -954,6 +993,7 @@ nsSSLIOLayerHelpers::adjustForTLSIntolerance(const nsACString& hostName, range.max = entry.intolerant - 1; } } + strongCipherStatus = entry.strongCipherStatus; } PRErrorCode @@ -1169,6 +1209,15 @@ retryDueToTLSIntolerance(PRErrorCode err, nsNSSSocketInfo* socketInfo) .forgetIntolerance(socketInfo->GetHostName(), socketInfo->GetPort()); return false; + } else if (err == SSL_ERROR_NO_CYPHER_OVERLAP && + nsNSSComponent::AreAnyWeakCiphersEnabled()) { + if (socketInfo->SharedState().IOLayerHelpers() + .rememberStrongCiphersFailed(socketInfo->GetHostName(), + socketInfo->GetPort())) { + Telemetry::Accumulate(Telemetry::SSL_WEAK_CIPHERS_FALLBACK, true); + return true; + } + Telemetry::Accumulate(Telemetry::SSL_WEAK_CIPHERS_FALLBACK, false); } // When not using a proxy we'll see a connection reset error. @@ -2535,21 +2584,26 @@ nsSSLIOLayerSetOptions(PRFileDesc* fd, bool forSTARTTLS, return NS_ERROR_FAILURE; } - uint16_t maxEnabledVersion = range.max; - + uint16_t maxEnabledVersion = range.max; + StrongCipherStatus strongCiphersStatus = StrongCipherStatusUnknown; infoObject->SharedState().IOLayerHelpers() .adjustForTLSIntolerance(infoObject->GetHostName(), infoObject->GetPort(), - range); + range, strongCiphersStatus); PR_LOG(gPIPNSSLog, PR_LOG_DEBUG, - ("[%p] nsSSLIOLayerSetOptions: using TLS version range (0x%04x,0x%04x)\n", + ("[%p] nsSSLIOLayerSetOptions: using TLS version range (0x%04x,0x%04x)%s\n", fd, static_cast(range.min), - static_cast(range.max))); + static_cast(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) { diff --git a/security/manager/ssl/src/nsNSSIOLayer.h b/security/manager/ssl/src/nsNSSIOLayer.h index c86b1e37951..d99a37d2eb1 100644 --- a/security/manager/ssl/src/nsNSSIOLayer.h +++ b/security/manager/ssl/src/nsNSSIOLayer.h @@ -173,6 +173,12 @@ private: nsCOMPtr mClientCert; }; +enum StrongCipherStatus { + StrongCipherStatusUnknown, + StrongCiphersWorked, + StrongCiphersFailed +}; + class nsSSLIOLayerHelpers { public: @@ -203,6 +209,7 @@ private: uint16_t tolerant; uint16_t intolerant; PRErrorCode intoleranceReason; + StrongCipherStatus strongCipherStatus; void AssertInvariant() const { @@ -216,9 +223,11 @@ public: bool rememberIntolerantAtVersion(const nsACString& hostname, int16_t port, uint16_t intolerant, uint16_t minVersion, PRErrorCode intoleranceReason); + bool rememberStrongCiphersFailed(const nsACString& hostName, int16_t port); void forgetIntolerance(const nsACString& hostname, int16_t port); void adjustForTLSIntolerance(const nsACString& hostname, int16_t port, - /*in/out*/ SSLVersionRange& range); + /*in/out*/ SSLVersionRange& range, + /*out*/ StrongCipherStatus& strongCipherStatus); PRErrorCode getIntoleranceReason(const nsACString& hostname, int16_t port); void setRenegoUnrestrictedSites(const nsCString& str); diff --git a/security/manager/ssl/tests/gtest/TLSIntoleranceTest.cpp b/security/manager/ssl/tests/gtest/TLSIntoleranceTest.cpp index a08c6595200..516b6336362 100644 --- a/security/manager/ssl/tests/gtest/TLSIntoleranceTest.cpp +++ b/security/manager/ssl/tests/gtest/TLSIntoleranceTest.cpp @@ -19,7 +19,7 @@ protected: nsSSLIOLayerHelpers helpers; }; -TEST_F(TLSIntoleranceTest, Test_1_2_through_3_0) +TEST_F(TLSIntoleranceTest, Test_Full_Fallback_Process) { helpers.mVersionFallbackLimit = SSL_LIBRARY_VERSION_3_0; @@ -27,10 +27,25 @@ TEST_F(TLSIntoleranceTest, Test_1_2_through_3_0) { SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0, SSL_LIBRARY_VERSION_TLS_1_2 }; - helpers.adjustForTLSIntolerance(HOST, PORT, range); + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.min); ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_2, range.max); + ASSERT_EQ(StrongCipherStatusUnknown, strongCipherStatus); + ASSERT_TRUE(helpers.rememberStrongCiphersFailed(HOST, PORT)); + } + + { + SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.min); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_2, range.max); + ASSERT_EQ(StrongCiphersFailed, strongCipherStatus); + + ASSERT_FALSE(helpers.rememberStrongCiphersFailed(HOST, PORT)); ASSERT_TRUE(helpers.rememberIntolerantAtVersion(HOST, PORT, range.min, range.max, 0)); } @@ -38,10 +53,13 @@ TEST_F(TLSIntoleranceTest, Test_1_2_through_3_0) { SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0, SSL_LIBRARY_VERSION_TLS_1_2 }; - helpers.adjustForTLSIntolerance(HOST, PORT, range); + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.min); ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_1, range.max); + ASSERT_EQ(StrongCiphersFailed, strongCipherStatus); + ASSERT_FALSE(helpers.rememberStrongCiphersFailed(HOST, PORT)); ASSERT_TRUE(helpers.rememberIntolerantAtVersion(HOST, PORT, range.min, range.max, 0)); } @@ -49,10 +67,13 @@ TEST_F(TLSIntoleranceTest, Test_1_2_through_3_0) { SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0, SSL_LIBRARY_VERSION_TLS_1_2 }; - helpers.adjustForTLSIntolerance(HOST, PORT, range); + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.min); ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_0, range.max); + ASSERT_EQ(StrongCiphersFailed, strongCipherStatus); + ASSERT_FALSE(helpers.rememberStrongCiphersFailed(HOST, PORT)); ASSERT_TRUE(helpers.rememberIntolerantAtVersion(HOST, PORT, range.min, range.max, 0)); } @@ -61,10 +82,13 @@ TEST_F(TLSIntoleranceTest, Test_1_2_through_3_0) SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0, SSL_LIBRARY_VERSION_TLS_1_2 }; - helpers.adjustForTLSIntolerance(HOST, PORT, range); + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.min); ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.max); + ASSERT_EQ(StrongCiphersFailed, strongCipherStatus); + ASSERT_FALSE(helpers.rememberStrongCiphersFailed(HOST, PORT)); // false because we reached the floor set by range.min ASSERT_FALSE(helpers.rememberIntolerantAtVersion(HOST, PORT, range.min, range.max, 0)); @@ -73,11 +97,13 @@ TEST_F(TLSIntoleranceTest, Test_1_2_through_3_0) { SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0, SSL_LIBRARY_VERSION_TLS_1_2 }; - helpers.adjustForTLSIntolerance(HOST, PORT, range); + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); 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); + ASSERT_EQ(StrongCipherStatusUnknown, strongCipherStatus); } } @@ -125,9 +151,11 @@ TEST_F(TLSIntoleranceTest, Test_Fallback_Limit_Below_Min) { SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0, SSL_LIBRARY_VERSION_TLS_1_2 }; - helpers.adjustForTLSIntolerance(HOST, PORT, range); + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.min); ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_1, range.max); + ASSERT_EQ(StrongCipherStatusUnknown, strongCipherStatus); } ASSERT_FALSE(helpers.rememberIntolerantAtVersion(HOST, PORT, @@ -145,9 +173,11 @@ TEST_F(TLSIntoleranceTest, Test_Tolerant_Overrides_Intolerant_1) 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); + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.min); ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_1, range.max); + ASSERT_EQ(StrongCiphersWorked, strongCipherStatus); } TEST_F(TLSIntoleranceTest, Test_Tolerant_Overrides_Intolerant_2) @@ -159,9 +189,11 @@ TEST_F(TLSIntoleranceTest, Test_Tolerant_Overrides_Intolerant_2) helpers.rememberTolerantAtVersion(HOST, PORT, SSL_LIBRARY_VERSION_TLS_1_2); SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0, SSL_LIBRARY_VERSION_TLS_1_2 }; - helpers.adjustForTLSIntolerance(HOST, PORT, range); + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.min); ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_2, range.max); + ASSERT_EQ(StrongCiphersWorked, strongCipherStatus); } TEST_F(TLSIntoleranceTest, Test_Intolerant_Does_Not_Override_Tolerant) @@ -175,9 +207,11 @@ TEST_F(TLSIntoleranceTest, Test_Intolerant_Does_Not_Override_Tolerant) 0)); SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0, SSL_LIBRARY_VERSION_TLS_1_2 }; - helpers.adjustForTLSIntolerance(HOST, PORT, range); + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.min); ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_2, range.max); + ASSERT_EQ(StrongCiphersWorked, strongCipherStatus); } TEST_F(TLSIntoleranceTest, Test_Port_Is_Relevant) @@ -195,14 +229,16 @@ TEST_F(TLSIntoleranceTest, Test_Port_Is_Relevant) { SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0, SSL_LIBRARY_VERSION_TLS_1_2 }; - helpers.adjustForTLSIntolerance(HOST, 1, range); + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, 1, range, strongCipherStatus); 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); + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, 2, range, strongCipherStatus); ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_1, range.max); } } @@ -242,6 +278,147 @@ TEST_F(TLSIntoleranceTest, Test_Intolerance_Reason_Cleared) ASSERT_EQ(0, helpers.getIntoleranceReason(HOST, 1)); } +TEST_F(TLSIntoleranceTest, Test_Strong_Ciphers_Failed) +{ + helpers.mVersionFallbackLimit = SSL_LIBRARY_VERSION_TLS_1_1; + + ASSERT_TRUE(helpers.rememberStrongCiphersFailed(HOST, PORT)); + + { + SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.min); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_2, range.max); + ASSERT_EQ(StrongCiphersFailed, strongCipherStatus); + + ASSERT_TRUE(helpers.rememberIntolerantAtVersion(HOST, PORT, + range.min, range.max, 0)); + } + + { + SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.min); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_1, range.max); + ASSERT_EQ(StrongCiphersFailed, strongCipherStatus); + + ASSERT_FALSE(helpers.rememberIntolerantAtVersion(HOST, PORT, + range.min, range.max, 0)); + } + + { + SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + 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); + ASSERT_EQ(StrongCipherStatusUnknown, strongCipherStatus); + } +} + +TEST_F(TLSIntoleranceTest, Test_Strong_Ciphers_Failed_At_1_1) +{ + helpers.mVersionFallbackLimit = SSL_LIBRARY_VERSION_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 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_TRUE(helpers.rememberIntolerantAtVersion(HOST, PORT, + range.min, range.max, 0)); + } + + { + SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_TRUE(helpers.rememberStrongCiphersFailed(HOST, PORT)); + } + + { + SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.min); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_1, range.max); + ASSERT_EQ(StrongCiphersFailed, strongCipherStatus); + + ASSERT_TRUE(helpers.rememberIntolerantAtVersion(HOST, PORT, + range.min, range.max, 0)); + } + + { + SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.min); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_0, range.max); + ASSERT_EQ(StrongCiphersFailed, strongCipherStatus); + } +} + +TEST_F(TLSIntoleranceTest, Test_Strong_Ciphers_Failed_With_High_Limit) +{ + // this value disables version fallback entirely: with this value, all efforts + // to mark an origin as version intolerant fail + helpers.mVersionFallbackLimit = SSL_LIBRARY_VERSION_TLS_1_2; + // ...but weak ciphers fallback will not be disabled + ASSERT_TRUE(helpers.rememberStrongCiphersFailed(HOST, PORT)); + ASSERT_FALSE(helpers.rememberIntolerantAtVersion(HOST, PORT, + SSL_LIBRARY_VERSION_3_0, + SSL_LIBRARY_VERSION_TLS_1_2, + 0)); + ASSERT_FALSE(helpers.rememberIntolerantAtVersion(HOST, PORT, + SSL_LIBRARY_VERSION_3_0, + SSL_LIBRARY_VERSION_TLS_1_1, + 0)); + ASSERT_FALSE(helpers.rememberIntolerantAtVersion(HOST, PORT, + SSL_LIBRARY_VERSION_3_0, + SSL_LIBRARY_VERSION_TLS_1_0, + 0)); +} + +TEST_F(TLSIntoleranceTest, Test_Tolerant_Does_Not_Override_Weak_Ciphers_Fallback) +{ + ASSERT_TRUE(helpers.rememberStrongCiphersFailed(HOST, PORT)); + // No adjustment made when intolerant is zero. + helpers.rememberTolerantAtVersion(HOST, PORT, SSL_LIBRARY_VERSION_TLS_1_1); + SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.min); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_2, range.max); + ASSERT_EQ(StrongCiphersFailed, strongCipherStatus); +} + +TEST_F(TLSIntoleranceTest, Test_Weak_Ciphers_Fallback_Does_Not_Override_Tolerant) +{ + // No adjustment made when there is no entry for the site. + helpers.rememberTolerantAtVersion(HOST, PORT, SSL_LIBRARY_VERSION_TLS_1_1); + // false because strongCipherWorked is set by rememberTolerantAtVersion. + ASSERT_FALSE(helpers.rememberStrongCiphersFailed(HOST, PORT)); + SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.min); + ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_2, range.max); + ASSERT_EQ(StrongCiphersWorked, strongCipherStatus); +} + TEST_F(TLSIntoleranceTest, TLS_Forget_Intolerance) { { @@ -252,9 +429,11 @@ TEST_F(TLSIntoleranceTest, TLS_Forget_Intolerance) SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0, SSL_LIBRARY_VERSION_TLS_1_2 }; - helpers.adjustForTLSIntolerance(HOST, PORT, range); + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.min); ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_1, range.max); + ASSERT_EQ(StrongCipherStatusUnknown, strongCipherStatus); } { @@ -262,22 +441,49 @@ TEST_F(TLSIntoleranceTest, TLS_Forget_Intolerance) SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0, SSL_LIBRARY_VERSION_TLS_1_2 }; - helpers.adjustForTLSIntolerance(HOST, PORT, range); + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.min); ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_2, range.max); + ASSERT_EQ(StrongCipherStatusUnknown, strongCipherStatus); + } +} + +TEST_F(TLSIntoleranceTest, TLS_Forget_Strong_Cipher_Failed) +{ + { + ASSERT_TRUE(helpers.rememberStrongCiphersFailed(HOST, PORT)); + + SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(StrongCiphersFailed, strongCipherStatus); + } + + { + helpers.forgetIntolerance(HOST, PORT); + + SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0, + SSL_LIBRARY_VERSION_TLS_1_2 }; + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); + ASSERT_EQ(StrongCipherStatusUnknown, strongCipherStatus); } } TEST_F(TLSIntoleranceTest, TLS_Dont_Forget_Tolerance) { { - helpers.rememberTolerantAtVersion(HOST, 1, SSL_LIBRARY_VERSION_TLS_1_1); + 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); + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.min); ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_2, range.max); + ASSERT_EQ(StrongCiphersWorked, strongCipherStatus); } { @@ -288,9 +494,11 @@ TEST_F(TLSIntoleranceTest, TLS_Dont_Forget_Tolerance) SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0, SSL_LIBRARY_VERSION_TLS_1_2 }; - helpers.adjustForTLSIntolerance(HOST, PORT, range); + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.min); ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_1, range.max); + ASSERT_EQ(StrongCiphersWorked, strongCipherStatus); } { @@ -298,8 +506,10 @@ TEST_F(TLSIntoleranceTest, TLS_Dont_Forget_Tolerance) SSLVersionRange range = { SSL_LIBRARY_VERSION_3_0, SSL_LIBRARY_VERSION_TLS_1_2 }; - helpers.adjustForTLSIntolerance(HOST, PORT, range); + StrongCipherStatus strongCipherStatus = StrongCipherStatusUnknown; + helpers.adjustForTLSIntolerance(HOST, PORT, range, strongCipherStatus); ASSERT_EQ(SSL_LIBRARY_VERSION_3_0, range.min); ASSERT_EQ(SSL_LIBRARY_VERSION_TLS_1_2, range.max); + ASSERT_EQ(StrongCiphersWorked, strongCipherStatus); } } diff --git a/toolkit/components/telemetry/Histograms.json b/toolkit/components/telemetry/Histograms.json index ed625020a61..9545c8a1b09 100644 --- a/toolkit/components/telemetry/Histograms.json +++ b/toolkit/components/telemetry/Histograms.json @@ -6382,6 +6382,11 @@ "n_values": 64, "description": "TLS/SSL version intolerance was falsely detected, server rejected handshake" }, + "SSL_WEAK_CIPHERS_FALLBACK": { + "expires_in_version": "never", + "kind": "boolean", + "description": "Fallback attempted when server did not support any strong cipher suites" + }, "SSL_CIPHER_SUITE_FULL": { "expires_in_version": "never", "kind": "enumerated",